首页
社区
课程
招聘
利用Frida破解黑盒环境的Dex函数抽取壳(题目出自看雪高研班2021年10月份作业第二题)
2022-7-17 17:36 16303

利用Frida破解黑盒环境的Dex函数抽取壳(题目出自看雪高研班2021年10月份作业第二题)

2022-7-17 17:36
16303

题目 : 函数抽取壳导致了被保护的函数始终运行在解释模式下。提供的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代码如下:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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触发。

 

运行脚本得到的输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[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  0123456789ABCDEF
c4bce9f8  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  0123456789ABCDEF
c4bcea2e  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  0123456789ABCDEF
c4bcea34  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  0123456789ABCDEF
c4bcea3a  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中查找对应的签名:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
method start : >>>>>>>>>>>void com.xgtl.aggregate.activities.MainActivity.a(android.view.View)
lr:0x206a57 = goto 1b
lr:0x203a79 = const v0, 0x0000185f
lr:0x203f87 =  invoke-static {v0},kind@df4e = invoke-static {v0},Ls/h/e/l/l/H;->i(I)V
lr: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,#0
lr:0x204c49 = invoke-virtual {v0, v1}, kind@61b5
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageView.drawableStateChanged()
lr:0x2064ad = invoke-super {v0}, kind@614c
lr:0x2046e7 = iget-object v0,v1,field@32ae
lr:0x20466d = if-eqz v0,0x0005
lr: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@3285
lr:0x204c49 = invoke-virtual {v0},kind@5cd5
lr:0x206c21 = move-result-object v0
lr:0x20466d = if-eqz v0,0x0022
lr:0x20c951 = return-void
lr:0x2046e7 = iget-object v0,v1,field@32af
lr:0x20466d = if-eqz v0,0x0005
lr:0x204c49 = invoke-virtual {v0},kind@4bef
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageHelper.applySupportImageTint()
lr:0x2046e7 = iget-object v0,v3,field@32ad
lr:0x204c49 = invoke-virtual {v0},kind@6150
lr:0x206c21 = move-result-object v0
lr:0x20466d = if-eqz v0,0x0005
lr:0x203f87 = invoke-static {v0}, kind@4e6e
method start : >>>>>>>>>>>void android.support.v7.widget.DrawableUtils.fixDrawable(android.graphics.drawable.Drawable)
lr:0x203f09 = sget v0, field@0467
lr:0x204023 = const/16 v1, 0x0015
lr:0x204301  = if-ne v0,v1,0x0015
lr:0x20c951 = return-void
lr:0x20466d = if-eqz v0,0x0022
lr:0x206729 = invoke-direct {v3}, kind@4bf8
method start : >>>>>>>>>>>boolean android.support.v7.widget.AppCompatImageHelper.shouldApplyFrameworkTintUsingColorFilter()
lr:0x203f09 = sget v0, field@0467
lr:0x205ccf = const/4 v1,#1
lr:0x205ccf = const/4 v2,#0
lr:0x204023 = const/16 v3, 0x0015
lr:0x204b7b = if-le v0,v3,0x0009
lr:0x2046e7 = iget-object v0,v4,field@32ab
lr:0x20466d = if-eqz v0,0x0003
lr:0x205ccf = const/4 v1,#0
lr:0x20c9f1 = return v1
lr:0x2051d5 = move-result v1
lr:0x20466d = if-eqz v1,0x0009
lr:0x2046e7 = iget-object v1,v3,field@32aa
lr:0x20466d = if-eqz v1,0x000c
lr:0x2046e7 = iget-object v1,v3,field@32ab
lr:0x20466d = if-eqz v1,0x0003
lr:0x20c951 = return-void
lr:0x20c951 = return-void
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelperV17.applyCompoundDrawablesTints()
lr:0x2064ad = invoke-super {v3},kind@4cbb
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelper.applyCompoundDrawablesTints()
lr:0x2046e7 = iget-object v0,v3,field@32e5
lr:0x206943 = if-nez v0,+0x000e
lr:0x2046e7 = iget-object v0,v3,field@32e7
lr:0x206943 = if-nez v0,+0x000a
lr:0x2046e7 = iget-object v0,v3,field@32e6
lr:0x206943 = if-nez v0,+0x0006
lr:0x2046e7 = iget-object v0,v3,field@32e4
lr:0x20466d = if-eqz v0,0x0028
lr:0x20c951 = return-void
lr:0x2046e7 = iget-object v0,v3,field@32ec
lr:0x206943 = if-nez v0,+0x0006
lr:0x2046e7 = iget-object v0,v3,field@32eb
lr:0x20466d = if-eqz v0,0x0018
lr:0x20c951 = return-void
lr:0x2046e7 = iget-object v0,v2,field@818a
lr:0x204c49 = invoke-virtual {v0,v1},kind@61b5
lr:0x2046e7 = iget-object v0,v2,field@818b
lr:0x204c49 = invoke-virtual {v0,v1},kind@61b5
lr:0x2046e7 = iget-object v0,v2,field@8189
lr:0x204c49 = invoke-virtual {v0,v1},kind@61b5
lr:0x205ccf = const/4 v0,#1
lr:0x204c49 = invoke-virtual {v3,v0},kind@5dab
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageView.drawableStateChanged()
lr:0x2064ad = invoke-super {v1},kind@614c
lr:0x2046e7 = iget-object v0,v1,field@32ae
lr:0x20466d = if-eqz v0,+0x0005
lr:0x204c49 = invoke-virtual {v0},kind@4b5b = void android.support.v7.widget.AppCompatBackgroundHelper.applySupportBackgroundTint()
lr:0x2046e7 = iget-object v0,v1,field@32af
lr:0x20466d = if-eqz v0,+0x0005
lr:0x204c49 = invoke-virtual {v0},kind@4bef
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatImageHelper.applySupportImageTint()
lr:0x2046e7 = iget-object v0,v3,field@32ad
lr:0x204c49 = invoke-virtual {v0},kind@6150
lr:0x206c21 = move-result-object v0
lr:0x20466d = if-eqz v0,0x0005
lr:0x203f87 = invoke-static {v0}, kind@4e6e
method start : >>>>>>>>>>>void android.support.v7.widget.DrawableUtils.fixDrawable(android.graphics.drawable.Drawable)
lr:0x203f09 = sget v0, field@0467
lr:0x204023 = const/16 v1, 0x0015
lr:0x204301 = if-ne v0,v1,0x0015
lr:0x20c951 = return-void
lr:0x20466d = if-eqz v0,0x0022
lr:0x206729 = invoke-direct {v3}, kind@4bf8
method start : >>>>>>>>>>>boolean android.support.v7.widget.AppCompatImageHelper.shouldApplyFrameworkTintUsingColorFilter()
lr:0x203f09 = sget v0, field@0467
lr:0x205ccf = const/4 v1,#1
lr:0x205ccf = const/4 v2,#0
lr:0x204023 = const/16 v3, 0x0015
lr:0x204b7b = if-le v0,v3,0x0009
lr:0x2046e7 = iget-object v0,v4,field@32ab
lr:0x20466d = if-eqz v0,0x0003
lr:0x205ccf = const/4 v1,#0
lr:0x20c9f1 = return v1
lr:0x2051d5 = move-result v1
lr:0x20466d = if-eqz v1,0x0009
lr:0x2046e7 = iget-object v1,v3,field@32aa
lr:0x20466d = if-eqz v1,0x000c
lr:0x2046e7 = iget-object v1,v3,field@32ab
lr:0x20466d = if-eqz v1,0x0003
lr:0x20c951 = return-void
lr:0x20c951 = return-void
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelperV17.applyCompoundDrawablesTints()
lr:0x2064ad = invoke-super {v3},kind@4cbb
method start : >>>>>>>>>>>void android.support.v7.widget.AppCompatTextHelper.applyCompoundDrawablesTints()
lr:0x2046e7 = iget-object v0,v3,field@32e5
lr:0x206943 = if-nez v0,+0x000e
lr:0x2046e7 = iget-object v0,v3,field@32e7
lr:0x206943 = if-nez v0,+0x000a
lr:0x2046e7 = iget-object v0,v3,field@32e6
lr:0x206943 = if-nez v0,+0x0006
lr:0x2046e7 = iget-object v0,v3,field@32e4
lr:0x20466d = if-eqz v0,0x0028
lr:0x20c951 = return-void
lr:0x2046e7 = iget-object v0,v3,field@32ec
lr:0x206943 = if-nez v0,+0x0006
lr:0x2046e7 = iget-object v0,v3,field@32eb
lr:0x20466d = if-eqz v0,0x0018
lr:0x20c951 = return-void
lr:0x20c951 = return-void
method 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


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

上传的附件:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 64
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
零丶 2022-7-28 17:33
2
0
6666666666666666
游客
登录 | 注册 方可回帖
返回