# Bomb Lab 解題嘗試
contributed by <`afcidk`,`potter903p`>
## 預備知識
打開`README` 資料內有提示到之 useful tools :
- [arm-linux-gnueabihf-objdump](https://manpages.debian.org/testing/binutils-arm-linux-gnueabihf/arm-linux-gnueabihf-objdump.1.en.html)
- [arm-none-eabi-gdb](https://manned.org/arm-none-eabi-gdb/7308522e)
[ARM Assembly Basics](https://azeria-labs.com/writing-arm-assembly-part-1/)
## 實驗流程
[說明影片](https://www.youtube.com/watch?v=LmKRahXvBAE)
### `phase_0`
```asm=
Dump of assembler code for function phase_0:
0x000087d8 <+0>: push {r7, lr}
0x000087da <+2>: sub sp, #16
0x000087dc <+4>: add r7, sp, #0
0x000087de <+6>: str r0, [r7, #4]
0x000087e0 <+8>: movw r3, #35628 ; 0x8b2c
0x000087e4 <+12>: movt r3, #0
0x000087e8 <+16>: str r3, [r7, #12]
0x000087ea <+18>: ldr r0, [r7, #4]
0x000087ec <+20>: ldr r1, [r7, #12]
0x000087ee <+22>: blx 0x84c8 <strcmp@plt>
0x000087f2 <+26>: mov r3, r0
0x000087f4 <+28>: cmp r3, #0
0x000087f6 <+30>: beq.n 0x87fc <phase_0+36>
0x000087f8 <+32>: bl 0x87c0 <explode_bomb>
0x000087fc <+36>: adds r7, #16
0x000087fe <+38>: mov sp, r7
0x00008800 <+40>: pop {r7, pc}
End of assembler dump.
```
設定好斷點為 `phase_0` 後故意輸入錯誤答案 `asdf` ,gdb 提示跳到斷點位置 `0x87e4` ,接著往下看發現會先遇到 `strcmp` 再判斷是不是要執行 `explode_bomb`,因此可以猜測這個部份只是單純的比對字串而已。
觀察組合語言,發現 r0 指向我們輸入的字串,r1 則是指向答案的字串開頭。
(參考 [AAPCS, §5.1.1 - Core Registers](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf) - Page 14 of 33)
在 gdb 裡印出該區塊的內容,因為我們知道比對的會是字串,直接印出來就好。
```gdb
(gdb) x/s $r1
0x8b2c: "help"
```
### `phase_1`
```asm=
Dump of assembler code for function phase_1:
0x00008804 <+0>: push {r7, lr}
0x00008806 <+2>: sub sp, #16
0x00008808 <+4>: add r7, sp, #0
0x0000880a <+6>: str r0, [r7, #4]
0x0000880c <+8>: movw r3, #35636 ; 0x8b34
0x00008810 <+12>: movt r3, #0
0x00008814 <+16>: str r3, [r7, #12]
0x00008816 <+18>: ldr r0, [r7, #4]
0x00008818 <+20>: ldr r1, [r7, #12]
0x0000881a <+22>: blx 0x84c8 <strcmp@plt>
0x0000881e <+26>: mov r3, r0
0x00008820 <+28>: cmp r3, #0
0x00008822 <+30>: beq.n 0x8828 <phase_1+36>
0x00008824 <+32>: bl 0x87c0 <explode_bomb>
0x00008828 <+36>: adds r7, #16
0x0000882a <+38>: mov sp, r7
0x0000882c <+40>: pop {r7, pc}
End of assembler dump.
```
同 `phase_0`,在 gdb 裡面找到 r1 指向的資料,就可以找到了。
```gdb
(gdb) x/s $r1
0x8b34: "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."
```
### `phase_2`
```asm=
Dump of assembler code for function phase_2:
0x00008830 <+0>: push {r7, lr}
0x00008832 <+2>: sub sp, #40 ; 0x28
0x00008834 <+4>: add r7, sp, #8
0x00008836 <+6>: str r0, [r7, #4]
0x00008838 <+8>: add.w r2, r7, #12
0x0000883c <+12>: add.w r3, r7, #16
0x00008840 <+16>: add.w r1, r7, #20
0x00008844 <+20>: str r1, [sp, #0]
0x00008846 <+22>: add.w r1, r7, #24
0x0000884a <+26>: str r1, [sp, #4]
0x0000884c <+28>: ldr r0, [r7, #4]
0x0000884e <+30>: movw r1, #35784 ; 0x8bc8
0x00008852 <+34>: movt r1, #0
0x00008856 <+38>: blx 0x8540 <__isoc99_sscanf@plt>
0x0000885a <+42>: mov r3, r0
0x0000885c <+44>: cmp r3, #4
0x0000885e <+46>: beq.n 0x8864 <phase_2+52>
0x00008860 <+48>: bl 0x87c0 <explode_bomb>
0x00008864 <+52>: movs r3, #0
0x00008866 <+54>: str r3, [r7, #28]
0x00008868 <+56>: b.n 0x8876 <phase_2+70>
0x0000886a <+58>: ldr r3, [r7, #16]
0x0000886c <+60>: adds r3, #1
0x0000886e <+62>: str r3, [r7, #16]
0x00008870 <+64>: ldr r3, [r7, #28]
0x00008872 <+66>: adds r3, #1
0x00008874 <+68>: str r3, [r7, #28]
0x00008876 <+70>: ldr r3, [r7, #28]
0x00008878 <+72>: cmp r3, #9
0x0000887a <+74>: ble.n 0x886a <phase_2+58>
0x0000887c <+76>: ldr r2, [r7, #16]
0x0000887e <+78>: ldr r3, [r7, #20]
0x00008880 <+80>: cmp r2, r3
0x00008882 <+82>: beq.n 0x8888 <phase_2+88>
0x00008884 <+84>: bl 0x87c0 <explode_bomb>
0x00008888 <+88>: adds r7, #32
0x0000888a <+90>: mov sp, r7
0x0000888c <+92>: pop {r7, pc}
End of assembler dump.
```
發現有兩個 `explode_bomb`,代表我們有兩個條件需要克服才能破解 `phase_2`。
* 第一部份
```asm
0x0000885a <+42>: mov r3, r0
0x0000885c <+44>: cmp r3, #4
```
在呼叫完 sscanf 後,會檢查 r0 (回傳值) 是否等於 4 。對照 [sscanf man page](https://linux.die.net/man/3/sscanf),得知回傳值是成功配對且指派 (assign) 成功的數量。
> These functions return the number of input items successfully matched and assigned
想要得到回傳值為 4 ,我們知道要成功配對 4 個變數,因此使用 gdb 查看第二個參數(format string)為何。
> int sscanf(const char *str, const char *format, ...);
```gdb
(gdb) x/s $r1
0x8bc8: "%d %d %d %d"
```
因此只要輸入任意 4 個數字就可以通過第一部份
* 第二部份
```asm=
0x00008864 <+52>: movs r3, #0
0x00008866 <+54>: str r3, [r7, #28]
0x00008868 <+56>: b.n 0x8876 <phase_2+70>
0x0000886a <+58>: ldr r3, [r7, #16]
0x0000886c <+60>: adds r3, #1
0x0000886e <+62>: str r3, [r7, #16]
0x00008870 <+64>: ldr r3, [r7, #28]
0x00008872 <+66>: adds r3, #1
0x00008874 <+68>: str r3, [r7, #28]
0x00008876 <+70>: ldr r3, [r7, #28]
0x00008878 <+72>: cmp r3, #9
0x0000887a <+74>: ble.n 0x886a <phase_2+58>
0x0000887c <+76>: ldr r2, [r7, #16]
0x0000887e <+78>: ldr r3, [r7, #20]
0x00008880 <+80>: cmp r2, r3
0x00008882 <+82>: beq.n 0x8888 <phase_2+88>
```
這個部份看起來比較複雜一些,但是如果先轉換為我們熟悉的表達方式,就可以比較好理解。
```
r3 = 0
*(r7+28) = r3
goto Start
--------------Restart------------
r3 = *(r7+16) \
++r3 | 效果等同於 *(r7+16) += 1
*(r7+16) = r3 /
r3 = *(r7+28) \
++r3 | 效果等同於 *(r7+28) += 1
*(r7+28) = r3 /
---------------Start-------------
r3 = *(r7+28)
if (r3 <= 9) goto Restart
if (*(r7+16) == *(r7+20)) YOU_PASS
else FAIL
```
可以發現在 Restart 和 Start 之間總共跑了 10 次迴圈,其中 `*(r7+16)` 加了 10 次 1。
回頭看一開始,我們輸入的數字分別被存到 `$r7+12`,`$r7+16`,`$r7+20`,`$r7+24` 裡面。
因為最後我們比較的是 `$r7+20` 和 `$r7+16`,但是在迴圈裡面 `$r7+16` 被加上了 10,所以得到結論: 第二個數字加上 10 要等於第三個數字。
結合兩個部份,得到答案: 輸入 4 個數字,其中第二個數字加 10 要等於第三個數字。
像是 `1 -3 7 4` 和 `0 0 10 0` 都會是可以通過的輸入。
### `phase_3`
```asm=
Dump of assembler code for function phase_3:
0x00008890 <+0>: push {r7, lr}
0x00008892 <+2>: sub sp, #32
0x00008894 <+4>: add r7, sp, #8
0x00008896 <+6>: str r0, [r7, #4]
0x00008898 <+8>: add.w r2, r7, #12
0x0000889c <+12>: add.w r3, r7, #16
0x000088a0 <+16>: add.w r1, r7, #20
0x000088a4 <+20>: str r1, [sp, #0]
0x000088a6 <+22>: ldr r0, [r7, #4]
0x000088a8 <+24>: movw r1, #35796 ; 0x8bd4
0x000088ac <+28>: movt r1, #0
0x000088b0 <+32>: blx 0x8540 <__isoc99_sscanf@plt>
0x000088b4 <+36>: mov r3, r0
0x000088b6 <+38>: cmp r3, #3
0x000088b8 <+40>: beq.n 0x88be <phase_3+46>
0x000088ba <+42>: bl 0x87c0 <explode_bomb>
0x000088be <+46>: ldr r3, [r7, #12]
0x000088c0 <+48>: cmp r3, #40 ; 0x28
0x000088c2 <+50>: beq.n 0x88ce <phase_3+62>
0x000088c4 <+52>: ldr r2, [r7, #16]
0x000088c6 <+54>: ldr r3, [r7, #20]
0x000088c8 <+56>: add r3, r2
0x000088ca <+58>: str r3, [r7, #16]
0x000088cc <+60>: b.n 0x88d0 <phase_3+64>
0x000088ce <+62>: nop
0x000088d0 <+64>: ldr r2, [r7, #16]
0x000088d2 <+66>: ldr r3, [r7, #20]
0x000088d4 <+68>: cmp r2, r3
0x000088d6 <+70>: beq.n 0x88dc <phase_3+76>
0x000088d8 <+72>: bl 0x87c0 <explode_bomb>
0x000088dc <+76>: adds r7, #24
0x000088de <+78>: mov sp, r7
0x000088e0 <+80>: pop {r7, pc}
End of assembler dump.
```
感覺應該是和 `phase_2` 相似的作法,都是使用到 `sscanf`。
先把 format string 印出來看看,知道輸入應該是三個 int 型態的數字。
```gdb
(gdb) x/s 0x8bd4
0x8bd4: "%d %d %d"
```
回頭看儲存的資料和位址分別是
r0: input
r1: format string (`%d %d %d`)
$r7+12: 數字 a
$r7+16: 數字 b
$r7+20: 數字 c
下面的 code 分成三個部份
1. 判斷 a 是不是 40,是就跳到 3.
```asm=18
0x000088be <+46>: ldr r3, [r7, #12]
0x000088c0 <+48>: cmp r3, #40 ; 0x28
0x000088c2 <+50>: beq.n 0x88ce <phase_3+62>
```
3. b = b + c
```asm=21
0x000088c4 <+52>: ldr r2, [r7, #16]
0x000088c6 <+54>: ldr r3, [r7, #20]
0x000088c8 <+56>: add r3, r2
0x000088ca <+58>: str r3, [r7, #16]
0x000088cc <+60>: b.n 0x88d0 <phase_3+64>
```
5. 比較 b 和 c
```asm=26
0x000088ce <+62>: nop
0x000088d0 <+64>: ldr r2, [r7, #16]
0x000088d2 <+66>: ldr r3, [r7, #20]
0x000088d4 <+68>: cmp r2, r3
0x000088d6 <+70>: beq.n 0x88dc <phase_3+76>
```
綜合上面分析出來的三個 block,可以歸納出兩個條件
* 如果 a 等於 40, b 要等於 c。
* 如果 a 不等於 40,b 要等於 0,c 可為任意值。
滿足上面條件的輸入,像是 `40 9 9`,`0 0 7` 都可以通過。
### `phase_4`
```asm=
Dump of assembler code for function phase_4:
0x0000890c <+0>: push {r7, lr}
0x0000890e <+2>: sub sp, #16
0x00008910 <+4>: add r7, sp, #0
0x00008912 <+6>: str r0, [r7, #4]
0x00008914 <+8>: add.w r3, r7, #12
0x00008918 <+12>: ldr r0, [r7, #4]
0x0000891a <+14>: movw r1, #35808 ; 0x8be0
0x0000891e <+18>: movt r1, #0
0x00008922 <+22>: mov r2, r3
0x00008924 <+24>: blx 0x8540 <__isoc99_sscanf@plt>
0x00008928 <+28>: mov r3, r0
0x0000892a <+30>: cmp r3, #1
0x0000892c <+32>: beq.n 0x8932 <phase_4+38>
0x0000892e <+34>: bl 0x87c0 <explode_bomb>
0x00008932 <+38>: ldr r3, [r7, #12]
0x00008934 <+40>: mov r0, r3
0x00008936 <+42>: bl 0x88e4 <fun4>
0x0000893a <+46>: mov r3, r0
0x0000893c <+48>: cmp.w r3, #1024 ; 0x400
0x00008940 <+52>: beq.n 0x8946 <phase_4+58>
0x00008942 <+54>: bl 0x87c0 <explode_bomb>
0x00008946 <+58>: adds r7, #16
0x00008948 <+60>: mov sp, r7
0x0000894a <+62>: pop {r7, pc}
End of assembler dump.
```
和前面的步驟相同,先查看 format string 是什麼
```gdb
(gdb) x/s 0x8be0
0x8be0: "%d"
```
發現只需要輸入一個數字,這個數字會被指派到 `$r7+12`。
接下來看到第 16 行,程式把 `$r7+12` 的內容放到 `$r0` 裡面當做參數,並呼叫 `fun4`。
```asm=
Dump of assembler code for function fun4:
0x000088e4 <+0>: push {r7, lr}
0x000088e6 <+2>: sub sp, #8
0x000088e8 <+4>: add r7, sp, #0
0x000088ea <+6>: str r0, [r7, #4]
0x000088ec <+8>: ldr r3, [r7, #4]
0x000088ee <+10>: cmp r3, #0
0x000088f0 <+12>: bne.n 0x88f6 <fun4+18>
0x000088f2 <+14>: movs r3, #1
0x000088f4 <+16>: b.n 0x8904 <fun4+32>
0x000088f6 <+18>: ldr r3, [r7, #4]
0x000088f8 <+20>: subs r3, #1
0x000088fa <+22>: mov r0, r3
0x000088fc <+24>: bl 0x88e4 <fun4>
0x00008900 <+28>: mov r3, r0
0x00008902 <+30>: lsls r3, r3, #1
0x00008904 <+32>: mov r0, r3
0x00008906 <+34>: adds r7, #8
0x00008908 <+36>: mov sp, r7
0x0000890a <+38>: pop {r7, pc}
End of assembler dump.
```
觀察一下 assembly code,我們拆成五個區塊比較好理解
1. **進入 `fun4`**
```asm=4
0x000088e8 <+4>: add r7, sp, #0
0x000088ea <+6>: str r0, [r7, #4]
0x000088ec <+8>: ldr r3, [r7, #4]
0x000088ee <+10>: cmp r3, #0
0x000088f0 <+12>: bne.n 0x88f6 <fun4+18>
```
這個區塊會利用輸入的參數 (`$r0`) 判斷等一下要跳到區塊 2. 或是區塊 3.
2. **參數等於 0**
```asm=9
0x000088f2 <+14>: movs r3, #1
0x000088f4 <+16>: b.n 0x8904 <fun4+32>
```
把 `$r3` 指派成 1 ,再跳到區塊 5.
3. **參數不等於 0 (遞迴呼叫前)**
```asm=11
0x000088f6 <+18>: ldr r3, [r7, #4]
0x000088f8 <+20>: subs r3, #1
0x000088fa <+22>: mov r0, r3
0x000088fc <+24>: bl 0x88e4 <fun4>
```
把參數減 1 遞迴呼叫 `fun4`,回到區塊 1.
4. **參數不等於 0 (遞迴呼叫後)**
```asm=15
0x00008900 <+28>: mov r3, r0
0x00008902 <+30>: lsls r3, r3, #1
```
取回上一次遞迴回傳值,再將他左移 1 位元
5. **離開 `fun4`**
```asm=17
0x00008904 <+32>: mov r0, r3
```
結束遞迴流程,回傳 `$r0`
理解了上面五個區塊,可以發現 `fun4` 的作用是回傳 1<<x,其中 x 是輸入的數字。
回到 `phase_4` 區塊,發現會檢查回傳值 和 1024 是否相等
```asm=19
0x0000893a <+46>: mov r3, r0
0x0000893c <+48>: cmp.w r3, #1024 ; 0x400
0x00008940 <+52>: beq.n 0x8946 <phase_4+58>
```
得知我們輸入的數字需要是 10 ,因為 $2^{10}=1024$
### 總結答案
綜合上面的分析,我們測試一組答案看有沒有通過
```
Example phase 0, please type in help
> help
Phase 0 defused. It's your turn now, cheer up =)
> 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.
Phase 1 defused.
> 0 0 10 0
Phase 2 defused.
> 40 99 99
Phase 3 defused.
> 10
Phase 4 defused.
Congratulations! You've defused the bomb!
```
通過了~
## 小技巧~
* objdump
可以透過 `$ arm-linux-gnueabihf-objdump -d bomb > bomb_assembly` 將執行檔的 assembly code 拿出來,再分段對程式碼做分析。
* define hook-stop
每次會在程式中斷的時候執行 hook-stop 裡面 define 好的指令。
e.g.
```
(gdb) define hook-stop
Redefine command "hook-stop"? (y or n) y <-- 出現這行代表有 define 過 ,
Type commands for definition of "hook-stop". 按 y 會把原本的覆蓋掉
End with a line saying just "end".
>x/5i $pc
>end
```
像是這樣就會在每次中斷的時候印出 pc 後五行指令。
## 參考資料
- [ARM Architecture Reference Manual](https://www.scss.tcd.ie/~waldroj/3d1/arm_arm.pdf)
- [GNU ARM Assembler Quick Reference ](http://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf)