boom lab分析

上山砍大树發表於2024-04-24

單步除錯:

(gdb) bt
#1  0x0000000000401347 in strings_not_equal ()
#2  0x0000000000400eee in phase_1 ()
#3  0x0000000000400e3f in main (argc=<optimized out>, argv=<optimized out>) at bomb.c:74

這裡的執行流程為:

    /* Hmm...  Six phases must be more secure than one phase! */
    input = read_line();             /* Get input                   */
    phase_1(input);                  /* Run the phase               */
    phase_defused();                 /* Drat!  They figured it out!
				      * Let me know how they did it. */
    printf("Phase 1 defused. How about the next one?\n");

可以發現只有phase_1正常返回的時候,才會執行下面的phase_defused()函式將階段1的炸彈拆除。現在就想讓phase_1正常執行完,那就需要讓此函式中所有不正常的跳轉都不執行。

首先檢視phase_1函式的彙編程式碼:

(gdb) disassemble phase_1
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:     mov    $0x402400,%esi               #0x402400 --> %esi
   0x0000000000400ee9 <+9>:     call   0x401338 <strings_not_equal> #呼叫strings_not_equal函式
   0x0000000000400eee <+14>:    test   %eax,%eax                    
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>		#如果%eax值為0,則正常返回
   0x0000000000400ef2 <+18>:    call   0x40143a <explode_bomb>		#如果%eax值不為0,則觸發炸彈
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    ret

test 指令將 %eax 暫存器的值與自身進行邏輯與操作,並設定標誌位,但不修改 %eax 的值。如果 %eax 的值為零,則標誌位會被置為零;如果 %eax 的值不為零,則標誌位會被置為非零。

如果想讓程式正常執行,那麼就需要讓獲得%esi暫存器值的函式strings_not_equal返回值為0。

下面檢視函式strings_not_equal的程式碼,並且分析下主要的控制程式碼,看看如何讓返回值為0:

(gdb) disassemble strings_not_equal 
Dump of assembler code for function strings_not_equal:
   0x0000000000401338 <+0>:     push   %r12
   0x000000000040133a <+2>:     push   %rbp
   0x000000000040133b <+3>:     push   %rbx
   0x000000000040133c <+4>:     mov    %rdi,%rbx						
   0x000000000040133f <+7>:     mov    %rsi,%rbp
   0x0000000000401342 <+10>:    call   0x40131b <string_length>			# 將%rbx和 %rbp傳入函式string_length
   0x0000000000401347 <+15>:    mov    %eax,%r12d						# 第一次返回值%eax賦給 %r12d
   0x000000000040134a <+18>:    mov    %rbp,%rdi						# %rbp —> %rdi
   0x000000000040134d <+21>:    call   0x40131b <string_length>			# %rdi的值作為入參	
   0x0000000000401352 <+26>:    mov    $0x1,%edx
   0x0000000000401357 <+31>:    cmp    %eax,%r12d 						# 對比兩次函式長度呼叫的結果
   0x000000000040135a <+34>:    jne    0x40139b <strings_not_equal+99>  # 若長度不同,返回1
   
   # 以上程式碼均為判斷兩個字串的長度是否相同,現在的%r12d、%eax分別儲存了第一次和第二次的函式呼叫結果,即兩個字串的長度
   # 暫存器rbx 和 rbp 分別儲存著函式的兩個引數
   
   # 35c-41:若函式第一個引數低8位為0,則返回0
   0x000000000040135c <+36>:    movzbl (%rbx),%eax						# 引數1的低1位位元組賦值給eax
   0x000000000040135f <+39>:    test   %al,%al							# %al & %al
   0x0000000000401361 <+41>:    je     0x401388 <strings_not_equal+80>  # Jump condition:ZF---(Equal / zero)
   # 35c-37f:對比引數1和引數2的每個位元組,均相同的話遍
   0x0000000000401363 <+43>:    cmp    0x0(%rbp),%al					# 引數2的低1位位元組與數1的低1位位元組對比
   0x0000000000401366 <+46>:    je     0x401372 <strings_not_equal+58>	# 如果相等,則對比更高1位的位元組
   0x0000000000401368 <+48>:    jmp    0x40138f <strings_not_equal+87>  # 不相等,返回錯誤1
   0x000000000040136a <+50>:    cmp    0x0(%rbp),%al					
   0x000000000040136d <+53>:    nopl   (%rax)
   0x0000000000401370 <+56>:    jne    0x401396 <strings_not_equal+94> #若引數1、2的第2個位元組不同,則返回錯誤1
   0x0000000000401372 <+58>:    add    $0x1,%rbx
   0x0000000000401376 <+62>:    add    $0x1,%rbp
   0x000000000040137a <+66>:    movzbl (%rbx),%eax
   0x000000000040137d <+69>:    test   %al,%al
   0x000000000040137f <+71>:    jne    0x40136a <strings_not_equal+50>
   
   
   0x0000000000401381 <+73>:    mov    $0x0,%edx
   0x0000000000401386 <+78>:    jmp    0x40139b <strings_not_equal+99>
   0x0000000000401388 <+80>:    mov    $0x0,%edx
   0x000000000040138d <+85>:    jmp    0x40139b <strings_not_equal+99>
   0x000000000040138f <+87>:    mov    $0x1,%edx
   0x0000000000401394 <+92>:    jmp    0x40139b <strings_not_equal+99>
   0x0000000000401396 <+94>:    mov    $0x1,%edx
   0x000000000040139b <+99>:    mov    %edx,%eax
   0x000000000040139d <+101>:   pop    %rbx
   0x000000000040139e <+102>:   pop    %rbp
   0x000000000040139f <+103>:   pop    %r12
   0x00000000004013a1 <+105>:   ret    
End of assembler dump.

這裡的幾個比較重要的跳轉程式碼:

0x40139b <strings_not_equal+99>:函式將以%edx作為返回值返回。

0x401388 <strings_not_equal+80>:返回0。

0x40136a <strings_not_equal+50>:比較 %al 暫存器中的值與 %rbp 暫存器偏移 0 的記憶體地址處的值,如果不相等,則跳轉到地址 0x401396 處執行相應的程式碼(函式會返回1),否則繼續執行後續指令。

重點理解的指令有:

  • test S1, S2:相當於S1&S2,若結果為0,則設定ZF為1
  • jmp的所有指令。重點有je,jump condition為ZF = 1
  • movzbl:0擴充資料。Move zero-extended byte to double word(movz S,R: R ← ZeroExtend(S))

綜上,可以得知如果想拆除階段1的炸彈,則需要輸入合理的字串,而這個字串可以透過以下分析得出:

  1. 檢視phase_1函式的彙編程式碼,發現入參%rdi需要跟另一個引數0x402400 --> %rsi進入函式strings_not_equal 進行計算,若結果為0,則可以拆除炸彈,否則爆炸。
  2. 檢視strings_not_equal 函式的彙編程式碼,可以分析出此函式是將入參暫存器(%rdi%rsi)所指地址處的字串進行比對,如果長度相同並且每個字都相同(按照位元組對比),則返回0

透過上面兩個函式的分析,就可以得知我們所輸入的字串,需要跟地址為0x402400處的字串進行比對,比對成功則炸彈拆除。

那這樣,透過gdb的命令x/30s 0x402400,以 ASCII 格式列印出從地址 0x402400 開始的 10 個位元組的資料(假設這些資料是以 0 結尾的字串),並且找出第一個字串即可,這裡列印的值為:

(gdb) x/30s 0x402400                                                                                                                                           
0x402400:       "Border relations with Canada have never been better."
				#translation:邊境與加拿大的關係從未如此良好。

相關文章