Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
299 lines (260 sloc) 11.1 KB
# -*- coding: UTF-8 -*-
# Tencent is pleased to support the open source community by making QTA available.
# Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the BSD 3-Clause License (the "License"); you may not use this
# file except in compliance with the License. You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
import six
import json
import logging
import os
import shutil
import sys
import subprocess
import tempfile
from qt4a.apktool.apkfile import APKFile
from qt4a.apktool.manifest import AndroidManifest
class MergeDexError(RuntimeError):
class TooManyMethodsError(MergeDexError):
class OutOfMemoryError(RuntimeError):
def get_apk_signature(rsa_file_path):
if not os.path.exists(rsa_file_path):
raise RuntimeError('Rsa file %s not exist' % rsa_file_path)
cur_path = os.path.dirname(os.path.abspath(__file__))
jar_path = os.path.join(cur_path, 'tools', 'apkhelper.jar')
return os.popen('java -jar %s getSignature %s' % (jar_path, rsa_file_path)).read()
def dex2jar(dex_path, jar_path):
if not os.path.exists(dex_path):
raise RuntimeError('dex %s not exist' % dex_path)
cur_path = os.path.dirname(os.path.abspath(__file__))
tools_path = os.path.join(cur_path, 'tools')
class_paths = []
for it in os.listdir(tools_path):
if it.endswith('.jar'):
class_paths.append(os.path.join(tools_path, it))
# -Xms512m -Xmx1024m
sep = ';' if sys.platform == 'win32' else ':'
cmdline = ['java', '-Xmx1024m', '-cp', sep.join(class_paths), '',
'-f', '-o', jar_path, dex_path]' '.join(cmdline))
proc = subprocess.Popen(
cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = proc.communicate()[1]
if not b'->' in result:
raise RuntimeError('convert dex %s to jar failed: %s' %
(dex_path, result))
def jar2dex(jar_path, dex_path, max_heap_size=0):
if not os.path.exists(jar_path):
raise RuntimeError('jar %s not exist' % jar_path)
cur_path = os.path.dirname(os.path.abspath(__file__))
dx_path = os.path.join(cur_path, 'tools', 'dx.jar')
# cmdline = 'java -Xmx1024m -cp "%s" --dex --force-jumbo --output="%s" "%s"' % (dx_path, dex_path, jar_path)
memory = 1024
if max_heap_size:
memory = max_heap_size
elif os.path.getsize(jar_path) >= 8 * 1024 * 1024:
memory = 2048 # 此时必须用64位java
cmdline = ['java', '-Xmx%dm' % memory, '-cp', dx_path, '',
'--dex', '--force-jumbo', '--output=%s' % dex_path, jar_path]' '.join(cmdline))
proc = subprocess.Popen(
cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = proc.communicate()[1]
if result:
err_msg = result
if sys.platform == 'win32':
err_msg = err_msg.decode('gbk')
err_msg = err_msg.decode('utf8')
if proc.returncode:
if 'java.lang.OutOfMemoryError' in err_msg:
raise OutOfMemoryError(err_msg)
raise RuntimeError(
'Convert jar %s to dex failed: %s' % (jar_path, err_msg))
def rebuild_dex_with_jumbo(dex_path, max_heap_size=0):
jar_path = tempfile.mktemp('.jar')
dex2jar(dex_path, jar_path)
jar2dex(jar_path, dex_path, max_heap_size)
def merge_dex(dst_dex, src_dexs, max_heap_size=0):
cur_path = os.path.dirname(os.path.abspath(__file__))
jar_path = os.path.join(cur_path, 'tools', 'apkhelper.jar')
cmdline = ['java', '-jar', jar_path, 'mergeDex', dst_dex]
cmdline.extend(src_dexs)' '.join(cmdline))
proc = subprocess.Popen(
cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = proc.communicate()
if not b'Took' in result[0]:
if b'non-jumbo instruction' in result[1]:
# 应用未使用jumbo模式编译,这里做一次转换
logging.warn('change dex to jumbo mode')
rebuild_dex_with_jumbo(src_dexs[0], max_heap_size)
return merge_dex(dst_dex, src_dexs, max_heap_size)
elif b'DexIndexOverflowException' in result[1]:
raise TooManyMethodsError('Merge dex failed: %s' % result[1])
raise MergeDexError('Merge dex failed: %s' % result[1])
def resign_apk(apk_path):
if not os.path.exists(apk_path):
raise RuntimeError('apk %s not exist' % apk_path)
cur_path = os.path.dirname(os.path.abspath(__file__))
key_file_path = os.path.join(cur_path, 'tools', 'qt4a.keystore')
save_path = apk_path[:-4] + '-signed.apk'
# 'jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore %s -signedjar %s %s qt4a' % (key_file_path, save_path, apk_path)
cmdline = ['jarsigner', '-digestalg', 'SHA1', '-sigalg', 'MD5withRSA', '-keystore',
key_file_path, '-signedjar', save_path, apk_path, 'qt4a']' '.join(cmdline))
proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE)
out, err = proc.communicate(b'test@123')
if out:'jarsigner: ' + out)
if err:
logging.warn('jarsigner: ' + err)
return save_path
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)
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))
sub_provider_name, authorities + str(i), process)
if activity_list:
for activity in activity_list:
print('Add activity %s' % activity['name'])
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:
print('Save dex %s to %s' % (merge_dex_path, dex_file))
if force_append or low_memory:
# 低内存下直接跳过已有dex,进行加速
dex_index += 1
if os.path.exists(classes_dex_path):
apk_file.extract_file(dex_file, classes_dex_path)
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
print('Merge dex into %s success' % dex_file)
with open(classes_dex_path, 'rb') as f:
if dex_index > 1:
# 合并进非主dex只支持5.0以上系统
print('WARNING: APK can only be installed in android above 5.0')
manifest.min_sdk_version = 21
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 =
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)'%s signature is %s' %
(manifest.package_name, orig_signature))
signature_dict[manifest.package_name] = orig_signature
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):
file_name = os.path.split(apk_path_or_list[i])[-1][:-4] + '-repack.apk'
tmp_apk_path = os.path.join(temp_dir, file_name)
new_path = resign_apk(tmp_apk_path)
if len(out_apk_list) == 1:
return out_apk_list[0]
return out_apk_list
if __name__ == '__main__':
You can’t perform that action at this time.