2015年2月20日 星期五

[Embedded] 一個練習組合語言的小遊戲 -- Defusing bomb (part 1)

此遊戲的設計目的是要讓使用者學會利用 gdb 與 objdump 分析執行檔,並學習 hack 程式!!!! 遊戲說明請參考  http://wiki.csie.ncku.edu.tw/embedded/Lab34

以下記錄自己的解答方式。本篇僅紀錄 part1 的解法,簡單來說就是使用gdb找出正確輸入值,避免程式進入 explode_bomb()。
一、拆引線之前的準備動作
1. 先將程式反組譯為組語程式碼,下列所有分析需對照組語才能了解。
$ arm-linux-gnueabihf-objdump -xDs bomb > code.s

2. 執行程式,指定port,等待gdb連結 ( -g: wait gdb connection to 'port'):
$ qemu-arm -L /usr/arm-linux-gnueabihf -g 1234 bomb

3. 使用 gdb 追蹤程式流程
$ arm-none-eabi-gdb bomb
進入之後要輸入port ( target remote : 1234 )

4. 設定中斷點
(gdb) b *phase_1
(gdb) b *phase_2
(gdb) b *phase_3
(gdb) b *phase_4
(gdb) continue

二、開始拆引線,此遊戲 Part1 共設計五個關卡

phase_0:
答案:help
解法:題目說明直接提示此答案


phase_1:
答案:Yea, though I walk through the valley of the shadow of death, I will fear no evil; for thou art with me: thy rod and thy staff, they comfort me.
解法:如下
1. 將使用者輸入的參數所對應的位址,放入[r7, #4]
880a: 6078       str r0, [r7, #4]
2. 取出預設字串對應的位址,並將此位址複製到 [r7, #12]
880c:   f648 3334   movw r3, #35636 ; 0x8b34
8810:   f2c0 0300   movt r3, #0
8814:   60fb           str r3, [r7, #12]
3. 取出兩個字串並呼叫 strcmp 進行比較,將比較結果放入 r3,確認結果是否為0
8816: 6878           ldr r0, [r7, #4]
8818: 68f9            ldr r1, [r7, #12]
881a: f7ff ee56     blx 84c8
881e: 4603            mov r3, r0
8820: 2b00            cmp r3, #0
8822: d001            beq.n 8828
4. 如果兩個字串相等時,跳到下下一行執行程式,否則引爆炸彈
8822: d001            beq.n 8828
8824: f7ff ffcc       bl 87c0
8828: 3710            adds r7, #16
5. 因此可知解答為一字串,放在 0x8b34,使用下列指令檢視字串可確認答案
(gdb) x/s 0x8b34

phase_2:
答案:5 6 16 7
解法:如下
1. phase_2 會有兩次引爆炸彈的時機。分別是 0x8860 與 0x8884
8860: f7ff ffae bl 87c0
8884: f7ff ff9c bl 87c0  
2. 0x8860 檢查 scanf 輸入的參數,此時會確認是否 scanf 讀入四個值
884e: f648 31c8 movw r1, #35784 ; 0x8bc8 
8852: f2c0 0100 movt r1, #0
8856: f7ff ee74 blx 8540 <__isoc99_sscanf plt="">
885a: 4603       mov r3, r0
885c: 2b04       cmp r3, #4
885e: d001       beq.n 8864  
3. ARM組合語言中,呼叫 scanf 時,會將欲讀取的資料格式放在 r1,此處可由 0x8bc8 得知,輸入的四個值都是整數
(gdb) x/s 0x8bc8
0x8bc8: "%d %d %d %d" 
4. 確認四個值分別存放的位置,以及數值之間的關係。由於後續組語都直接使用r7做計算,因此須先搞清楚這個值。
8834: af02       add r7, sp, #8
此表示 r7 存放的值為 sp + #8,可檢視記憶體得知 r7+n 放入哪些值, 
假設輸入的值為 4, 5, 6, 7。(註:此組合會引爆炸彈) 
(gdb) x/40d $r7
0x407ffe00: 0 0 0 0 52 -2 127 64
0x407ffe08: -36 0 0 0 4 0 0 0
0x407ffe10: 5 0 0 0 6 0 0 0
0x407ffe18: 7 0 0 0 52 -2 127 64
0x407ffe20: 40 -2 127 64 69 -121 0
可以得知 r7+#12,  r7+#16,  r7+#20,  r7+#24 分別依順序存入scanf輸入的四個值,由於 r7 = sp + #8,所以四個值的位址分別是 sp +#20,  sp +#24,  sp +#28,  sp +#32
5. 0x8864 ~ 0x887a 為一個迴圈,會將第二個數加10,還原為C語言如下
for(i = 0 ; i <= 9 ; i++)
    d1++;  // [r7, #16],此為第二個數 
6. 四個整數的關係為 “第二個數加10 等於第三個數”
887c: 693a       ldr r2, [r7, #16]
887e: 697b       ldr r3, [r7, #20]
8880: 429a       cmp r2, r3
8882: d001       beq.n 8888
8884: f7ff ff9c bl 87c0
7. 還原為 C 語言
if(sscanf(str,"%d %d %d %d",&d0,&d1,&d2,&d3) != 4)
explode_bomb();
for(int i = 0 ; i <= 9 ; i++)
d1++;
if(d1 != d2)
explode_bomb(); 
8. 因此答案是任意四個整數,其中第二個數加10 等於第三個數,此處我取 5 6 16 7

phase_3:
答案:40 2 2
解法:如下
1. phase_3 會有兩次引爆炸彈的時機。分別是 0x88ba 與 0x88d8
88ba: f7ff ff81 bl 87c0
88d8: f7ff ff72 bl 87c0  
2. 0x88ba 檢查 scanf 輸入的參數,此時會確認是否 scanf 讀入三個值
88a8: f648 31d4 movw r1, #35796 ; 0x8bd4 
88ac: f2c0 0100 movt r1, #0
88b0: f7ff ee46 blx 8540 <__isoc99_sscanf plt="">
88b4: 4603       mov r3, r0
88b6: 2b03       cmp r3, #3
88b8: d001       beq.n 88be
88ba: f7ff ff81 bl 87c0  
3. 確認 Scanf參數型態
(gdb) x/s 0x8bd4
0x8bd4: "%d %d %d" 
4. 三個變數分別放在 [r7, #12], [r7, #16], [r7, #20],若變數一等於 40, 跳至 0x88ce,否則  [r7, #16] =  [r7, #16] +  [r7, #20]
88be: 68fb       ldr r3, [r7, #12]
88c0: 2b28       cmp r3, #40 ; 0x28
88c2: d004       beq.n 88ce
88c4: 693a       ldr r2, [r7, #16]
88c6: 697b       ldr r3, [r7, #20]
88c8: 4413       add r3, r2
88ca: 613b       str r3, [r7, #16]
88cc: e000       b.n 88d0
88ce: bf00       nop
88d0: 693a       ldr r2, [r7, #16]
88d2: 697b       ldr r3, [r7, #20]
88d4: 429a       cmp r2, r3
88d6: d001       beq.n 88dc
88d8: f7ff ff72 bl 87c0

5. 還原為 C 語言
if(sscanf(str,"%d %d %d",&d0,&d1,&d2) != 3)
    explode_bomb();
if(d0 != 40)
    d1 += d2;
if(d1 != d2)
    explode_bomb();

phase_4:
答案:10
解法:如下
1. phase_4 會有兩次引爆炸彈的時機。分別是 0x892e 與 0x8942
892e: f7ff ff47 bl 87c0
8942: f7ff ff3d bl 87c0  
2. 0x892e 檢查 scanf 輸入的參數,此時會確認是否 scanf 讀入一個值
891a: f648 31e0 movw r1, #35808 ; 0x8be0 
891e: f2c0 0100 movt r1, #0
8922: 461a       mov r2, r3
8924: f7ff ee0c blx 8540 <__isoc99_sscanf plt="">
8928: 4603       mov r3, r0
892a: 2b01       cmp r3, #1
892c: d001       beq.n 8932
892e: f7ff ff47 bl 87c0  
3. 確認參數型態為整數
(gdb) x/s 0x8be0
0x8be0: "%d" 
4. 呼叫 func4,傳入參數的位址為 [r7, #12],func4 組語如下,內部實作分析如後
000088e4 :
    88e4: b580       push {r7, lr}
    88e6: b082       sub sp, #8
    88e8: af00       add r7, sp, #0
    88ea: 6078       str r0, [r7, #4]
    88ec: 687b       ldr r3, [r7, #4]
    88ee: 2b00       cmp r3, #0
    88f0: d101       bne.n 88f6
    88f2: 2301       movs r3, #1
    88f4: e006       b.n 8904
    88f6: 687b       ldr r3, [r7, #4]
    88f8: 3b01       subs r3, #1
    88fa: 4618       mov r0, r3
    88fc: f7ff fff2 bl 88e4
    8900: 4603       mov r3, r0
    8902: 005b       lsls r3, r3, #1
    8904: 4618       mov r0, r3
    8906: 3708       adds r7, #8
    8908: 46bd       mov sp, r7
    890a: bd80       pop {r7, pc} 
4.1. func4 會自行呼叫 func4, 所以是遞迴函數。
88fc: f7ff fff2 bl 88e4  
4.2. 當 [r7, #4] == 0 時,程式會設定r3==1,跳至 0x8904,離開 func4,否則會跳至 0x88f6,再次呼叫 func4。
88ea: 6078       str r0, [r7, #4]
88ec: 687b       ldr r3, [r7, #4]
88ee: 2b00       cmp r3, #0
88f0: d101       bne.n 88f6
88f2: 2301       movs r3, #1
88f4: e006       b.n 8904  

4.3. 每次進入遞迴函數,[r7, #4] = [r7, #4] - 1。
88f6: 687b       ldr r3, [r7, #4]
88f8: 3b01       subs r3, #1
88fa: 4618       mov r0, r3 
4.4. 每次離開遞迴函數之前,[r7, #4] 會往左 shift 一位 (乘上2)。
8900: 4603       mov r3, r0
8902: 005b       lsls r3, r3, #1   ; logical shift left
8904: 4618       mov r0, r3 
4.5. func4的C語言如下
int fun4(int c){
    if(!c)
        return 1;
    return fun4(c--)<<1 br="">} 
4.6. func4 執行結束後,預期回傳值須等於 1024,要使此函數返回 1024, 則 c=10。
893a: 4603        mov r3, r0
893c: f5b3 6f80 cmp.w r3, #1024 ; 0x400
8940: d001        beq.n 8946
8942: f7ff ff3d  bl 87c0  
5. 還原為 C 語言
int fun4(int c){
    if(!c)
        return 1;
    return fun4(c--)<<1 br="">} 
int d0;
if(sscanf(str,format,&d0) != 1)
    explode_bomb();
if(fun4(d0) != 1024)
    explode_bomb();

三、可以將五個phase的答案放入solution.txt,利用以下的方式執行。
$ qemu-arm -L /usr/arm-linux-gnueabihf bomb solution.txt