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}