本文为2016年0ctf中的mobile题目State of the ART的writeup。

1

State of the ART writeup

首先点我下载题目,这道题提供了三个文件,分别为:

a:内存布局文件
b:oatdump的结果文件
c:boot.oat文件

经过对几个文件的初步观察,发现在b文件中找到一个可疑函数oat.sjl.gossip.oat.MainActivity.check(java.lang.String),函数名字可不会乱取,此函数肯定和Flag密切相关,因此需要还原出该方法。可惜,出题者有意抹去了oatdump中的dalvik字节码,不然这道题会简单的多。
我们分段分析汇编代码,首先看下面一段:

1
2
3
4
5
6
7
8
9
10
11
0x00371e8c: b099 sub sp, sp, #100
0x00371e8e: 9000 str r0, [sp, #0]
0x00371e90: 9121 str r1, [sp, #132]
0x00371e92: 9222 str r2, [sp, #136]
0x00371e94: f8d9e11c ldr.w lr, [r9, #284] ; pAllocArrayResolved
0x00371e98: 9900 ldr r1, [sp, #0]
0x00371e9a: 2606 movs r6, #6
0x00371e9c: 1c32 mov r2, r6
0x00371e9e: f64e0020 movw r0, #59424
0x00371ea2: f2c7005b movt r0, #28763
0x00371ea6: 47f0 blx lr

其中pAllocArrayResolved对应的是artAllocArrayFromCode函数,原型为:
extern "C" mirror::Array* artAllocArrayFromCode##suffix##suffix2(uint32_t type_idx, mirror::ArtMethod* method, int32_t component_count, Thread* self, StackReference<mirror::ArtMethod>* sp)
根据分析,其功能是创建数组,其包含3个参数,r0为创建对象的类型,r1为method对象,r2为待创建数组元素的个数,返回值存放在r0,返回类型为mirror::Array*。
下一段代码为:

1
2
3
4
5
0x00371ea8: f8d9e190 ldr.w lr, [r9, #400] ; pHandleFillArrayData
0x00371eac: 4682 mov r10, r0
0x00371eae: 4650 mov r0, r10
0x00371eb0: f20f6144 adr r1, +1604 (0x003724f8)
0x00371eb4: 47f0 blx lr

其中pHandleFillArrayData对应的是artHandleFillArrayDataFromCode函数,原型为:
extern "C" int artHandleFillArrayDataFromCode(mirror::Array* array, const Instruction::ArrayDataPayload* payload, Thread* self, StackReference<mirror::ArtMethod>* sp)
根据分析,其功能是初始化数组,其包含2个参数,r0为待初始化的数组对象,也就是pAllocArrayResolved的返回值,r1指向赋值内容,其类型为Instruction::ArrayDataPayload*,对应的数据结构为:

1
2
3
4
5
6
7
struct PACKED(4) ArrayDataPayload {
const uint16_t ident; // 标志
const uint16_t element_width; // 每一个元素的大小
const uint32_t element_count; // 元素的个数
const uint8_t data[]; //指向真正的数据
...
};

r1的值为0x003724f8,其对应的区域为:

1
2
3
4
5
6
7
8
0x003724f8: 0300 lsls r0, r0, #12
0x003724fa: 0001 lsls r1, r0, #0
0x003724fc: 0006 lsls r6, r0, #0
0x003724fe: 0000 lsls r0, r0, #0
0x00372500: 4578 cmp r0, pc
0x00372502: 3278 adds r2, #120
0x00372504: 3757 adds r7, #87
0x00372506: 0000 lsls r0, r0, #0

ident为固定值,也就是0x0300,element_width为1,表示每一个元素的大小为1,也就是说这是一个byte数组,element_count为6,表示数组包含6个byte元素。同理后续几段汇编依次创建并初始化了5个byte数组。
上述汇编共创建并初始化了6个byte数组,对应的Java源码为:

1
2
3
4
5
6
7
// 每行的注释指的的是该数组对象的存放位置
byte[] s1 = new byte[]{0x78, 0x45, 0x78, 0x32, 0x57, 0x37}; // r10
byte[] s2 = new byte[]{0x22, 0x29, 0x44, 0x55, 0x60, 0x33}; // r7
byte[] s3 = new byte[]{0x17, 0x94, 0x35, 0x03, 0x90}; // [sp, #56]
byte[] s4 = new byte[]{0x45, 0x64, 0x5f, 0x41,0x52, 0x54, 0x7d}; // [sp, #60]
byte[] s5 = new byte[]{0x58, 0x75, 0x1b, 0xf0, 0x0f, 0x4c}; // r11
byte[] s6 = new byte[]{0x69, 0x0c, 0x1b, 0xbe, 0xf2, 0x49}; //[sp, #52]

再看下面一段:

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
0x00371f60: 2500 movs r5, #0
0x00371f62: 68be ldr r6, [r7, #8] // r7指向s2数组的mirror::Array对象,r6为s2数组包含的元素个数
0x00371f64: 42b5 cmp r5, r6
0x00371f66: f2808034 bge.w +104 (0x00371fd2) // 这里就是一个for循环
0x00371f6a: 68ba ldr r2, [r7, #8]
0x00371f6c: f117030c adds r3, r7, #12
0x00371f70: 4295 cmp r5, r2
0x00371f72: f0808248 bcs.w +1168 (0x00372406) // 对s2数组进行边界判断
0x00371f76: 575e ldrsb r6, [r3, r5] // r6 = s2[r5],也就是取出s2数组的元素
0x00371f78: 68b8 ldr r0, [r7, #8]
0x00371f7a: f1160636 adds r6, r6, #54 // r6 = s2[r5] + 54
0x00371f7e: f3460607 UNKNOWN 52 // SBFX.W R6, R6, #0, #8
0x00371f82: f1170c0c adds r12, r7, #12
0x00371f86: 4285 cmp r5, r0
0x00371f88: f0808242 bcs.w +1156 (0x00372410) // 对s2数组进行边界判断
0x00371f8c: f80c6005 strb r6, [r12, r5]
0x00371f90: 68b9 ldr r1, [r7, #8]
0x00371f92: f117020c adds r2, r7, #12
0x00371f96: 428d cmp r5, r1
0x00371f98: f080823f bcs.w +1150 (0x0037241a) // 对s2数组进行边界判断
0x00371f9c: 5756 ldrsb r6, [r2, r5]
0x00371f9e: 9b0d ldr r3, [sp, #52] // 取出s6数组的mirror::Array对象的首地址
0x00371fa0: f8d3c008 ldr.w r12, [r3, #8]
0x00371fa4: f113000c adds r0, r3, #12 // 取出s6数组的mirror::Array对象中数据区域的首地址
0x00371fa8: 4565 cmp r5, r12
0x00371faa: f080823a bcs.w +1140 (0x00372422) // 对s6数组进行边界判断
0x00371fae: f9108005 UNKNOWN 17 // ldrsb.w r8, [r0, r5],也就是r8 = s6[r5]
0x00371fb2: 68ba ldr r2, [r7, #8]
0x00371fb4: ea860608 eor.w r6, r6, r8 // r6 = (s2[r5] + 54) ^ s6[r5]
0x00371fb8: f3460607 UNKNOWN 52 // SBFX.W R6, R6, #0, #8
0x00371fbc: f117010c adds r1, r7, #12
0x00371fc0: 4295 cmp r5, r2
0x00371fc2: f0808233 bcs.w +1126 (0x0037242c) // 对s2数组进行边界判断
0x00371fc6: 554e strb r6, [r1, r5] // s2[r5] = (s2[r5] + 54) ^ s6[r5]
0x00371fc8: 1c6d adds r5, r5, #1 // r5 += 1
0x00371fca: 3c01 subs r4, #1
0x00371fcc: f47fafc9 bne.w -110 (0x00371f62) // 循环,这里跳回去

可以看到,上面汇编代码里含有大量的边界判断的跳转,这些应该是系统自动添加的一些处理,我们可以忽略掉。另外需要我们对mirror::Array类型有一定的了解。汇编代码中出现的UNKNOWN部分,应该是oatdump没有识别出这些指令,我们可以利用ida识别出来。这段对应的Java源码为:

1
2
3
for (int i = 0; i < s2.length; ++i) {
s2[i] = (byte) ((s2[i] + 54) ^ s6[i]);
}

继续下一段:

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
0x00371fd2: 2500 movs r5, #0
0x00371fd4: f8da6008 ldr.w r6, [r10, #8]
0x00371fd8: 42b5 cmp r5, r6
0x00371fda: f2808051 bge.w +162 (0x00372080) // 这里就是一个for循环
0x00371fde: f8da3008 ldr.w r3, [r10, #8]
0x00371fe2: f11a0c0c adds r12, r10, #12
0x00371fe6: 429d cmp r5, r3
0x00371fe8: f0808229 bcs.w +1106 (0x0037243e) // 对s1数组进行边界判断
0x00371fec: f91c6005 UNKNOWN 17 // ldrsb.w r6, [r12, r5],也就是r6 = s1[r5]
0x00371ff0: f04f0857 mov.w r8, #87
0x00371ff4: 4546 cmp r6, r8 // s1[r5]和87比较
0x00371ff6: f0408009 bne.w +18 (0x0037200c) // 如果不等则跳向0x0037200c,等于则跳向0x00371ffa
0x00371ffa: f8da1008 ldr.w r1, [r10, #8]
0x00371ffe: 2669 movs r6, #105 // r6 = 105
0x00372000: f11a000c adds r0, r10, #12
0x00372004: 428d cmp r5, r1
0x00372006: f080821f bcs.w +1086 (0x00372448) // 对s1数组进行边界判断
0x0037200a: 5546 strb r6, [r0, r5] // s1[r5] = 105
0x0037200c: f8da2008 ldr.w r2, [r10, #8]
0x00372010: f11a030c adds r3, r10, #12
0x00372014: 4295 cmp r5, r2
0x00372016: f080821b bcs.w +1078 (0x00372450) // 对s1数组进行边界判断
0x0037201a: 575e ldrsb r6, [r3, r5] // r6 = s1[r5]
0x0037201c: f04f0832 mov.w r8, #50
0x00372020: 4546 cmp r6, r8 // s1[r5]和50比较
0x00372022: f040800b bne.w +22 (0x0037203c) // 如果不等则跳向0x0037203c,等于则跳向0x00372026
0x00372026: f8da0008 ldr.w r0, [r10, #8]
0x0037202a: f06f067b mvn r6, #123 // r6 = ~123
0x0037202e: f11a0c0c adds r12, r10, #12
0x00372032: 4285 cmp r5, r0
0x00372034: f0808211 bcs.w +1058 (0x0037245a) // 对s1数组进行边界判断
0x00372038: f80c6005 strb r6, [r12, r5] // s1[r5] = ~123
0x0037203c: f8da1008 ldr.w r1, [r10, #8]
0x00372040: f11a020c adds r2, r10, #12
0x00372044: 428d cmp r5, r1
0x00372046: f080820d bcs.w +1050 (0x00372464) // 对s1数组进行边界判断
0x0037204a: 5756 ldrsb r6, [r2, r5]
0x0037204c: f8db3008 ldr.w r3, [r11, #8]
0x00372050: f11b0c0c adds r12, r11, #12
0x00372054: 429d cmp r5, r3
0x00372056: f0808209 bcs.w +1042 (0x0037246c) // 对s5数组进行边界判断
0x0037205a: f91c8005 UNKNOWN 17 // ldrsb.w r8, [r12, r5],也就是r8 = s5[r5]
0x0037205e: f8da1008 ldr.w r1, [r10, #8]
0x00372062: ea860608 eor.w r6, r6, r8 // r6 = s1[r5] ^ s5[r5]
0x00372066: f3460607 UNKNOWN 52 // SBFX.W R6, R6, #0, #8
0x0037206a: f11a000c adds r0, r10, #12
0x0037206e: 428d cmp r5, r1
0x00372070: f0808201 bcs.w +1026 (0x00372476) // 对s1数组进行边界判断
0x00372074: 5546 strb r6, [r0, r5] // s1[r5] = s1[r5] ^ s5[r5]
0x00372076: 1c6d adds r5, r5, #1
0x00372078: 3c01 subs r4, #1
0x0037207a: f47fafab bne.w -170 (0x00371fd4) // 循环,这里跳回去

这段对应的Java源码为:

1
2
3
4
5
6
7
8
9
for (int i = 0; i < s1.length; i++) {
if (s1[i] == 87) {
s1[i] = 105;
}
if (s1[i] == 50) {
s1[i] = ~(123);
}
s1[i] = (byte) (s1[i] ^ s5[i]);
}

继续下一段:

1
2
3
4
5
6
7
8
9
0x00372080: f8da6008 ldr.w r6, [r10, #8] // r6 = s1.length
0x00372084: f8d9e11c ldr.w lr, [r9, #284] ; pAllocArrayResolved
0x00372088: f8d78008 ldr.w r8, [r7, #8] // r8 = s2.length
0x0037208c: 9900 ldr r1, [sp, #0]
0x0037208e: eb160608 adds.w r6, r6, r8 // r6 = s1.length + s2.length
0x00372092: 1c32 mov r2, r6
0x00372094: f64e0020 movw r0, #59424
0x00372098: f2c7005b movt r0, #28763
0x0037209c: 47f0 blx lr

对应的Java源码为:

1
byte[] d1 = new byte[s1.length + s2.length]; // [sp, #40]

继续下一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0x0037209e: f8da2008 ldr.w r2, [r10, #8]
0x003720a2: 900a str r0, [sp, #40]
0x003720a4: 2600 movs r6, #0
0x003720a6: 9800 ldr r0, [sp, #0]
0x003720a8: f04f0800 mov.w r8, #0
0x003720ac: 9216 str r2, [sp, #88]
0x003720ae: 9a16 ldr r2, [sp, #88]
0x003720b0: f8cd8010 str.w r8, [sp, #16]
0x003720b4: 68c0 ldr r0, [r0, #12]
0x003720b6: f24f0cd8 movw r12, #61656
0x003720ba: f850000c ldr.w r0, [r0, r12]
0x003720be: 9205 str r2, [sp, #20]
0x003720c0: f8d0e028 ldr.w lr, [r0, #40]
0x003720c4: 9b0a ldr r3, [sp, #40] // r3为d1数组对象
0x003720c6: 4651 mov r1, r10 // r1为s1数组对象
0x003720c8: 1c32 mov r2, r6 // r2 = 0
0x003720ca: 47f0 blx lr // 这是执行什么函数?

这里最后的跳转,lr的值是无法计算出来的。最开始我尝试对获取lr的过程进行分析,但是这里取值的过程绕了多次,因此放弃了这种方法。我们在整个文件中搜索#61656,找到下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3: java.lang.Object[] android.support.v4.content.FileProvider.copyOf(java.lang.Object[], int) (dex_method_idx=2961)
DEX CODE:
0x0000: const/4 v1, #+0
0x0001: new-array v0, v3, java.lang.Object[] // type@2035
0x0003: invoke-static {v2, v1, v0, v1, v3}, void java.lang.System.arraycopy(java.lang.Object, int, java.lang.Object, int, int) // method@15411
...
0x00216b14: 1c05 mov r5, r0
0x00216b16: 1c38 mov r0, r7
0x00216b18: 68c0 ldr r0, [r0, #12]
0x00216b1a: 2200 movs r2, #0
0x00216b1c: 9204 str r2, [sp, #16]
0x00216b1e: f24f0cd8 movw r12, #61656
0x00216b22: f850000c ldr.w r0, [r0, r12]
0x00216b26: 9605 str r6, [sp, #20]
0x00216b28: f8d0e028 ldr.w lr, [r0, #40]
0x00216b2c: 4641 mov r1, r8
0x00216b2e: 2200 movs r2, #0
0x00216b30: 1c2b mov r3, r5
0x00216b32: 47f0 blx lr

和上面的代码对比,不难发现执行的即为java.lang.System.arraycopy函数,因此前面汇编代码对应的Java源码为:

1
System.arraycopy(s1, 0, d1, 0, s1.length);

继续下一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x003720cc: f8da8008 ldr.w r8, [r10, #8]
0x003720d0: 68b8 ldr r0, [r7, #8]
0x003720d2: 2600 movs r6, #0
0x003720d4: 9016 str r0, [sp, #88]
0x003720d6: 9800 ldr r0, [sp, #0]
0x003720d8: 9a16 ldr r2, [sp, #88]
0x003720da: f8cd8010 str.w r8, [sp, #16]
0x003720de: 68c0 ldr r0, [r0, #12]
0x003720e0: f24f0cd8 movw r12, #61656
0x003720e4: f850000c ldr.w r0, [r0, r12]
0x003720e8: 9205 str r2, [sp, #20]
0x003720ea: f8d0e028 ldr.w lr, [r0, #40]
0x003720ee: 9b0a ldr r3, [sp, #40] // r3为o1数组对象
0x003720f0: 1c39 mov r1, r7 // r1为b2数组对象
0x003720f2: 1c32 mov r2, r6 // r2为0
0x003720f4: 47f0 blx lr // 跳转System.arraycopy函数

对应的Java源码为:

1
System.arraycopy(s2, 0, d1, s2.length, s2.length);

继续下一段:

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
0x003720f6: 980e ldr r0, [sp, #56] // r0为s3数组对象
0x003720f8: 2604 movs r6, #4
0x003720fa: f04f0804 mov.w r8, #4
0x003720fe: 6881 ldr r1, [r0, #8]
0x00372100: 2904 cmp r1, #4
0x00372102: f24081c0 bls.w +896 (0x00372486) // 对s3数组进行边界判断
0x00372106: f9908010 ldrsb.w r8, [r0, #16] // r8 = s3[4]
0x0037210a: 6882 ldr r2, [r0, #8]
0x0037210c: f1b80831 subs r8, r8, #49 // r8 = s3[4] - 49
0x00372110: f3480807 UNKNOWN 52 // SBFX.W R8, R8, #0, #8
0x00372114: 2a04 cmp r2, #4
0x00372116: f24081ba bls.w +884 (0x0037248e) // 对s3数组进行边界判断
0x0037211a: f8808010 strb r8, [r0,#16] // s3[4] = s3[4] - 49
0x0037211e: 6883 ldr r3, [r0, #8]
0x00372120: 2603 movs r6, #3
0x00372122: f04f0803 mov.w r8, #3
0x00372126: 2b03 cmp r3, #3
0x00372128: f24081b6 bls.w +876 (0x00372498) // 对s3数组进行边界判断
0x0037212c: f990800f ldrsb.w r8, [r0, #15] // r8 = s3[3]
0x00372130: f8d0c008 ldr.w r12, [r0, #8]
0x00372134: f118082f adds r8, r8, #47 // r8 = s3[3] + 47
0x00372138: f3480807 UNKNOWN 52 // SBFX.W R8, R8, #0, #8
0x0037213c: f1bc0f03 cmp.w r12, #3
0x00372140: f24081af bls.w +862 (0x003724a2) // 对s3数组进行边界判断
0x00372144: f880800f strb r8, [r0,#15] // s3[3] = s3[3] + 47
0x00372148: 6881 ldr r1, [r0, #8]
0x0037214a: 2602 movs r6, #2
0x0037214c: f04f0802 mov.w r8, #2
0x00372150: 2902 cmp r1, #2
0x00372152: f24081ab bls.w +854 (0x003724ac) // 对s3数组进行边界判断
0x00372156: f990800e ldrsb.w r8, [r0, #14] // r8 = s3[2]
0x0037215a: 6882 ldr r2, [r0, #8]
0x0037215c: f118082a adds r8, r8, #42 // r8 = s3[2] + 42
0x00372160: f3480807 UNKNOWN 52 // SBFX.W R8, R8, #0, #8
0x00372164: 2a02 cmp r2, #2
0x00372166: f24081a5 bls.w +842 (0x003724b4) // 对s3数组进行边界判断
0x0037216a: f880800e strb r8, [r0,#14] // s3[2] = s3[2] + 42
0x0037216e: 6883 ldr r3, [r0, #8]
0x00372170: 2601 movs r6, #1
0x00372172: f04f0801 mov.w r8, #1
0x00372176: 2b01 cmp r3, #1
0x00372178: f24081a1 bls.w +834 (0x003724be) // 对s3数组进行边界判断
0x0037217c: f990800d ldrsb.w r8, [r0, #13] // r8 = s3[1]
0x00372180: f8d0c008 ldr.w r12, [r0, #8]
0x00372184: f1b80822 subs r8, r8, #34 // r8 = s3[1] - 34
0x00372188: f3480807 UNKNOWN 52 // SBFX.W R8, R8, #0, #8
0x0037218c: f1bc0f01 cmp.w r12, #1
0x00372190: f240819a bls.w +820 (0x003724c8) // 对s3数组进行边界判断
0x00372194: f880800d strb r8, [r0,#13] // s3[1] = s3[1] - 34
0x00372198: 6881 ldr r1, [r0, #8]
0x0037219a: 2600 movs r6, #0
0x0037219c: f04f0800 mov.w r8, #0
0x003721a0: 2900 cmp r1, #0
0x003721a2: f0008196 beq.w +812 (0x003724d2) // 对s3数组进行边界判断
0x003721a6: f990800c ldrsb.w r8, [r0, #12] // r8 = s3[0]
0x003721aa: 6882 ldr r2, [r0, #8]
0x003721ac: f118082e adds r8, r8, #46 // r8 = s3[0] + 46
0x003721b0: f3480807 UNKNOWN 52 // SBFX.W R8, R8, #0, #8
0x003721b4: 2a00 cmp r2, #0
0x003721b6: f0008190 beq.w +800 (0x003724da) // 对s3数组进行边界判断
0x003721ba: 9900 ldr r1, [sp, #0]
0x003721bc: f880800c strb r8, [r0,#12] // s3[0] = s3[0] + 46

对应的Java源码为:

1
2
3
4
5
s3[4] = (byte) (s3[4] - 49);
s3[3] = (byte) (s3[3] + 47);
s3[2] = (byte) (s3[2] + 42);
s3[1] = (byte) (s3[1] - 34);
s3[0] = (byte) (s3[0] + 46);

继续下一段:

1
2
3
4
0x003721c0: f8d9e12c ldr.w lr, [r9, #300] ; pAllocObjectInitialized
0x003721c4: f64b10e0 movw r0, #47584
0x003721c8: f2c70049 movt r0, #28745
0x003721cc: 47f0 blx lr

其中pAllocObjectInitialized是初始化一个对象,类型通过r0确定,我们在整个文件搜索movw r0, #47584movt r0, #28745,从而确定了此处对应的dalvik字节码为new-instance v0, java.lang.StringBuilder
继续下一段:

1
2
3
4
5
6
0x003721ce: f8d9e12c ldr.w lr, [r9, #300] ; pAllocObjectInitialized
0x003721d2: 9900 ldr r1, [sp, #0]
0x003721d4: 9012 str r0, [sp, #72]
0x003721d6: f64b00f0 movw r0, #47344
0x003721da: f2c7003e movt r0, #28734
0x003721de: 47f0 blx lr

同样的方法,此处对应的dalvik字节码为new-instance v1, java.lang.String
继续下一段:

1
2
3
4
5
6
7
8
0x003721e0: 9a0e ldr r2, [sp, #56] // r2为s3数组对象
0x003721e2: 1c06 mov r6, r0 // r6为上面新建的String对象
0x003721e4: f2461ef9 movw lr, #25081
0x003721e8: f2c72ea0 movt lr, #29344
0x003721ec: f64300b0 movw r0, #14512
0x003721f0: f2c70044 movt r0, #28740
0x003721f4: 1c31 mov r1, r6
0x003721f6: 47f0 blx lr

这里遇到一个问题,在整个文件中并没有找到匹配的地方,无法得知这里跳转到哪里。我们来计算下lr的值,通过movw lr, #25081movt lr, #29344计算得到lr的值为0x72a061F9,也就是说这里跳转到了0x72a061F9这个地址。那么0x72a061F9这里又是什么呢?注意题目一共给我们提供了三个文件,到目前为止我们只用了b文件,a文件提供了内存布局信息,看a文件中的下面一段信息:

1
2
3
70eee000-7298b000 r--p 00000000 b3:17 185109 /data/dalvik-cache/arm/system@framework@boot.oat
7298b000-73e43000 r-xp 01a9d000 b3:17 185109 /data/dalvik-cache/arm/system@framework@boot.oat
73e43000-73e44000 rw-p 02f55000 b3:17 185109 /data/dalvik-cache/arm/system@framework@boot.oat

上面是boot.oat的内存布局,地址0x72a061F9刚好位于boot.oat的代码段,而boot.oat就是题目提供给我们的c文件。我们来算一算0x72a061F9对应到c文件中的地址。首先我们设置这样几个变量:offset_in_file(文件中的偏移),offset_in_memory(在内存中的偏移),virtual_start_addr(虚拟内存区域的起始地址),physics_start_addr(文件中的起始地址)。offset_in_file的计算公式为:

1
offset_in_file = offset_in_memory - virtual_start_addr + physics_start_addr

因此地址0x72a061F9对应的boot.oat文件中的地址为:

1
2
3
offset_in_file = 0x72a061F9 - 0x7298b000 + 0x01a9d000 - 1
= 0x1B181F8
// 因为thumb的关系,所以需要-1

ok,现在我们找到跳转的地址为boot.oat中的0x1B181F8,接下来我们需要得到boot.oat中的代码,使用oatdump即可。不幸的是我得到这样一个错误:

1
Failed to open oat file from '/home/secauo/Android/0ctf_2016/state_of_the_art/state_of_the_art/c': Invalid oat magic for '/home/secauo/Android/0ctf_2016/state_of_the_art/state_of_the_art/c'

好吧,oat magic错误,那么就是oat版本的原因了,从b文件得知,magic为039,因此对应的Android版本应该为5.0。再次使用Android5.0的oatdump,将结果保存为c.dump。由于未知的原因(或许是对齐?),地址差了0x1000,也就是为0x1B181F8 - 0x1000 = 0x1b171f8,查看c.dump,此处汇编为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
6: void java.lang.String.<init>(byte[]) (dex_method_idx=3180)
DEX CODE:
0x0000: const/4 v0, #+0
0x0001: array-length v1, v3
0x0002: invoke-direct {v2, v3, v0, v1}, void java.lang.String.<init>(byte[], int, int) // method@3182
0x0005: return-void
OatMethodOffsets (offset=0x0150b098)
code_offset: 0x01b171f9
gc_map: (offset=0x015fbe3b)
OatQuickMethodHeader (offset=0x01b171e0)
mapping_table: (offset=0x018e15b7)
vmap_table: (offset=0x01a7c99a)
v3/r5, v1/r6, v2/r7, v65534/r8, v65535/r15
QuickMethodFrameInfo
frame_size_in_bytes: 64
core_spill_mask: 0x000081e0 (r5, r6, r7, r8, r15)
fp_spill_mask: 0x00000000
CODE: (code_offset=0x01b171f9 size_offset=0x01b171f4 size=80)...
0x01b171f8: f5bd5c00 subs r12, sp, #8192

也就是说0x1b171f8(即内存地址0x72a061F9)指向java.lang.String.<init>(byte[])函数。回到b文件中check函数的分析,0x003721f6此处的’blx lr’,也就是跳转到java.lang.String.<init>(byte[])函数。

1
2
3
4
5
6
7
8
0x003721e0: 9a0e ldr r2, [sp, #56] // r2为s3数组对象
0x003721e2: 1c06 mov r6, r0 // r6为上面新建的String对象
0x003721e4: f2461ef9 movw lr, #25081
0x003721e8: f2c72ea0 movt lr, #29344
0x003721ec: f64300b0 movw r0, #14512
0x003721f0: f2c70044 movt r0, #28740
0x003721f4: 1c31 mov r1, r6
0x003721f6: 47f0 blx lr // 跳转到java.lang.String.<init>(byte[])

对应的Java源码为:

1
String str1 = new String(s3);

后面类似的跳转到c文件的函数,将不再详细分析,本文直接给出结果,读者可按照上述方法自行分析。后续的Java源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
StringBuilder sb1 = new StringBuilder(str1);
sb1 = sb1.reverse();
String str2 = sb1.toString();
StringBuilder sb2 = new StringBuilder();
String str3 = new String(d1);
String str4 = str3.replace('S', 'e');
String str5 = str4.replace('d', 'n');
String str6 = str5.trim();
StringBuilder sb3 = sb2.append(str6);
StringBuilder sb4 = sb3.append(str2);
String str7 = new String(s4);
String str8 = str7.substring(2, 7);
StringBuilder sb5 = sb4.append(str8);
String str9 = sb5.toString();

整合前面全部Java源码,最后得到获取Flag的Java代码为:

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
public String getFlag() {
byte[] s1 = new byte[]{0x78, 0x45, 0x78, 0x32, 0x57, 0x37}; // r10
byte[] s2 = new byte[]{0x22, 0x29, 0x44, 0x55, 0x60, 0x33}; // r7
byte[] s3 = new byte[]{0x17, (byte) 0x94, 0x35, 0x03, (byte) 0x90}; // [sp, #56]
byte[] s4 = new byte[]{0x45, 0x64, 0x5f, 0x41,0x52, 0x54, 0x7d}; // [sp, #60]
byte[] s5 = new byte[]{0x58, 0x75, 0x1b, (byte) 0xf0, 0x0f, 0x4c}; // r11
byte[] s6 = new byte[]{0x69, 0x0c, 0x1b, (byte) 0xbe, (byte) 0xf2, 0x49}; //[sp, #52]
for (int i = 0; i < s2.length; ++i) {
s2[i] = (byte) ((s2[i] + 54) ^ s6[i]);
}
for (int i = 0; i < s1.length; i++) {
if (s1[i] == 87) {
s1[i] = 105;
}
if (s1[i] == 50) {
s1[i] = ~(123);
}
s1[i] = (byte) (s1[i] ^ s5[i]);
}
byte[] d1 = new byte[s1.length + s2.length]; // [sp, #40]
System.arraycopy(s1, 0, d1, 0, s1.length);
System.arraycopy(s2, 0, d1, s2.length, s2.length);
s3[4] = (byte) (s3[4] - 49);
s3[3] = (byte) (s3[3] + 47);
s3[2] = (byte) (s3[2] + 42);
s3[1] = (byte) (s3[1] - 34);
s3[0] = (byte) (s3[0] + 46);
String str1 = new String(s3);
StringBuilder sb1 = new StringBuilder(str1);
sb1 = sb1.reverse();
String str2 = sb1.toString();
StringBuilder sb2 = new StringBuilder();
String str3 = new String(d1);
String str4 = str3.replace('S', 'e');
String str5 = str4.replace('d', 'n');
String str6 = str5.trim();
StringBuilder sb3 = sb2.append(str6);
StringBuilder sb4 = sb3.append(str2);
String str7 = new String(s4);
String str8 = str7.substring(2, 7);
StringBuilder sb5 = sb4.append(str8);
String str9 = sb5.toString();
return str9;
}

最后的Flag为:0ctf{1ea5n_2_rE_ART}