《CSAPP》- Bomb Lab (下)

2021-02-20 滿賦諸機
簡介

Bomb Lab 屬於《深入理解計算機系統》第三章 —— 程序的機器級表示,主要通過練習加深彙編知識的相關記憶。《CSAPP》- Bomb Lab (上) 中介紹了前三關的具體解答,本文繼續解答後三關和隱藏關。

繼續闖關獲取第四個字符串

我們在通過前面的步驟成功獲取了前三個字符串,此時我們可以開始獲取第四個字符串。讓我們重新運行程序 (gdb bomb),在 phase_4 入口處打上斷點,然後運行程序使其進入函數 phase_4,並獲取 phase_4 彙編代碼:

...Breakpoint 1, 0x000000000040100c in phase_4 ()# 獲取函數 phase_4 的彙編代碼(gdb) disasDump of assembler code for function phase_4:=> 0x40100c <+0>:   sub    $0x18,%rsp   0x401010 <+4>:   lea    0xc(%rsp),%rcx   0x401015 <+9>:   lea    0x8(%rsp),%rdx   0x40101a <+14>:  mov    $0x4025cf,%esi   0x40101f <+19>:  mov    $0x0,%eax   0x401024 <+24>:  callq  0x400bf0 <__isoc99_sscanf@plt>   0x401029 <+29>:  cmp    $0x2,%eax   0x40102c <+32>:  jne    0x401035 <phase_4+41>   0x40102e <+34>:  cmpl   $0xe,0x8(%rsp)   0x401033 <+39>:  jbe    0x40103a <phase_4+46>   0x401035 <+41>:  callq  0x40143a <explode_bomb>   0x40103a <+46>:  mov    $0xe,%edx   0x40103f <+51>:  mov    $0x0,%esi   0x401044 <+56>:  mov    0x8(%rsp),%edi   0x401048 <+60>:  callq  0x400fce <func4>   0x40104d <+65>:  test   %eax,%eax   0x40104f <+67>:  jne    0x401058 <phase_4+76>   0x401051 <+69>:  cmpl   $0x0,0xc(%rsp)   0x401056 <+74>:  je     0x40105d <phase_4+81>   0x401058 <+76>:  callq  0x40143a <explode_bomb>   0x40105d <+81>:  add    $0x18,%rsp   0x401061 <+85>:  retqEnd of assembler dump.

0x40100c <+0> ~ 0x40102c <+32>: 和前面類似,主要是通過 sscanf 獲取輸入字符串的兩個 32 位有符號整數,假設分別存儲在 a (0x8(%rsp)) 和 b (0xc(%rsp)) 中。如果沒有成功獲取兩個整數,那麼就會引爆炸彈,無法繼續運行。

0x40102e <+34> ~ 0x401033 <+39>: 主要是判斷 0 <= a <= 14 是否成立(注意跳轉使用的是 jbe 指令,所以是把 a 當作無符號數時必須小於等於 14 ,即 a 必須非負且小於等於 14),成立則跳轉至 0x40103a <+46> 繼續運行;不成立則會引爆炸彈,無法繼續運行。

0x40103a <+46> ~ 0x401048 <+60>: 主要是調用函數 func4 ,該函數籤名大致為 int func4(int i, int j, int k) 。我們調用語句大致為 int c = func4(a, 0, 14); 。

0x40104d <+65> ~ 0x40104f <+67>: 判斷 c == 0 是否成立 ,不成立則引爆炸彈,成立時繼續運行。此時我們確定 func4 函數的調用結果必須為 0 ,從而反推出第一個數 (a) 的值。

0x401051 <+69> ~ 0x401056 <+74>: 判斷 b == 0 是否成立,不成立則引爆炸彈,成立時繼續運行並可順利結束。此時我們已經確定了第二個數 (b) 為 0 。

此時我們已經確認了第二個數的值,還需確認第一個數的值。我們需要給 func4 入口處打上斷點,並運行至 func4 入口處,查看 func4 的彙編代碼:

# 在函數 func4 入口處打上斷點(gdb) break func4Breakpoint 2 at 0x400fce# 繼續運行至 func4 處打上斷點(gdb) continueContinuing.
Breakpoint 2, 0x0000000000400fce in func4 ()# 獲取函數 func4 的彙編代碼(gdb) disasDump of assembler code for function func4:=> 0x400fce <+0>: sub $0x8,%rsp 0x400fd2 <+4>: mov %edx,%eax 0x400fd4 <+6>: sub %esi,%eax 0x400fd6 <+8>: mov %eax,%ecx 0x400fd8 <+10>: shr $0x1f,%ecx 0x400fdb <+13>: add %ecx,%eax 0x400fdd <+15>: sar %eax 0x400fdf <+17>: lea (%rax,%rsi,1),%ecx 0x400fe2 <+20>: cmp %edi,%ecx 0x400fe4 <+22>: jle 0x400ff2 <func4+36> 0x400fe6 <+24>: lea -0x1(%rcx),%edx 0x400fe9 <+27>: callq 0x400fce <func4> 0x400fee <+32>: add %eax,%eax 0x400ff0 <+34>: jmp 0x401007 <func4+57> 0x400ff2 <+36>: mov $0x0,%eax 0x400ff7 <+41>: cmp %edi,%ecx 0x400ff9 <+43>: jge 0x401007 <func4+57> 0x400ffb <+45>: lea 0x1(%rcx),%esi 0x400ffe <+48>: callq 0x400fce <func4> 0x401003 <+53>: lea 0x1(%rax,%rax,1),%eax 0x401007 <+57>: add $0x8,%rsp 0x40100b <+61>: retqEnd of assembler dump.

這一段代碼比較繁瑣,而且涉及到遞歸調用,直接根據每塊指令看不太容易理解,所以直接可以先直譯出相關的 C 代碼:

inf func4(int i, int j, int k) {            int l = k - j;
int m = (l >> 31) & 0x1;
l = (l + m) >> 1;
m = l + j;
if (l <= i) {
if (l >= i) {
return 0; } else {
return 2 * func4(i, j + 1, k) + 1 } } else {
return 2 * func4(i, j, k - 1) }}

直譯後的代碼還是有點難懂(難怪 main.c 裡要問數學好不好),但是我們知道這個函數需要返回 0 才行,找到對應的分支語句,可以反推出 i == l , i 就是我們需要確定的值,而 l 只與 j, k 有關,並且第一次調用 func4 時 j = 0, k = 14 ,根據函數最開始的語句可以算出 l = 7 ,所以 i 的值也為 7 ,並且滿足 0 <= i <= 14 ,所以 i = 7 是一個合法的解。(已經獲取了 func4 的函數,所以可以直接枚舉 i = 0 ~ 14 ,可得合法的解為: 0, 1, 3, 7)

至此我們已經推斷出了字符串中兩個數字的值分別為 7 和 0 ,那麼第四個字符串為: 7 0 。

獲取第五個字符串

我們在通過前面的步驟成功獲取了前四個字符串,此時我們可以開始獲取第五個字符串。讓我們重新運行程序 (gdb bomb),在 phase_5 入口處打上斷點,然後運行程序使其進入函數 phase_5,並獲取 phase_5 彙編代碼:

...Breakpoint 1, 0x0000000000401062 in phase_5 ()# 獲取函數 phase_5 的彙編代碼(gdb) disasDump of assembler code for function phase_5:=> 0x401062 <+0>:    push   %rbx   0x401063 <+1>:    sub    $0x20,%rsp   0x401067 <+5>:    mov    %rdi,%rbx   0x40106a <+8>:    mov    %fs:0x28,%rax   0x401073 <+17>:   mov    %rax,0x18(%rsp)   0x401078 <+22>:   xor    %eax,%eax   0x40107a <+24>:   callq  0x40131b <string_length>   0x40107f <+29>:   cmp    $0x6,%eax   0x401082 <+32>:   je     0x4010d2 <phase_5+112>   0x401084 <+34>:   callq  0x40143a <explode_bomb>   0x401089 <+39>:   jmp    0x4010d2 <phase_5+112>   0x40108b <+41>:   movzbl (%rbx,%rax,1),%ecx   0x40108f <+45>:   mov    %cl,(%rsp)   0x401092 <+48>:   mov    (%rsp),%rdx   0x401096 <+52>:   and    $0xf,%edx   0x401099 <+55>:   movzbl 0x4024b0(%rdx),%edx   0x4010a0 <+62>:   mov    %dl,0x10(%rsp,%rax,1)   0x4010a4 <+66>:   add    $0x1,%rax   0x4010a8 <+70>:   cmp    $0x6,%rax   0x4010ac <+74>:   jne    0x40108b <phase_5+41>   0x4010ae <+76>:   movb   $0x0,0x16(%rsp)   0x4010b3 <+81>:   mov    $0x40245e,%esi   0x4010b8 <+86>:   lea    0x10(%rsp),%rdi   0x4010bd <+91>:   callq  0x401338 <strings_not_equal>   0x4010c2 <+96>:   test   %eax,%eax   0x4010c4 <+98>:   je     0x4010d9 <phase_5+119>   0x4010c6 <+100>:  callq  0x40143a <explode_bomb>   0x4010cb <+105>:  nopl   0x0(%rax,%rax,1)   0x4010d0 <+110>:  jmp    0x4010d9 <phase_5+119>   0x4010d2 <+112>:  mov    $0x0,%eax   0x4010d7 <+117>:  jmp    0x40108b <phase_5+41>   0x4010d9 <+119>:  mov    0x18(%rsp),%rax   0x4010de <+124>:  xor    %fs:0x28,%rax   0x4010e7 <+133>:  je     0x4010ee <phase_5+140>   0x4010e9 <+135>:  callq  0x400b30 <__stack_chk_fail@plt>   0x4010ee <+140>:  add    $0x20,%rsp   0x4010f2 <+144>:  pop    %rbx   0x4010f3 <+145>:  retqEnd of assembler dump.

0x40106a <+8> ~ 0x401073 <+17>: 看到 mov %fs:0x28,%rax 就想起了這是棧破壞檢測的相關代碼 (P199) ,主要是檢測棧是否被破壞,也說明了我們剛剛定義了一個數組,並且這個數組在後面會作為參數傳入一個函數中

0x401078 <+22> ~ 0x401082 <+32>: 主要判斷我們輸入的字符串的長度是否為 6 ,不為 6 則引爆炸彈,否則繼續運行後續代碼

0x4010d2 <+112> ~ 0x4010d7 <+117>: 將寄存器 %eax 賦值為 0 (書中 P123 提到 movl 指令以寄存器作為目的時,它會把該寄存器的高位 4 字節設置為 0 ,因此沒必要使用 movq $0x0, %rax)

0x40108b <+41> ~ 0x4010ae <+76>: 這一段是循環,主要是遍歷輸入字符串的 6 個字符 ch ,並取該字符的低 4 位作為一個下標 index ,然後將相對地址 0x4024b0 偏移為 index 的一個字節賦值給我們定義的數組中的對應位置(假設輸入字符串為 s ,棧中數組的定義語句為 char d[7]; ,地址 0x4024b0 對應的字符數組為 chs ,那麼這一段循環的意思為 d[i] = chs[s[i] & 0xf];)

•0x40108b <+41>: 使用了 MOVZ 類指令,這類指令會把目的中剩餘的字節填充為 0 ;與其對應的 MOVS 類指令會通過符號擴展填充剩餘的字節(即將源操作數的最高位進行賦值填充到目的中剩餘的字節) P123•print * (char *) 0x4024b0@16: 查看地址 0x4024b0 開始的 8 個字符為 maduiersnfotvbyl•0x4010ae <+76>: d[6] = '\0' ,即把我們棧中定義的字符數組最後設置為 0 ,表示其代表的字符串長度為 6

0x4010b3 <+81> ~ 0x4010c4 <+98>: 判斷字符串 d 與以地址 0x40245e 開始的字符串是否相等,不相等則引爆炸彈,相等則繼續運行校驗棧是否被破壞,然後通過第五個字符串的驗證

•print * (char *) 0x40245e@7: 查看地址 0x40245e 開始的 7 個字符為 flyers (注意最後一個 \0 沒有顯示)

綜上可知:我們需要生成的字符串 d 為 flyers ,可用於生成目標字符串的字母表為 maduiersnfotvbyl ,然後我們可以反推出我們可輸入的字符的低 4 位,然後對照 man ascii 指令輸出的表找出對應哪些可輸入的字符。

id[i]s[i] & 0xf可輸入字符0f0x9) 9 I Y i y1l0xf/ ? O _ o2y0xe. > N ^ n ~3e0x5% 5 E U e u4r0x6& 6 F V f v5s0x7' 7 G W g w

第五個字符串每個位置任意選取對應的可輸入字符即可通過第五個校驗。

獲取第六個字符串

我們在通過前面的步驟成功獲取了前五個字符串,此時我們可以開始獲取第六個字符串。讓我們重新運行程序 (gdb bomb),在 phase_6 入口處打上斷點,然後運行程序使其進入函數 phase_6,並獲取 phase_6 彙編代碼:

...Breakpoint 1, 0x00000000004010f4 in phase_6 ()(gdb) disasDump of assembler code for function phase_6:=> 0x4010f4 <+0>:    push   %r14   0x4010f6 <+2>:    push   %r13   0x4010f8 <+4>:    push   %r12   0x4010fa <+6>:    push   %rbp   0x4010fb <+7>:    push   %rbx   0x4010fc <+8>:    sub    $0x50,%rsp   0x401100 <+12>:   mov    %rsp,%r13   0x401103 <+15>:   mov    %rsp,%rsi   0x401106 <+18>:   callq  0x40145c <read_six_numbers>   0x40110b <+23>:   mov    %rsp,%r14   0x40110e <+26>:   mov    $0x0,%r12d   0x401114 <+32>:   mov    %r13,%rbp   0x401117 <+35>:   mov    0x0(%r13),%eax   0x40111b <+39>:   sub    $0x1,%eax   0x40111e <+42>:   cmp    $0x5,%eax   0x401121 <+45>:   jbe    0x401128 <phase_6+52>   0x401123 <+47>:   callq  0x40143a <explode_bomb>   0x401128 <+52>:   add    $0x1,%r12d   0x40112c <+56>:   cmp    $0x6,%r12d   0x401130 <+60>:   je     0x401153 <phase_6+95>   0x401132 <+62>:   mov    %r12d,%ebx   0x401135 <+65>:   movslq %ebx,%rax   0x401138 <+68>:   mov    (%rsp,%rax,4),%eax   0x40113b <+71>:   cmp    %eax,0x0(%rbp)   0x40113e <+74>:   jne    0x401145 <phase_6+81>   0x401140 <+76>:   callq  0x40143a <explode_bomb>   0x401145 <+81>:   add    $0x1,%ebx   0x401148 <+84>:   cmp    $0x5,%ebx   0x40114b <+87>:   jle    0x401135 <phase_6+65>   0x40114d <+89>:   add    $0x4,%r13   0x401151 <+93>:   jmp    0x401114 <phase_6+32>   0x401153 <+95>:   lea    0x18(%rsp),%rsi   0x401158 <+100>:  mov    %r14,%rax   0x40115b <+103>:  mov    $0x7,%ecx   0x401160 <+108>:  mov    %ecx,%edx   0x401162 <+110>:  sub    (%rax),%edx   0x401164 <+112>:  mov    %edx,(%rax)   0x401166 <+114>:  add    $0x4,%rax   0x40116a <+118>:  cmp    %rsi,%rax   0x40116d <+121>:  jne    0x401160 <phase_6+108>   0x40116f <+123>:  mov    $0x0,%esi   0x401174 <+128>:  jmp    0x401197 <phase_6+163>   0x401176 <+130>:  mov    0x8(%rdx),%rdx   0x40117a <+134>:  add    $0x1,%eax   0x40117d <+137>:  cmp    %ecx,%eax   0x40117f <+139>:  jne    0x401176 <phase_6+130>   0x401181 <+141>:  jmp    0x401188 <phase_6+148>   0x401183 <+143>:  mov    $0x6032d0,%edx   0x401188 <+148>:  mov    %rdx,0x20(%rsp,%rsi,2)   0x40118d <+153>:  add    $0x4,%rsi   0x401191 <+157>:  cmp    $0x18,%rsi   0x401195 <+161>:  je     0x4011ab <phase_6+183>   0x401197 <+163>:  mov    (%rsp,%rsi,1),%ecx   0x40119a <+166>:  cmp    $0x1,%ecx   0x40119d <+169>:  jle    0x401183 <phase_6+143>   0x40119f <+171>:  mov    $0x1,%eax   0x4011a4 <+176>:  mov    $0x6032d0,%edx   0x4011a9 <+181>:  jmp    0x401176 <phase_6+130>   0x4011ab <+183>:  mov    0x20(%rsp),%rbx   0x4011b0 <+188>:  lea    0x28(%rsp),%rax   0x4011b5 <+193>:  lea    0x50(%rsp),%rsi   0x4011ba <+198>:  mov    %rbx,%rcx   0x4011bd <+201>:  mov    (%rax),%rdx   0x4011c0 <+204>:  mov    %rdx,0x8(%rcx)   0x4011c4 <+208>:  add    $0x8,%rax   0x4011c8 <+212>:  cmp    %rsi,%rax   0x4011cb <+215>:  je     0x4011d2 <phase_6+222>   0x4011cd <+217>:  mov    %rdx,%rcx   0x4011d0 <+220>:  jmp    0x4011bd <phase_6+201>   0x4011d2 <+222>:  movq   $0x0,0x8(%rdx)   0x4011da <+230>:  mov    $0x5,%ebp   0x4011df <+235>:  mov    0x8(%rbx),%rax   0x4011e3 <+239>:  mov    (%rax),%eax   0x4011e5 <+241>:  cmp    %eax,(%rbx)   0x4011e7 <+243>:  jge    0x4011ee <phase_6+250>   0x4011e9 <+245>:  callq  0x40143a <explode_bomb>   0x4011ee <+250>:  mov    0x8(%rbx),%rbx   0x4011f2 <+254>:  sub    $0x1,%ebp   0x4011f5 <+257>:  jne    0x4011df <phase_6+235>   0x4011f7 <+259>:  add    $0x50,%rsp   0x4011fb <+263>:  pop    %rbx   0x4011fc <+264>:  pop    %rbp   0x4011fd <+265>:  pop    %r12   0x4011ff <+267>:  pop    %r13   0x401201 <+269>:  pop    %r14   0x401203 <+271>:  retqEnd of assembler dump.

第一眼看到這麼長的彙編代碼有點懵,但大概一看沒有太複雜的邏輯,還有前面遇到過的函數,所以還是冷靜下來繼續分析,由於拆開很難看懂,所以還是需要直譯成 C 代碼再研究(部分判斷轉換成等價的語句,減少縮進)。為了細化每一塊的功能,將把原始碼拆成多部分進行分析。

0x401100 <+12> ~ 0x401106 <+18>: 調用函數 read_six_numbers 從輸入的字符串讀入 6 個數字,並按順序存儲在 nums 中

直譯的 C 代碼如下:

void phase_6(char *s) {    int *ap, *bp, *endp;    int c, d, e;    struct Node **nextpp, **endpp;    struct Node *nodep, *curp;    struct Node *arr[6];    int nums[6];
ap = nums;
read_six_numbers(s, nums); ...}

0x40110b <+23> ~ 0x40114d <+89>: 主要是判斷 nums 中的數字是否全都滿足 0 <= nums[i] <= 6 且互不相等

直譯的 C 代碼如下:

    ...        bp = nums;        c = 0;
whilt (true) { if ((unsigned) (*ap - 1) > 5)) { explode_bomb(); }
c += 1; if (c == 6) { break; }
d = c; do { if (nums[d] == *ap) { explode_bomb(); }
d += 1;
} while(d <= 5);
ap += 1; } ...}

0x401153 <+95> ~ 0x40116d <+121>: 重新計算每個數組中的數字: nums[i] = 7 - nums[i]

直譯的 C 代碼如下:

    ...        endp = nums + 6;            ap = bp;        c = 7;
do { *ap = c - *ap;
ap += 1;
} while(ap != endp); ...

0x40116f <+123> ~ 0x4011a9 <+181>: 這一段有點繞,第一次看的時候還丟了一段代碼,導致後面怎麼都對不上。主要還是缺少類型信息,不知道某些地址對應的數據類型,導致推理走了歧路。

這一段代碼中出現了一個地址 0x6032d0 ,我們可以使用 x /14g 0x6032d0 查看裡面存儲的數據:

(gdb) x /14g 0x6032d00x6032d0 <node1>:    0x000000010000014c    0x00000000006032e00x6032e0 <node2>:    0x00000002000000a8    0x00000000006032f00x6032f0 <node3>:    0x000000030000039c    0x00000000006033000x603300 <node4>:    0x00000004000002b3    0x00000000006033100x603310 <node5>:    0x00000005000001dd    0x00000000006033200x603320 <node6>:    0x00000006000001bb    0x00000000000000000x603330:    0x0000000000000000    0x0000000000000000

這裡面存儲了 6 個 Node 類型的數據,可以發現後面的數字是一個地址,且為後面一個 Node 的地址,因此可以推導出 Node 類型及預先定義的一個 Node 鍊表:

struct Node {    long val;    struct Node *next;};
struct Node nodes[6];

有了以上信息,我們就能懂得這部分彙編代碼的含義了:將鍊表的第 nums[c] 個 Node 賦值給 arr[c] ,即: arr[c] = nodes[nums[c]]

直譯的 C 代碼如下:

    ...        c = 0;
do { d = nums[c];
if (d <= 1) { nodep = nodes[0]; } else { e = 1; nodep = nodes;
do { nodep = nodep -> next; e += 1;
} while(e != d); } arr[c] = nodep;
} while(c != 6); ...

0x4011ab <+183> ~ 0x4011d2 <+222>:主要是將原鍊表中的 Node 按照目前在 arr 的順序重新連起來,即: arr[i] -> next = arr[i + 1]

直譯的 C 代碼如下:

    ...        nodep = arr[0];        nextpp = arr + 1;        endpp = arr + 6;        curp = nextpp;
while(true) { curp -> next = *nextpp;
nextpp += 1;
if (nextpp == endpp) { break; }
curp = *nextpp; }
node_gp -> next = NULL; ...

0x4011da <+230> ~ 0x4011f5 <+257>: 這一段主要是判斷新鍊表中的值 val 強轉成 32 位有符號整型後是不是嚴格遞減的

    ...        c = 5;    do {                                        int d = (int) (nodep -> next -> val)
if (d >= (int) (nodep -> val)) { explode_bomb(); }
nodep = nodep -> next; c -= 1; } while(c != 0);}

至此我們已經知道 phase_6 中每一部分的邏輯,串起來就是:先從輸入的字符串中讀入 6 個在範圍 [1, 6] 內且互不相同數字,按順序放入 nums 數組中;再對數組中的每個數字執行以下操作: nums[i] = 7 - nums[i] ;然後將預先定義的一個鍊表 nodes 按照新順序重新連起來,將原來的第 nums[i] 個節點放在第 i 個位置;最後判斷新順序下鍊表節點中的值強轉成 32 位有符號整型後是否為嚴格遞減,嚴格遞減的通過本關,否則引爆炸彈。

原鍊表中的數字順序為: 0x14c -> 0x0a8 -> 0x39c -> 0x2b3 -> 0x1dd -> 0x1bb

可以通過數字數組 nums = {3, 4, 5, 6, 1, 2} 將其轉換為降序順序,由於前面還執行了 nums[i] = 7 - nums[i] ,所以實際讀取的數字數組為: nums = {4, 3, 2, 1, 6, 5} ,對應的輸入字符串為: 4 3 2 1 6 5

進入隱藏關

至此我們已經通過全部六關,但 main.c 最後還有一段注釋提示我們可能忽略了什麼:

Wow, they got it! But isn't something... missing? Perhaps something they overlooked? Mua ha ha ha ha!

我們再回看以下前面六關中 main 函數內的代碼,可以發現每一關都是先調用 read_line 函數讀取輸入字符串 ,然後傳入每關的校驗函數 phase_x ,再然後調用了同一個方法 phase_defused ,最後在提示通過了當前關。

回憶一下我們的校驗函數,裡面都直接對輸入的字符串進行了校驗,不需要後面再進行處理,也就是後面的 phase_defused 可能另有奧秘(最開始我還以為這個函數是進行後續校驗的),所以我們先看看這個函數究竟做了什麼事情。

Breakpoint 1, 0x00000000004015c4 in phase_defused ()# 查看 phase_defused 的彙編代碼(gdb) disasDump of assembler code for function phase_defused:=> 0x4015c4 <+0>:    sub    $0x78,%rsp   0x4015c8 <+4>:    mov    %fs:0x28,%rax   0x4015d1 <+13>:   mov    %rax,0x68(%rsp)   0x4015d6 <+18>:   xor    %eax,%eax   0x4015d8 <+20>:   cmpl   $0x6,0x202181(%rip)        # 0x603760 <num_input_strings>   0x4015df <+27>:   jne    0x40163f <phase_defused+123>   0x4015e1 <+29>:   lea    0x10(%rsp),%r8   0x4015e6 <+34>:   lea    0xc(%rsp),%rcx   0x4015eb <+39>:   lea    0x8(%rsp),%rdx   0x4015f0 <+44>:   mov    $0x402619,%esi   0x4015f5 <+49>:   mov    $0x603870,%edi   0x4015fa <+54>:   callq  0x400bf0 <__isoc99_sscanf@plt>   0x4015ff <+59>:   cmp    $0x3,%eax   0x401602 <+62>:   jne    0x401635 <phase_defused+113>   0x401604 <+64>:   mov    $0x402622,%esi   0x401609 <+69>:   lea    0x10(%rsp),%rdi   0x40160e <+74>:   callq  0x401338 <strings_not_equal>   0x401613 <+79>:   test   %eax,%eax   0x401615 <+81>:   jne    0x401635 <phase_defused+113>   0x401617 <+83>:   mov    $0x4024f8,%edi   0x40161c <+88>:   callq  0x400b10 <puts@plt>   0x401621 <+93>:   mov    $0x402520,%edi   0x401626 <+98>:   callq  0x400b10 <puts@plt>   0x40162b <+103>:  mov    $0x0,%eax   0x401630 <+108>:  callq  0x401242 <secret_phase>   0x401635 <+113>:  mov    $0x402558,%edi   0x40163a <+118>:  callq  0x400b10 <puts@plt>   0x40163f <+123>:  mov    0x68(%rsp),%rax   0x401644 <+128>:  xor    %fs:0x28,%rax   0x40164d <+137>:  je     0x401654 <phase_defused+144>   0x40164f <+139>:  callq  0x400b30 <__stack_chk_fail@plt>   0x401654 <+144>:  add    $0x78,%rsp   0x401658 <+148>:  retqEnd of assembler dump.

0x401630 <+108>: callq 0x401242 <secret_phase> 這一句看起來會調用一個隱藏關的函數,所以我們需要先破解當前函數使得我們可以進入隱藏關。

0x4015d8 <+20> ~ 0x4015df <+27>: 判斷 0x202181(%rip) 中的值是否為 6 ,不為 6 則直接返回當前函數,否則會執行函數體。通過注釋 0x603760 <num_input_strings> 我們可以判斷出這個值記錄的是輸入的字符串的個數,只有當輸入 6 個字符串後才有可能進入隱藏關,即我們必須通過前六關。

0x4015e1 <+29> ~ 0x401602 <+62>: 從 0x603870 開始的字符串讀入 3 個參數,前兩個是有符號整型,第三個是字符串,如果未成功讀入 3 個參數,則直接返回

•x /s 0x402619: 查看模式串為 %d %d %s ,從而可以得出三個變量的類型分別為:int, int, char *•x /s 0x603870: 當我們打上斷點,在通過第六關後進入 phase_defused ,運行這個命令可得字符串 7 0 ,這個看起來是第四關的字符串,那麼就需要我們將第四關的字符串替換為 7 0 DrEvil ,這樣我們就能成功進入隱藏關

0x401604 <+64> ~ 0x401615 <+81>: 判斷剛剛讀入的第三個字符串是否等於 DrEvil ,不等則直接返回

•x /s 0x402622: 可知第三個字符串需要和 DrEvil 相等

0x401617 <+83> ~ 0x40163a <+118>: 輸出兩端話提示我們進入隱藏關,然後通過隱藏關後再提示我們成功拆除炸彈

破解隱藏關

main.c 中開始可以使用兩種方式讀入字符串,第一種是使用標準輸入,第二種是從文件中讀入,我們可以將字符串都存入 in.txt 中,然後通過運行 (gdb) run in.txt 避免多次重複輸入字符串

# 直接查看 0x401242 所在函數的彙編代碼(gdb) disas 0x401242Dump of assembler code for function secret_phase:   0x401242 <+0>:   push   %rbx   0x401243 <+1>:   callq  0x40149e <read_line>   0x401248 <+6>:   mov    $0xa,%edx   0x40124d <+11>:  mov    $0x0,%esi   0x401252 <+16>:  mov    %rax,%rdi   0x401255 <+19>:  callq  0x400bd0 <strtol@plt>   0x40125a <+24>:  mov    %rax,%rbx   0x40125d <+27>:  lea    -0x1(%rax),%eax   0x401260 <+30>:  cmp    $0x3e8,%eax   0x401265 <+35>:  jbe    0x40126c <secret_phase+42>   0x401267 <+37>:  callq  0x40143a <explode_bomb>   0x40126c <+42>:  mov    %ebx,%esi   0x40126e <+44>:  mov    $0x6030f0,%edi   0x401273 <+49>:  callq  0x401204 <fun7>   0x401278 <+54>:  cmp    $0x2,%eax   0x40127b <+57>:  je     0x401282 <secret_phase+64>   0x40127d <+59>:  callq  0x40143a <explode_bomb>   0x401282 <+64>:  mov    $0x402438,%edi   0x401287 <+69>:  callq  0x400b10 <puts@plt>   0x40128c <+74>:  callq  0x4015c4 <phase_defused>   0x401291 <+79>:  pop    %rbx   0x401292 <+80>:  retqEnd of assembler dump.

0x401243 <+1> ~ 0x401255 <+19>: 讀入一個字符串並將其按照 10 進位對待轉換成一個長整型 num

0x40125a <+24> ~ 0x401267 <+37>: 判斷 (unsigned) (num - 1) <= 0x3e8 是否成立,不成立則引爆炸彈

0x40126c <+42> ~ 0x40128c <+74>: 調用函數 fun7(0x6030f0, num) ,判斷返回值是否為 2 ,是則提示成功拆除炸彈,不是則引爆炸彈

# 直接查看 0x401204 所在函數的彙編代碼(gdb) disas 0x401204Dump of assembler code for function fun7:   0x401204 <+0>:   sub    $0x8,%rsp   0x401208 <+4>:   test   %rdi,%rdi   0x40120b <+7>:   je     0x401238 <fun7+52>   0x40120d <+9>:   mov    (%rdi),%edx   0x40120f <+11>:  cmp    %esi,%edx   0x401211 <+13>:  jle    0x401220 <fun7+28>   0x401213 <+15>:  mov    0x8(%rdi),%rdi   0x401217 <+19>:  callq  0x401204 <fun7>   0x40121c <+24>:  add    %eax,%eax   0x40121e <+26>:  jmp    0x40123d <fun7+57>   0x401220 <+28>:  mov    $0x0,%eax   0x401225 <+33>:  cmp    %esi,%edx   0x401227 <+35>:  je     0x40123d <fun7+57>   0x401229 <+37>:  mov    0x10(%rdi),%rdi   0x40122d <+41>:  callq  0x401204 <fun7>   0x401232 <+46>:  lea    0x1(%rax,%rax,1),%eax   0x401236 <+50>:  jmp    0x40123d <fun7+57>   0x401238 <+52>:  mov    $0xffffffff,%eax   0x40123d <+57>:  add    $0x8,%rsp   0x401241 <+61>:  retqEnd of assembler dump.

0x401208 <+4> ~ 0x40120b <+7>: 判斷第一個參數是否為 0 ,是則直接返回 0xffffffff ,不是則繼續運行

現在基本可以判斷第一個參數是一個指針,剛剛的語句在判斷指針是否為空,目前還需要判斷這個指針指向的數據類型。

(gdb) x /64xg 0x6030f00x6030f0 <n1>:      0x24         0x6031100x603100 <n1+16>:   0x603130     0x00x603110 <n21>:     0x8          0x6031900x603120 <n21+16>:  0x603150     0x00x603130 <n22>:     0x32         0x6031700x603140 <n22+16>:  0x6031b0     0x00x603150 <n32>:     0x16         0x6032700x603160 <n32+16>:  0x603230     0x00x603170 <n33>:     0x2d         0x6031d00x603180 <n33+16>:  0x603290     0x00x603190 <n31>:     0x6          0x6031f00x6031a0 <n31+16>:  0x603250     0x00x6031b0 <n34>:     0x6b         0x6032100x6031c0 <n34+16>:  0x6032b0     0x00x6031d0 <n45>:     0x28         0x00x6031e0 <n45+16>:  0x0          0x00x6031f0 <n41>:     0x1          0x00x603200 <n41+16>:  0x0          0x00x603210 <n47>:     0x63         0x00x603220 <n47+16>:  0x0          0x00x603230 <n44>:     0x23         0x00x603240 <n44+16>:  0x0          0x00x603250 <n42>:     0x7          0x00x603260 <n42+16>:  0x0          0x00x603270 <n43>:     0x14         0x00x603280 <n43+16>:  0x0          0x00x603290 <n46>:     0x2f         0x00x6032a0 <n46+16>:  0x0          0x00x6032b0 <n48>:     0x3e9        0x00x6032c0 <n48+16>:  0x0          0x00x6032d0 <node1>:   0x10000014c  0x6032e00x6032e0 <node2>:   0x2000000a8  0x6032f0

看起來這個數據類型佔 32 字節,第一個存儲一個數,後面兩個存儲指針,分別指向相同的類型,所以可以猜測這是一個二叉樹節點。通過 0x40120d <+9>: mov (%rdi),%edx 可以猜測第一個欄位是整型,而後面兩個欄位分別是左右子節點,剩餘的字節未使用。因此這個節點的定義大致如下:

type TreeNode {    int val;    struct *TreeNode left, right;};

現在我們可以直譯出 func7 對應的 C 代碼了:

int func7 (TreeNode *root, int num) {            if (root == NULL) {        return 0xffffffff;    }
if (root -> val <= num) { if (root -> val == num) { return 0; } else { return 2 * fun7(root -> right, num) + 1 } } else { return 2 * fun7(root -> left); }}

至此我們知道了 fun7 的代碼,並且需要 fun7(0x6030f0, num) 的返回值為 2 ,為了方便計算,需要將樹中的每個值畫出來

這是一個四層的滿二叉樹,根據遞歸的計算方式和所需的最終返回值可以繼續推導:

第一層:節點值為 0x24 ,需要返回 2 ,那麼就需要走 return 2 * fun7(root -> left) 這個邏輯,限定了 num < 0x24

第二層:節點值為 0x8 ,需要返回 1 ,那麼需要走 return 2 * fun7(root -> right, num) + 1 這個邏輯,限定了 num > 0x8

第三層:節點值為 0x16 ,需要返回 0 ,有兩個邏輯可走,限定了 num <= 0x16

•走 return 0 這個邏輯:限定了 num = 0x16•走 return 2 * fun7(root -> left) 這個邏輯,限定了 num < 0x16

第四層:節點值為 0x14 ,需要返回 0 ,那麼需要走 return 0 這個邏輯,限定了 num = 0x14

綜上可知,隱藏關有兩個解:0x14 和 0x16 ,對應的十進位數分別為: 20 和 22

小結

前五關都是平鋪直述的各種簡單語句,讓我信心大增,內心 OS :就這?第六關立刻教我做人,來來回回很久怎麼翻譯都不對(主要就是因為類型沒推斷出來,導致後續的推導不自洽),看來類型還是很重要的,如果沒有推斷出類型,那很容易誤入歧途。

這些關卡強化了彙編的相關記憶(基本指令的具體含義、 gdb 的各種基本命令以及常用的寄存器的含義),中後期基本不需要再翻書查閱了。

相關焦點

  • 《CSAPP》- Bomb Lab (上)
    本次需要使用的程序依舊需要在 Docker 中運行,將本地 Lab 的目錄掛載進容器中即可:docker run -ti -v {PWD}:/csapp ubuntu:18.04進入容器後需要安裝 gdb :apt-get update
  • 《CSAPP》- Attack Lab
    : You called touch1()Valid solution for level 1 with target ctargetPASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:ctarget
  • Bomb lab
    前言這是CMU的Computer Systems: A Programmer's Perspective配套的lab作業。
  • LAB完結
    紀念ics最後一個lab~在馬原課上終於把最後一個case調好了,至此一共8個lab就全部做完了(data, bomb,
  • 《CSAPP》- Cache Lab (下)
    /test-trans 命令時需要確保程序在 Linux 機器內的文件夾下,而非我們掛載的目錄中,否則會出現跑不出結果的問題。先跑一下這個結果,發現未命中次數為 1183 次,離目標 300 次以下還有很遠,這時和做 Architecture Lab 最後一部分一樣有點不知所措,不過指南最後提到了使用分塊的方法可以優化,就繼續學習 分塊技術 ( http://csapp.cs.cmu.edu/public/waside/waside-blocking.pdf ) 。
  • CSAPP lab4 archlab
    CSAPP lab4 archlablab真心難,這一章的體系結構部分還是真心很難,總共分為parta、partb、partc三部分,三部分都還挺難的,parta,partb部分還算比較容易,partc部分真心還是挺難的,需要仔細了解和學習相關知識。
  • 「bomb」是「炸彈」,但make a bomb可不一定是「做炸彈」啊!
    1)make a bomb第一個要學習的表達叫做「make a bomb」。「bomb」是「炸彈」,但make a bomb可不一定是「做炸彈」啊!事實上,在英語口語中,這個詞組意思往往是這樣的——make a bomb = make a fortune;正如你所見,make a bomb其實就相當於make a fortune,fortune表示一筆財富。
  • 「bomb」和「the bomb」相差一個the,意思卻完全相反!
    the bomb&bombbomb,我們最熟悉它的意思就是「炸彈, 爆炸」, 但在在口語中,還有一個常用的用法:做名詞表示「a total failure」"徹底的失敗」,做動詞表示「徹底失敗」。
  • 星日常:娃娃美貌Kei,美貌明朗樸彩瑛,妖精bombbomb趙美延
    星日常:娃娃美貌Kei,美貌明朗樸彩英,妖精bombbomb趙美延畫報日常?清爽日常?還是帥氣日常?明星們的日常,通過明星來親自告訴你!來看看第3組告訴你日常的明星是,Gidle趙美延,無法隱藏的清爽魅力……妖精bombbomb女子組合Gidle的成員美延公開了近況。Gidle的官方社交媒體網站帳號上上傳了幾張Gidle成員趙美延的照片。
  • 18 killed, 72 wounded in spate of bomb attacks in Baghdad
    BAGHDAD -- Up to 18 people were killed and 72 wounded in a series of bomb attacks in and near Baghdad on Thursday morning, an Interior Ministry source said.
  • Suspect: plane bomb hoax a "prank"
    Police said Tuesday that the man who allegedly made a hoax bomb threat described his deeds as a "prank," Xinhua reported.
  • photobomb 是什麼,你會翻譯嗎?
    我們看到了bomb,對,photobomb就是「照片炸彈」的意思,我們這裡的「炸彈」雖沒有真的炸彈那麼有殺傷力,但也是極度令人反感的……你是否有過這樣的經歷呢?朋友聚會的時候,你跟好友站在一起要拍一張親密照,就在按下快門的那一刻,有個不知好歹的傢伙也站了進來,好端端的二人世界生生被破壞了……【定義】It often refers to an normal photo that has been ruined or spoiled by someone who was not supposed to be in the photograph
  • 流行美語:bomb; mess up
    他們在對話中會用到兩個美國年輕人常用的說法, 一個是to bomb;另一個是to mess up。L: Michael, 怎麼啦?你好象有心事。M: I think I just bombed my history test. I don't think I'm going to pass.L: 噢,你怕剛才歷史測驗會不及格。
  • 趣味英語:「cost a bomb」跟炸彈有關係嗎?
    上周Ella逛了一天街,微信裡的錢和支付寶裡的錢用光光,下個月準備吃土了,不過她看起來一點也不擔心,還開心對我說:」My new diamond necklace costs a bomb."我笑著回答她說:「I hope it doesn't cost a bomb to insure it." 這段對話什麼意思呢?bomb是炸彈的意思,"cost a bomb"指代價極其昂貴,就像炸彈一般厲害。那麼Ella的意思是她的鑽石項鍊好貴啊,而我是有點戲謔地說希望不要花很多錢來給這條昂貴的項鍊買保險。
  • make a bomb 竟然不是「造原子彈」,1分鐘帶你了解
    今天,我同事Jacky 喜滋滋地跑到我座位上,跟我說:Last night he makes a bomb。what??? 難道他昨天晚上去「造原子彈」去了嗎?原來,在英語口語中,make a bomb表達的不是」製造炸彈「。
  • Bomb damages Lenin statue
    The bomb had the power of about 400g of TNT, he said.(See photo)俄羅斯警方證實,1日凌晨,位於聖彼得堡的一尊列寧銅像遭炸彈襲擊,受損嚴重,列寧"風衣"被炸出一個大洞。警方發言人稱,炸彈威力相當於400克TNT炸藥的威力,所幸事件中沒有人員傷亡,爆炸原因仍在調查中。
  • 韓組合sixbomb香腸裝被電視臺全封殺 尺度太大(圖)
    據韓國Mydaily媒體報導韓國女子組合sixbomb的最新舞臺服裝日前遭到了韓國所有電視臺的聯合封殺。  2012年出道的sixbomb本月19日公布了第三張單曲《只要等十年就好,BABY》的MV。
  • 抖音幫幫幫被捉噔噔噔噔是什麼歌 bomb a drop完整歌詞介紹
    抖音幫幫幫被捉噔噔噔噔是什麼歌 bomb a drop完整歌詞介紹  Bomb A Drop (投一枚炸彈) - Garmiani  Written by:Kemar Ottey/Jiar Garmiani/Delroy Pairt/Ricardo Johnson  Garmiani like a one man army
  • X-lab nurtures the future for China, Germany
    It covers student exchanges, education sharing and creating and Tsinghua's X-lab is closely involved.