FART 脱壳某大厂 App + CodeItem 修复 dex + 反编译还原源码

news2025/6/7 5:18:58

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

FART 脱壳

fartthread 方法在 app 启动的时候(ActivityThread)开启 fart 线程,休眠 60 秒,等待 app 启动完成后自动开始遍历 ClassLoader 的 类列表,发起主动调用。

FART 脱壳结束得到的文件列表(分 Execute 与 主动调用两类):

  1. Execute 脱壳点得到的 dex (*_dex_file_execute.dex)和 dex 中的所有类列表( txt 文件)

  2. 主动调用时 dump 得到的 dex (*_dex_file.dex)和此时 dex 中的所有类列表,以及该 dex 中所有函数的 CodeItem( bin 文件)

wayne:/data/data/com.cyrus.example/cyrus # ls
1321896_class_list.txt         1437648_dex_file_execute.dex   1488168_class_list_execute.txt 1605504_ins_4714.bin
1321896_class_list_execute.txt 1437648_ins_4714.bin           1488168_dex_file.dex           198768_class_list.txt
1321896_dex_file.dex           1448488_class_list.txt         1488168_dex_file_execute.dex   198768_class_list_execute.txt
1321896_dex_file_execute.dex   1448488_class_list_execute.txt 1488168_ins_4714.bin           198768_dex_file.dex
1321896_ins_4714.bin           1448488_dex_file.dex           1496608_class_list.txt         198768_dex_file_execute.dex
1351008_class_list.txt         1448488_dex_file_execute.dex   1496608_class_list_execute.txt 198768_ins_4714.bin
1351008_class_list_execute.txt 1448488_ins_4714.bin           1496608_dex_file.dex           3782924_class_list_execute.txt
1351008_dex_file.dex           1461504_class_list.txt         1496608_dex_file_execute.dex   3782924_dex_file_execute.dex
1351008_dex_file_execute.dex   1461504_class_list_execute.txt 1496608_ins_4714.bin           400440_class_list_execute.txt
1351008_ins_4714.bin           1461504_dex_file.dex           1537456_class_list.txt         400440_dex_file_execute.dex
1403328_class_list.txt         1461504_dex_file_execute.dex   1537456_class_list_execute.txt 4376620_class_list_execute.txt
1403328_class_list_execute.txt 1461504_ins_4714.bin           1537456_dex_file.dex           4376620_dex_file_execute.dex
1403328_dex_file.dex           1472352_class_list.txt         1537456_dex_file_execute.dex   590624_class_list.txt
1403328_dex_file_execute.dex   1472352_class_list_execute.txt 1537456_ins_4714.bin           590624_class_list_execute.txt
1403328_ins_4714.bin           1472352_dex_file.dex           1571616_class_list.txt         590624_dex_file.dex
1423432_class_list.txt         1472352_dex_file_execute.dex   1571616_class_list_execute.txt 590624_dex_file_execute.dex
1423432_class_list_execute.txt 1472352_ins_4714.bin           1571616_dex_file.dex           590624_ins_4714.bin
1423432_dex_file.dex           1481472_class_list.txt         1571616_dex_file_execute.dex   7387912_class_list_execute.txt
1423432_dex_file_execute.dex   1481472_class_list_execute.txt 1571616_ins_4714.bin           7387912_dex_file_execute.dex
1423432_ins_4714.bin           1481472_dex_file.dex           1605504_class_list.txt         8391596_class_list_execute.txt
1437648_class_list.txt         1481472_dex_file_execute.dex   1605504_class_list_execute.txt 8391596_dex_file_execute.dex
1437648_class_list_execute.txt 1481472_ins_4714.bin           1605504_dex_file.dex           9085048_class_list_execute.txt
1437648_dex_file.dex           1488168_class_list.txt         1605504_dex_file_execute.dex   9085048_dex_file_execute.dex

关于 FART 的详细介绍参考下面的文章:

  • FART 自动化脱壳框架简介与脱壳点的选择

  • FART 主动调用组件设计和源码分析

  • 移植 FART 到 Android 10 实现自动化脱壳

  • FART 自动化脱壳框架一些 bug 修复记录

  • 使用 Frida 增强 FART:实现更强大的 Android 脱壳能力

  • 攻防 FART 脱壳:特征检测识别 + 对抗绕过全解析

  • FART 精准脱壳:通过配置文件控制脱壳节奏与范围

FART 生成的 bin 文件(CodeItem)格式

5元组填充:

{name:函数名, method_idx:函数索引, offset:偏移, code_item_len:长度, ins:函数体CodeItem的base64字符串};

其中:method_idx 和 ins 是必须的,其他不是必要的,但是可以作为参考。即只需要 method_idx 进行区分函数以及该函数的 CodeItem 内容即可。

修复组件就是通过读取 bin 文件的 method_idx 和 ins 进行 dex 的修复的。

只要符合 bin 文件 5 元组的规范都可以使用 FART 修复组件进行 dex 函数的修复。

某电商APP脱壳

1. 脱壳前准备

编写一个 bat 实现如下功能:

  1. 禁止加载 cdex,清空 /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 目录下的所有文件

  2. 解决 frida 反调试,删除 /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libmsaoaidsec.so

  3. 询问是否清空 /data/data/com.shizhuang.duapp/cyrus 目录下的所有文件?如果是就清空

  4. 最后 pause

unpack_before.bat

@echo off

:: 启用超级管理员权限
adb root

:: 设置变量
set PACKAGE_NAME=com.shizhuang.duapp
set APK_DIR=/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==
set LIB_DIR=%APK_DIR%/lib/arm64
set OAT_DIR=%APK_DIR%/oat/arm64
set CYRUS_DIR=/data/data/%PACKAGE_NAME%/cyrus

:: 步骤 1
echo 【1】禁止加载 cdex,清空 OAT 文件...
adb shell "rm -rf \"%OAT_DIR%\"/*"
echo 已清空 %OAT_DIR%
echo.

:: 步骤 2
echo 【2】解决 Frida 反调试,删除 libmsaoaidsec.so ...
adb shell "rm -f \"%LIB_DIR%/libmsaoaidsec.so\""
echo 已删除 libmsaoaidsec.so
echo.

:: 步骤 3
set /p INPUT=【3】是否清空 cyrus 配置目录?[%CYRUS_DIR%] [y/N]:

if /i "%INPUT%"=="y" (
    echo 正在清空 cyrus...
    adb shell "rm -rf \"%CYRUS_DIR%\"/*"
    echo 已清空 %CYRUS_DIR%
) else (
    echo 跳过清空 cyrus 配置目录
)

echo.
echo 所有操作已完成。
pause

执行脚本

word/media/image1.png

2. 开始脱壳

通过 frida 脚本配合 FART 精准对目标类脱壳

// 前缀过滤逻辑
function shouldLoadClass(name) {
    return name.startsWith("ff.")
}

function hookLoadClassAndInvoke() {
    const ActivityThread = Java.use('android.app.ActivityThread');

    if (ActivityThread.dispatchClassTask) {
        ActivityThread.dispatchClassTask.implementation = function (classloader, className, method) {
            if (shouldLoadClass(className)) {
                console.log('[load] dispatchClassTask: ' + className);
                return this.dispatchClassTask(classloader, className, method); // 正常调用
            }
            console.log('[skip] dispatchClassTask: ' + className);
            return; // 不调用原函数
        };
    } else {
        console.log('[-] ActivityThread.dispatchClassTask not found');
    }
}

function fartOnDexclassloader() {
    var DexClassLoader = Java.use("dalvik.system.DexClassLoader");
    var ActivityThread = Java.use("android.app.ActivityThread");

    DexClassLoader.$init.overload(
        'java.lang.String',     // dexPath
        'java.lang.String',     // optimizedDirectory
        'java.lang.String',     // librarySearchPath
        'java.lang.ClassLoader' // parent
    ).implementation = function (dexPath, optimizedDirectory, libPath, parent) {
        console.log("[+] DexClassLoader created:");
        console.log("    |- dexPath: " + dexPath);
        console.log("    |- optimizedDirectory: " + optimizedDirectory);
        console.log("    |- libPath: " + libPath);

        var cl = this.$init(dexPath, optimizedDirectory, libPath, parent);

        // 调用 startCodeInspection 方法
        try {
            console.log("[*] Calling fartWithClassLoader...");
            ActivityThread.startCodeInspectionWithCL(this);
            console.log("[+] fartWithClassLoader finished.");
        } catch (e) {
            console.error("[-] Error calling fartWithClassLoader:", e);
        }

        return cl;
    };
}

function invokeAllClassloaders() {
    try {
        // 获取 ActivityThread 类
        var ActivityThread = Java.use("android.app.ActivityThread");

        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    // 过滤掉 BootClassLoader
                    if (loader.toString().includes("BootClassLoader")) {
                        console.log("[-] 跳过 BootClassLoader");
                        return;
                    }

                    // 调用 fartWithClassLoader
                    console.log("[*] 调用 startCodeInspectionWithCL -> " + loader);
                    ActivityThread.startCodeInspectionWithCL(loader);
                } catch (e) {
                    console.error("[-] 调用失败: " + e);
                }
            },
            onComplete: function () {
                console.log("[*] 枚举并调用完毕");
            }
        });
    } catch (err) {
        console.error("[-] 脚本执行异常: " + err);
    }
}


setImmediate(function () {
    Java.perform(function () {
        // 过滤需要主动调用的类
        hookLoadClassAndInvoke()
        // 解决局部变量的 ClassLoader 枚举不出来问题
        fartOnDexclassloader()
        // 解决非双亲委派关系下动态加载的 dex 脱壳问题
        invokeAllClassloaders()
    })
})

源码:https://github.com/CYRUS-STUDIO/frida_fart

执行 frida 脚本

frida -H 127.0.0.1:1234 -F -l fart_filter.js -o log.txt

参考:

  • 移植 FART 到 Android 10 实现自动化脱壳

  • 使用 Frida 增强 FART:实现更强大的 Android 脱壳能力

  • 攻防 FART 脱壳:特征检测识别 + 对抗绕过全解析

3. 脱壳完成

脱壳文件列表

1|wayne:/data/data/com.shizhuang.duapp/cyrus # ls
10637856_class_list_execute.txt 12300396_class_list_execute.txt 12888744_dex_file_execute.dex  8324572_class_list.txt
10637856_dex_file_execute.dex   12300396_dex_file_execute.dex   3273924_class_list_execute.txt 8324572_class_list_execute.txt
11125808_class_list_execute.txt 12319700_class_list_execute.txt 3273924_dex_file_execute.dex   8324572_dex_file.dex
11125808_dex_file_execute.dex   12319700_dex_file_execute.dex   3782924_class_list_execute.txt 8324572_dex_file_execute.dex
11994176_class_list.txt         12587972_class_list_execute.txt 3782924_dex_file_execute.dex   8324572_ins_8950.bin
11994176_class_list_execute.txt 12587972_dex_file_execute.dex   400440_class_list_execute.txt  8391604_class_list_execute.txt
11994176_dex_file.dex           12590752_class_list_execute.txt 400440_dex_file_execute.dex    8391604_dex_file_execute.dex
11994176_dex_file_execute.dex   12590752_dex_file_execute.dex   4376620_class_list_execute.txt 8681372_class_list_execute.txt
11994176_ins_8950.bin           12592256_class_list_execute.txt 4376620_dex_file_execute.dex   8681372_dex_file_execute.dex
12081268_class_list_execute.txt 12592256_dex_file_execute.dex   7387912_class_list_execute.txt 9085048_class_list_execute.txt
12081268_dex_file_execute.dex   1260244_class_list_execute.txt  7387912_dex_file_execute.dex   9085048_dex_file_execute.dex
12213596_class_list_execute.txt 1260244_dex_file_execute.dex    8183732_class_list_execute.txt
12213596_dex_file_execute.dex   12888744_class_list_execute.txt 8183732_dex_file_execute.dex

通过 grep -rl 命令查找目标类所在的 dex 是 11994176_ 开头的 dex

wayne:/data/data/com.shizhuang.duapp/cyrus # grep -rl "ff.l0" *.txt
11994176_class_list.txt
11994176_class_list_execute.txt

所以,需要使用 11994176_ins_8950.bin 修复 11994176_dex_file.dex

wayne:/data/data/com.shizhuang.duapp/cyrus # ls -hl 11994176_*
-rw------- 1 u0_a139 u0_a139 612K 2025-06-04 09:55 11994176_class_list.txt
-rw------- 1 u0_a139 u0_a139 612K 2025-06-04 09:54 11994176_class_list_execute.txt
-rw------- 1 u0_a139 u0_a139  11M 2025-06-04 09:55 11994176_dex_file.dex
-rw------- 1 u0_a139 u0_a139  11M 2025-06-04 09:54 11994176_dex_file_execute.dex
-rw------- 1 u0_a139 u0_a139 114K 2025-06-04 09:55 11994176_ins_8950.bin

先把脱壳文件拉取到本地

adb pull /data/data/com.shizhuang.duapp/cyrus

迁移 fart.py 到 python3

fart.py 是用于解析dex文件,将 bin 的 CodeItem 修复打印 的 python 脚本。但 fart.py 是基于 pythion2 的。

迁移到 python3,主要修改如下:

  • 使用 range 替换 xrange

  • print 写法变化

  • 在 Python 3 中,访问 bytes[i] 得到的是 int(不再是字符),所以不需要再用 ord()。

  • 将所有 dict.has_key(key) 替换成 key in dict

  • 把 ‘[’ in name 改成 b’[’ in name

源码地址:https://github.com/CYRUS-STUDIO/FART/blob/master/fart3.py

但 fart.py 仅仅是打印,并没有把 CodeItem 真正修复到 dex

word/media/image2.png

使用 dexfixer 修复 dex

dexfixer 项目来源于 Youpk,支持对 fart 的脱壳结果进行修复合并到 dex。

开源地址:https://github.com/dqzg12300/dexfixer

使用 dexfixer 修复

java -jar ./dexfixer.jar dexpath binpath outpath

目前 dexfixer 项目是基于 eclipse 的,我把它迁移到了 IDEA。

开源地址:https://github.com/CYRUS-STUDIO/FartFixer

把源码 clone 到本地后,导入到 IDEA,编辑运行配置,添加运行参数(添加 dex、bin、修复后的dex文件路径)

D:\Python\anti-app\abc\cyrus\11994176_dex_file.dex 
D:\Python\anti-app\abc\cyrus\11994176_ins_8950.bin 
D:\Python\anti-app\abc\cyrus\11994176_dex_file_fix.dex

word/media/image3.png

运行 com.android.dx.unpacker.DexFixer 的 main 函数

word/media/image4.png

修复完成

word/media/image5.png

使用 IntelliJ IDEA 导出 FartFixer.jar

1. 配置 Artifacts

  • 点击菜单栏 File → Project Structure(或快捷键 Ctrl+Alt+Shift+S)。

  • 在左侧选择 Artifacts,点击右上角的 + → JAR → From modules with dependencies。

  • 在弹出框中选择主类(DexFixer),点击 OK。

word/media/image6.png

2. 构建 Artifact

  • 点击 Build → Build Artifacts…。

  • 选择你刚刚配置的 Artifact → Build。

  • 构建完成后,在 out/artifacts/xxx.jar 目录下可找到 JAR 文件。

word/media/image7.png

使用 FartFixer.jar 修复 dex

执行下面命令修复 dex

java -jar ./FartFixer.jar dexpath binpath outpath

效果如下:

word/media/image8.png

批量自动修复 dex

编写一个辅助脚本,实现批量自动修复 dex,功能如下:

  • 提示用户输入一个 bin 文件所在目录;

  • 查找该目录下所有以 .bin 结尾的文件(如 11994176_ins_8950.bin);

  • 通过 bin 文件名前缀(如 11994176_)在同目录中查找对应的 dex 文件(如 11994176_dex_file.dex);

  • 在 dex 所在目录下创建 fix 目录;

  • 调用 java -jar ./FartFixer.jar 修复 dex,输出到 fix/ 目录。

java -jar ./FartFixer.jar dexpath binpath outpath

FartFixer.bat(Windows)

@echo off
setlocal enabledelayedexpansion

:: 输入 bin 文件目录
set /p BIN_DIR=请输入 bin 文件所在目录:

:: 判断目录是否存在
if not exist "%BIN_DIR%" (
    echo 错误:目录 %BIN_DIR% 不存在
    pause
    exit /b
)

:: 遍历 bin 文件
for %%B in ("%BIN_DIR%\*.bin") do (
    set "BIN_FILE=%%~nxB"
    set "PREFIX="
    set "BIN_PATH=%%~fB"

    :: 提取前缀,比如 11994176_
    for /f "tokens=1 delims=_" %%P in ("%%~nB") do (
        set "PREFIX=%%P_"
    )

    :: 查找匹配的 dex 文件
    for %%D in ("%BIN_DIR%\!PREFIX!*.dex") do (
        set "DEX_FILE=%%~nxD"
        set "DEX_PATH=%%~fD"

        :: 创建 fix 目录
        set "FIX_DIR=%%~dpDfix"
        if not exist "!FIX_DIR!" (
            mkdir "!FIX_DIR!"
        )

        :: 构造输出路径
        set "FIXED_DEX=!FIX_DIR!\!DEX_FILE:_file=_file_fix!"

        echo 修复 !DEX_FILE!,使用 !BIN_FILE! ...
        java -jar ./FartFixer.jar "!DEX_PATH!" "!BIN_PATH!" "!FIXED_DEX!"
    )
)

echo 全部修复完成。
pause

FartFixer.sh(Linux / macOS)

#!/bin/bash

if [ $# -ne 1 ]; then
    echo "Usage: $0 <bin_dir>"
    exit 1
fi

BIN_DIR="$1"

# 查找所有 .bin 文件
find "$BIN_DIR" -type f -name "*.bin" | while read BIN_PATH; do
    BIN_FILE=$(basename "$BIN_PATH")
    PREFIX="${BIN_FILE%%_*}_"  # 提取前缀,例如 11994176_

    # 查找对应的 dex 文件
    find "$BIN_DIR" -type f -name "${PREFIX}*.dex" | while read DEX_PATH; do
        DEX_FILE=$(basename "$DEX_PATH")
        DEX_DIR=$(dirname "$DEX_PATH")

        # 创建 fix 目录
        FIX_DIR="$DEX_DIR/fix"
        mkdir -p "$FIX_DIR"

        # 构造输出路径
        FIXED_DEX="$FIX_DIR/${DEX_FILE/_file/_file_fix}"

        echo "修复 $DEX_FILE,使用 $BIN_FILE ..."
        java -jar ./FartFixer.jar "$DEX_PATH" "$BIN_PATH" "$FIXED_DEX"
    done
done

添加执行权限:

chmod +x FartFixer.sh

运行脚本,输入 bin 文件目录

word/media/image9.png

修复成功,修复后的 dex 在 fix 目录下

word/media/image10.png

dex2jar

dex2jar 是一个将 Android 的 .dex 文件转换为 Java 的 .jar 文件的工具,方便反编译和分析 APK。

开源地址:https://github.com/pxb1988/dex2jar

下载 Release 版本 dex2jar:https://github.com/pxb1988/dex2jar/releases

通过 ./d2j-dex2jar.bat 或 ./d2j-dex2jar.sh 把 dex 文件转换为 jar

(base) PS D:\Python\anti-app\dex2jar> ./d2j-dex2jar.bat -f D:\Python\anti-app\abc\cyrus\11994176_dex_file.dex
dex2jar D:\Python\anti-app\abc\cyrus\11994176_dex_file.dex -> .\11994176_dex_file-dex2jar.jar

脚本中实际调用的是 com.googlecode.dex2jar.tools.Dex2jarCmd

word/media/image11.png

Dex2jarCmd 中各参数说明如下:

package com.googlecode.dex2jar.tools;

import com.googlecode.d2j.dex.Dex2jar;
import com.googlecode.d2j.reader.BaseDexFileReader;
import com.googlecode.d2j.reader.DexFileReader;
import com.googlecode.d2j.reader.MultiDexFileReader;
import com.googlecode.dex2jar.ir.ET;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;

@BaseCmd.Syntax(cmd = "d2j-dex2jar", syntax = "[options] <file0> [file1 ... fileN]", desc = "convert dex to jar")
public class Dex2jarCmd extends BaseCmd {

    public static void main(String... args) {
        new Dex2jarCmd().doMain(args);
    }

    // 异常信息输出文件路径
    @Opt(opt = "e", longOpt = "exception-file", description = "detail exception file, default is $current_dir/[file-name]-error.zip", argName = "file")
    private Path exceptionFile;

    // 是否强制覆盖已有文件
    @Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
    private boolean forceOverwrite = false;

    // 是否不处理异常
    @Opt(opt = "n", longOpt = "not-handle-exception", hasArg = false, description = "not handle any exceptions thrown by dex2jar")
    private boolean notHandleException = false;

    // jar 输出路径
    @Opt(opt = "o", longOpt = "output", description = "output .jar file, default is $current_dir/[file-name]-dex2jar.jar", argName = "out-jar-file")
    private Path output;

    // 是否复用寄存器(对可读性有影响)
    @Opt(opt = "r", longOpt = "reuse-reg", hasArg = false, description = "reuse register while generate java .class file")
    private boolean reuseReg = false;

    // 是否按拓扑排序生成代码(提高可读性)
    @Opt(opt = "s", hasArg = false, description = "same with --topological-sort/-ts")
    private boolean topologicalSort1 = false;

    @Opt(opt = "ts", longOpt = "topological-sort", hasArg = false, description = "sort block by topological, that will generate more readable code, default enabled")
    private boolean topologicalSort = false;

    // 是否翻译 debug 信息
    @Opt(opt = "d", longOpt = "debug-info", hasArg = false, description = "translate debug info")
    private boolean debugInfo = false;

    // 是否打印中间 IR(中间表示)
    @Opt(opt = "p", longOpt = "print-ir", hasArg = false, description = "print ir to System.out")
    private boolean printIR = false;

    // 是否优化 synchronized 块
    @Opt(opt = "os", longOpt = "optmize-synchronized", hasArg = false, description = "optimize-synchronized")
    private boolean optmizeSynchronized = false;

    // 是否跳过异常块
    @Opt(longOpt = "skip-exceptions", hasArg = false, description = "skip-exceptions")
    private boolean skipExceptions = false;

    // 不生成代码(只提取结构)
    @Opt(opt = "nc", longOpt = "no-code", hasArg = false, description = "")
    private boolean noCode = false;

    @Override
    protected void doCommandLine() throws Exception {
        if (remainingArgs.length == 0) {
            usage(); // 没有参数则显示用法
            return;
        }

        if ((exceptionFile != null || output != null) && remainingArgs.length != 1) {
            System.err.println("-e/-o can only used with one file");
            return;
        }
        if (debugInfo && reuseReg) {
            System.err.println("-d/-r can not use together");
            return;
        }

        Path currentDir = new File(".").toPath();

        // 如果指定了输出文件且文件已存在,默认不覆盖
        if (output != null) {
            if (Files.exists(output) && !forceOverwrite) {
                System.err.println(output + " exists, use --force to overwrite");
                return;
            }
        } else {
            for (String fileName : remainingArgs) {
                Path file = currentDir.resolve(getBaseName(new File(fileName).toPath()) + "-dex2jar.jar");
                if (Files.exists(file) && !forceOverwrite) {
                    System.err.println(file + " exists, use --force to overwrite");
                    return;
                }
            }
        }

        // 处理每个 dex 文件
        for (String fileName : remainingArgs) {
            String baseName = getBaseName(new File(fileName).toPath());
            Path file = output == null ? currentDir.resolve(baseName + "-dex2jar.jar") : output;
            System.err.println("dex2jar " + fileName + " -> " + file);

            // 打开 .dex 文件
            BaseDexFileReader reader = MultiDexFileReader.open(Files.readAllBytes(new File(fileName).toPath()));

            // 创建异常处理器(可选)
            BaksmaliBaseDexExceptionHandler handler = notHandleException ? null : new BaksmaliBaseDexExceptionHandler();

            // 执行 dex 到 jar 的转换
            Dex2jar.from(reader)
                    .withExceptionHandler(handler)
                    .reUseReg(reuseReg)
                    .topoLogicalSort()
                    .skipDebug(!debugInfo)
                    .optimizeSynchronized(this.optmizeSynchronized)
                    .printIR(printIR)
                    .noCode(noCode)
                    .skipExceptions(skipExceptions)
                    .to(file);

            // 如果有异常,保存异常信息到文件
            if (!notHandleException && handler.hasException()) {
                Path errorFile = exceptionFile == null
                        ? currentDir.resolve(baseName + "-error.zip")
                        : exceptionFile;
                System.err.println("Detail Error Information in File " + errorFile);
                System.err.println(BaksmaliBaseDexExceptionHandler.REPORT_MESSAGE);
                handler.dump(errorFile, originalArgs);
            }
        }
    }

    @Override
    protected String getVersionString() {
        return "reader-" + DexFileReader.class.getPackage().getImplementationVersion() + ", translator-"
                + Dex2jar.class.getPackage().getImplementationVersion() + ", ir-"
                + ET.class.getPackage().getImplementationVersion();
    }
}

批量 dex2jar

编写一个批处理脚本用于批量转换 dex 文件,功能如下:

  • 提示用户输入一个 .dex 文件目录;

  • 查找该目录下的所有 .dex 文件(支持子目录);

  • 对每个 .dex 文件调用 ./d2j-dex2jar.bat 进行转换;

  • 输出的 .jar 文件保存在 ${dex目录}\jar 文件夹下。

dex2jar.bat(Windows)

@echo off
setlocal enabledelayedexpansion

:: 提示输入 dex 文件目录
set /p dex_dir=请输入 dex 文件所在的目录(绝对路径或相对路径):

:: 判断目录是否存在
if not exist "%dex_dir%" (
    echo 目录不存在: %dex_dir%
    pause
    exit /b
)

:: 创建输出 jar 目录
set "jar_dir=%dex_dir%\jar"
if not exist "%jar_dir%" (
    mkdir "%jar_dir%"
)

:: 查找所有 dex 文件
for /r "%dex_dir%" %%f in (*.dex) do (
    set "dex_file=%%f"
    set "file_name=%%~nf"
    echo 正在转换: !dex_file!

    :: 构造输出路径
    set "out_jar=%jar_dir%\!file_name!.jar"

    :: 调用 dex2jar 脚本
    call ./d2j-dex2jar.bat -f -o "!out_jar!" "!dex_file!"
)

echo 所有 dex 文件已转换完成,jar 输出目录: %jar_dir%
pause

dex2jar.sh(Linux / macOS)

#!/bin/bash

# 读取用户输入的 dex 文件目录
read -p "请输入 dex 文件所在目录: " dex_dir

# 判断目录是否存在
if [ ! -d "$dex_dir" ]; then
    echo "目录不存在: $dex_dir"
    exit 1
fi

# 创建输出 jar 目录
jar_dir="$dex_dir/jar"
mkdir -p "$jar_dir"

# 遍历 dex 文件并转换为 jar
find "$dex_dir" -type f -name "*.dex" | while read dex_file; do
    file_name=$(basename "$dex_file" .dex)
    out_jar="$jar_dir/${file_name}.jar"

    echo "正在转换: $dex_file"
    ./d2j-dex2jar.sh -f -o "$out_jar" "$dex_file"
done

echo "所有 dex 文件已转换完成,jar 输出目录: $jar_dir"

给脚本添加执行权限:

chmod +x dex2jar.sh

运行脚本,输入 dex 目录

word/media/image12.png

转换完成

word/media/image13.png

使用 jadx 反编译 jar

直接把 jar 文件拖入 jadx 可以看到被抽取的类和函数都已经修复好了。

word/media/image14.png

完整源码

开源地址:

  • https://github.com/CYRUS-STUDIO/FartFixer

  • https://github.com/CYRUS-STUDIO/dex2jar

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2402504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速用 uv 模拟发布一个 Python 依赖包到 TestPyPI 上,以及常用命令

目录 1. uv 介绍2. uv 安装&#xff08;Windows版&#xff09;3. 快速模拟一个要发布到TestPyPI上的依赖包&#xff0c;scoful-test-lib3.1 初始化 uv init3.2 进入scoful-test-lib3.3 修改pyproject.toml3.4 使用命令 uv sync3.5. 使用命令 uv lock3.6 使用命令 uv build3.7 获…

Python读取PDF:文本、图片与文档属性

在日常的数据采集、文档归档与信息挖掘过程中&#xff0c;PDF格式因其版式固定、内容稳定而被广泛使用。Python 开发者若希望实现 PDF 内容的自动化提取&#xff0c;选择一个易用且功能完善的库至关重要。本文将介绍如何用Python实现 PDF文本读取、图片提取 以及 文档属性读取 …

基于SpringBoot+Vue2的租房售房二手房小程序

角色&#xff1a; 管理员、房东、租客/买家 技术&#xff1a; springbootvue2mysqlmybatispagehelper 核心功能&#xff1a; 租房售房小程序是一个专注于房屋租赁和销售的综合性平台&#xff0c;基于SpringBootVue2MySQLMyBatisPageHelper技术栈开发&#xff0c;为用户提供…

基于本地LLM与MCP架构构建AI智能体全指南

一、AI智能体开发的新范式 随着人工智能技术的快速演进&#xff0c;AI智能体&#xff08;AI Agents&#xff09;正成为连接技术创新与实际应用的核心载体。从智能家居的温控系统到复杂的金融风控决策&#xff0c;AI智能体通过感知环境并执行目标导向的行为&#xff0c;正在重塑…

AT2659_GNSS低噪声放大器芯片

AT2659 射频放大器在SiGe工艺平台上实现23dB增益与0.71dB噪声系数的优异组合&#xff0c;专为BDS/GPS/GLONASS/GALILEO多模导航系统优化设计。其宽电压适应能力&#xff08;1.4-3.6V&#xff09;与低至4.4mA的功耗特性&#xff0c;配合1.5mm1mm0.55mm的6脚DFN封装&#xff08;R…

MADlib —— 基于 SQL 的数据挖掘解决方案(4)—— 数据类型之矩阵

目录 一、矩阵定义 二、MADlib 中的矩阵表示 1. 稠密 2. 稀疏 三、MADlib 中的矩阵运算函数 1. 矩阵操作函数分类 &#xff08;1&#xff09;表示函数 &#xff08;2&#xff09;计算函数 &#xff08;3&#xff09;提取函数 &#xff08;4&#xff09;归约函数&…

ServBay 1.13.0 更新,新增第三方反向代理/内网穿透

ServBay 作为一款简化本地开发环境搭建与管理的强大工具&#xff0c;致力于打造一个开箱即用、稳定可靠的本地开发平台&#xff0c;让用户专注于代码编写&#xff0c;提升开发效率。 ServBay 1.13.0 正式发布&#xff01;本次更新聚焦于提升本地开发项目的外部可访问性、增强国…

Docker构建自定义的镜像

构建自定义的 Docker 镜像是 Docker 使用中的核心操作之一。通过自定义镜像&#xff0c;你可以将应用程序及其依赖环境打包成一个可移植的容器化镜像。以下是详细的步骤和注意事项&#xff1a; 1. 准备工作 在构建自定义镜像之前&#xff0c;你需要准备以下内容&#xff1a; D…

【SSM】SpringMVC学习笔记8:拦截器

这篇学习笔记是Spring系列笔记的第8篇&#xff0c;该笔记是笔者在学习黑马程序员SSM框架教程课程期间的笔记&#xff0c;供自己和他人参考。 Spring学习笔记目录 笔记1&#xff1a;【SSM】Spring基础&#xff1a; IoC配置学习笔记-CSDN博客 对应黑马课程P1~P20的内容。 笔记2…

井川里予瓜pdf完整版

井川里予瓜pdf完整版 下载链接&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/c75455d6be60 在网红文化盛行的当下&#xff0c;井川里予无疑是一位备受瞩目的人物。这位2001年出生于广东湛江的姑娘&#xff0c;凭借独特风格在网络世界掀起波澜&#xff0c;其发展轨迹…

基于 Zynq 平台的 EtherCAT 主站的软硬件协同设计

摘要: 针对工业自动化对控制能力和强实时性的需求&#xff0c;提出了一种基于 FPGA 的改进型 EtherCAT 硬件主站方案 。 该方案利用 Zynq&#xff0d;7000 平台&#xff0c;在 PL 端实现 FPGA 协议栈&#xff0c;以保证核心功能的高效执 行 。 基于 AXI4 总线设计…

聊一聊 .NET在Linux下的IO多路复用select和epoll

一&#xff1a;背景 1. 讲故事 在windows平台上&#xff0c;相信很多人都知道.NET异步机制是借助了Windows自带的 IO完成端口 实现的异步交互&#xff0c;那在 Linux 下.NET 又是怎么玩的呢&#xff1f;主要还是传统的 select&#xff0c;poll&#xff0c;epoll 的IO多路复用…

从零开始的嵌入式学习day33

网络编程及相关概念 UDP网络通信程序 UDP网络通信操作 一、网络编程及相关概念 1. 网络编程概念&#xff1a; 指通过计算机网络实现程序间通信的技术&#xff0c;涉及协议、套接字、数据传输等核心概念。常见的应用场景包括客户端-服务器模型、分布式系统、实时通信等。…

黑马Java面试笔记之框架篇(Spring、SpringMvc、Springboot)

一. 单例bean Spring框架中的单例bean是线程安全的吗&#xff1f; Spring框架中的bean是单例的&#xff0c;可以在注解Scope()进行设置 singleton&#xff1a;bean在每一个Spring IOC容器中只有一个实例。prototype&#xff1a;一个bean的定义可以有多个实例 总结 二. AOP AOP称…

全球IP归属地查询接口如何用C#进行调用?

一、什么是全球IP归属地查询接口 在全球化互联网时代&#xff0c;IP地址作为网络世界的地理位置标识&#xff0c;扮演着至关重要的角色。全球IP归属地查询接口通过解析IP地址&#xff0c;提供包括国家、省、市、区县和运营商在内的详细信息。 二、应用场景 1. 访问识别 全球…

NumPy 比较、掩码与布尔逻辑

文章目录 比较、掩码与布尔逻辑示例&#xff1a;统计下雨天数作为通用函数&#xff08;Ufuncs&#xff09;的比较运算符使用布尔数组计数条目布尔运算符 布尔数组作为掩码使用关键字 and/or 与运算符 &/| 的区别 比较、掩码与布尔逻辑 本文介绍如何使用布尔掩码来检查和操…

力扣HOT100之二分查找:35. 搜索插入位置

这道题属于是二分查找的入门题了&#xff0c;我依稀记得一些二分查找的编码要点&#xff0c;但是最后还是写出了一个死循环&#xff0c;无语(ˉ▽ˉ&#xff1b;)…又回去看了下自己当时的博客和卡哥的视频&#xff0c;这才发现自己分情况只分了两种&#xff0c;最后导致死循环…

使用API有效率地管理Dynadot域名,查看域名市场中所售域名的详细信息

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

IM即时通讯软件,构建企业局域网内安全协作

安全与权限&#xff1a;协同办公的企业级保障 在协同办公场景中&#xff0c;BeeWorks 将安全机制贯穿全流程。文件在局域网内传输与存储时均采用加密处理&#xff0c;企业网盘支持水印预览、离线文档权限回收等功能&#xff0c;防止敏感资料外泄&#xff1b;多人在线编辑文档时…

VueScan:全能扫描,高清输出

在数字化办公和图像处理的领域&#xff0c;扫描仪扮演着不可或缺的角色。无论是文档的数字化存档、照片的高清复制&#xff0c;还是创意项目的素材采集&#xff0c;一款性能卓越、操作便捷的扫描软件能大幅提升工作效率和成果质量。VueScan正是这样一款集多功能于一身的扫描仪软…