更新某营业厅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
导出/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
定位
XposedHookThread.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(旧版本是在加载,现在改到attachBaseContext了)
由so的加载流程可知,检测函数的调用要么是在JNI_OnLoad,要么是在.init或.init_array段
使用IDA动态调试,发现该加固是在JNI_OnLoad中调用检测函数(并非本文重点,就不详细介绍了)
由于最终还是要调用Java层的getClassName方法,考虑到通用性,决定编写Xposed模块,绕过堆栈检测
绕过
XposedHookStackTraceElement.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);
}
});