超精講-逐例分析CS:LAB2-Bomb!(上)

0. 環境要求

關於環境已經在lab1裏配置過了這裏要記得安裝gdbhtml

安裝命令 sudo yum install gdbc++

實驗的下載地址 http://csapp.cs.cmu.edu/3e/labs.htmlapp

gbd的命令地址 http://csapp.cs.cmu.edu/2e/docs/gdbnotes-x86-64.pdfdom

知乎同款鏈接 https://zhuanlan.zhihu.com/p/339461318函數

這裏咱們須要使用objdump -d ./bomb >> bomb.s反彙編工具來獲得彙編代碼。工具

下面就開始舉世盛名bomb 實驗吧學習

1. 第一關

  1. 粗讀 main 函數this

    initialize_bomb();
    
       printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
       printf("which to blow yourself up. Have a nice day!\n");
        /* 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!

    經過簡單的閱讀理解應該知道這裏面的phase_1 就是咱們的第一關了,而後根據函數名稱 input = read_line() 應該是要驗證咱們的輸入是否合理,咱們先亂輸入一個看看先運行起來spa

    (gdb) r
    Starting program: /csapp/bomb/bomb 
    warning: Error disabling address space randomization: Operation not permitted
    Welcome to my fiendish little bomb. You have 6 phases with
    which to blow yourself up. Have a nice day!

    輸入hello wordlscala

    hello world
    BOOM!!!
    The bomb has blown up.
    [Inferior 1 (process 67) exited with code 010]

    果真💥了下面開始分析咱們的彙編代碼來嘗試獲得結果

  2. 閱讀bomb.s 理清思路

    0000000000400ee0 <phase_1>:
      400ee0:	48 83 ec 08          	sub    $0x8,%rsp
      400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
      400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>
      400eee:	85 c0                	test   %eax,%eax
      400ef0:	74 05                	je     400ef7 <phase_1+0x17>
      400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
      400ef7:	48 83 c4 08          	add    $0x8,%rsp
      400efb:	c3                   	retq

    這個邏輯應該是很簡單的不熟悉彙編代碼的同窗能夠參考一下csapp原書的第三章

    • 1 行分配棧幀

    • 2 行把當即數0x402400 放到寄存器esi中那咱們知道這是用來第二個參數的寄存器

    • 3-4 行 調用 strings_not_equal 函數而後判斷返回值是否爲0,若是是0就跳到400ef7而後恢復棧幀結束不然就調用explode_bomb引爆炸彈因此核心就在於strings_not_equal函數

  3. 閱讀strings_not_equal 函數

    emm 這個函數太長就只放重點了 咱們重點關注esi寄存器由於上面咱們傳遞了一個參數

    40133f:	48 89 f5             	mov    %rsi,%rbp
     40134a:	48 89 ef             	mov    %rbp,%rdi
     40134d:	e8 c9 ff ff ff       	callq  40131b <string_length> 
     40135c:	0f b6 03             	movzbl (%rbx),%eax
     401363:	3a 45 00             	cmp    0x0(%rbp),%al

    這裏咱們能夠發現他把m[rbx]->eax 而後比較m[rbp]al寄存器裏面的值(也就是%eax裏面的值)

    這就是說咱們須要比較的就是在0x402400位置處的字符串和題目預約輸入的字符串是否相同,中間有不少判斷字符串長度是否相同的代碼請你們自行閱讀吧。因此咱們能夠查看0x402400位置的值而後直接輸入這個結果就好

    (gdb) x/s 0x402400
    0x402400:	"Border relations with Canada have never been better."

    咱們輸入Border relations with Canada have never been better.就能夠拆掉第一個炸彈

2. 第二關

首先仍是差很少的代碼邏輯對於輸入進行判斷,合格就能夠經過

input = read_line();
    phase_2(input);
    phase_defused();
    printf("That's number 2.  Keep going!\n");
  1. 慢讀phase_2的彙編代碼
400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  • 分配大小爲40字節的棧幀而後把棧幀指針傳遞給%rsi寄存器接下來調用read_six_numbers
  1. 轉入read_six_numbers函數
000000000040145c <read_six_numbers>:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  40148f:	83 f8 05             	cmp    $0x5,%eax
  401492:	7f 05                	jg     401499 <read_six_numbers+0x3d>
  401494:	e8 a1 ff ff ff       	callq  40143a <explode_bomb>
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	retq

簡單分析一下這個彙編代碼這個須要咱們輸入6個數字而後進行比較這裏還有若是數字不知足6個的健壯性判斷

下面按順序構建sscanf所須要的參數

首先咱們分析一下sscanf函數

(gdb) x/s 0x4025c3
0x4025c3: "%d %d %d %d %d %d"

咱們發現sscanf函數所須要的第二個參數竟然是這樣的格式這就表示咱們會依次把六個數讀入到他所傳遞的3-8個參數裏面咱們知道3-6個參數能夠用寄存器傳遞剩下的兩個參數則要利用棧幀來傳遞

  • %rsi 存儲在phase_2裏面傳入的%rsp的值也就是棧幀的起始地址

    咱們查看%esi的值發現竟然是這樣那麼就表示咱們的sscanf函數須要的是

  • %rdx,由%rsi給出,%rsi又由phrase_2%rsp給出,因此phrase2中的%rsp地址處存放sscanf中第一個輸入的值

  • %rcx也就是在phase_2%rsp+0x4中存放着第二個值

  • %r8也就是在phase_2%rsp+0x8中存放着第三個值

  • %r9也就是在phase_2%rsp+0xc中存放着第四個值

  • 在該函數的%rsp 中存放着第五個值也就是咱們把0x10(%rsi),%rax放進了rsp

  • 在該函數的%rsp+0x8 中存放着第六個值也就是咱們把0x14(%rsi),%rax放進了rsp

上面就是對全部值的分析下面咱們來依次分析這些值

上面的描述會比較亂,這裏用一個很是醜的圖來表示這個參數的構造過程1st表示第一個參數依次類推。
3. 回到phase_2

400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29>
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq
  • (rsp) 這裏有一個和1的比較能夠發現這裏必須等於1否則直接引爆炸彈了因此第一個值就是1

  • (%rsp+0x4)

    400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
     400f1a:	01 c0                	add    %eax,%eax
     400f1c:	39 03                	cmp    %eax,(%rbx)

    這三行就是關鍵代碼由於咱們在跳轉以後把rsp+0x4賦給了rbp 那上述代碼的前兩行就是把rsp裏的值翻倍而後放到rsp+0x4 也就是第二個參數是2

剩下的過程就能夠循環了六個數分別應該是1,2,4,8,16,32

Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!

3. 第三關

好傢伙我倒要看看complex code 有多複雜邏輯 首先仍是同樣都是判斷輸入是否合法

/* I guess this is too easy so far.  Some more complex code will
     * confuse people. */
    input = read_line();
    phase_3(input);
    phase_defused();
    printf("Halfway there!\n");

1. 閱讀phase_3

400f43:	48 83 ec 18          	sub    $0x18,%rsp
 400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
 400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
 400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
 400f56:	b8 00 00 00 00       	mov    $0x0,%eax
 400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
 400f60:	83 f8 01             	cmp    $0x1,%eax
 400f63:	7f 05                	jg     400f6a <phase_3+0x27>
  • 分配棧幀

  • 利用%rdx 也就是0x8+%rsp和利用%rcx 也就是0xc+%rsp傳遞sscanf函數用的第三和第四個參數

  • 第二個參數爲一個常數0x4025cf隨後調用<__isoc99_sscanf@plt> 這裏注意一下sscanf若是調用成功的話會返回2這裏若是成功的話會跳轉到400f6a這裏讀入的rdxrcx的值就是咱們輸入的第一個和第二個數

2. 繼續閱讀phase_3

400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax
  400fab:	eb 11                	jmp    400fbe <phase_3+0x7b>
  400fad:	e8 88 04 00 00       	callq  40143a <explode_bomb>
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq
  1. %rdx裏的值必須小於等於7不然直接爆炸而後關鍵來了咱們把第一個輸入的值傳給%eax而後在就是一步關鍵操做

  2. 咱們要跳轉到m[0x402470+r[%rax]*8]這裏咱們以傳入的第一個參數也就是%rdx的值爲0爲例子那我要跳轉的地址就是m[0x402470] check 一下這裏面是什麼值

    (gdb) x/x 0x402470
    0x402470:	0x00400f7c

    能夠發現咱們接下來要跳到0x400f7c這裏去

  3. 節選有關代碼分析

    400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
      400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
      400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
      400fc2:	74 05                	je     400fc9 <phase_3+0x86>
      400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
      400fc9:	48 83 c4 18          	add    $0x18,%rsp

    這裏咱們先把0xcf傳遞給%eax 而後比較0xc(%rsp)也就是咱們輸入的第二個參數若是和eax相等則完成這表示0 和 0xcf=207 就是一個合法答案

0 207
Halfway there!

​ 這裏注意咱們的第一個參數能夠任取<=7的任何數而後去查看不一樣的地址獲得不一樣的跳轉地址也就會有不一樣 的答案這裏再給出一個例子若是第一個參數爲1那麼m[0x402470+r[%rax]*8]=m[0x402470+8]也就是咱們 要去看一下m[0x402478]

(gdb) x/x 0x402478
0x402478:	0x00400fb9

​ 此次變成了另外一地址咱們接着分析一下後面會發生什麼事

400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
 400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
 400fc2:	74 05                	je     400fc9 <phase_3+0x86>
 400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
 400fc9:	48 83 c4 18          	add    $0x18,%rsp
 400fcd:	c3                   	retq

​ 那這就是判斷第二個參數是否等於0x137下面咱們來試試1 和 311是否能夠

1 311
Halfway there!

能夠發現也是能夠的其餘例子就不在演示了

4. 第四關

好傢伙考察數學能力開衝

/* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */
    input = read_line();
    phase_4(input);
    phase_defused();
    printf("So you got that one.  Try this one.\n");

1.讀phase_4

40100c:	48 83 ec 18          	sub    $0x18,%rsp
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax
  401024:	e8 c7 fb ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,%eax
  40102c:	75 07                	jne    401035 <phase_4+0x29>

前面的代碼幾乎和上面的沒差,rdx存儲了咱們讀入的第一個參數rcx存儲了咱們讀入的第二個參數這裏一樣有對sscanf的判斷若是不成功直接爆炸

2.繼續分析phase_4

40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp)
  401033:	76 05                	jbe    40103a <phase_4+0x2e>
  401035:	e8 00 04 00 00       	callq  40143a <explode_bomb>
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx
  40103f:	be 00 00 00 00       	mov    $0x0,%esi
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi
  401048:	e8 81 ff ff ff       	callq  400fce <func4>
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 <phase_4+0x4c>
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d <phase_4+0x51>
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	retq

這裏咱們須要把m[rsp+8]=r[%rdx] 的值和0xe比較。咱們設第一個輸入的參數是aa > 0xe則直接爆炸。不然咱們構建對調用func4的參數func4(0x8(%rsp),0,0xe) 這裏的第一個參數就是咱們的a注意後面的咱們把0和m[0xc(%rsp)]=r[%rcx] 進行比較若是想等則退出也就表示了咱們第二個參數必須爲0

3.去看func4

對於代碼的分析直接寫到註釋上了

0000000000400fce <func4>:(a,0,0xe)
  400fce:	48 83 ec 08          	sub    $0x8,%rsp  //分配棧幀
  400fd2:	89 d0                	mov    %edx,%eax  // %eax=0xe
  400fd4:	29 f0                	sub    %esi,%eax  // %eax=%eax-0=0xe
  400fd6:	89 c1                	mov    %eax,%ecx  // %ecx=%eax=0xe
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx //對%ecx的值邏輯右移31位
  400fdb:	01 c8                	add    %ecx,%eax  //%eax=%eax+%ecx=0xe
  400fdd:	d1 f8                	sar    %eax       //%eax=%eax>>1=0x7
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx //%ecx=0x7+0x0=0x7
  400fe2:	39 f9                	cmp    %edi,%ecx  //%ecx-%edi=0x7-a
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>  //if <=0 則跳轉

咱們能夠看到對於咱們輸入的第一個參數a 存在if a>=7 則會跳轉到400ff2 咱們不妨輸入a=7

下面看一下0x400ff2

400ff2:	b8 00 00 00 00       	mov    $0x0,%eax    //返回值爲0
 400ff7:	39 f9                	cmp    %edi,%ecx    //再一次比較%ecx-%edi=0x7-a 
 400ff9:	7d 0c                	jge    401007 <func4+0x39> // if a<=7 則跳出
 400ffb:	8d 71 01             	lea    0x1(%rcx),%esi  // 不然咱們把%esi++重寫執行
 400ffe:	e8 cb ff ff ff       	callq  400fce <func4>
 401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
 401007:	48 83 c4 08          	add    $0x8,%rsp
 40100b:	c3                   	retq

能夠發現這是一個簡單的for 循環咱們直接用a=7 直接卡了這個循環的邊界條件直接能夠過掉本題

很是迅速的作完了第四個嘿嘿

7 0
So you got that one.  Try this one.

不過本着學習的目的咱們仍是要知道這整個遞歸函數都發生了什麼。在網上隨便找了一個版本的c語言代碼來分析一下

void func4(int x,int y,int z)  //y的初始值爲0,z的初始值爲14,t->%rax,k->%ecx
{
  int t=z-y;
  int k=t>>31;
  t=(t+k)>>1;
  k=t+y;
  if(k>x)
  {
    z=k-1;
    func4(x,y,z);
    t=2t;
    return;
  }
  else
   {
     t=0;
     if(k<x)
     {
        y=k+1;
        func4(x,y,z);
        t=2*t+1;
        return;
     }
     else
     {
         return;
     }
   }
}

分析得x = k的時候,t=0,即 %eax 爲 0 。而後就能夠回到 phase_4 。

相關文章
相關標籤/搜索