概述
更新某营业厅app(版本号7.4.0
)后,发现加载Xposed
模块会导致app crash。
通过分析Tombstone
日志找到检测点,编写Xposed
模块绕过堆栈检测。
日志分析
过滤Error
级别的日志,得到以下信息:
A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x78c in tid 20120 (c10086.activity), pid 20120 (c10086.activity)
A/DEBUG: pid: 20120, tid: 20120, name: c10086.activity >>> com.greenpoint.android.mc10086.activity <<<
A/DEBUG: uid: 10480
A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x78c
A/DEBUG: Cause: null pointer dereference
A/DEBUG: r0 00000000 r1 ffb273e6 r2 0000078c r3 b6a287de
A/DEBUG: r4 c6049d88 r5 00000000 r6 00000000 r7 c6049d88
A/DEBUG: r8 b6a287de r9 0000078c r10 f72255a0 r11 ffb284d0
A/DEBUG: ip f4c7d0a4 sp 00000000 lr 00000000 pc 0000078c
A/DEBUG: backtrace:
A/DEBUG: #00 pc 0000078c <unknown>
A/DEBUG: #01 pc 00000000 <unknown>
E/tombstoned: Tombstone written to: /data/tombstones/tombstone_18
导出/data/tombstones/tombstone_18
到电脑,查看内存(方法栈):
memory near r1 ([stack]):
ffb273c4 00000305 00000017 000002f1 00000581 ................
ffb273d4 f4c8125c 00000000 00000000 736f7078 \...........xpos
ffb273e4 1d006465 00000000 43746567 7373616c ed......getClass
ffb273f4 656d614e 0000e400 72727563 54746e65 Name....currentT
ffb27404 61657268 00f00064 53746567 6b636174 hread...getStack
ffb27414 63617254 00000065 6176616a 6e616c2f Trace...java/lan
ffb27424 68542f67 64616572 0000f200 6a4c2928 g/Thread....()Lj
ffb27434 2f617661 676e616c 7268542f 3b646165 ava/lang/Thread;
ffb27444 00009d00 6a4c2928 2f617661 676e616c ....()Ljava/lang
ffb27454 7274532f 3b676e69 00004100 6176616a /String;.A..java
ffb27464 6e616c2f 74532f67 546b6361 65636172 /lang/StackTrace
ffb27474 6d656c45 00746e65 000000ef 4c5b2928 Element.....()[L
ffb27484 6176616a 6e616c2f 74532f67 546b6361 java/lang/StackT
ffb27494 65636172 6d656c45 3b746e65 00005100 raceElement;.Q..
ffb274a4 14765802 00000000 c6049d88 f4c8125c .Xv.........\...
ffb274b4 00000000 f72255a0 c6001aa5 31362f64 .....U".....d/61
首先通过java.lang.Thread.currentThread
获得当前线程对象,然后调用getStackTrace
获得StackTraceElement
数组,遍历该数组,调用getClassName
,判断是否包含xposed
定位
Xposed
HookThread.getStackTrace
,打印调用栈(函数返回值):
findAndHookMethod(Thread.class, "getStackTrace", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
StackTraceElement[] st = (StackTraceElement[]) param.getResult();
String sts = "";
for (StackTraceElement ste : st) {
sts += ste.toString() + "\n";
}
Log.e("StackTrace", sts);
super.afterHookedMethod(param);
}
});
(如果要HookStackTraceElement.getClassName
,打印返回值即可)
日志如下:
dalvik.system.VMStack.getThreadStackTrace(Native Method)
java.lang.Thread.getStackTrace(Thread.java:1736)
java.lang.reflect.Method.invoke(Native Method)
de.robv.android.xposed.LspHooker.handleHookedMethod(Unknown Source:107)
LspHooker_.getStackTrace(Unknown Source:8)
java.lang.Runtime.nativeLoad(Native Method)
java.lang.Runtime.nativeLoad(Runtime.java:1131)
java.lang.Runtime.loadLibrary0(Runtime.java:1085)
java.lang.Runtime.loadLibrary0(Runtime.java:1008)
java.lang.System.loadLibrary(System.java:1664)
com.secneo.apkwrapper.AW.attachBaseContext(Unknown Source:17)
可以看到调用者是java.lang.Runtime.nativeLoad
,即so加载时调用(在Native
层通过JNI
调用getStackTrace
)
分析
使用JEB
反编译classes.dex
,定位到com.secneo.apkwrapper.AW.attachBaseContext
:
@Override // android.content.ContextWrapper
protected void attachBaseContext(Context context) {
// ...
AW.mC = context;
System.loadLibrary("DexHelper");
H.i();
AW.ᵢ = this;
super.attachBaseContext(context);
// ...
}
可以看到加载的是libDexHelper.so
(旧版本是在<clinit>
加载,现在改到attachBaseContext
了)
由so的加载流程可知,检测函数的调用要么是在JNI_OnLoad
,要么是在.init
或.init_array
段
使用IDA
动态调试,发现该加固是在JNI_OnLoad
中调用检测函数(并非本文重点,就不详细介绍了)
由于最终还是要调用Java
层的getClassName
方法,考虑到通用性,决定编写Xposed
模块,绕过堆栈检测
绕过
Xposed
HookStackTraceElement.getClassName
方法,判断是否包含xposed
,如果包含则替换返回值为android.os.Handler
注意:需要在DexHelper
加载前Hook
findAndHookMethod(StackTraceElement.class, "getClassName", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String className = (String) param.getResult();
if (className != null && className.contains("xposed")) {
param.setResult("android.os.Handler");
}
super.afterHookedMethod(param);
}
});