查看原文
其他

利用Frida破解黑盒环境的Dex函数抽取壳

飞翔的猫咪 看雪学苑 2022-08-18



恭喜看雪论坛ID【飞翔的猫咪】获得看雪安卓应用安全能力认证高级安全工程师!


看雪论坛作者ID:飞翔的猫咪


题目出自看雪高研班2021年10月份作业第二题:

抽取壳导致了被保护的函数始终运行在解释模式下。提供的pixel镜像已经将默认解释器修改成了Switch实现,请分析该Switch解释器,并编写frida hook脚本,能够实现对解释器解释执行的每一条smali指令的跟踪记录。提供的测试apk采用了自解密的函数抽取技术。


分析:


Dex函数抽取壳目前算是比较常见的加壳方式,对此种壳目前来说比较流行的脱壳工具是寒冰大佬的fart工具。当然这种壳的脱壳方式不止一种,就像题目中描述的那样,由于Dex函数最终还是需要通过art虚拟机来执行的,因此在虚拟机执行引擎内部做手脚也可以直接达成脱壳目标,题目已经给出了解法:直接分析Switch实现的解释器,即可获取函数执行指令流。就是如果分析的函数比较多,函数比较复杂,在每一条指令执行时做手脚会一定程度上影响App的性能。

对于题目中的8.0系统,解释器分为Switch实现和Mterp汇编实现的解释器,Switch实现要好分析很多,因此手头上如果有源码的情况下可以强制解释器用Switch来实现。

做这道题目可以开扩脱壳的思路,原来就算没有源码的情况下,用ida分析so也可以找到脱壳点。了解壳的根本原理以及虚拟机执行的原理才是以不变应万变的法则,剩下的就靠创造力去发挥即可。

解答:


1.先将题目提供的镜像刷写至Pixel手机中,然后将apk安装上去,发现apk以32位模式运行,那么使用adb pull将/system/lib/libart.so文件拉出来放到ida中分析。
 
2.镜像已经带有了整体脱壳功能,其/data/data下面的8299712_dexfile.dex就是MainActivity类所在的dex文件,可以看到里边很多的函数都是抽取型函数,我这里选择分析其中一个函数:com.xgtl.aggregate.activities.MainActivity.a(android.view.View)。
 
3.在ida的exports中寻找解释器的实现函数ExecuteSwitchImpl(可以结合有源码的系统来对比,厂商一般不会改动原生虚拟机),找到取指阶段的指令地址,通过查看aosp源码得知该函数为模板函数,实际编译器生成的函数是四个,它们的地址分别位于0x2089B6,0x021FA66,0x01FA2E0,0x0215028,接下来尝试着用frida进行inline hook,总是跑着跑着就崩溃了,不是报SIGSEGV就是报TRAP,试了各种方法,还是无法解决崩溃的问题,只好换其他的方式来解决。
 
4.通过在源码中搜索,每一条解释器指令执行前都会调用PREAMBLE(),因此如果可以hook住PREAMBLE也可以跟踪每条指令的执行分支。通过观察可以发现PREAMBLE中会调用shadow_frame.GetThisObject(),因此hook shadow_frame.GetThisObject就可以跟踪指令的执行,但是必须有个前提:if (UNLIKELY(instrumentation->HasDexPcListeners())) {这个分支必须满足才会执行下面的shadow_frame.GetThisObject()。
 
5.通过跟踪ida中指令发现执行ExecuteSwitchImpl的时候instrumentation对象的地址存放在r4寄存器中,而HasDexPcListeners()函数被内联了,它是通过r4+480偏移来判断if (UNLIKELY(instrumentation->HasDexPcListeners()),因此只要可以通过frida来修改r4寄存器+480的值,使得instrumentation->HasDexPcListeners()返回为true,这样就可以使shadow_frame.GetThisObject()被调用,然后再hook这个函数即可。
 
frida代码如下:
var prettyMethodPtr;var prettyMethod; function pretty_method(art_method) { if (!prettyMethodPtr) { prettyMethodPtr = Module.getExportByName("libart.so", "_ZN3art9ArtMethod12PrettyMethodEb"); prettyMethod = new NativeFunction(prettyMethodPtr, 'pointer', ['pointer', 'pointer', 'bool']); } var result = Memory.alloc(0x100); prettyMethod(ptr(result), art_method, 1); return result.add(0x8).readPointer().readCString();} var in_method = false;var target_method_name = "com.xgtl.aggregate.activities.MainActivity.a(android.view.View)"; function hookGetThisObject(base) { var get_this_object_addr = Module.getExportByName("libart.so", "_ZNK3art11ShadowFrame13GetThisObjectEt"); Interceptor.attach(get_this_object_addr, { onEnter: function (args) { console.log("pid :" + Process.getCurrentThreadId() + ", lr:" + ptr(this.context.lr).sub(base).add(0xB000)); console.log(hexdump(ptr(this.context.r11), { length: 32 })); console.log() }, onLeave: function (retval) { } });} function hook() { var libartmodule = Process.getModuleByName("libart.so"); var base = libartmodule.base; libartmodule.enumerateSymbols().forEach(function (symbol) { if (symbol.name.indexOf("ExecuteSwitchImpl") != -1 && symbol.name.indexOf("CodeItem") != -1) { Interceptor.attach(symbol.address, { onEnter: function (args) { var instance = ptr(0x43898C).add(base).sub(0xB000).add(0xca9f4).add(480); instance.writeU8(1); var shadow_frame = args[3]; var art_method = ptr(shadow_frame).add(Process.pointerSize).readPointer(); this.method_name = pretty_method(art_method); if (this.method_name.indexOf(target_method_name) != -1) { console.log("method start : >>>>>>>>>>>" + this.method_name); hookGetThisObject(base, this.method_name); in_method = true; } if (in_method) { console.log("method start : >>>>>>>>>>>" + this.method_name); } }, onLeave: function (retval) { if (this.method_name.indexOf(target_method_name) != -1) { console.log("method end : >>>>>>>>>>>" + this.method_name); Interceptor.detachAll(); in_method = false; } } }); } });}function main() { hook();}setImmediate(main);

6.这里要跟踪的函数为com.xgtl.aggregate.activities.MainActivity.a(android.view.View),需要app进入以后点击界面下方的tab触发。
 
运行脚本得到的输出如下:
[AOSP on msm8996::com.xgtl.assistant]-> method start : >>>>>>>>>>>void com.xgtl.aggregate.activities.MainActivity.a(android.view.View)method start : >>>>>>>>>>>void com.xgtl.aggregate.activities.MainActivity.a(android.view.View)pid :16444, lr:0x206a57 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFc4bce9f8 28 1b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (...............c4bcea08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ pid :16444, lr:0x203a79 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFc4bcea2e 14 00 5f 18 00 00 71 10 4e df 00 00 28 e0 02 00 .._...q.N...(...c4bcea3e 02 00 02 00 00 00 00 00 00 00 09 00 00 00 12 01 ................ pid :16444, lr:0x203f87 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFc4bcea34 71 10 4e df 00 00 28 e0 02 00 02 00 02 00 00 00 q.N...(.........c4bcea44 00 00 00 00 09 00 00 00 12 01 70 20 87 ca 10 00 ..........p .... pid :16444, lr:0x206a57 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFc4bcea3a 28 e0 02 00 02 00 02 00 00 00 00 00 00 00 09 00 (...............c4bcea4a 00 00 12 01 70 20 87 ca 10 00 0c 01 71 20 50 ca ....p ......q P.

每条smali指令的执行都会打印出一条lr以及指令所在地址处的值。
 
接下来分析打印:
第一条lr为0x206a57,使用ida跳转至此地址处,发现该地址处的switch-case对应的case为40,也就是0x28,与c4bce9f8 28 1b这条打印匹配,还原了指令以后就是goto 1b,因此第一条指令为goto 1b。接下来把所有的log按dex格式还原即可,遇到method id或者field id,则在8299712_dexfile.dex中查找对应的签名:
method start : >>>>>>>>>>>void com.xgtl.aggregate.activities.MainActivity.a(android.view.View)lr:0x206a57 = goto 1blr:0x203a79 = const v0, 0x0000185flr:0x203f87 = invoke-static {v0},kind@df4e = invoke-static {v0},Ls/h/e/l/l/H;->i(I)Vlr:0x206a57 = goto e0 lr:0x2046e7 = iget-object v0,v2,field@8188 = Landroid/support/v4/media/MediaBrowserServiceCompat;->mSession:Landroid/support/v4/media/session/MediaSessionCompat$Token;lr:0x205ccf = const/4 vA, #+B = const/4 v1,#0lr:0x204c49 = invoke-virtual {v0, v1}, kind@61b5method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageView.drawableStateChanged()lr:0x2064ad = invoke-super {v0}, kind@614clr:0x2046e7 = iget-object v0,v1,field@32aelr:0x20466d = if-eqz v0,0x0005lr:0x204c49 = invoke-virtual {v0},kind@4b5b = void android.support.v7.widget.AppCompatBackgroundHelper.applySupportBackgroundTint()method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatBackgroundHelper.applySupportBackgroundTint()lr:0x2046e7 = iget-object v0,v3,field@3285lr:0x204c49 = invoke-virtual {v0},kind@5cd5lr:0x206c21 = move-result-object v0lr:0x20466d = if-eqz v0,0x0022lr:0x20c951 = return-voidlr:0x2046e7 = iget-object v0,v1,field@32aflr:0x20466d = if-eqz v0,0x0005lr:0x204c49 = invoke-virtual {v0},kind@4befmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageHelper.applySupportImageTint()lr:0x2046e7 = iget-object v0,v3,field@32adlr:0x204c49 = invoke-virtual {v0},kind@6150lr:0x206c21 = move-result-object v0lr:0x20466d = if-eqz v0,0x0005lr:0x203f87 = invoke-static {v0}, kind@4e6emethod start : >>>>>>>>>>>void android.support.v7.widget.DrawableUtils.fixDrawable(android.graphics.drawable.Drawable)lr:0x203f09 = sget v0, field@0467lr:0x204023 = const/16 v1, 0x0015lr:0x204301 = if-ne v0,v1,0x0015lr:0x20c951 = return-voidlr:0x20466d = if-eqz v0,0x0022lr:0x206729 = invoke-direct {v3}, kind@4bf8method start : >>>>>>>>>>>boolean android.support.v7.widget.AppCompatImageHelper.shouldApplyFrameworkTintUsingColorFilter()lr:0x203f09 = sget v0, field@0467lr:0x205ccf = const/4 v1,#1lr:0x205ccf = const/4 v2,#0lr:0x204023 = const/16 v3, 0x0015lr:0x204b7b = if-le v0,v3,0x0009lr:0x2046e7 = iget-object v0,v4,field@32ablr:0x20466d = if-eqz v0,0x0003lr:0x205ccf = const/4 v1,#0lr:0x20c9f1 = return v1lr:0x2051d5 = move-result v1lr:0x20466d = if-eqz v1,0x0009lr:0x2046e7 = iget-object v1,v3,field@32aalr:0x20466d = if-eqz v1,0x000clr:0x2046e7 = iget-object v1,v3,field@32ablr:0x20466d = if-eqz v1,0x0003lr:0x20c951 = return-voidlr:0x20c951 = return-voidmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelperV17.applyCompoundDrawablesTints()lr:0x2064ad = invoke-super {v3},kind@4cbbmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelper.applyCompoundDrawablesTints()lr:0x2046e7 = iget-object v0,v3,field@32e5lr:0x206943 = if-nez v0,+0x000elr:0x2046e7 = iget-object v0,v3,field@32e7lr:0x206943 = if-nez v0,+0x000alr:0x2046e7 = iget-object v0,v3,field@32e6lr:0x206943 = if-nez v0,+0x0006lr:0x2046e7 = iget-object v0,v3,field@32e4lr:0x20466d = if-eqz v0,0x0028lr:0x20c951 = return-voidlr:0x2046e7 = iget-object v0,v3,field@32eclr:0x206943 = if-nez v0,+0x0006lr:0x2046e7 = iget-object v0,v3,field@32eblr:0x20466d = if-eqz v0,0x0018lr:0x20c951 = return-voidlr:0x2046e7 = iget-object v0,v2,field@818alr:0x204c49 = invoke-virtual {v0,v1},kind@61b5lr:0x2046e7 = iget-object v0,v2,field@818blr:0x204c49 = invoke-virtual {v0,v1},kind@61b5lr:0x2046e7 = iget-object v0,v2,field@8189lr:0x204c49 = invoke-virtual {v0,v1},kind@61b5lr:0x205ccf = const/4 v0,#1lr:0x204c49 = invoke-virtual {v3,v0},kind@5dabmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageView.drawableStateChanged()lr:0x2064ad = invoke-super {v1},kind@614clr:0x2046e7 = iget-object v0,v1,field@32aelr:0x20466d = if-eqz v0,+0x0005lr:0x204c49 = invoke-virtual {v0},kind@4b5b = void android.support.v7.widget.AppCompatBackgroundHelper.applySupportBackgroundTint()lr:0x2046e7 = iget-object v0,v1,field@32aflr:0x20466d = if-eqz v0,+0x0005lr:0x204c49 = invoke-virtual {v0},kind@4befmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageHelper.applySupportImageTint()lr:0x2046e7 = iget-object v0,v3,field@32adlr:0x204c49 = invoke-virtual {v0},kind@6150lr:0x206c21 = move-result-object v0lr:0x20466d = if-eqz v0,0x0005lr:0x203f87 = invoke-static {v0}, kind@4e6emethod start : >>>>>>>>>>>void android.support.v7.widget.DrawableUtils.fixDrawable(android.graphics.drawable.Drawable)lr:0x203f09 = sget v0, field@0467lr:0x204023 = const/16 v1, 0x0015lr:0x204301 = if-ne v0,v1,0x0015lr:0x20c951 = return-voidlr:0x20466d = if-eqz v0,0x0022lr:0x206729 = invoke-direct {v3}, kind@4bf8method start : >>>>>>>>>>>boolean android.support.v7.widget.AppCompatImageHelper.shouldApplyFrameworkTintUsingColorFilter()lr:0x203f09 = sget v0, field@0467lr:0x205ccf = const/4 v1,#1lr:0x205ccf = const/4 v2,#0lr:0x204023 = const/16 v3, 0x0015lr:0x204b7b = if-le v0,v3,0x0009lr:0x2046e7 = iget-object v0,v4,field@32ablr:0x20466d = if-eqz v0,0x0003lr:0x205ccf = const/4 v1,#0lr:0x20c9f1 = return v1lr:0x2051d5 = move-result v1lr:0x20466d = if-eqz v1,0x0009lr:0x2046e7 = iget-object v1,v3,field@32aalr:0x20466d = if-eqz v1,0x000clr:0x2046e7 = iget-object v1,v3,field@32ablr:0x20466d = if-eqz v1,0x0003lr:0x20c951 = return-voidlr:0x20c951 = return-voidmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelperV17.applyCompoundDrawablesTints()lr:0x2064ad = invoke-super {v3},kind@4cbbmethod start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelper.applyCompoundDrawablesTints()lr:0x2046e7 = iget-object v0,v3,field@32e5lr:0x206943 = if-nez v0,+0x000elr:0x2046e7 = iget-object v0,v3,field@32e7lr:0x206943 = if-nez v0,+0x000alr:0x2046e7 = iget-object v0,v3,field@32e6lr:0x206943 = if-nez v0,+0x0006lr:0x2046e7 = iget-object v0,v3,field@32e4lr:0x20466d = if-eqz v0,0x0028lr:0x20c951 = return-voidlr:0x2046e7 = iget-object v0,v3,field@32eclr:0x206943 = if-nez v0,+0x0006lr:0x2046e7 = iget-object v0,v3,field@32eblr:0x20466d = if-eqz v0,0x0018lr:0x20c951 = return-voidlr:0x20c951 = return-voidmethod end : >>>>>>>>>>>void com.xgtl.aggregate.activities.MainActivity.a(android.view.View)

由于方法里边调用了其他方法如void android.support.v7.widget.AppCompatBackgroundHelper.applySupportBackgroundTint(),我这里打印出了执行的每条smali指令。
这里通过查看指令码就可以大致看出程序的逻辑,如果还想更进一步,就需要分隔各个方法,写到dex文件中去,用jadx这种工具来打开即可。
 
由于附件超出了8M,我用split命令将原始文件分割了开了,合并起来:
cat test.apk.split1 test.apk.split2 test.apk.split3 > test.apk



看雪ID:飞翔的猫咪

https://bbs.pediy.com/user-home-607812.htm

*本文由看雪论坛 飞翔的猫咪 原创,转载请注明来自看雪社区



# 往期推荐

1.AFL速通——流程及afl-fuzz.c源码简析

2.Android题目分析部分思路总结

3.sql注入学习笔记

4.文件上传和文件包含的各种姿势

5.《安卓高级研修班》推出【看雪安卓应用安全能力认证】证书

6.React Native Hermes 逆向实践






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存