逆向工具脚本:命令行使用轮子

前言

轮子脚本,本质为使用subprocess替代大量无谓的交互command,内部方法自由扩展

呃我是有用AndroidKiller,但耐不住常用到adb自定义的臭长命令,例如经常取httpcanary的hcy回本地分析

代码

usage

  1. 通常

    1. 路径为apk绝对地址,则解包

    2. 路径为目录地址,则尝试打包

  2. 自定义

    • 自定义方法与参数
Usage: python dece.py [-i INSTALL] [-e EXTRA] path

Integrate decompile related complicated command.

Arguments:
  path                  program/apk path | special use "/"
  -i INSTALL, --install INSTALL
                        adb remote install apk
  -e EXTRA, --extra EXTRA
                        use inner func individually, |adb_install(apk_path)
                        |get_info(apk_path) |jadx(*args) |as_debug

Examples:
  dece.py D:\\com.android.unpack_catalog -i true
  dece.py / -e func++args1++args2  (params split with ++)

Source code

dece.py(cmd等实际轮子路径填自己的)

#!/usr/bin/python
__author__ = "jsoneri"
__version__ = "1.0"

import time
import os
import re
import chardet
import shutil
import argparse
import subprocess
from loguru import logger

keystore = r'D:\xxxxx\my-release-key.keystore'  # 签名证书
nickname = 'my_alias'  # 签名证书别称
keypwd = b'123456'  # 签名证书密码
device = "FA68H0XXXXXX"  # 手机设备号 模拟器则IP端口
cmd = {
    'aapt': r'D:\xxxxx\aapt',  # 看包信息,与get_info关联
    'apktool': r'D:\xxxxx\apktool.bat',
    'jadx': r'D:\xxxxx\bin\jadx-gui.bat'
}
bash = {
    'aapt': r'/mnt/d/xxxxx/aapt',
    'apktool': r'java -jar -Duser.language=en -Dfile.encoding=UTF8 /mnt/d/xxxxx/apktool.jar',
    'jadx': ValueError('在WSL上开gui需要vncsever,xhost等操作较麻烦,本人自禁用')  #
}


class DeceUtils:
    def packapk(self, path, apk):     # 回编译
        command = f'{cmd["apktool"]} b {path}'
        subprocess.run(command.split(' '))
        return self.signing(path, apk)

    def unpack(self, path):           # 反编译
        command = f"{cmd['apktool']} d -f {path}"
        subprocess.run(command.split(' '))

    def jadx(self, *args):
        ori = f"{cmd['jadx']}"
        if args:
            for arg in args:
                ori += f' {arg}'
        subprocess.run(ori)

    @logger.catch
    def adb_install(self, apk_path):
        subprocess.run(f'adb -s {device} install {apk_path}', stdout=subprocess.PIPE)
        logger.debug('-*- apk installation running -*- ')
	
    @logger.catch
    def signing(self, path, apk):
        """一般签名是在打包时才用的,,所以绑定为pack后使用,通常非单独用"""
        dist = f'{path}\\dist'
        name = apk.replace('.apk', '')
        keyname = keystore.split('\\')[-1]
        shutil.copy(keystore, dist)
        command = f'jarsigner -verbose -keystore {keyname} -signedjar {name}_signed.apk {apk} {nickname}'
        obj = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=dist)
        obj.communicate(keypwd)
        obj.terminate()
        obj.kill()
        logger.debug('-*- apk signed -*- ')
        return f'{name}_signed.apk'
    
    @logger.catch
    def as_debug(self):
        """设备当前窗口进程转发端口给AS的5005调用"""
        command = 'adb shell dumpsys activity activities|grep Resumed'
        infos = subprocess.run(command, stdout=subprocess.PIPE)
        r = re.search(r'u0 ([\w.]+)/[\w.]+ ', infos.stdout.decode(chardet.detect(infos.stdout)['encoding'])).group(1)
        infos = subprocess.run(f'adb shell ps | grep {r}', stdout=subprocess.PIPE)
        r = re.search(r' (\d+) ', infos.stdout.decode(chardet.detect(infos.stdout)['encoding'])).group(1)
        subprocess.run(f'adb forward tcp:5005 jdwp:{r}', stdout=subprocess.PIPE)
        
    def get_info(self, apk_path):
        try:
            command2 = f'{cmd["aapt"]} dump badging '
            infos = subprocess.run(command2 +apk_path, shell=True, stdout=subprocess.PIPE)
            # infos.stdout.decode(_code)
            return infos.stdout.decode(chardet.detect(infos.stdout)['encoding'])
        except Exception as e:
            logger.error(f"occur exception: {str(type(e))}:: {str(e)}")

    @staticmethod
    def get_apk(path):
        if '.apk' in os.path.split(path)[-1]:
            logger.info(f'输入为apk绝对地址,准备解包')
            return 'unpack', ''
        else:
            name = path.split("\\")[-1]
            dist = f'{path}\\dist'
            apk = f'{name}.apk'
            if not os.path.exists(f'{dist}\\{apk}'):
                logger.info(f'输入为目录且未发现打包痕迹,准备打包此目录')
                return 'pack', apk
            else:
                logger.info(f'输入为目录但发现有打包痕迹,请检查/删除再操作')
                os.startfile(dist)
            return '', ''

    def main(self, path, install_flag=False):
        flag, apk = self.get_apk(path)
        if flag == 'unpack':
            self.unpack(path)
        elif flag == 'pack':
            apk = self.packapk(path, apk)
            self.adb_install(f'{path}\\dist\\{apk}') if install_flag else None
            info = self.get_info(f'{path}\\dist\\{apk}')
            package = re.search(r'package: name=\'(com\.[\w.]+)\'', info).group(1)
            mainActivity = re.search(r'activity name=\'(com\.[\w.]+)\'', info).group(1)
            print(f'>>> {apk} infos: ↓↓ \npackage: [{package}], mainActivity: [{mainActivity}]')

            
if __name__ == '__main__':
    class ArgParser(argparse.ArgumentParser):
        def error(self, message):
            print(f"DecompileUtils v{__version__}\nby {__author__}\n\nError: {message}\n")
            print(self.format_help().replace("usage:", "Usage:"))
            self.exit(0)

    parser = ArgParser(
        add_help=False,
        description="Integrate decompile related complicated command.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=r"""
Examples:
  %(prog)s D:\\com.android.unpack_catalog -i true
  %(prog)s / -e func++args1++args2  (params split with ++)
    """)

    if os.name == 'posix':
        cmd = bash

    args = parser.add_argument_group("Arguments")
    args.add_argument('path', help='program/apk path | special use "/"')
    args.add_argument('-i', '--install', type=bool, default=False, help='adb remote install apk')
    args.add_argument('-e', '--extra', type=str, required=False, help=r'use inner func individually, '
                                                                      r'|adb_install(apk_path) |get_info(apk_path) |jadx(*args) |as_debug')
    parsed = parser.parse_args()
    if parsed.extra:
        _args = parsed.extra.split('++')
        logger.debug(f'>> extra >>  {_args}')
        ret = getattr(DeceUtils(), _args[0])(*_args[1:])
        print(ret) if ret else None
        time.sleep(0.05)
        logger.debug(f'func【 {_args[0]} 】 end')
    else:
        DeceUtils().main(parsed.path, parsed.install)

其他

chcp 936
keytool -genkey -keystore my-release-key.keystore -alias my_alias -keyalg RSA -keysize 4096 -validity 10000
# my-release-key.keystore -> 证书文件名  //   my_alias -> 证书别名
# 以下是交互,口令是密码,其他随便填,确认y
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  qw
您的组织单位名称是什么?
  [Unknown]:  qw
您的组织名称是什么?
  [Unknown]:  qw
您所在的城市或区域名称是什么?
  [Unknown]:  qw
您所在的省/市/自治区名称是什么?
  [Unknown]:  qw
该单位的双字母国家/地区代码是什么?
  [Unknown]:  qw
CN=qw, OU=qw, O=qw, L=qw, ST=qw, C=qw是否正确?
  [否]:  y

输入 <my_alias> 的密钥口令
        (如果和密钥库口令相同, 按回车):
再次输入新口令:

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore my-release-key.keystore -destkeystore my-release-key.keystore -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

keytool -importkeystore -srckeystore my-release-key.keystore -destkeystore my-release-key.keystore -deststoretype pkcs12

………………

如若对你有用请点个赞给点动力; 代码并不完美,若有更优的方案还请交流赐教