示例#1
0
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
示例#2
0
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())
示例#3
0
 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()
示例#4
0
文件: repack.py 项目: zhunzhong/QT4A
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