Info
Category: PPC ( 個人覺得比較像 Reverse )
Point: 150
Solver: bruce30262 @ BambooFox
Analyzing
題目給了我們一隻 binary, 32 bit PowerPC ELF
這題運氣不錯,打 CTF 用的 docker image 裡面剛好有裝 qemu-ppc-static,加上這題剛好是一隻 static linked 的 binary, 不需要再額外裝 PPC 的 libc,因此透過以下 command 即可將 binary run 起來:
# root @ 9c51322c8256 in /mnt/files/hitcon-ctf-2016-qual/flame [7:51:02] $ qemu-ppc-static ./flame ************************************* * * * HITCON CTF 2016 Flag Verifier * * * ************************************* Check your flag before submission: AAAA Your flag is incorrect :(
可以看到程式會要求我們輸入 flag,然後對我們的 flag 做驗證,之後吐出驗證結果。
這題靜態分析方面是直接丟 IDA Pro,至於動態分析的部分,則是利用 qemu-ppc-static -g 10001 ./flame 指令先開一個 gdb connection,之後搭配 gdb-multiarch + target remote 來 debug 程式。
那麼開始 reverse。這題驗證 flag 的演算法其實沒有很複雜,可以整理成以下的 pseudo code:
int main()
{
    scanf("%s", flag); // lol buffer overflow
    if( strlen(flag) == 35)
    {
        srandom(0x1e61);
        int i;
        for (i = 0 ; i < 35 ; i++)
        {
            r = rand();
            check[i] = flag[i] ^ (r & 0xfff);
        }
        for (i = 0 ; i < 35 ; i++)
        {
            if ( check[i] != secret[i] )
            {
                fail();
            }
        }
        success();
    }
    else
    {
        fail();
    }
}
比較麻煩的地方就是 check[i] = flag[i] ^ (r & 0xfff); 這行,其實際的 PPC assembly 長這樣:
// r = rand();
bl        rand
mr        r9, r3
// r = r & 0xfff
clrlwi    r10, r9, 20 <-- clear the high-order 20 bits
lwz       r9, 0x18(r31)
slwi      r9, r9, 2
addi      r8, r31, 0x1A0
add       r9, r8, r9
addi      r9, r9, -0x180
stw       r10, 0(r9)
lwz       r9, 0x18(r31)
slwi      r9, r9, 2
addi      r10, r31, 0x1A0
add       r9, r10, r9
addi      r9, r9, -0x180
lwz       r9, 0(r9)
mr        r8, r9
// c = flag[i]
addi      r10, r31, 0x138
lwz       r9, 0x18(r31)
add       r9, r10, r9
lbz       r9, 0(r9)
// check[i] = c ^ r
xor       r9, r8, r9
mr        r10, r9
lwz       r9, 0x18(r31)
slwi      r9, r9, 2
addi      r8, r31, 0x1A0
add       r9, r8, r9
addi      r9, r9, -0x180
stw       r10, 0(r9)
// i++ (loop counter)
lwz       r9, 0x18(r31)
addi      r9, r9, 1
stw       r9, 0x18(r31)
不過只要花點時間 + 瘋狂 google 應該不難搞懂
Solution
總而言之程式會先檢查我們的 flag 是不是 35 個字元,如果是的話就會針對每一個字元做一些運算,然後將結果存入 check 這個 buffer 裡面。之後會檢查 check buffer 與 secret buffer 的內容是否相同,是的話即通過檢查。
secret buffer 的內容可以透過 debugger dump 出來:
0xf6fff86c: 0x00000cfe 0x00000859 0x0000095d 0x00000871 0xf6fff87c: 0x0000040d 0x00000006 0x00000ade 0x00000fa8 0xf6fff88c: 0x00000561 0x000009da 0x00000878 0x00000682 0xf6fff89c: 0x00000fa9 0x00000f5f 0x0000025e 0x00000db0 0xf6fff8ac: 0x00000fbf 0x00000bc6 0x00000d38 0x0000095d 0xf6fff8bc: 0x00000d09 0x000007ed 0x00000307 0x000001c0 0xf6fff8cc: 0x00000399 0x00000956 0x00000a45 0x00000292 0xf6fff8dc: 0x00000c8a 0x0000092f 0x0000004a 0x00000964 0xf6fff8ec: 0x00000194 0x000009da 0x0000011f
之後就是寫些程式將 flag 給 recover 回來:
#!/usr/bin/env ruby
resp = `./test`.split("\n")
seed = []
ans =[0x00000cfe, 0x00000859, 0x0000095d, 0x00000871, 0x0000040d,0x00000006,0x00000ade, 0x00000fa8, 0x00000561,  0x000009da , 0x00000878, 0x00000682, 0x00000fa9 , 0x00000f5f, 0x0000025e, 0x00000db0, 0x00000fbf, 0x00000bc6 , 0x00000d38 , 0x0000095d, 0x00000d09, 0x000007ed , 0x00000307, 0x000001c0, 0x00000399, 0x00000956 , 0x00000a45 , 0x00000292, 0x00000c8a,0x0000092f , 0x0000004a , 0x00000964, 0x00000194,  0x000009da, 0x0000011f]
 
for s in resp
    seed << (s.to_i(16) & 0xfff)
end
flag = ""
for a,b in seed.zip(ans)
    flag += (a^b).chr
end
puts flag
其中 test 是個先將 random value 給 gen 好的 C 程式
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    int i = 0;
    srand(0x1e61);
    for(i = 0 ; i < 35 ; i++)
    {
        printf("0x%x\n", rand());
    }
    return 0;
}
執行結果:
# root @ 9c51322c8256 in /mnt/files/hitcon-ctf-2016-qual/flame [8:42:43] C:126
$ ruby ./sol.rb 
hitcon{P0W3rPc_a223M8Ly_12_s0_345y}
flag: hitcon{P0W3rPc_a223M8Ly_12_s0_345y}