0. 打印 log
print '[+] Apktool decompiled Target.apk successfully!' + string
#print 后直接加单引号字符串
条件语句:
if not applicationName.startswith(packageName) and applicationName.startswith('.'): #语句格式
print '[+] Target app\'s Application: ', applicationName
else:
print '[+] Target app\'s Application: ', applicationName
1. 获取当前工作路径
retvall = os.getcwd() #查看修改后的工作目录
print 'current work dir :' + retvall
2. 删除当前工作路径下的文件、文件夹
import shutil #导入这个库
remove_without_exception('Target', 'd') #调用
def remove_without_exception(item, type): #删除方法
if type == 'd':
try:
shutil.rmtree(item)
except Exception as e:
pass
else:
try:
os.remove(item)
except Exception as e:
pass
3. 在当前工作路径下执行命令
import subprocess #导入库
subprocess.Popen(["gradlew","clean"], shell=True, stdout=subprocess.PIPE).stdout.read()
# 这句就是在 CMD 下执行 $:gradlew clean 命令
out = subprocess.Popen(["gradlew","build"], shell=True, stdout=subprocess.PIPE).stdout.read()
out.find('BUILD SUCCESSFUL') #判断返回值是否包含标识字符串
#这个返回值是执行命令后的返回参数
4. 改变工作路径
os.chdir('TuokeApk/') #改变工作路径到指定的路径
#这句意思就是进入 TuokeApk 文件夹,命令等同 cd TuokeApk
os.chdir('../') #意思是返回上一级
#命令等同: cd ..
5. 拷贝文件为目标文件
shutil.copyfile(input_filename, 'Target.apk') #拷贝 input_filename 到 Target.apk 文件
#就是将传入的文件、复制为 Target.apk 文件
shutil.copyfile(extracted_dir + '/classes.dex', 'classes.dex') #跨目录拷贝
6. 解析 xml 文件,并获取对应元素的值
from xml.dom import minidom #导入库
import codecs #导入
doc = minidom.parse('Target/AndroidManifest.xml') #解析文件
root = doc.documentElement #获取文件
application_node = root.getElementsByTagName('application')[0] #获取对应的参数条目
applicationName = application_node.getAttribute('android:name') #根据条目访问对应的属性、参数
packageName = root.getAttribute('package') #获取 package 条目
application_node.setAttribute('android:name', 'org.hackcode.ProxyApplication') #往条目中写入属性值
file_handle = codecs.open('Target/AndroidManifest.xml','w', 'utf-8') #codecs 新建,打开 xml
root.writexml(file_handle) #重写 xml
file_handle.close() #这里是保存完毕了
7. 根据字符串路径打开文件
import re #导入模块
file_path = '/hackcode/ProxyApplication.java'
new_file_path = '/hackcode/ProxyApplication2.java'
file_in = open(file_path) #打开文件
file_out = open(new_file_path, 'w') #新建文件并赋予可写权限
while 1:
line = file_in.readline() #逐行读取
if not line:
break
pattern = re.compile(r'.*String.*appClassName.*=.*\".*\";.*') #正则匹配字符串
if re.search(pattern, line): #找到了要更改的行
print '[+] Find \"String appClassName = ...\", replace it with \"' + application_name + '\"'
file_out.write('\t\t\tString appClassName = \"' + application_name + '\";\n') #重写这一行
else:
file_out.write(line) #是从 in 逐行写到 out 内
file_in.close()
file_out.close()
os.remove(file_path)
os.rename(new_file_path, file_path) #更改文件名字,完成字符串匹配更改
8. zip 解压
import zipfile #导入
extracted_dir = un_zip('Target.modified.apk') #调用函数
f = zipfile.ZipFile('TargetApk.zip', 'w', zipfile.ZIP_DEFLATED) #新建可写 zip 文件
f.write('classes.dex') #写入 dex 文件
f.close() #写入完毕
def un_zip(file_name):
"""unzip zip file""" #注释
zip_file = zipfile.ZipFile(file_name) #获取压缩文件,创建 zipfile 对象
if os.path.isdir(file_name + "_files"):
pass
else:
os.mkdir(file_name + "_files")
for names in zip_file.namelist():
zip_file.extract(names,file_name + "_files/") #解压到某个文件夹内,解压全部文件
zip_file.close()
return file_name + "_files" #返回解压后的文件夹名字
8.1 zip 压缩
zip_dir(extracted_dir, 'Target.modified.2.apk') #将 extracted_dir 目录压缩为 Target.modified.2.apk 文件
def zip_dir(dirname,zipfilename): #压缩为 zipfilename
filelist = []
if os.path.isfile(dirname):
filelist.append(dirname)
else :
for root, dirs, files in os.walk(dirname):
for name in files:
filelist.append(os.path.join(root, name))
zf = zipfile.ZipFile(zipfilename, "w", zipfile.zlib.DEFLATED)
for tar in filelist:
arcname = tar[len(dirname):]
zf.write(tar,arcname)
zf.close()
9. 拷贝对应 so 文件
if not os.path.exists(extracted_dir + '/lib/'): #是否存在 lib 文件夹
os.mkdir(extracted_dir + '/lib/') #创建
for item in os.listdir('TuokeApk\app\src\main\libs'): #遍历 libs 下的文件夹目录
if not os.path.exists(extracted_dir + '/lib/' + item):
shutil.copytree('TuokeApk/app/src/main/libs/' + item, extracted_dir + '/lib/' + item) #拷贝树
else:
shutil.copyfile('TuokeApk/app/src/main/libs/' + item + '/libhackcodejiagu.so', extracted_dir + '/lib/' + item + '/libhackcodejiagu.so')
else:
for item in os.listdir(extracted_dir + '/lib/'):
print '[+] item ...' + item
shutil.copyfile('TuokeApk/app/src/main/libs/' + item + '/libhackcodejiagu.so', extracted_dir + '/lib/' + item + '/libhackcodejiagu.so')
.
#!/usr/bin/env python
#coding:utf-8
import subprocess
import shutil
from xml.dom import minidom
import zipfile
import os
import re
import glob
import sys
import codecs
import random
import string
import time
from elf_header import ELF
'''
一、针对目标app不存在自定义application的情况
1.反编译目标app
apktool.bat d Target.apk
2.检测manifest文件是否有自定义的Application,并假设没有自定义Application
3.如果没有自定义Application,则复制smali文件夹,跟反编译后的app下的smali合并: cp -rf smali Target/
4.修改manifest文件,将自定义Application设定为“org.hackcode.ProxyApplication”
5.重打包目标app
6.提取重打包后的apk中的classes.dex文件,并压缩为TargetApk.zip文件,并将重打包的app命名为Target.modified.apk
7.合并TuokeApk项目下的classes.dex和TargetApk.zip(加固),生成classes.dex
8.将合并生成的新classes.dex文件与Target.modified.apk中的classes.dex替换
9.复制TuokeApk项目下的lib目录下的所有文件和文件夹到目标app中
10.将修改后的app重压缩成zip文件
11.签名
'''
def un_zip(file_name):
"""unzip zip file"""
zip_file = zipfile.ZipFile(file_name)
if os.path.isdir(file_name + "_files"):
pass
else:
os.mkdir(file_name + "_files")
for names in zip_file.namelist():
zip_file.extract(names,file_name + "_files/")
zip_file.close()
return file_name + "_files"
def zip_dir(dirname,zipfilename):
filelist = []
if os.path.isfile(dirname):
filelist.append(dirname)
else :
for root, dirs, files in os.walk(dirname):
for name in files:
filelist.append(os.path.join(root, name))
zf = zipfile.ZipFile(zipfilename, "w", zipfile.zlib.DEFLATED)
for tar in filelist:
arcname = tar[len(dirname):]
zf.write(tar,arcname)
zf.close()
def recompile_TuokeApk_Project(application_name):
'''
1.修改 String appClassName = "com.targetapk.MyApplication";
2.重新编译
'''
file_path = 'TuokeApk/app/src/main/java/org/hackcode/ProxyApplication.java'
new_file_path = 'TuokeApk/app/src/main/java/org/hackcode/ProxyApplication2.java'
file_in = open(file_path)
file_out = open(new_file_path, 'w')
print 'rename ApplicationName start'
while 1:
line = file_in.readline()
if not line:
break
pattern = re.compile(r'.*String.*appClassName.*=.*\".*\";.*')
if re.search(pattern, line):
print '[+] Find \"String appClassName = ...\", replace it with \"' + application_name + '\"'
file_out.write('\t\t\tString appClassName = \"' + application_name + '\";\n')
else:
file_out.write(line)
file_in.close()
file_out.close()
os.remove(file_path)
os.rename(new_file_path, file_path)
# 重新编译TuokeApk工程
os.chdir('TuokeApk/')
subprocess.Popen(["gradlew","clean"], shell=True, stdout=subprocess.PIPE).stdout.read()
out = subprocess.Popen(["gradlew","build"], shell=True, stdout=subprocess.PIPE).stdout.read()
if out.find('BUILD SUCCESSFUL') < 0:
print 'Build error!'
return False
print '[+] Rebuild TuokeApk project successfully!'
os.chdir('../')
return True
def remove_without_exception(item, type):
if type == 'd':
try:
shutil.rmtree(item)
except Exception as e:
pass
else:
try:
os.remove(item)
except Exception as e:
pass
def clean():
print 'clean start'
remove_without_exception('Target', 'd')
remove_without_exception('Target.modified.apk_files', 'd')
remove_without_exception('Target.apk', 'f')
remove_without_exception('Target.modified.apk', 'f')
remove_without_exception('Target.modified.2.apk', 'f')
remove_without_exception('classes.dex', 'f')
remove_without_exception('TargetApk.zip', 'f')
remove_without_exception('tuoke.dex', 'f')
os.chdir('TuokeApk/') #改变工作路径到指定的路径
retval = os.getcwd() #查看修改后的工作目录
subprocess.Popen(["gradlew","clean"], shell=True, stdout=subprocess.PIPE).stdout.read()
os.chdir('../')
def genRandomStr(length):
chars=string.ascii_letters+string.digits
return ''.join([random.choice(chars) for i in range(length)])#得出的结果中字符会有重复的
def modify_ehdr_and_delete_shdr(apk_dir):
'''
修改ELF header(e_shoff和e_shnum属性)和删除section header table
TODO: 指定目标so文件
'''
for root, dirs, files in os.walk(apk_dir):
for name in files:
filepath = root + os.path.sep + name
if filepath.endswith('libhackcodejiagu.so'):
print ' - Modifying \"', filepath, '\" ELF header...'
dex = ELF(filepath)
file_size = os.path.getsize(filepath)
shdr_offset = dex.elf32_Ehdr.e_shoff
shdr_size = dex.elf32_Ehdr.e_shnum * dex.elf32_Ehdr.e_shentsize
src_file = file(filepath, 'rb')
dst_file = file(filepath + '2', 'wb')
# 1.破坏ELF Header
dst_file.write(src_file.read(32)) # 保存e_shoff之前的内容
src_file.read(4)
dst_file.write(genRandomStr(4)) # 修复e_shoff
dst_file.write(src_file.read(12)) # 保存e_shoff到e_shnum之间的内容
src_file.read(2)
dst_file.write(genRandomStr(2)) # 修复e_shnum
# 2.删除section header table
#读取section header table之前的内容
dst_file.write(src_file.read(shdr_offset - 50))
#读取section header table之后的内容
src_file.seek(shdr_offset + shdr_size, 0)
dst_file.write(src_file.read())
src_file.close()
dst_file.close()
shutil.move(filepath + '2', filepath)
def main(filepath = None):
clean()
if filepath:
input_filename = filepath
else:
input_filename = sys.argv[1]
print 'copy apk :' + input_filename
shutil.copyfile(input_filename, 'Target.apk')
retvall = os.getcwd() #查看修改后的工作目录
print 'current work dir :' + retvall
# Step1: 反编译目标app
out = subprocess.Popen('apktool.bat d Target.apk', stdout=subprocess.PIPE).stdout.read()
if out.find('error') > 0 or out.find('exception') > 0:
print '[Error] apktool decompiled error!'
return
print '[+] Apktool decompiled Target.apk successfully!'
# Step2: 检测manifest文件是否有自定义的Application
doc = minidom.parse('Target/AndroidManifest.xml')
root = doc.documentElement
application_node = root.getElementsByTagName('application')[0]
applicationName = application_node.getAttribute('android:name')
packageName = root.getAttribute('package')
if applicationName:
if not applicationName.startswith(packageName) and applicationName.startswith('.'):
applicationName = packageName + applicationName
print '[+] Target app\'s Application: ', applicationName
# Step3: 修改JiguApk工程中ProxyApplication中的applicationName变量为目标app的Application名称
recompile_TuokeApk_Project(applicationName)
else:
print '[+] Target.apk has no self-defined Application!'
applicationName = 'com.targetapk.MyApplication'
recompile_TuokeApk_Project(applicationName)
# Step3: 复制smali文件夹,跟反编译后的app下的smali合并
print '[+] Copy smali folder into Target folder...'
out = subprocess.Popen('cp -rf smali Target/', stdout=subprocess.PIPE).stdout.read()
# Step4: 修改manifest文件,将自定义Application设定为“org.hackcode.ProxyApplication”
print '[+] Modified AndroidManifest.xml...'
application_node.setAttribute('android:name', 'org.hackcode.ProxyApplication')
file_handle = codecs.open('Target/AndroidManifest.xml','w', 'utf-8')
root.writexml(file_handle)
file_handle.close()
# Step5: 重打包目标app
out = subprocess.Popen('apktool.bat b Target', stdout=subprocess.PIPE).stdout.read()
if out.find('error') > 0 or out.find('exception') > 0:
print '[Error] apktool recompiled error!'
return
print '[+] Apktool recompiled Target successfully!'
# Step6: 将重打包的app命名为Target.modified.apk,并提取重打包后的apk中的classes.dex文件,并压缩为TargetApk.zip文件
print '[+] Rename target app: \"Target.modified.apk\"'
shutil.copyfile('Target/dist/Target.apk', 'Target.modified.apk')
extracted_dir = un_zip('Target.modified.apk')
print '[+] Extracted classes.dex from Target.modifed.apk into TargetApk.zip'
shutil.copyfile(extracted_dir + '/classes.dex', 'classes.dex')
print '>>> copyfile :' + extracted_dir
if os.path.exists(extracted_dir + '/classes2.dex'): #过滤 multiDEX 型的 app
shutil.copyfile('Target.modified.apk', 'TargetApk.zip')
else:
#写入classes.dex
f = zipfile.ZipFile('TargetApk.zip', 'w', zipfile.ZIP_DEFLATED)
f.write('classes.dex')
f.close()
os.remove('classes.dex')
# Step7: 合并TuokeApk/bin/classes.dex和TargetApk.zip(加固),生成classes.dex
shutil.copyfile('TuokeApk/app/build/intermediates/transforms/dex/release/0/classes.dex', 'tuoke.dex')
subprocess.Popen('java -jar dexjoint.jar tuoke.dex TargetApk.zip', stdout=subprocess.PIPE).stdout.read()
# Step8: 将合并生成的新classes.dex文件与Target.modified.apk中的classes.dex 替换
print '[+] Replace \"%s\" with \"classes_joint.dex\"' % (extracted_dir + '/classes.dex', )
shutil.copyfile('classes_joint.dex', extracted_dir + '/classes.dex')
# Step9: 复制TuokeApk/libs目录下的所有文件和文件夹到目标app中
print '[+] Copying TuokeApk\app\src\main\libs...'
if not os.path.exists(extracted_dir + '/lib/'):
os.mkdir(extracted_dir + '/lib/')
for item in os.listdir('TuokeApk\app\src\main\libs'):
if not os.path.exists(extracted_dir + '/lib/' + item):
shutil.copytree('TuokeApk/app/src/main/libs/' + item, extracted_dir + '/lib/' + item)
else:
shutil.copyfile('TuokeApk/app/src/main/libs/' + item + '/libhackcodejiagu.so', extracted_dir + '/lib/' + item + '/libhackcodejiagu.so')
else:
for item in os.listdir(extracted_dir + '/lib/'):
print '[+] item ...' + item
shutil.copyfile('TuokeApk/app/src/main/libs/' + item + '/libhackcodejiagu.so', extracted_dir + '/lib/' + item + '/libhackcodejiagu.so')
# 破坏SO文件的ELF头部(删除 ELF header)
modify_ehdr_and_delete_shdr(extracted_dir)
# Step10: 将修改后的app重压缩成zip文件
print '[+] Compress %s folder into Target.modified.2.apk' % extracted_dir
zip_dir(extracted_dir, 'Target.modified.2.apk')
# Step11: 签名
print '[+] Signning...'
output_filename = input_filename[:input_filename.rfind('apk')] + 'signed.apk'
retvall = os.getcwd() #查看修改后的工作目录
print '[+] Signning file name : ' + output_filename + ', work : ' + retvall
out = subprocess.Popen('java -jar sign/signapk.jar sign/testkey.x509.pem sign/testkey.pk8 Target.modified.2.apk ' + output_filename, stdout=subprocess.PIPE).stdout.read()
#clean()
if __name__ == '__main__':
start = time.time()
main()
end = time.time()
print "Total time running %s seconds" %(str(end - start))
这段代码的意思:一键加固 APK 脚本