主流应用加固厂商及其特征识别

主流应用加固厂商及其特征识别

📚 前置知识

本配方涉及以下核心技术,建议先阅读相关章节:

Android 应用加固是一种保护 App 不被轻易逆向、篡改或攻击的技术手段。对于逆向工程师而言,在开始分析一个 App 之前,首要任务就是识别出它所使用的加固厂商,因为不同的加固方案需要不同的脱壳和分析策略。

本指南旨在系统性地总结中国市场主流加固厂商的静态特征"指纹",帮助分析人员快速识别目标。


通用识别思路

识别加固厂商通常遵循以下静态分析路径:

  1. 检查 DEX 文件:解压 APK,查看主 classes.dex 文件的大小。如果它非常小(通常小于 1MB),而 APK 本身体积很大,那么它很可能是一个"壳",负责加载真正的、被加密隐藏起来的 DEX。
  2. 检查 SO 库:查看 lib/[arch]/ 目录下的 .so 文件列表。加固厂商通常会放入带有自身品牌标识的 SO 库,这是最明显的特征。
  3. 检查 assets 目录:很多加固方案会将加密后的 DEX 文件、配置文件或其他组件放入 assets 目录。
  4. 检查 AndroidManifest.xml:加固方案通常会用自己的代理 Application 类替换掉原始的 Application 类。检查 application 标签下的 android:name 属性,可以找到代理类的名字,其包名往往暴露厂商信息。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
+------------------+     +------------------+     +------------------+
|  解压 APK        | --> |  检查 DEX 大小    | --> |  DEX < 1MB?      |
|  (unzip/apktool) |     |  ls -la *.dex    |     |  是 -> 可能加固   |
+------------------+     +------------------+     +------------------+
         |                                               |
         +-------->  检查 lib/ SO 文件  -----> 匹配厂商特征库
         |                                               |
         +-------->  检查 assets 目录   -----> 匹配加密文件特征
         |                                               |
         +-------->  检查 Manifest      -----> 匹配 Application 类名
                                                         |
                                                  APKiD 交叉验证

主流厂商特征详解

梆梆安全 (Bangcle)

  • SO 库特征: libSecShell.solibsecexe.solibsecmain.solibDexHelper.so
  • Java 层特征: 代理类 com.bangcle.protectcom.secshell.shell
  • assets 目录特征: bangcle_classes.jarsecData0.jar

360 加固 (Qihoo 360)

  • SO 库特征: libjiagu.solibprotectClass.solibjiagu_art.so
  • Java 层特征: com.qihoo.utilcom.stub.StubApp
  • assets 目录特征: libjiagu.so(有时放在 assets 里)、.jiagu 后缀文件

腾讯乐固 (Tencent Legu)

  • SO 库特征: liblegu.solibshella-xxxx.so(xxxx 为版本号)、libshellx-xxxx.so
  • Java 层特征: com.tencent.bugly.legucom.tencent.StubShell.TxAppEntry
  • assets 目录特征: legu_data.sotosversion
  • 其他: DEX 文件头被修改为 legu

网易易盾 (Netease Yidun)

  • SO 库特征: libnesec.so(最核心特征)、libnetease.so
  • Java 层特征: com.netease.nis.wrapper.MyApplication
  • assets 目录特征: nesec.datclasses.dex.ys

爱加密 (Ijiami)

  • SO 库特征: libexec.solibexecmain.solibijiami.so
  • Java 层特征: com.ijiami.client.protect
  • assets 目录特征: ijiami.datijm_lib 目录

娜迦 (Nagain / NAGA)

  • SO 库特征: libchaosvmp.solibnagavm.solibddog.so
  • Java 层特征: com.nagain.protect
  • assets 目录特征: nagain.datclasses.dex.dat

几维安全 (Kiwisec)

  • SO 库特征: libkwscmm.solibkwscr.solibkwslinker.so
  • Java 层特征: 包名含 com.kiwisec
  • assets 目录特征: kiwi.dat

加固厂商识别指纹总表

加固厂商核心 SO 特征Manifest Application 类assets 特征
梆梆安全libSecShell.socom.secshell.secData.ShellApplicationbangcle_classes.jar
360 加固libjiagu.so, libprotectClass.socom.stub.StubApp.jiagu 文件
腾讯乐固liblegu.so, libshella-*.socom.tencent.StubShell.TxAppEntrylegu_data.so
网易易盾libnesec.socom.netease.nis.wrapper.MyApplicationnesec.dat
爱加密libexec.so, libijiami.socom.shell.NativeApplicationijiami.dat
娜迦libchaosvmp.so, libddog.socom.nagain.NagainApplicationnagain.dat
几维安全libkwscmm.sokiwi 关键字kiwi.dat
顶象libx3g.so, libdxbase.sodingxiang 关键字dx_res/
通付盾libegis.soegis 关键字egis.dat

识别方法

Manifest 检查

1
2
# 快速查看 Application 类名
aapt dump xmltree target.apk AndroidManifest.xml | grep "android:name"

lib 目录分析

1
2
# 列出所有 SO 文件
unzip -l target.apk | grep "\.so$"
SO 文件名关键字加固厂商
jiagu360
legu / shella腾讯乐固
SecShell / secexe梆梆安全
nesec网易易盾
ijiami爱加密
chaosvmp / ddog娜迦
kwscmm几维安全

DEX 文件头检查

1
2
3
# 正常 DEX 头为 "dex\n035",被修改则说明加固
xxd classes.dex | head -1
# legu -> 腾讯乐固 | 全零 -> 梆梆/爱加密

APKiD 自动识别

1
2
3
4
pip install apkid
apkid target.apk          # 基本扫描
apkid -j target.apk       # JSON 输出
apkid -r ./apk_samples/   # 批量扫描

各加固方案特征对比

特性360 加固腾讯乐固梆梆安全爱加密网易易盾娜迦几维安全
DEX 加密整体加密整体加密整体+抽取整体加密整体+VMP整体+VMP整体加密
DEX VMP部分函数部分函数支持支持深度 VMP深度 VMP核心方案
SO 混淆基础基础OLLVM 变种OLLVM深度定制深度定制VMP 虚拟化
Anti-Debugptrace+时间ptrace多层检测ptrace+信号内核级多层检测多层检测
Anti-HookGOT/PLT 检测基础Inline 检测GOT 检测全面检测全面检测VMP 保护
Anti-Frida端口+进程基础maps+端口端口检测全面检测maps+内存maps+端口
保护强度★★★☆☆★★★☆☆★★★★☆★★★☆☆★★★★★★★★★☆★★★★☆
脱壳难度★★☆☆☆★★☆☆☆★★★★☆★★★☆☆★★★★★★★★★☆★★★★☆

注意:以上评级基于旗舰版产品,免费版保护强度通常低 1-2 级。


壳的分代

Android 加固技术经历了明显的代际演变:

1
2
3
4
5
6
7
8
第一代 (2013-2015)         第二代 (2015-2018)         第三代 (2018-至今)
+-------------------+     +-------------------+     +-------------------+
| DEX 整体加密      |     | DEX 函数抽取      |     | 指令级 VMP        |
| - 运行时解密还原  | --> | - 函数体替为 nop  | --> | - 字节码虚拟化    |
| - 内存中完整 DEX  |     | - 运行时动态填回  |     | - 自定义解释器    |
+-------------------+     +-------------------+     +-------------------+
  脱壳: 内存 dump           脱壳: FART 主动调用        脱壳: 逆向虚拟机
  难度: ★★☆☆☆             难度: ★★★☆☆             难度: ★★★★★

第一代:DEX 整体加密

classes.dex 整体加密,运行时壳代码解密后用 DexClassLoader 加载。内存中存在完整 DEX,直接 dump 即可。

第二代:DEX 函数抽取

在 DEX 加密基础上,将关键函数的方法体抽取出来,替换为 nop。运行时动态填回。dump 出的 DEX 中关键函数体为空,需要用 FART 主动调用每个方法触发填回后再 dump。

第三代:指令级 VMP

将字节码转换为自定义虚拟机指令,由壳自带的解释器执行。原始字节码在任何时刻都不以标准形式存在。需要逆向虚拟机解释器或转为动态分析策略。

代际辨别方法:

辨别维度第一代第二代第三代
dump DEX 是否完整完整可用函数体为空无标准 DEX
Jadx 打开效果正常反编译关键函数为空完全无法反编译
壳 SO 大小< 500KB0.5-2MB> 2MB
性能影响几乎无轻微明显

自动化识别工具

APKiD 详细用法

1
2
3
4
5
6
7
$ apkid target.apk
[+] target.apk!classes.dex
 |-> compiler : dexlib 2.x
 |-> packer : Tencent Legu
[+] target.apk!lib/armeabi-v7a/libshella-2.10.3.1.so
 |-> packer : Tencent Legu
 |-> anti_disassembly : Tencent Legu

自定义 Python 识别脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env python3
"""APK 加固厂商识别脚本"""
import zipfile, sys
from pathlib import Path

SIGNATURES = {
    "360加固":   {"so": ["libjiagu", "libprotectClass"],
                  "assets": [".jiagu"], "app": ["com.stub.StubApp"]},
    "腾讯乐固": {"so": ["liblegu", "libshella", "libshellx"],
                  "assets": ["legu_data", "tosversion"], "app": ["com.tencent.StubShell"]},
    "梆梆安全": {"so": ["libSecShell", "libsecexe", "libsecmain"],
                  "assets": ["bangcle_classes"], "app": ["com.bangcle", "com.secshell"]},
    "网易易盾": {"so": ["libnesec"],
                  "assets": ["nesec.dat", "classes.dex.ys"], "app": ["com.netease.nis"]},
    "爱加密":   {"so": ["libijiami", "libexecmain"],
                  "assets": ["ijiami.dat"], "app": ["com.ijiami"]},
    "娜迦":     {"so": ["libchaosvmp", "libddog", "libnagavm"],
                  "assets": ["nagain"], "app": ["com.nagain"]},
    "几维安全": {"so": ["libkwscmm", "libkwscr"],
                  "assets": ["kiwi.dat"], "app": ["com.kiwisec"]},
}

def identify(apk_path):
    results = {}
    with zipfile.ZipFile(apk_path) as zf:
        files = zf.namelist()
        for name, sigs in SIGNATURES.items():
            score, matches = 0, []
            for f in files:
                fl = f.lower()
                for p in sigs["so"]:
                    if p.lower() in fl and f.endswith(".so"):
                        score += 3; matches.append(f"SO: {f}")
                for p in sigs["assets"]:
                    if p.lower() in fl and "assets" in fl:
                        score += 2; matches.append(f"Assets: {f}")
            # 检查 DEX 大小
            for f in files:
                if f == "classes.dex" and zf.getinfo(f).file_size < 500*1024:
                    score += 1; matches.append("DEX < 500KB")
            if score > 0:
                results[name] = {"score": score, "matches": matches}
    return results

if __name__ == "__main__":
    apk = sys.argv[1] if len(sys.argv) > 1 else exit("用法: python detect.py <apk>")
    for name, data in sorted(identify(apk).items(), key=lambda x: -x[1]["score"]):
        level = "高" if data["score"] >= 5 else "中" if data["score"] >= 3 else "低"
        print(f"[{level}] {name} (分数:{data['score']})")
        for m in data["matches"]:
            print(f"    - {m}")

RASP 运行时检测脚本 (Frida)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// rasp_detect.js - 运行时检测加固类型
Java.perform(function() {
    var packer_map = {
        "libjiagu": "360加固", "liblegu": "腾讯乐固", "libshella": "腾讯乐固",
        "libSecShell": "梆梆安全", "libnesec": "网易易盾",
        "libijiami": "爱加密", "libchaosvmp": "娜迦", "libkwscmm": "几维安全",
    };
    Process.enumerateModules().forEach(function(mod) {
        for (var key in packer_map) {
            if (mod.name.indexOf(key) !== -1) {
                console.log("[!] " + mod.name + " -> " + packer_map[key]
                    + " (base:" + mod.base + " size:" + mod.size + ")");
            }
        }
    });
});

针对不同壳的脱壳策略

加固厂商壳代数推荐脱壳方法推荐工具难度
360 基础版一代内存 dumpFrida dump / FDex2★☆☆☆☆
360 企业版二代FART 主动调用FART / Youpk★★★☆☆
腾讯乐固一代修复 DEX 头 + dumpFrida dump★★☆☆☆
梆梆基础一代内存 dumpFrida dump★★☆☆☆
梆梆企业二/三FART + 手动修复FART / IDA★★★★☆
爱加密一/二FART 主动调用FART / Frida★★★☆☆
网易易盾三代Hook 关键 API / 不脱壳Frida Hook / 动态分析★★★★★
娜迦二/三FART + VMP 逆向FART / IDA★★★★☆
几维安全三代VMP 逆向 / 运行时 HookIDA / Frida★★★★☆
1
2
3
4
5
决策流程:
  识别加固 --> 一代壳 --> Frida dump DEX --> Jadx 反编译
          --> 二代壳 --> FART 脱壳 --> 回填 CodeItem --> Jadx
          --> 三代壳 --> 值得脱壳? -- 是 --> 逆向 VMP (数天~数周)
                                    -- 否 --> Hook API / 抓包 / 内存搜索

SO 保护识别

OLLVM 混淆识别

OLLVM 通过控制流平坦化增加逆向难度。IDA 中典型表现为星型 CFG(所有基本块回到一个中心分发器)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
正常函数 CFG:               OLLVM 平坦化后 CFG:
   +-----+                     +----------+
   | 入口 |                     |  分发器   |<-------+
   +--+--+                     | (switch)  |        |
      |                        +--+--+--+--+        |
   +--v--+                       |  |  |  |         |
   | 判断 |                  +---+  |  |  +---+     |
   +--+--+                  v      v  v      v     |
      |                    BB1   BB2  BB3   BB4    |
   +--v--+                  +------+--+------+     |
   | 返回 |                        +---------------+
   +-----+
特征正常函数OLLVM 平坦化函数
基本块数量< 20> 50
CFG 形状树状/DAG星型(中心分发器)
函数大小正常膨胀 3-10 倍
反编译效果正常大量嵌套 switch

SO VMP 识别

SO 层 VMP 将原生 ARM/ARM64 指令转换为自定义字节码,由嵌入的虚拟机解释执行。

识别特征:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
原始 SO 函数:                VMP 保护后:
  func_a:                    func_a:
    mov r0, #1                 push {r0-r12}
    add r1, r0, #2             ldr r0, =vm_bytecode_a
    str r1, [sp]               bl  vm_dispatcher    ----+
    bx lr                      pop {r0-r12}             |
                               bx lr                    |
                                                        |
                             vm_dispatcher:    <---------+
                               ; 读取自定义字节码
                               ; 查 opcode handler 表
                               ; 分发执行
                               ; 循环直到 VM_EXIT
判断依据具体表现
函数入口统一调用 dispatcherIDA 中大量函数只有 bl vm_entry
存在大段不可反汇编数据IDA 标记为 .dataDCB 字节
函数指针数组(handler 表)IDA 中可见连续的函数指针表
SO 体积异常膨胀相比未保护版本大 2-5 倍

Anti-Tampering 识别

SO 文件完整性校验常见于加固后的 Native 库中。

校验方式识别方法绕过思路
CRC32 校验搜索 crc32 函数调用Hook crc32 返回期望值
SHA256 校验搜索 SHA256_Init/Update/FinalHook SHA256 函数族
自实现校验搜索 mmap + 循环读取 + 比较定位校验函数并 NOP
.init_array 校验检查 ELF .init_array 段中的函数修改 .init_array 入口
JNI_OnLoad 校验分析 JNI_OnLoad 中的校验逻辑Hook JNI_OnLoad 跳过
1
2
3
# 用 readelf 检查 .init_array(校验逻辑常藏在这里)
readelf -d libnative.so | grep INIT
readelf -x .init_array libnative.so

运行时保护识别

1
2
3
4
5
6
7
8
9
RASP 保护层次
+----------------------------------------------+
| 应用层: Root/Magisk/KernelSU 检测, 模拟器检测 |
+----------------------------------------------+
| 框架层: Frida/Xposed 检测, 签名校验, DEX CRC  |
+----------------------------------------------+
| 系统层: ptrace 反调试, /proc/self/maps,       |
|         TracerPid, SELinux 状态               |
+----------------------------------------------+
检测目标检测方法识别特征(Frida 可观测)
Frida扫描 27042 端口 / maps 中搜 fridaconnect() 到 27042 / fopen
Root检查 /system/bin/su 等路径File.exists() 访问 su 路径
Xposed查找 de.robv.android.xposedClassLoader.loadClass() 调用
调试器ptrace(PTRACE_TRACEME) / TracerPidptrace syscall / 读取 status
完整性DEX CRC / APK 签名校验getPackageInfo() + 签名比对

实战:快速判断加固方案

💡 思路一句话: 解压 APK → 检查 lib/ 目录下的 SO 文件名(libjiagu.so=360, libsecexe.so=梆梆, libexec.so=爱加密)→ 检查 AndroidManifest 中的 Application 类名 → 5 秒判断加固方案。

完整工作流(约 5 分钟):

第 1 步:基础检查

1
2
3
4
5
6
7
8
# 解压并检查 DEX 大小 -- 小于 500KB 基本确认加固
unzip -l target.apk | grep "classes.dex"

# 列出所有 SO 文件
unzip -l target.apk | grep "\.so$"

# 查看 DEX 文件头
unzip -p target.apk classes.dex | xxd | head -1

第 2 步:APKiD 扫描

1
apkid target.apk

第 3 步:Manifest 确认

1
2
aapt dump xmltree target.apk AndroidManifest.xml | grep "android:name"
# 比对本文「加固厂商识别指纹总表」中的 Application 类名

第 4 步:制定策略

1
2
3
4
5
6
7
8
识别结果 --> 查阅「针对不同壳的脱壳策略」表
  |
  +-> 一代壳 -> Frida dump -> Jadx 反编译 -> 分析
  +-> 二代壳 -> FART 脱壳 -> 回填 CodeItem -> 分析
  +-> 三代壳 -> 评估是否值得脱壳
  |     +-- 是 -> 逆向 VMP 解释器(耗时数天到数周)
  |     +-- 否 -> Hook 关键 API / 网络抓包 / 内存搜索
  +-> 未加固 -> 直接 Jadx 反编译

完整示例 (360 加固)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ apkid target.apk
[+] target.apk!classes.dex
 |-> packer : Qihoo 360 (DexProtector)
[+] target.apk!lib/armeabi-v7a/libjiagu.so
 |-> packer : Qihoo 360

$ unzip -l target.apk | grep "\.so$"
  lib/armeabi-v7a/libjiagu.so
  lib/armeabi-v7a/libjiagu_art.so
# 确认 360 加固,一代壳 -> Frida dump 即可

$ frida -U -f com.target.app -l frida_dex_dump.js --no-pause
# dump 后用 Jadx 打开分析

总结:加固识别是逆向分析的第一步。掌握各厂商的静态特征"指纹"后,结合 APKiD 自动化工具和手动验证,可以在 5 分钟内完成加固方案的判定,从而选择最高效的脱壳和分析策略。


相关内容

如果这篇文章对你有帮助,请我喝杯咖啡吧~
+5 支付宝支付宝
+5 微信微信