逆向工具腳本:命令行使用輪子

前言

輪子腳本,本質為使用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

………………

結

如若對你有用請點個贊給點動力; 代碼並不完美,若有更優的方案還請交流賜教