Android apk解析


功能

批量解析apk,apk信息的判断,可批量执行apk配置文件检查用例
在这里插入图片描述


一、使用说明

  1. 初次使用apktool,将apktool.dat及apktool.jar放置“C:\Windows”目录下,运行.dat脚本
  2. 使用.apk文件时,要更改存放apk的文件夹地址、存在结果的地址、aapt地址及当期apk版本号,其他可视情况更改
  3. 主要用于批量处理apk配置信息: 版本号 appkey 分析系统地址 配置中心地址 sdkVersion VersionCode VersionName 包名
  4. 生成结果为txt文本形式
  5. 运行过程中会生成临时文件,但除结果外,其他临时文件均会删除
  6. 由于需要反编译apk,故耗时会较长,一个apk反编译时间大概在30s左
  7. py文件编译使用的是Python3.9
  8. aapt路径中的aapt非某个文件或可执行文件,aapt路径就是aapt.exe文件所在的路径+“\aapt”
  9. 由于aapt解析的问题,apk路径不能含有中文及中文字符,否则会出现“dump failed because no AndroidManifest.xml found”的解析错误,导致解析失败
  10. apk_analysis.py会在当前目录下生成data.txt数据文件及其他缓存文件,故最好将apk配置文件解析程序放置在一个文件夹中
  11. 首次使用需输入apk路径、存放结果路径及aapt路径,及确认appkey信息/apk版本信息后续再需修改可再执行时,输入对应命令进行更改,也可对data.txt文件进行更改对应设置
  12. 每次启动时均会打印apk路径、存放结果路径及aapt路径及apk版本信息,由于每次测试的apk版本不同,故每次启动时,均会询问是否更改apk版本信息
  13. 执行解析完成后,会在终端上打印解析结果,也会在存放结果路径下生成对应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文件下载地址

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值