def repack_apk(apk_path_or_list, provider_name, merge_dex_path, activity_list=None, res_file_list=None, debuggable=None, vm_safe_mode=None, max_heap_size=0, force_append=False): ''' ''' if not isinstance(apk_path_or_list, list): apk_path_or_list = [apk_path_or_list] elif len(apk_path_or_list) == 0: raise ValueError('apk path not specified') apk_file_list = [] signature_dict = {} for it in apk_path_or_list: apk_file = APKFile(it) apk_file_list.append(apk_file) manifest = AndroidManifest(apk_file) if debuggable != None: manifest.debuggable = debuggable # 修改debuggable属性 if vm_safe_mode != None: manifest.vm_safe_mode = vm_safe_mode # 修改安全模式 process_list = manifest.get_process_list() authorities = manifest.package_name + '.authorities' print('Add provider %s' % provider_name) manifest.add_provider(provider_name, authorities) # 主进程 for i, process in enumerate(process_list): sub_provider_name = provider_name + '$InnerClass' + str(i + 1) print('Add provider %s in process %s' % (sub_provider_name, process)) manifest.add_provider(sub_provider_name, authorities + str(i), process) if activity_list: for activity in activity_list: print('Add activity %s' % activity['name']) manifest.add_activity(activity['name'], activity['exported'], activity['process']) # 合并dex文件 print('Merge dex %s' % merge_dex_path) classes_dex_path = tempfile.mktemp('.dex') dex_index = 1 low_memory = False while True: dex_file = 'classes%s.dex' % (dex_index if dex_index > 1 else '') if not apk_file.get_file(dex_file): # 作为最后一个classes.dex with open(merge_dex_path, 'rb') as f: apk_file.add_file(dex_file, f.read()) print('Save dex %s to %s' % (merge_dex_path, dex_file)) break if force_append or low_memory: # 低内存下直接跳过已有dex,进行加速 dex_index += 1 continue if os.path.exists(classes_dex_path): os.remove(classes_dex_path) apk_file.extract_file(dex_file, classes_dex_path) try: merge_dex(classes_dex_path, [classes_dex_path, merge_dex_path], max_heap_size) except TooManyMethodsError: print('Merge dex into %s failed due to methods number' % dex_file) dex_index += 1 except OutOfMemoryError: print('Merge dex into %s failed due to out of memory error' % dex_file) low_memory = True dex_index += 1 else: print('Merge dex into %s success' % dex_file) with open(classes_dex_path, 'rb') as f: apk_file.add_file(dex_file, f.read()) break if dex_index > 1: # 合并进非主dex只支持5.0以上系统 print('WARNING: APK can only be installed in android above 5.0') manifest.min_sdk_version = 21 manifest.save() if res_file_list: for src_path, dst_path in res_file_list: with open(src_path, 'rb') as f: print('Copy file %s => %s' % (src_path, dst_path)) data = f.read() apk_file.add_file(dst_path, data) for it in apk_file.list_dir('META-INF'): if it.lower().endswith('.rsa'): print('Signature file is %s' % it) tmp_rsa_path = tempfile.mktemp('.rsa') apk_file.extract_file('META-INF/%s' % it, tmp_rsa_path) orig_signature = get_apk_signature(tmp_rsa_path).strip() os.remove(tmp_rsa_path) logging.info('%s signature is %s' % (manifest.package_name, orig_signature)) signature_dict[manifest.package_name] = orig_signature break else: raise RuntimeError('Can not find .sf file in META-INF dir') for it in apk_file.list_dir('META-INF'): apk_file.delete_file('META-INF/%s' % it) out_apk_list = [] # 写入原始签名信息 print('Write original signatures: %s' % json.dumps(signature_dict)) temp_dir = tempfile.mkdtemp('-repack') for i, apk_file in enumerate(apk_file_list): apk_file.add_file('assets/qt4a_package_signatures.txt', json.dumps(signature_dict)) file_name = os.path.split(apk_path_or_list[i])[-1][:-4] + '-repack.apk' tmp_apk_path = os.path.join(temp_dir, file_name) apk_file.save(tmp_apk_path) new_path = resign_apk(tmp_apk_path) os.remove(tmp_apk_path) out_apk_list.append(new_path) if len(out_apk_list) == 1: return out_apk_list[0] else: return out_apk_list
class AndroidManifest(object): ''' ''' def __init__(self, apk_fp_or_path): if isinstance(apk_fp_or_path, APKFile): self._apk_file = apk_fp_or_path else: self._apk_file = APKFile(apk_fp_or_path) self._dom = self.get_dom() def get_dom(self): '''获取AndroidManifest.xml文件的dom树 ''' fp = self._apk_file.get_file('AndroidManifest.xml') if fp == None: raise RuntimeError('apk %s is invalid' % self._apk_path) axf = AXMLFile(fp) return axf.to_xml() @property def package_name(self): '''包名 ''' return self._dom.getElementsByTagName('manifest')[0].getAttribute( 'package') @property def version_code(self): '''versionCode ''' return int( self._dom.getElementsByTagName('manifest')[0].getAttribute( 'android:versionCode')) @property def version_name(self): '''versionName ''' return self._dom.getElementsByTagName('manifest')[0].getAttribute( 'android:versionName') @property def min_sdk_version(self): '''最低SDK版本 ''' return int( self._dom.getElementsByTagName('uses-sdk')[0].getAttribute( 'android:minSdkVersion')) @property def target_sdk_version(self): '''目标SDK版本 ''' return int( self._dom.getElementsByTagName('uses-sdk')[0].getAttribute( 'android:targetSdkVersion')) @property def application_name(self): ''' ''' return self._dom.getElementsByTagName('application')[0].getAttribute( 'android:name') @property def debuggable(self): '''是否是调试版本 ''' return self._dom.getElementsByTagName('application')[0].getAttribute( 'android:debuggable') @debuggable.setter def debuggable(self, value): '''设置调试标志位 ''' self._dom.getElementsByTagName('application')[0].setAttribute( 'android:debuggable', 'true' if value else 'false') @property def start_activity(self): '''启动Activity ''' for activity in self._dom.getElementsByTagName('activity'): for filter in activity.getElementsByTagName('action'): if filter.getAttribute( 'android:name') == 'android.intent.action.MAIN': activity_name = activity.getAttribute('android:name') if activity_name.startswith('.'): activity_name = self.package_name + activity_name return activity_name raise APKError('apk %s do not have start activity' % self._apk_path) def get_process_list(self): '''获取进程列表 ''' process_list = [] for tag_name in ('activity', 'service', 'provider', 'receiver'): for it in self._dom.getElementsByTagName(tag_name): process = it.getAttribute('android:process') if process and not process in process_list: process_list.append(process) return process_list def add_provider(self, name, authorities, process=None): '''添加ContentProvider ''' elem = self._dom.createElement('provider') elem.setAttribute('android:name', name) elem.setAttribute('android:authorities', authorities) if process: elem.setAttribute('android:process', process) self._dom.getElementsByTagName('application')[0].appendChild(elem) def add_activity(self, activity, exported="true", process=None): elem = self._dom.createElement('activity') elem.setAttribute('android:name', activity) elem.setAttribute('android:exported', exported) if process: elem.setAttribute('android:process', process) self._dom.getElementsByTagName('application')[0].appendChild(elem) def save(self): '''保存修改到apk文件 ''' axml = AXMLFile.from_xml(self._dom) self._apk_file.add_file('AndroidManifest.xml', axml.serialize())
def __init__(self, apk_fp_or_path): if isinstance(apk_fp_or_path, APKFile): self._apk_file = apk_fp_or_path else: self._apk_file = APKFile(apk_fp_or_path) self._dom = self.get_dom()
def repack_apk(apk_path_or_list, debuggable=True): '''重打包apk :param apk_path_or_list: apk路径或apk路径列表 :type apk_path_or_list: string/list :param debuggable: 重打包后的apk是否是调试版本: True - 是 False - 否 None - 与原apk保持一致 :type debuggable: bool/None ''' cur_path = os.path.dirname(os.path.abspath(__file__)) if not isinstance(apk_path_or_list, list): apk_path_or_list = [apk_path_or_list] elif len(apk_path_or_list) == 0: raise ValueError('apk path not specified') apk_file_list = [] signature_dict = {} for it in apk_path_or_list: apk_file = APKFile(it) apk_file_list.append(apk_file) manifest = AndroidManifest(apk_file) if debuggable != None: manifest.debuggable = debuggable # 修改debuggable属性 process_list = manifest.get_process_list() provider_name = 'com.test.androidspy.inject.DexLoaderContentProvider' authorities = manifest.package_name + '.authorities' manifest.add_provider(provider_name, authorities) # 主进程 manifest.add_activity('com.test.androidspy.inject.CmdExecuteActivity', "true", ':qt4a_cmd') for i, process in enumerate(process_list): manifest.add_provider(provider_name + '$InnerClass' + str(i + 1), authorities + str(i), process) manifest.save() # 合并dex文件 dexloader_path = os.path.join(cur_path, 'tools', 'dexloader.dex') classes_dex_path = tempfile.mktemp('.dex') apk_file.extract_file('classes.dex', classes_dex_path) merge_dex(classes_dex_path, [classes_dex_path, dexloader_path]) with open(classes_dex_path, 'rb') as f: apk_file.add_file('classes.dex', f.read()) # 添加QT4A测试桩文件 file_list = [ 'AndroidSpy.jar', 'arm64-v8a/libdexloader.so', 'arm64-v8a/libdexloader64.so', 'arm64-v8a/libandroidhook.so', 'armeabi/libdexloader.so', 'armeabi/libandroidhook.so', 'armeabi-v7a/libdexloader.so', 'armeabi-v7a/libandroidhook.so', 'x86/libdexloader.so', 'x86/libandroidhook.so' ] tools_path = os.path.join(os.path.dirname(cur_path), 'androiddriver', 'tools') for it in file_list: file_path = os.path.join(tools_path, it) with open(file_path, 'rb') as f: data = f.read() apk_file.add_file('assets/qt4a/%s' % it, data) for it in apk_file.list_dir('META-INF'): if it.lower().endswith('.rsa'): print('Signature file is %s' % it) tmp_rsa_path = tempfile.mktemp('.rsa') apk_file.extract_file('META-INF/%s' % it, tmp_rsa_path) orig_signature = get_apk_signature(tmp_rsa_path).strip() os.remove(tmp_rsa_path) logging.info('%s signature is %s' % (manifest.package_name, orig_signature)) signature_dict[manifest.package_name] = orig_signature break else: raise RuntimeError('Can not find .sf file in META-INF dir') for it in apk_file.list_dir('META-INF'): apk_file.delete_file('META-INF/%s' % it) out_apk_list = [] # 写入原始签名信息 temp_dir = tempfile.mkdtemp('-repack') for i, apk_file in enumerate(apk_file_list): apk_file.add_file('assets/qt4a_package_signatures.txt', json.dumps(signature_dict)) file_name = os.path.split(apk_path_or_list[i])[-1][:-4] + '-repack.apk' tmp_apk_path = os.path.join(temp_dir, file_name) apk_file.save(tmp_apk_path) new_path = resign_apk(tmp_apk_path) os.remove(tmp_apk_path) out_apk_list.append(new_path) if len(out_apk_list) == 1: return out_apk_list[0] else: return out_apk_list