Android 逆向APK 文件处理脚本 ApkTool.py ( 脚本简介 | 用法 | 分析 APK 文件 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向APK 文件处理脚本 ApkTool.py ( 脚本简介 | 用法 | 分析 APK 文件 )相关的知识,希望对你有一定的参考价值。





一、APK 文件处理脚本 ApkTool.py



ApkTool.py 是一个 APK 文件处理脚本 , 主要针对 APK 文件进行各种处理 , 如文件分析 ;

该脚本需要使用 apktool.jar 和 aapt.exe 工具 , 将这两个文件放在 ApkTool.py 同级目录中 ;


完整运行环境参考 https://github.com/han1202012/APK ;


ApkTool.py 脚本内容 :

# coding=utf-8
import os
import sys
import argparse
from subprocess import Popen, PIPE

import sys
reload(sys)
sys.setdefaultencoding('utf8')


class ApkTool:
    def __init__(self, keystore=None, password=None, alias=None):
        if sys.platform == 'win32':
            self.file_separator = '\\\\'
        else:
            self.file_separator = '/'
        path = ''
        if hasattr(sys, '_MEIPASS'):
            path = sys._MEIPASS + self.file_separator
        self.apktooljar = path + 'apktool.jar'
        self.aapt = path + 'aapt.exe'
        self.objdump_x86 = path + 'objdump_x86.exe'
        self.objdump_arm = path + 'objdump_arm.exe'
        if keystore is None:
            self.keystore = path + 'mykey-123456.keystore'
        else:
            self.keystore = keystore
        if password is None:
            self.password = '123456'
        else:
            self.password = password
        if alias is None:
            self.alias = 'mykey'
        else:
            self.alias = alias
        self.cur_apk = {}
        return

    def unpack(self, apk, path):
        cmd = 'java -jar ' + self.apktooljar + (' d -f -o %s %s' % (path, apk))
        os.system(cmd)
        return

    def pack(self, path, apk):
        cmd = 'java -jar ' + self.apktooljar + (' b %s -o %s' % (path, apk))
        os.system(cmd)
        return

    def sign(self, apk, signed_apk):
        path, file = os.path.split(signed_apk)
        if os.path.exists(path) is False:
            os.makedirs(path)
        keystore = ' -keystore %s -storepass %s' % (self.keystore, self.password)
        signedjar = ' -signedjar %s %s -digestalg SHA1 -sigalg MD5withRSA %s' % (signed_apk, apk, self.alias)
        cmd = 'jarsigner -verbose ' + keystore + signedjar
        print(cmd)
        os.system(cmd)

    def get_apk_label(self, apk_path):
        pipe = Popen([self.aapt, 'dump', 'badging', apk_path], stdout=PIPE)
        if pipe is not None:
            while True:
                line = pipe.stdout.readline()
                line = line.decode('utf-8')
                if len(line) == 0:
                    break
                pos = line.find('application-label:')
                if pos != -1:
                    return line[pos + 19:len(line) - 3]  # \\r\\n占了2个,加上单引号一共3个字符
        return ''

    def get_apk_package_name(self, apk):
        pipe = Popen([self.aapt, 'dump', 'badging', apk], stdout=PIPE)
        if pipe is not None:
            while True:
                line = pipe.stdout.readline()
                line = line.decode('utf-8')
                if len(line) == 0:
                    break
                pos = line.find('package:')
                if pos != -1:
                    return line[pos + 8:len(line) - 2]  # \\r\\n占了2个,加上单引号一共3个字符
        return ''


def get_game_engine(libpath, file_separator):
    data = {
        'libcocos2dcpp.so': 'cocos引擎 cpp',
        'libcocos2dlua.so': 'cocos引擎 lua',
        'libcocos2djs.so': 'cocos引擎 javascipt',
        'libunity.so': 'unity3D引擎',
        'libgdx.so': 'libgdx引擎'
    }
    dir = ['armeabi-v7a', 'armeabi', 'x86']
    for d in dir:
        lib = libpath + d + file_separator
        for f in data.keys():
            if os.path.exists(lib + f):
                return data[f]
    return '未知引擎'


def analyse(apk, tool):
    path, file = os.path.split(apk)
    if len(path) == 0:
        path = '.'
    out_name = file[:-4]
    out_txt = out_name + '.txt'
    f_out = open(out_txt, 'w+')
    line = '文件名称:%s\\n' % apk
    f_out.write(line)
    line = '应用名称:%s\\n' % tool.get_apk_label(apk)
    f_out.write(line)
    line = '应用信息:%s\\n' % tool.get_apk_package_name(apk)
    f_out.write(line)
    unpack_path = path + tool.file_separator + 'unpack' + tool.file_separator + out_name
    if os.path.exists(unpack_path) is False:
        os.makedirs(unpack_path)
    repack_path = path + tool.file_separator + 'repack' + tool.file_separator + out_name + '.apk'
    if os.path.exists(path + tool.file_separator + 'repack') is False:
        os.makedirs(path + tool.file_separator + 'repack')
    sign_path = path + tool.file_separator + 'sign' + tool.file_separator + out_name + '.apk'
    if os.path.exists(path + tool.file_separator + 'sign') is False:
        os.makedirs(path + tool.file_separator + 'sign')
    if os.path.exists(unpack_path + tool.file_separator + 'lib') is False:
        tool.unpack(apk, unpack_path)
    if os.path.exists(repack_path) is False:
        tool.pack(unpack_path, repack_path)
    if os.path.exists(sign_path) is False:
        tool.sign(repack_path, sign_path)
    if os.path.exists(repack_path) is False:
        line = '打包检测:重打包失败,无法重打包\\n'
    else:
        line = '打包检测:重打包成功\\n'
        is_repack_ok = True
    f_out.write(line)
    if os.path.exists(sign_path) is False:
        line = '签名检测:重签名失败,无法重签名\\n'
    else:
        line = '签名检测:重签名成功\\n'
    f_out.write(line)
    libpath = unpack_path + tool.file_separator
    libpath += 'lib' + tool.file_separator
    line = '引擎检测:%s\\n' % get_game_engine(libpath, tool.file_separator)
    f_out.write(line)
    f_out.write(
        '----------------------------------------------------------------------------------------------------------------------------------\\n')
    f_out.close()
    pass


def main():
    parser = argparse.ArgumentParser(prog=sys.argv[0], usage='%(prog)s [options]')
    help = """help 或者 -h 显示本帮助文档 """
    parser.add_argument('-help', help=help, action='store_const', const='help')
    parser.add_argument('-keystore', nargs='?', help='指定签名文件,默认mykey-123456.keystore')
    parser.add_argument('-passwd', nargs='?', help='指定签名密码,默认123456')
    parser.add_argument('-alias', nargs='?', help='指定签名别名,默认mykey')
    parser.add_argument('-label', help='获取包名', action='store_const', const='label')
    parser.add_argument('-unpack', help='解包文件', action='store_const', const='unpack')
    parser.add_argument('-pack', help='打包文件', action='store_const', const='pack')
    parser.add_argument('-sign', help='签名文件', action='store_const', const='sign')
    parser.add_argument('-analyse', help='分析包', action='store_const', const='analyse')
    parser.add_argument('-inapk', nargs='?', help='指定输入apk路径')
    parser.add_argument('-outapk', nargs='?', help='指定输出apk路径')
    parser.add_argument('-outpath', nargs='?', help='指定输出目录')
    parser.add_argument('-inpath', nargs='?', help='指定输入目录')
    args = parser.parse_args()
    if args.help is not None:
        parser.print_help()
        return
    attrs = ['keystore', 'passwd', 'alias', 'inapk', 'outapk', 'inpath', 'outpath', 'help']
    value_map = {}
    for attr in attrs:
        value_map[attr] = getattr(args, attr, None)
    tool = ApkTool(value_map['keystore'], value_map['passwd'], value_map['alias'])
    if args.unpack is not None:
        # -unpack -inapk D:\\bamenGame\\测试游戏\\17.12.18jhzd.apk -outpath D:\\bamenGame\\测试游戏\\out\\17.12.18jhzd
        if value_map['inapk'] is None:
            print('需要指定输入apk路径')
            return
        if value_map['outpath'] is None:
            print('需要指定输出目录')
            return
        tool.unpack(value_map['inapk'], value_map['outpath'])
        return
    if args.pack is not None:
        # -pack -outapk D:\\bamenGame\\测试游戏\\repack\\17.12.18jhzd.apk -inpath D:\\bamenGame\\测试游戏\\out\\17.12.18jhzd
        if value_map['inpath'] is None:
            print('需要指定输入目录')
            return
        if value_map['outapk'] is None:
            print('需要指定输出包路径')
            return
        tool.pack(value_map['inpath'], value_map['outapk'])
        return
    if args.sign is not None:
        # -sign -inapk D:\\bamenGame\\测试游戏\\repack\\17.12.18jhzd.apk -outapk D:\\bamenGame\\测试游戏\\sign\\17.12.18jhzd.apk
        if value_map['inapk'] is None:
            print('需要指定输入目录')
            return
        if value_map['outapk'] is None:
            print('需要指定输出包路径')
            return
        tool.sign(value_map['inapk'], value_map['outapk'])
        return
    if args.label is not None:
        if value_map['inapk'] is None:
            print('需要指定输入游戏包')
            return
        print(tool.get_apk_label(value_map['inapk']))
        return
    if args.analyse is not None:
        if value_map['inapk'] is None:
            print('需要指定输入游戏包,现在分析当前目录下所有的apk文件')
            for file in os.listdir(os.curdir):
                if os.path.isdir(file):
                    continue
                if os.path.splitext(file)[1] == '.apk':
                    analyse(file, tool)
            return
        analyse(value_map['inapk'], tool)
        return
    parser.print_help()


if __name__ == '__main__':
    main()





二、ApkTool.py 脚本用法



执行如下命令 , 分析 apk 文件 ;

python ApkTool.py -analyse -inapk apk/app-debug.apk

分析结果会放在 ApkTool.py 脚本所在目录的 app-debug.txt 文件中 , app-debug 是 apk 文件的名称 , 后缀改为 txt ;

分析完毕的内容如下 :

文件名称:apk/app-debug.apk
应用名称:EventBus_Demo
应用信息: name='com.eventbus_demo' versionCode='1' versionName='1.0' platformBuildVersionName=''
打包检测:重打包成功
签名检测:重签名成功
引擎检测:未知引擎
----------------------------------------------------------------------------------------------------------------------------------

如果文件比较多的话 , 通宵跑程序 ;

如果应用做了加固处理 , 是无法进行重打包的 ;





三、ApkTool.py 脚本分析 APK 输出结果



执行输出内容 :

Microsoft Windows [版本 10.0.19041.1237]
(c) Microsoft Corporation。保留所有权利。

D:\\002_Project\\011_Python\\APK>python -V
Python 2.7.18

D:\\002_Project\\011_Python\\APK>python ApkTool.py -analyse -inapk apk/app-debug.apk
I: Using Apktool 2.3.3 on app-debug.apk
I: Loading resource table...
I: Decoding androidManifest.xml with resources...
S: WARNING: Could not write to (C:\\Users\\octop\\AppData\\Local\\apktool\\framework), using C:\\Users\\octop\\AppData\\Local\\Temp\\ instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Loading resource table from file: C:\\Users\\octop\\AppData\\Local\\Temp\\1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Using Apktool 2.3.3
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
S: WARNING: Could not write to (C:\\Users\\octop\\AppData\\Local\\apktool\\framework), using C:\\Users\\octop\\AppData\\Local\\Temp\\ instead...
S: Please be aware this is a volatile directory and frameworks could go missing, p

以上是关于Android 逆向APK 文件处理脚本 ApkTool.py ( 脚本简介 | 用法 | 分析 APK 文件 )的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向使用 Python 编写 APK 批处理分析工具

Android 逆向使用 Python 编写 APK 批处理分析工具

Android 逆向逆向修改游戏应用 ( APK 解析工具 | 解包 -> 分析 -> 重打包 -> 签名 流程 )

android逆向总结

如何从 Android .apk 资产文件夹中解压缩 LUA 脚本?

Android APK文件的逆向反编译