一、加固后的 APK 加载流程
APK 实际结构
com.xxx.yyy.apk
├── classes.dex
│
│ 只有壳
│
├── assets
│
│ ├── 加固的厂商protect1.dex
│ ├── 加固的厂商protect2.dex
│ ├── 加固的厂商protect-sec.dex
│
│
├── lib
│
│ └── lib加固的厂商protect.so
│
│
└── 真正业务dex
↓
加密隐藏
启动:
Android系统
|
|
StubApplication.attachBaseContext()
|
|
加载lib加固的厂商protect.so
|
|
读取assets/加固的厂商protect*.dex
|
|
解密真实classes.dex
|
|
DexClassLoader
|
|
替换Application
|
|
真实的业务代码
二、脱壳方案:Frida + dumpdex
流程:
手机
|
|
启动APP
|
|
StubApplication解密
|
|
Frida注入
|
|
dump内存dex
|
|
jadx分析
三、环境准备
1. 手机要求
Root 、Magisk 、Frida Server
2、PC:
安装:
python3 -m pip install frida-tools
code ~/.zshrc
文件末尾:
export PATH="$HOME/Library/Python/3.9/bin:$PATH"
保存退出,执行:
source ~/.zshrc
frida --version
3、手机启动:
frida-server
unxz frida-server-17.15.2-android-arm64.xz
adb push frida-server-17.15.2-android-arm64 /data/local/tmp/
adb shell "su -c chmod 755 /data/local/tmp/frida-server”
(已重命名为frida-server)
问题:启动 frida-server 时遇到端口 27042 已被占用,这是因为该端口上已经有一个 frida-server 进程在运行(或者被其他程序占用)
方案:
1、杀掉旧进程,释放端口
netstat -tulnp | grep 27042
2、 杀掉该进程(假设 PID 是 1234)
kill -9 1234
3. 重新启动 frida-server
./frida-server
在PC端执行:
frida-ps -U
能看到:应用名称说明成功。
四、第一步:定位真实Dex加载位置
创建hook_dex.js,内容:
Java.perform(function(){
var DexClassLoader =
Java.use("dalvik.system.DexClassLoader");
DexClassLoader.$init.overload(
'java.lang.String',
'java.lang.String',
'java.lang.String',
'java.lang.ClassLoader'
)
.implementation=function(
dexPath,
optimizedDirectory,
librarySearchPath,
parent
){
console.log(
"[Dex Loaded] "
+ dexPath
);
return this.$init(
dexPath,
optimizedDirectory,
librarySearchPath,
parent
);
};
});
运行:
frida -U \
-f com.unitree.doggo2 \
-l hook_dex.js
在 Frida 控制台输入:
%resume
然后回车,作用:继续运行 APP。
如果没有输出 Dex Loaded,退出当前 Frida,执行
exit
修改 hook_dex.js
替换为:
Java.perform(function () {
console.log("[+] Hook start");
// hook DexClassLoader
var DexClassLoader =
Java.use("dalvik.system.DexClassLoader");
DexClassLoader.$init
.overload(
'java.lang.String',
'java.lang.String',
'java.lang.String',
'java.lang.ClassLoader'
)
.implementation=function(
dexPath,
optimizedDirectory,
librarySearchPath,
parent
){
console.log(
"\n[DexClassLoader]"
);
console.log(
dexPath
);
return this.$init(
dexPath,
optimizedDirectory,
librarySearchPath,
parent
);
};
try{
var InMemoryDexClassLoader =
Java.use(
"dalvik.system.InMemoryDexClassLoader"
);
console.log(
"[+] Found InMemoryDexClassLoader"
);
}catch(e){
console.log(
"[-] no InMemoryDexClassLoader"
);
}
});
重新启动:
frida -U \
-f com.unitree.doggo2 \
-l hook_dex.js
输入:
%resume
五、第二步:Dump真实dex
工具:frida-dexdump
python3 -m pip install frida-dexdump
先确认 dex 是否加载,输入:
Java.enumerateLoadedClasses({
onMatch:function(name){
if(name.indexOf("unitree")>=0){
console.log(name);
}
},
onComplete:function(){}
});
查看是否已经释放真实代码。
结果:
dalvik.system.PathClassLoader[
DexPathList[
[
zip file "/data/app/.../base.apk",
dex file "InMemoryDexFile[cookie=[0, ...]]",
dex file "InMemoryDexFile[cookie=[0, ...]]",
dex file "InMemoryDexFile[cookie=[0, ...]]",
dex file "InMemoryDexFile[cookie=[0, ...]]",
dex file "InMemoryDexFile[cookie=[0, ...]]"
]
]
]
说明: 不是 DexClassLoader,InMemoryDexFile也就是:内存加载 dex 模式。真实 dex:不是:/data/data/…/xxx.dex,是在:ART内存。
现在应该直接frida-dexdump。
python3 frida-dexdump \
-U \
-n appId
成功应该输出:dump/
classes.dex
classes2.dex
classes3.dex
classes4.dex
classes5.dex ......
至此,脱壳完成。