首页
社区
课程
招聘
[原创]0ctf 2016 State of the ART writeup
2016-3-22 19:44 6045

[原创]0ctf 2016 State of the ART writeup

2016-3-22 19:44
6045
首先点我下载题目,这道题提供了三个文件,分别为:

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

经过对几个文件的初步观察,发现在b文件中找到一个可疑函数oat.sjl.gossip.oat.MainActivity.check(java.lang.String),函数名字可不会乱取,此函数肯定和Flag密切相关,因此需要还原出该方法。可惜,出题者有意抹去了oatdump中的dalvik字节码,不然这道题会简单的多。
我们分段分析汇编代码,首先看下面一段:
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*。
下一段代码为:
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*,对应的数据结构为:
struct PACKED(4) ArrayDataPayload {
    const uint16_t ident;           // 标志
    const uint16_t element_width;   // 每一个元素的大小
    const uint32_t element_count;   // 元素的个数
    const uint8_t data[];           //指向真正的数据
    ...
};

r1的值为0x003724f8,其对应的区域为:
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源码为:
// 每行的注释指的的是该数组对象的存放位置
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]

再看下面一段:
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源码为:
for (int i = 0; i < s2.length; ++i) {
    s2[i] = (byte) ((s2[i] + 54) ^ s6[i]);
}

继续下一段:
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源码为:
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]);
}

继续下一段:
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源码为:
byte[] d1 = new byte[s1.length + s2.length];    // [sp, #40]

继续下一段:
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,找到下面一段代码:
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源码为:
System.arraycopy(s1, 0, d1, 0, s1.length);

继续下一段:
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源码为:
System.arraycopy(s2, 0, d1, s2.length, s2.length);

继续下一段:
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源码为:
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);

继续下一段:
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, #47584和movt r0, #28745,从而确定了此处对应的dalvik字节码为new-instance v0, java.lang.StringBuilder。
继续下一段:
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。
继续下一段:
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, #25081和movt lr, #29344计算得到lr的值为0x72a061F9,也就是说这里跳转到了0x72a061F9这个地址。那么0x72a061F9这里又是什么呢?注意题目一共给我们提供了三个文件,到目前为止我们只用了b文件,a文件提供了内存布局信息,看a文件中的下面一段信息:
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的计算公式为:
offset_in_file = offset_in_memory - virtual_start_addr + physics_start_addr

因此地址0x72a061F9对应的boot.oat文件中的地址为:
offset_in_file = 0x72a061F9 - 0x7298b000 + 0x01a9d000 - 1
               = 0x1B181F8

// 因为thumb的关系,所以需要-1
ok,现在我们找到跳转的地址为boot.oat中的0x1B181F8,接下来我们需要得到boot.oat中的代码,使用oatdump即可。不幸的是我得到这样一个错误:
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,此处汇编为:
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[])函数。
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源码为:
String str1 = new String(s3);

后面类似的跳转到c文件的函数,将不再详细分析,本文直接给出结果,读者可按照上述方法自行分析。后续的Java源码为:
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代码为:
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}。
blog:http://ele7enxxh.com/0ctf-2016-State-of-the-ART-writeup.html

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 3
打赏
分享
最新回复 (2)
雪    币: 53
活跃值: (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Zkeleven 2016-3-22 21:57
2
0
赞一个,出题思路和解题思路都很棒!
雪    币: 485
活跃值: (78)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
goddkiller 2016-3-22 23:36
3
0
666666
游客
登录 | 注册 方可回帖
返回