Android apk解析
功能
批量解析apk,apk信息的判断,可批量执行apk配置文件检查用例
一、使用说明
- 初次使用apktool,将apktool.dat及apktool.jar放置“C:\Windows”目录下,运行.dat脚本
- 使用.apk文件时,要更改存放apk的文件夹地址、存在结果的地址、aapt地址及当期apk版本号,其他可视情况更改
- 主要用于批量处理apk配置信息: 版本号 appkey 分析系统地址 配置中心地址 sdkVersion VersionCode VersionName 包名
- 生成结果为txt文本形式
- 运行过程中会生成临时文件,但除结果外,其他临时文件均会删除
- 由于需要反编译apk,故耗时会较长,一个apk反编译时间大概在30s左
- py文件编译使用的是Python3.9
- aapt路径中的aapt非某个文件或可执行文件,aapt路径就是aapt.exe文件所在的路径+“\aapt”
- 由于aapt解析的问题,apk路径不能含有中文及中文字符,否则会出现“dump failed because no AndroidManifest.xml found”的解析错误,导致解析失败
- apk_analysis.py会在当前目录下生成data.txt数据文件及其他缓存文件,故最好将apk配置文件解析程序放置在一个文件夹中
- 首次使用需输入apk路径、存放结果路径及aapt路径,及确认appkey信息/apk版本信息后续再需修改可再执行时,输入对应命令进行更改,也可对data.txt文件进行更改对应设置
- 每次启动时均会打印apk路径、存放结果路径及aapt路径及apk版本信息,由于每次测试的apk版本不同,故每次启动时,均会询问是否更改apk版本信息
- 执行解析完成后,会在终端上打印解析结果,也会在存放结果路径下生成对应result.txt文件
二、源码
1.apk_analysis.py
# -*- coding: utf-8 -*-
import zipfile
import os,sys
import xml.etree.cElementTree as ET
import string
import subprocess
import time
from shutil import copyfile
from pathlib import Path
#文件处理
class XmlInfo:
#版本号对比
def version_info(self,version,Version_Name,fd):
vua=1
for i,j in zip(version.split('.'),Version_Name.split('.')):
if int(i)!=int(j):
vua=0
if vua==0:
# pprint("版本号错误!")
fd.write("版本号错误!\n")
else:
# pprint("版本号正确!")
fd.write("版本号正确!\n")
return
#读取xml文件
def read_xml(self,tempPath):
xmlinfo=[]
fd=open(tempPath,'r+',encoding='utf-8')
for line in fd.readlines():
xmlinfo.append(str(line).split())
fd.close()
return xmlinfo
#关键字查找
def find_value(self,xmlinfo,key):
for i in xmlinfo:
if key in str(i):
if len(i)>1:
value=i[2].split('"')[1]
else:
value=i[0].split('>')[1].split('<')[0]
return value
#网址查找
def find_url(self,xmlinfo,key):
num=0
num_xxxx=0
url=[]
for i in xmlinfo:
if "xxxx(网址关键字)" in str(i):
num=num+1
elif "xxxx(网址关键字)" in str(i):
num_xxxx=num_xxxx+1
if key in str(i):
url.append(i[0])
return num,num_xxxx,url
class ApkInfo:
def __init__(self,apkOutPath,Out_Path):
self.apkOutPath=apkOutPath
self.Out_Path=Out_Path
self.xml_info=XmlInfo()
#apk信息处理
def apk_text(self,key,appkey_valuse,Version_Name):
fd=open(self.apkOutPath,'a')
for tempfile in os.listdir(self.Out_Path):
if tempfile=='AndroidManifest.xml':
xmlinfo=self.xml_info.read_xml(os.path.join(self.Out_Path,tempfile))
appkey=self.xml_info.find_value(xmlinfo,'android:name="xxx_APPKEY"')
analysis_url=self.xml_info.find_value(xmlinfo,'android:name="xxxx_URL"')
# pprint("appkey:"+appkey)
fd.write("appkey:"+appkey+'\n')
if appkey==appkey_valuse:
# pprint("apk包的appkey值正确!\n")
fd.write("apk包的appkey值正确!\n\n")
else:
# pprint("apk包的appkey值错误!\n")
fd.write("apk包的appkey值错误!\n\n")
# print("appkey:%s"%appkey)
# pprint("分析地址为:"+analysis_url)
fd.write("分析地址为:"+analysis_url+'\n')
if key=='xxxx':
if analysis_url=='http://xxxx':
# pprint("apk包的分析地址正确!\n")
fd.write("apk包的分析地址正确!\n\n")
else:
# pprint("apk包的分析地址错误\n")
fd.write("apk包的分析地址错误!\n\n")
elif key=='xxxx':
# pprint("apk包的分析地址正确!\n")
if analysis_url=='http://xxxxx':
fd.write("apk包的分析地址正确!\n\n")
else:
# pprint("apk包的分析地址错误\n")
fd.write("apk包的分析地址错误!\n\n")
for tempfile in os.listdir(self.Out_Path+r"\assets"):
if tempfile=='config.xml':
# print(tempfile)
xmlinfo=self.xml_info.read_xml(os.path.join(self.Out_Path+r"\assets",tempfile))
version=self.xml_info.find_value(xmlinfo,"<Version>")
# pprint("版本号为:"+version)
fd.write("版本号为:"+version+'\n')
self.xml_info.version_info(version,Version_Name,fd)
fd.write('\n')
# print("版本号为:%s"%version)
if tempfile=='network.xml':
xmlinfo=self.xml_info.read_xml(os.path.join(self.Out_Path+r"\assets",tempfile))
url=[]
num,num_xxxx,url=self.xml_info.find_url(xmlinfo,key)
# print("xxxxx系列地址有"+str(num)+'个')
fd.write("xxxx系列地址有"+str(num)+'个'+'\n')
# print("xxx系列地址有"+str(num_xxxx)+'个')
fd.write("xxx系列地址有"+str(num_xxxx)+'个'+'\n')
if len(url)>0:
# pprint("其中"+key+'系列地址为:')
fd.write("其中"+key+'系列地址为:\n')
for i in url:
# pprint(" "+i)
fd.write(" "+i+'\n')
else:
# pprint("配置中心地址列表正确!\n")
fd.write("配置中心地址列表正确!\n\n")
fd.close()
#处理aapt打印的信息处理
def aapt_version(self,readinfo,apk_name,list_value):
fd=open(self.apkOutPath,'a')
for i in readinfo:
if 'package' in str(i):
package_name=i[1].split('=')[1].strip("'")
# pprint('\n'+"packagename:"+package_name)
fd.write('\n'+"packagename:"+package_name+'\n')
# print("packagename:%s"%package_name)
if apk_name=='xxxx':
if package_name=='xxxxx':
# pprint('包名正确!')
fd.write('包名正确!\n')
else:
# pprint('包名错误!')
fd.write('包名错误!\n')
else:
if package_name=='xxxxx':
# pprint('包名正确!')
fd.write('包名正确!\n')
else:
# pprint('包名正确!')
fd.write('包名错误!\n')
versionCode=i[2].split('=')[1].strip("'")
# pprint("versionCode:"+versionCode)
fd.write("versionCode:"+versionCode+'\n')
if int(versionCode) >=int(list_value.get('versionCode')):
# pprint("app运营版本比线上版本大!")
fd.write("app运营版本比线上版本大!\n")
else:
# pprint("app运营版本比线上版本小!")
fd.write("app运营版本比线上版本小!\n")
# print("versionCode:%s"%versionCode)
versionName=i[3].split('=')[1].strip("'")
# pprint("VersionName:"+versionName)
fd.write("VersionName:"+versionName+'\n')
self.xml_info.version_info(versionName,list_value.get('VersionName'),fd)
# print("VersionName:%s"%versionName)
elif 'sdkVersion' in str(i):
sdkVersion=i[0].split(':')[1].strip("'")
# pprint("sdkVersion:"+sdkVersion)
fd.write("sdkVersion:"+sdkVersion+'\n')
if sdkVersion==list_value.get("sdkVersion"):
# pprint("支持的最低系统版本正确!")
fd.write("支持的最低系统版本正确!\n")
else:
# pprint("支持的最低系统版本错误!")
fd.write("支持的最低系统版本错误!\n")
# print("sdkVersion:%s"%sdkVersion)
elif 'targetSdkVersion' in str(i) and len(i)<2:
targetSdkVersion=i[0].split(':')[1].strip("'")
# pprint("targetSdkVersion:"+targetSdkVersion)
fd.write("targetSdkVersion:"+targetSdkVersion+'\n')
if targetSdkVersion==list_value.get("targetSdkVersion"):
# pprint("开发编译的系统版本正确!\n\n")
fd.write("开发编译的系统版本正确!\n\n\n")
else:
# pprint("开发编译的系统版本错误!\n\n")
fd.write("开发编译的系统版本错误!\n\n\n")
# print("targetSdkVersion:%s"%targetSdkVersion)
fd.close()
return
#调用其他程序
class CmdInfo:
#调用apktool,反编译apk
def decompile_apk(self,apkPath,apkOutPath):
cmd = 'apktool d -f '+apkPath+' -o '+apkOutPath+' >./textlog.txt'
# print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
num=os.system(cmd)
if num==0:
os.remove('./textlog.txt')
# print("反编译成功!")
return
else:
print("反编译失败!")
return -1
#调用aapt程序,打印apk信息
def aapt_apk(self,aaptPath,apkPath):
xml_info=XmlInfo()
# if os.path.exists('./log.txt'):
# os.remove('./log.txt')
command = aaptPath+' dump badging '+apkPath+' >./log.txt' #可以直接在命令行中执行的命令
# print(command)
r = os.popen(command) #执行该命令
time.sleep(1)
readinfo=xml_info.read_xml('./log.txt')
os.remove('./log.txt')
return readinfo
class Fileinfo:
def __init__(self,apkPath,aaptPath,apkOutPath,list_value):
if Path(apkPath).exists() and Path(aaptPath).exists():
self.apkPath=apkPath
self.aaptPath=aaptPath+r'\aapt'
else:
return
self.apkOutPath=apkOutPath+r'\result.txt'
if os.path.isfile(self.apkOutPath):
os.remove(self.apkOutPath)
self.list_value=list_value
#初始化文件
def initialize_file(self,Out_Path):
#判断文件是否存在,不存在则创建
if os.path.isdir(Out_Path):
# os.walk的第一个参数是要遍历并删除子文件夹和文件的目录
# 上面的代码运行后,文件夹"dex"里的文件夹和文件全被删除,但是文件夹"dex"还存在
# 具体可以查下os.walk()的用法,以及其参数topdown的功能
for root, dirs, files in os.walk(Out_Path,topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
else:
os.mkdir(Out_Path)
# print("创建临时目录成功!")
return
#初始化文件,调用函数
def info_apk(self,apk_Path,key,appkey_valuse):
# 初始化文件夹为空
self.initialize_file(r"dex")
cmd_info=CmdInfo()
apk_info=ApkInfo(self.apkOutPath,r"dex")
# 反编译apk
cmd_info.decompile_apk(apk_Path,r"dex")
if len(os.listdir("dex"))<1:
fd=open(self.apkOutPath,'a')
# pprint("反编译失败,无法执行!")
fd.write("反编译失败,无法执行!\n\n")
fd.close()
else:
# AndroidManifest.xml
apk_info.apk_text(key,appkey_valuse,self.list_value.get('VersionName'))
# aapt工具调用
readinfo=cmd_info.aapt_apk(self.aaptPath,apk_Path)
if len(readinfo)>0:
apk_info.aapt_version(readinfo,apk_Path.split('_')[4],self.list_value)
else:
fd=open(self.apkOutPath,'a')
# pprint("路径含有中文,无法aapt解析错误!")
fd.write("\n路径含有中文,无法aapt解析错误!\n\n")
fd.close()
self.initialize_file(r"dex")
try:
os.rmdir(r"dex")
# print("删除临时目录成功!")
except :
self.initialize_file(r"dex")
#遍历文件夹
def file_apk(self,appkey_drit):
for tempfile in os.listdir(self.apkPath):
if '.apk' in tempfile:
if " " in tempfile:
os.rename(os.path.join(self.apkPath,tempfile),os.path.join(self.apkPath,tempfile.replace(" ","")))
tempfile=tempfile.replace(" ","")
if 'xxx' in tempfile:
key='xxxx'
fd=open(self.apkOutPath,'a')
# pprint(tempfile+'\n')
fd.write(tempfile+'\n\n')
fd.close()
appkey_valuse=appkey_drit.get('dev')
self.info_apk(os.path.join(self.apkPath,tempfile),key,appkey_valuse)
elif 'xxxx' not in tempfile:
key='xxxx'
fd=open(self.apkOutPath,'a')
# pprint(tempfile+'\n')
fd.write(tempfile+'\n\n')
fd.close()
try:
appkey_valuse=appkey_drit.get(tempfile.split('_')[4])
except :
appkey_valuse=None
if appkey_valuse!=None:
self.info_apk(os.path.join(self.apkPath,tempfile),key,appkey_valuse)
else:
fd=open(self.apkOutPath,'a')
# print('未找到该键值,请确认键值字典中是否含有该键值'+'\n')
fd.write('未找到该键值,请确认键值字典中是否含有该键值'+'\n\n')
fd.close()
# print("apk解析完成")
return
#终端界面
class uiInfo:
def apk_version(self,list_value,type_value,tag=None):
key_info=input("请输入要更改的项((enter跳过不更改,多个更改项以空格隔开):\n")
key_pos=None
if len(key_info)>0:
key_info=key_info.split(" ")
n=0
for i in key_info:
if i not in list(list_value.keys()):
if tag==None:
print("更改项输入存在错误,请重新输入")
return self.apk_version(list_value,type_value)
else:
new_key=input("是否新增%s渠道包:(T/F)?\n"%i)
if new_key=='T' or new_key=='t':
new_key=i
key_pos=n
else:
key_info.remove(i)
n=n+1
if len(key_info)<1:
print("无更改项\n")
return list_value
value_info=input("请输入更改的数据(多个数据以空格隔开,每个数据需和上一步输入的信息项一一对应):\n")
value_info=value_info.split(" ")
if len(value_info)!=len(key_info):
print("更改项与更改数据不对应,请重新输入")
return self.apk_version(list_value,type_value)
else:
k=0
if type_value=="conserve":
for i in key_info:
if tag!=None and k==key_pos:
list_value[new_key]=value_info[k]
else:
list_value[i]=value_info[k]
k=k+1
print("\n更改后的信息:")
for key in list_value:
print(key+":"+list_value[key])
return list_value
else:
for i in key_info:
if tag!=None and k==key_pos:
if len(key_info)==1:
self.replace_info(i,value_info[k],1)
else:
self.replace_info(i,value_info[k],1)
else:
self.replace_info(i,value_info[k])
list_value[i]=value_info[k]
k=k+1
print("\n更改后的信息:")
for key in list_value:
print(key+":"+list_value[key])
def apk_write(self,list_value):
f=open('data.txt','a')
# if n!=0:
# list_value=n
for key in list_value:
f.write(key+":"+list_value[key]+'\n')
f.close()
def replace_info(self,key,value,tag=None):
f=open('data.txt','r+')
value=value+"\n"
read_line=f.readlines()
f.seek(0, 0)
for line in read_line:
if tag==None:
if key in line:
line=line.replace(line.split(":")[1],value)
elif tag==1:
if "versionCode" in line:
insert_line=key+":"+value
f.write(insert_line)
elif tag==2:
if key in line:
line=line.replace(line.split("=")[1],value)
f.write(line)
f.close()
def create_txt(self):
f=open('data.txt','a')
print("初次使用,请输入apk地址及结果存放地址\n")
while True:
apkPath=input("请输入存放apk的文件夹地址:\n")
if Path(apkPath).exists() and len(apkPath)>0:
f.write("apkPath="+apkPath+'\n')
break
else:
print("该文件地址错误或为空,请重新输入!")
while True:
apkOutPath=input("请输入存放结果的地址:(enter跳过默认与存放apk地址一致)\n")
if len(apkOutPath)<1:
apkOutPath=apkPath
f.write("apkOutPath="+apkOutPath+'\n')
print(apkOutPath)
break
else:
if Path(apkOutPath).exists():
f.write("apkOutPath="+apkOutPath+'\n')
break
else:
print("该文件地址错误请重新输入!")
while True:
aaptPath=input("请输入存放aapt地址:\n")
if Path(aaptPath).exists() and len(aaptPath)>0:
f.write("aaptPath="+aaptPath+'\n')
break
else:
print("该文件地址错误或为空,请重新输入!")
f.close()
#各渠道对应的appkey,若对应渠道包未找到对应appkey则不打印apk信息
appkey_drit={
'xxx':'xxx'
}
#apk核对信息
list_value={
'xxx':'xxx'
}
print("各渠道对应的appkey")
for key in appkey_drit:
print(key+":"+appkey_drit[key])
appkey_result=input("是否更改渠道appkey:(T/F)?\n")
if appkey_result=="T" or appkey_result=='t':
appkey_drit=self.apk_version(appkey_drit,"conserve",1)
self.apk_write(appkey_drit)
print("默认apk核对信息")
for key in list_value:
print(key+":"+list_value[key])
key_result=input("是否更改apk核对信息:(T/F)?\n")
if key_result=="T" or key_result=='t':
list_value=self.apk_version(list_value,"conserve")
self.apk_write(list_value)
self.read_txt()
def read_txt(self):
f=open('data.txt','r')
read_line=f.readlines()
f.close()
apkPath=read_line[0].strip("\n").split("=")[1]
apkOutPath=read_line[1].strip("\n").split("=")[1]
aaptPath=read_line[2].strip("\n").split("=")[1]
print("存放apk的文件夹地址:\n"+apkPath)
print("存放结果的地址:\n"+apkOutPath)
print("aapt地址:\n"+aaptPath)
version=read_line[-4:]
list_value={}
print("apk核对信息:")
for i in version:
print(i.strip("\n"))
key_result=input("是否更改apk核对信息:(T/F)?\n")
if key_result=="T" or key_result=='t':
appkey_drit=self.apk_version(version,"write")
print("appkey:appkey查询更改 apk:apk信息查询更改 apkPath:更改存放apk的文件夹地址 apkOutPath:更改存放结果的地址 aaptPath:更改aapt地址 Exit|N:退出")
while True:
cmd_info=input("是否执行apk配置文件读取?(T/F)\n")
f=open('data.txt','r')
read_lines=f.readlines()
version=read_lines[-4:]
appkey=read_lines[3:-4]
list_value={}
appkey_drit={}
for i in version:
key=i.strip("\n").split(":")[0]
value=i.strip("\n").split(":")[1]
list_value[key]=value
for i in appkey:
key=i.strip("\n").split(":")[0]
value=i.strip("\n").split(":")[1]
appkey_drit[key]=value
f.close()
if cmd_info=="T" or cmd_info=='t':
fileinfo=Fileinfo(apkPath,aaptPath,apkOutPath,list_value)
fileinfo.file_apk(appkey_drit)
print("配置文件执行结果:")
apkOutPath=apkOutPath+"\\result.txt"
try:
fd=open(apkOutPath,"r")
except:
print("读取结果失败!")
else:
read_lines=fd.readlines()
for line in read_lines:
print(line.strip("\n"))
fd.close()
elif cmd_info=="appkey":
print("各渠道对应的appkey")
for i in appkey:
print(i.strip("\n"))
self.apk_version(appkey_drit,"write",1)
elif cmd_info=="apk":
print("apk对应信息:")
for i in version:
print(i.strip("\n"))
self.apk_version(list_value,"write")
elif cmd_info=="apkPath":
while True:
apkPath=input("请输入存放apk的文件夹地址:\n")
if Path(apkPath).exists():
self.replace_info("apkPath",apkPath,2)
print("更改后的apk的文件夹地址为:\n"+apkPath)
break
else:
print("该文件地址错误请重新输入!")
elif cmd_info=="apkOutPath":
while True:
apkOutPath=input("请输入存放结果的地址:\n")
if Path(apkOutPath).exists():
self.replace_info("apkPath",apkOutPath,2)
print("更改后的存放结果的地址为:\n"+apkOutPath)
break
else:
print("该文件地址错误请重新输入!")
elif cmd_info=="aaptPath":
while True:
aaptPath=input("请输入存放结果的地址:\n")
if Path(aaptPath).exists():
self.replace_info("aaptPath",aaptPath,2)
print("更改后的存放结果的地址为:\n"+aaptPath)
break
else:
print("该文件地址错误请重新输入!")
else:
break
if __name__=='__main__':
uiinfo=uiInfo()
try:
f=open('data.txt','r')
except:
uiinfo.create_txt()
else:
f.seek(0,0)
uiinfo.read_txt()
2.apktool.bat
@echo off
if "%PATH_BASE%" == "" set PATH_BASE=%PATH%
set PATH=%CD%;%PATH_BASE%;
chcp 65001 2>nul >nul
java -jar -Duser.language=en -Dfile.encoding=UTF8 "%~dp0\apktool.jar" %*
apktool下载地址
apktool.jar文件下载地址