惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
博客园 - 司徒正美
罗磊的独立博客
D
Docker
Last Week in AI
Last Week in AI
爱范儿
爱范儿
M
MIT News - Artificial intelligence
V
V2EX
Google DeepMind News
Google DeepMind News
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
V
Visual Studio Blog
博客园 - 叶小钗
B
Blog RSS Feed
A
About on SuperTechFans
F
Fortinet All Blogs
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
P
Proofpoint News Feed

博客园 - loz2015

unity 游戏apk 分析 Tiktok 核心字段 IOS Tiktok(43.6.1)抓包 android java层字符串加密对抗 jeb4 增加内存 Android 设置no-proxy抓包处理 IDA dump so vm demo加固分析 Android Lazada抓包 Android 闲鱼抓包 Mirroring360 破解分析 电脑 直接下载cyida deb Alibaba Canal config 漏洞验证 检测magiskhide MAC 下Android ROM 提取文件 IDAPython 插件开发 Android ollvm 集成 某iOS APP反抓包分析 lark反调试 luajava逆向总结 ios app 可执行文件是中文名称如何进行调试
体育apk重打包记录
loz2015 · 2026-06-08 · via 博客园 - loz2015

# APK重打包后启动闪退分析 —— 深入逆向libapk-protect.so保护机制

## 一、问题现象:APK重打包后启动闪退

在对某游戏APK(包名:`com.qcfy01.xxxx`)进行重打包后,应用启动即闪退。通过 `adb logcat` 抓取日志,发现关键报错信息:

```

2026-06-08 14:28:51.995 17351-17351 apk_protect  V  RegisterNatives() --> ok

2026-06-08 14:28:52.273 17351-17351 apk_protect  I  z s

2026-06-08 14:28:52.301 17351-17351 apk_protect  I  json md5 2e8ac0c9fb4b4971aaad43d3cd5b9ddf, actual md5 1494E65A4528F7F9CAEFC35DD4F3A320

2026-06-08 14:28:52.301 17351-17351 apk_protect  I  single dex md5 not ok

2026-06-08 14:28:52.303 17351-17351 apk_protect  I  r m s

2026-06-08-14:28:52.303 17351-17351 apk_protect  I  json catmd5 b567516f51f05740e1a85b96605ab24c, actual catmd5 4CFB9F873A511558F75B73D7AD82D01A

2026-06-08 14:28:52.303 17351-17351 apk_protect  I  concat dex md5 not ok

2026-06-08 14:28:52.306 17351-17351 apk_protect  I  check not ok

2026-06-08 14:28:52.306 17351-17351 apk_protect  I  shame on you. A stupid hacker try cracking my game.

```

日志清晰地表明:**DEX文件的MD5校验失败**,应用检测到自身被篡改,主动终止运行。

---

## 二、定位日志:逐行分析保护流程

### 2.1 日志时间线还原

| 时间戳 | PID | Tag | 日志内容 | 含义 |

|--------|-----|------|----------|------|

| 14:28:51.995 | 17351 | apk_protect | `RegisterNatives() --> ok` | JNI动态注册成功 |

| 14:28:52.273 | 17351 | apk_protect | `z s` | 开始校验流程(混淆标记) |

| 14:28:52.301 | 17351 | apk_protect | `json md5 ..., actual md5 ...` | 读取JSON中的期望MD5,与实际MD5对比 |

| 14:28:52.301 | 17351 | apk_protect | `single dex md5 not ok` | 单个DEX的MD5校验失败 |

| 14:28:52.303 | 17351 | apk_protect | `r m s` | 进入拼接MD5校验(混淆标记) |

| 14:28:52.303 | 17351 | apk_protect | `json catmd5 ..., actual catmd5 ...` | 拼接DEX的MD5对比 |

| 14:28:52.303 | 17351 | apk_protect | `concat dex md5 not ok` | 拼接DEX的MD5校验失败 |

| 14:28:52.306 | 17351 | apk_protect | `check not ok` | 最终校验结果:不通过 |

| 14:28:52.306 | 17351 | apk_protect | `shame on you...` | 检测到篡改,应用退出 |

### 2.2 MD5校验对比

从日志可以直接提取两组MD5值:

**Single DEX MD5(单文件校验):**

| 项目 | 值 |

|------|----|

| JSON期望值 | `2e8ac0c9fb4b4971aaad43d3cd5b9ddf` |

| 实际计算值 | `1494E65A4528F7F9CAEFC35DD4F3A320` |

| 结果 | **不匹配** |

**Concat DEX MD5(拼接校验):**

| 项目 | 值 |

|------|----|

| JSON期望值 | `b567516f51f05740e1a85b96605ab24c` |

| 实际计算值 | `4CFB9F873A511558F75B73D7AD82D01A` |

| 结果 | **不匹配** |

两组校验均失败,说明重打包修改了`classes.dex`的内容,导致MD5值发生变化。

---

## 三、深入分析APK及SO修改方案

### 3.1 保护框架识别

通过JADX反编译APK,追踪保护加载链路:

```

AndroidManifest.xml

  └─> Application: com.flamingo.sdk.main.ThirdSdkApplication

       └─> attachBaseContext()

            ├─> System.loadLibrary("apk-protect")     // 加载 libapk-protect.so

            ├─> NativeUtil.javaInit(context)

            │    ├─> UnZipUtils.unzipDexFile()        // 解压DEX到cache目录

            │    └─> NativeUtil.init(context)          // JNI native方法,执行校验

            └─> 读取Manifest MetaData(gp_cp_appid等)

```

**关键Java类:**

- `ThirdSdkApplication` —— Application入口,加载SO

- `NativeUtil` —— 提供JNI桥接,`init()`和`doSomeThing()`为native方法

- `UnZipUtils` —— 解压APK中的DEX文件到缓存目录

- `MD5Helper` —— Java层的MD5计算工具类

- `InterfaceUtils` —— SO完整性校验(检查libapk-protect.so的MD5)

### 3.2 libapk-protect.so逆向分析

#### 3.2.1 SO基本信息

- 文件名:`libapk-protect.so`

- 架构:ARM64

- 内置库:**CryptoPP**(提供AES加解密、RSA、ECC等密码学算法)

- JSON解析库:**jsoncpp**

#### 3.2.2 导出函数

从SO的exports中定位到关键函数:

| 地址 | 函数名 | 说明 |

|------|--------|------|

| `0x202A08` | `JNI_OnLoad` | SO加载入口,执行RegisterNatives |

| `0x202728` | `init` | 核心校验函数(NativeUtil.init的JNI实现) |

| `0x202BC4` | `RegisterNatives` | 动态注册JNI方法 |

| `0x20E174` | `doSomeThing` | NativeUtil.doSomeThing的JNI实现 |

| `0x5A511` | `AESDecode` | AES解密函数 |

| `0x5A565` | `AESEncode` | AES加密函数 |

#### 3.2.3 关键字符串分析

通过提取SO的字符串表,梳理出完整保护逻辑涉及的所有关键字符串:

**校验相关:**

```

"third_encrypted_special_config.json"  → AES加密的校验配置文件

"dex_count"                            → DEX文件数量

"dex_md5_list"                         → 各DEX的MD5列表

"dex_md5_md5"                          → DEX MD5的MD5

"protect_so_count"                     → 需校验的SO数量

"protect_so_md5_list"                  → 需校验的SO的MD5列表

"classes.dex"                          → 被校验的DEX文件名

"single dex md5 not ok"               → 单DEX校验失败

"concat dex md5 not ok"               → 拼接DEX校验失败

"check not ok"                         → 最终校验失败

"shame on you. A stupid hacker try cracking my game." → 篡改检测提示

```

**反调试相关:**

```

"TracerPid"                → ptrace反调试检测

"/proc/%d/maps"            → 内存映射监控(inotify)

"/proc/%d/status"          → 进程状态检测

"/proc/%d/task/%d/status"  → 线程状态检测

"/proc/net/tcp"            → 网络端口检测(反frida)

"monitor_thread"           → 后台监控线程

"inotify_init"             → 文件系统变化监控

"inotify_add_watch"        → 添加监控目标

```

**JNI注册相关:**

```

"RegisterNatives() --> ok"       → JNI方法注册成功

"RegisterNatives() --> failed"   → JNI方法注册失败

"com/flamingo/sdk/util/NativeUtil" → 注册的目标Java类

"init"                           → 注册的方法名

"doSomeThing"                    → 注册的方法名

```

**其他:**

```

"/data/data/"              → 数据目录检测

"/cache/"                  → DEX缓存目录

"/cache/fake_"             → 反Hook检测(检测fake框架)

"sourceDir"                → APK路径获取

"getApplicationInfo"       → 获取应用信息

"libapk-protect.so"        → SO自身路径

"package_name"             → 包名校验

"z s" / "r m s"            → 混淆的日志标记

```

#### 3.2.4 保护逻辑流程图

```

┌─────────────────────────────────────────────────────────────┐

│                    JNI_OnLoad (0x202A08)                     │

│  1. RegisterNatives注册init/doSomeThing到NativeUtil类        │

│  2. 创建monitor_thread后台监控线程                            │

└──────────────────────────┬──────────────────────────────────┘

                           │

                           ▼

┌─────────────────────────────────────────────────────────────┐

│                  init() (0x202728)                           │

│  1. 读取 assets/third_encrypted_special_config.json          │

│  2. AESDecode 解密JSON配置                                   │

│  3. 提取 dex_count, dex_md5_list, dex_md5_md5               │

│  4. 提取 protect_so_count, protect_so_md5_list               │

│  5. 获取APK路径(sourceDir)                                   │

│  6. 计算classes.dex的实际MD5                                 │

│     ├── 单个DEX MD5 对比 → "single dex md5 not ok"          │

│     └── 所有DEX拼接MD5 对比 → "concat dex md5 not ok"       │

│  7. 校验libapk-protect.so自身的MD5                          │

│  8. 任何校验失败 → "check not ok" → 调用exit()终止进程       │

└──────────────────────────┬──────────────────────────────────┘

                           │

                           ▼

┌─────────────────────────────────────────────────────────────┐

│              monitor_thread (后台线程)                        │

│  1. inotify 监控 /proc/pid/maps(检测调试器附加)             │

│  2. 读取 /proc/pid/status 检查 TracerPid                     │

│  3. 读取 /proc/net/tcp 检测frida-server端口                  │

│  4. 检测到异常 → 终止进程                                    │

└─────────────────────────────────────────────────────────────┘

```

### 3.3 SO修改方案

根据以上分析,有三种绕过保护的方案:

---

#### 方案一:修改加密配置文件(替换MD5值)

**原理:** 将`third_encrypted_special_config.json`中的期望MD5替换为重打包后的实际MD5。

**步骤:**

1. 计算重打包后DEX的实际MD5(日志已给出):

   - Single MD5: `1494E65A4528F7F9CAEFC35DD4F3A320`

   - Concat MD5: `4CFB9F873A511558F75B73D7AD82D01A`

2. 解密JSON配置文件:

   - 配置文件使用AES加密,密钥硬编码在SO中

   - 可以通过Frida Hook `AESDecode` 函数,在运行时dump解密后的明文

3. 修改JSON中的MD5字段,重新AES加密后替换回APK

4. 同时需要更新`libapk-protect.so`自身的MD5(如果配置中包含)

**优缺点:**

- 优点:不修改SO,隐蔽性较好

- 缺点:需要获取AES密钥,流程较复杂

---

#### 方案二:Patch SO校验逻辑(推荐)

**原理:** 直接修改`libapk-protect.so`中的校验分支,使MD5比较永远返回"通过"。

**步骤:**

**第一步:定位校验函数**

`init`函数地址为`0x202728`,在该函数中搜索对关键字符串的引用:

- 引用`"check not ok"`(`0x17cd30`)的代码位置即为校验判断点

- 引用`"shame on you..."`(`0x17f3a7`)的代码位置即为退出执行点

image
image

**第二步:Patch校验分支**

使用IDA Pro或Ghidra打开SO,在`init`函数中:

```arm64

; 原始代码(伪代码):

CMP  W8, #0          ; 比较校验结果

B.EQ pass_check      ; 如果MD5匹配则跳转通过

; ↓ 校验失败路径

BL   exit            ; 调用exit()退出

; Patch方案1:修改条件跳转为无条件跳转

B    pass_check      ; 强制跳转到校验通过分支(将B.EQ改为B)

; Patch方案2:NOP掉exit调用

NOP                   ; 将BL exit替换为NOP

NOP

```

**第三步:Patch monitor_thread**

反调试线程也需要处理,定位`monitor_thread`的线程函数:

- 将`inotify_add_watch`调用NOP掉

- 或将线程函数中的检测分支强制走"安全"路径

**第四步:修复SO**

由于SO可能也校验自身完整性(`protect_so_md5_list`),还需要:

- 同时修改配置中的SO MD5值,或

- Patch掉SO自身MD5校验的判断分支

**优缺点:**

- 优点:一劳永逸,修改后无需运行时干预

- 缺点:需要ARM64汇编基础,需仔细定位所有校验点

---

#### 方案三:运行时Hook(Frida脚本)

**原理:** 在运行时Hook关键系统调用,使校验逻辑返回"通过"。

**Frida脚本示例:**

```javascript

// Hook MD5比较:让strcmp在比较32位十六进制字符串时返回0

var strcmp = Module.findExportByName("libc.so", "strcmp");

Interceptor.attach(strcmp, {

    onEnter: function(args) {

        var s1 = args[0].readUtf8String();

        var s2 = args[1].readUtf8String();

        if (s1 && s2 && s1.length === 32 && s2.length === 32 &&

            /^[0-9a-fA-F]{32}$/.test(s1)) {

            this.isMd5Compare = true;

        }

    },

    onLeave: function(retval) {

        if (this.isMd5Compare) {

            retval.replace(0); // 强制返回0(匹配)

        }

    }

});

// Hook exit/abort:阻止进程退出

var exit = Module.findExportByName("libc.so", "exit");

Interceptor.replace(exit, new NativeCallback(function(status) {

    console.log("[*] exit(" + status + ") blocked!");

}, 'void', ['int']));

// Hook memcmp:让MD5内存比较始终相等

var memcmp = Module.findExportByName("libc.so", "memcmp");

Interceptor.attach(memcmp, {

    onLeave: function(retval) {

        // 如果比较长度为16(MD5摘要长度),强制返回0

        if (this.compareLen === 16) {

            retval.replace(0);

        }

    },

    onEnter: function(args) {

        this.compareLen = args[2].toInt32();

    }

});

```

**优缺点:**

- 优点:无需修改文件,快速验证

- 缺点:每次运行都需要注入,容易被反Hook检测

---

## 四、总结

本文通过一次APK重打包闪退问题,完整分析了`libapk-protect.so`的保护机制:

1. **DEX完整性校验**:通过AES加密的JSON配置存储期望MD5,运行时计算实际MD5进行对比

2. **SO自身校验**:校验`libapk-protect.so`的MD5,防止SO被修改

3. **反调试保护**:TracerPid检测、inotify监控maps、TCP端口扫描

4. **后台监控线程**:持续运行的反调试检测

该保护方案的核心强度在于:**校验逻辑全部在Native层实现,使用AES加密存储期望值,并有持续监控线程**。但核心弱点也很明显:**校验结果最终要决定是否exit,只要找到这个判断分支并Patch掉,整个保护就失效了**。

逆向分析的关键路径:

```

日志 → 字符串定位 → 函数定位 → 逻辑分析 → Patch绕过

```

这是一条典型的"黑盒→白盒"分析链路,日志中的每一行信息都是定位保护逻辑的线索。