今天剛剛驗收CSAPP實驗3,趁著余溫,記錄一下這個實驗,順便回顧下CSAPP課程的相關知識。
實驗目的
1.使用gdb工具反匯編出匯編代碼,結合c語言文件找到每個關卡的入口函數。然后分析匯編代碼,分析得到每一關的通關密碼。
2.熟悉linux指令,學會如何使用gdb對程序進行調試
實驗環境
個人電腦PC,32位ubuntu系統環境
實驗內容及操作步驟
實驗內容
“二進制炸彈”是Linux可執行C程序,包含六個“階段”和一個秘密“階段”。 每個階段都要求輸入特定的字符串在stdin上。 如果輸入了預期的字符串,則該階段通過,進入下一階段。 否則,炸彈會通過打印“ BOOM !!!”而“爆炸”。
操作步驟
拆彈前準備
(1).進入Ubuntu系統,將自己的程序包復制到虛擬機中,并使用tar xvf bomb201808010515.tar進行解壓。
(2).輸入objdump –d bomb >my_bomb.txt,將反匯編代碼輸入到my_bomb.txt的文件中,便于拆彈時的查看和注釋。
(3).打開反匯編文件,發現phase_i系列的函數,結合實驗題目,可以知道這是每一個關卡對應的函數。
開始拆炸彈
PS:每個關卡匯編代碼下面有相應的文字分析。
phase_1:確定輸入字符串
查看匯編代碼
08048b50 :
8048b50:83 ec 1c sub $0x1c,%esp
8048b53:c7 44 24 04 84 a1 04 movl $0x804a184,0x4(%esp) //傳入一個立即數
8048b5a:08
8048b5b:8b 44 24 20 mov 0x20(%esp),%eax
8048b5f:89 04 24 mov %eax,(%esp)
8048b62:e8 3d 04 00 00 call 8048fa4 //輸入字符串進入函數進行比較
8048b67:85 c0 test %eax,%eax
8048b69:74 05 je 8048b70 //返回值為0,結束,否則爆炸
8048b6b:e8 46 05 00 00 call 80490b6
8048b70:83 c4 1c add $0x1c,%esp
8048b73:c3 ret
分析:
(1).可以看到在%eax!=0的時候就會調用,所以在調用 函數之后的返回值%eax必須為0。往前看,發現代碼movl $0x804a184,0x4(%esp)有立即數,是將此處地址的值拿來用,輸入gdb –q bomb進入調試狀態,用x/s 0x804a184查看內容。
(2).下面一步 mov 0x20(%esp),%eax就是把我們輸入的參數放進%eax中,然后放進(%esp) ,再調用函數。所以猜測要輸入的就是0x804a184中的字符。開始第一關的嘗試,重新打開一個終端,用來測試。測試結果如下,輸出"Phase 1 defused. How about the next one?",成功通關。
phase_2: 確定一個有規律的數列
分析:
08048b74 :
8048b74:56 push %esi
8048b75:53 push %ebx
8048b76:83 ec 34 sub $0x34,%esp //開辟棧幀
8048b79:8d 44 24 18 lea 0x18(%esp),%eax
8048b7d:89 44 24 04 mov %eax,0x4(%esp)
8048b81:8b 44 24 40 mov 0x40(%esp),%eax
8048b85:89 04 24 mov %eax,(%esp)
8048b88:e8 5e 06 00 00 call 80491eb
8048b8d:83 7c 24 18 01 cmpl $0x1,0x18(%esp) //esp+0x18-1
8048b92:74 05 je 8048b99 //esp+0x18==1
8048b94:e8 1d 05 00 00 call 80490b6 //esp+0x18!=1,爆炸
8048b99:8d 5c 24 1c lea 0x1c(%esp),%ebx //ebx=esp+0x1c
8048b9d:8d 74 24 30 lea 0x30(%esp),%esi //esi=esp+0x30
(1). 首先注意到這個函數,這里應該是讀取6個數字。接下來看到esp+0x18中存放的值和1進行比較,不相等則爆炸,由此猜測第1個數為1。連續的兩條lea語句是將esp+0x1c,esp+0x30的值存放入對應寄存器。從esp+0x18到esp+0x30剛好存放6個int 類型的數據,故esp+0x30是最后一個數據,esp+0x1c是第2個數據。
8048ba1:8b 43 fc mov -0x4(%ebx),%eax //eax=esp+0x1c-0x4=esp+0x18
8048ba4:01 c0 add %eax,%eax //eax=eax*2=2*(esp+0x18)
8048ba6:39 03 cmp %eax,(%ebx) //(ebx)-eax
8048ba8:74 05 je 8048baf //(ebx)==2
8048baa:e8 07 05 00 00 call 80490b6 //(ebx)!=2,爆炸
8048baf:83 c3 04 add $0x4,%ebx //ebx=ebx+4=esp+0x20,地址+4,下一個數字
8048bb2:39 f3 cmp %esi,%ebx //ebx-esi=esp+0x20-(esp+0x30)
8048bb4:75 eb jne 8048ba1 //不相等,繼續循環
8048bb6:83 c4 34 add $0x34,%esp //結束
8048bb9:5b pop %ebx
8048bba:5e pop %esi
8048bbb:c3 ret
(2). 接下來將ebx-0x4的中存放的值傳給eax,執行add %eax,%eax操作,即eax中存放的數乘以2,再將這個值與第2個數據進行比較,如果不相等就爆炸,說明第2個數就是在第1個數的基礎上乘以2。繼續往下看,執行add $0x4,%ebx,將ebx中的地址加0x4,即下一個數字,接著的判斷語句是和最后一個數字比較,不相等就跳轉到對應位置繼續循環。由此可知,這6個數字對應的遞推關系為a[i+1]=2*a[i],其中a[0]=1,i=0,1,…,4。
(3). 切換到之前測試用的終端,輸入1 2 4 8 16 32,終端顯示“That’s number 2. Keep going!”,第二關順利通過。結果如下圖所示:
phase_3 :switch語句
分析
08048bbc :
8048bbc:83 ec 2c sub $0x2c,%esp //開辟棧幀
8048bbf:8d 44 24 1c lea 0x1c(%esp),%eax
8048bc3:89 44 24 0c mov %eax,0xc(%esp) //參數y esp+0x1c
8048bc7:8d 44 24 18 lea 0x18(%esp),%eax
8048bcb:89 44 24 08 mov %eax,0x8(%esp) //參數x esp+0x18
8048bcf:c7 44 24 04 a3 a3 04 movl $0x804a3a3,0x4(%esp) //查看匯編后知道需要輸入兩個數(%d %d)
8048bd6:08
8048bd7:8b 44 24 30 mov 0x30(%esp),%eax
8048bdb:89 04 24 mov %eax,(%esp)
8048bde:e8 8d fc ff ff call 8048870 <__isoc99_sscanf>
(1).看到上面這段匯編代碼,注意到函數_isoc99_sscanf@plt,說明這部分存在輸入信息,往前看,發現movl $804a3a3,0x4(%esp)。進入GDB調試,設置斷點 b phase_3,嘗試使用x/s查看內容,發現是“%d %d”(如下圖),說明需要輸入兩個數據。結合前面傳入兩個參數,這兩個參數就是輸入的數據,設為x,y。
8048be3:83 f8 01 cmp $0x1,%eax //eax-1
8048be6:7f 05 jg 8048bed //eax>1
8048be8:e8 c9 04 00 00 call 80490b6 //eax<1,爆炸
8048bed:83 7c 24 18 07 cmpl $0x7,0x18(%esp) //x-7
8048bf2:77 3c ja 8048c30 //x>7或者x<0,引爆炸彈
8048bf4:8b 44 24 18 mov 0x18(%esp),%eax //eax=x
8048bf8:ff 24 85 e0 a1 04 08 jmp *0x804a1e0(,%eax,4) //switch語句,gdb查看*0x804a1e0
(2). 接下來看到cmpl $0x7,%eax,進行無符號數比較,如果大于7或者小于0就爆炸,故可以知道eax中存放的值(參數1)的取值范圍為0-7的整數。往下發現很多跳轉指令,結合jmp *0x804a1e0(,%eax,4)知道,這是一個跳轉表結構。
8048bf8:ff 24 85 e0 a1 04 08 jmp *0x804a1e0(,%eax,4) //switch語句,gdb查看*0x804a1e0
8048bff:b8 cc 01 00 00 mov $0x1cc,%eax //eax=0x1cc,x=0 對應查看*0x804a1e0
8048c04:eb 3b jmp 8048c41
8048c06:b8 b6 03 00 00 mov $0x3b6,%eax //eax=0x3b6,x=2 對應查看*0x804a1e8
8048c0b:eb 34 jmp 8048c41
8048c0d:b8 03 03 00 00 mov $0x303,%eax //eax=0x303,x=3
8048c12:eb 2d jmp 8048c41
8048c14:b8 1d 01 00 00 mov $0x11d,%eax //eax=0x11d,x=4
8048c19:eb 26 jmp 8048c41
8048c1b:b8 a0 01 00 00 mov $0x1a0,%eax //eax=0x1a0,x=5
8048c20:eb 1f jmp 8048c41
8048c22:b8 60 03 00 00 mov $0x360,%eax //eax=0x360,x=6
8048c27:eb 18 jmp 8048c41
8048c29:b8 6f 02 00 00 mov $0x26f,%eax //eax=0x26f,x=7
8048c2e:eb 11 jmp 8048c41
8048c30:e8 81 04 00 00 call 80490b6 //爆炸
8048c35:b8 00 00 00 00 mov $0x0,%eax //eax=0
8048c3a:eb 05 jmp 8048c41
8048c3c:b8 7a 00 00 00 mov $0x7a,%eax //eax=0x7a,x=1
(3).分析這個跳轉表
假設x為0,則得到地址0x804a1e0+4*0=0x804a1e0,使用x/8bx 0x804a1e0查看信息,為0x8048bff,即case 0的入口地址。
根據case 0的入口地址0x8048bff找到執行執行mov $0x1cc,%eax。
假設x為1,則得到地址0x804a1e0+4*1=0x804a1e4,使用x/8bx 0x804a1e4查看信息,為0x804c3c,即case 1的入口地址。
根據case 1的入口地址0x8048c3c找到執行執行mov $0x7a,%eax。同理可以確定其他幾組數據。
8048c41:3b 44 24 1c cmp 0x1c(%esp),%eax //eax-y
8048c45:74 05 je 8048c4c //eax==y
8048c47:e8 6a 04 00 00 call 80490b6 //eax!=y,爆炸
8048c4c:83 c4 2c add $0x2c,%esp //結束
8048c4f:c3 ret
(4).最后一部分將eax的值和輸入的參數y進行比較,不相等則爆炸,說明輸入的第2個數字y為case語句中傳給eax的值。由此可以確定這個關卡有8組不同的通關答案,分別是0 460,1 122,2 950, 3 771, 4 285, 5 416,6 864, 7 623。下面對2組數據進行驗證:
phase_4 :函數過程調用
分析
08048cb9 :
8048cb9:83 ec 2c sub $0x2c,%esp //開辟棧幀
8048cbc:8d 44 24 1c lea 0x1c(%esp),%eax
8048cc0:89 44 24 0c mov %eax,0xc(%esp) //參數y
8048cc4:8d 44 24 18 lea 0x18(%esp),%eax
8048cc8:89 44 24 08 mov %eax,0x8(%esp) //參數x
8048ccc:c7 44 24 04 a3 a3 04 movl $0x804a3a3,0x4(%esp) 查看匯編后知道需要輸入兩個數(%d %d)
8048cd3:08
(1).查看上面這一段代碼,分析得出輸入兩個數字(和關卡3一樣,用gdb調試看0x804a3a3中存放的內容)。結合前面傳入兩個參數,這兩個參數就是輸入的數據,設為x,y。
8048cd4:8b 44 24 30 mov 0x30(%esp),%eax
8048cd8:89 04 24 mov %eax,(%esp)
8048cdb:e8 90 fb ff ff call 8048870 <__isoc99_sscanf> //調用某個函數
8048ce0:83 f8 02 cmp $0x2,%eax //eax-2 判斷傳入參數的個數是否滿足條件
8048ce3:75 0d jne 8048cf2 //eax!=2 引爆
8048ce5:8b 44 24 18 mov 0x18(%esp),%eax //eax=x
8048ce9:85 c0 test %eax,%eax //判斷x
8048ceb:78 05 js 8048cf2 //x<0 引爆
8048ced:83 f8 0e cmp $0xe,%eax //x-0xe
8048cf0:7e 05 jle 8048cf7 //x<=0xe
8048cf2:e8 bf 03 00 00 call 80490b6
(2).分析這一部分代碼,test判斷x是否小于0,cmp判斷x是否大于0xe,故要是炸彈不爆炸,則參數x的取值范圍為0-0xe的整數。
8048cf7:c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp) //esp+0x8=0xe
8048cfe:00
8048cff:c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) //esp+0x4=0
8048d06:00
8048d07:8b 44 24 18 mov 0x18(%esp),%eax //eax=x
8048d0b:89 04 24 mov %eax,(%esp) //esp=x
8048d0e:e8 3d ff ff ff call 8048c50 //調用func4
(3). 在調用func4之前將參數壓入棧幀.
8048d13:83 f8 07 cmp $0x7,%eax //eax-7,eax里面存放的是func4的返回值
8048d16:75 07 jne 8048d1f //eax!=7,引爆
8048d18:83 7c 24 1c 07 cmpl $0x7,0x1c(%esp) //y-7
8048d1d:74 05 je 8048d24 //y==7,結束
8048d1f:e8 92 03 00 00 call 80490b6
8048d24:83 c4 2c add $0x2c,%esp
8048d27:c3 ret
(4). 繼續分析發現下面對func4的返回值和參數y分別對7進行比較,如果不為7,則引爆炸彈,分析到此,可以知道對于x屬于[0,0xe]使得func4(x,0,0xe)返回值為0x7的x即為所求。
08048c50 :
8048c50:83 ec 1c sub $0x1c,%esp //開辟棧幀
8048c53:89 5c 24 14 mov %ebx,0x14(%esp)
8048c57:89 74 24 18 mov %esi,0x18(%esp)
8048c5b:8b 44 24 20 mov 0x20(%esp),%eax //設esp+0x20存儲x=>eax=x
8048c5f:8b 54 24 24 mov 0x24(%esp),%edx //設esp+0x24存儲y=>edx=y
8048c63:8b 74 24 28 mov 0x28(%esp),%esi //設esp+0x28存儲z=>esi=z
8048c67:89 f1 mov %esi,%ecx //ecx=z
8048c69:29 d1 sub %edx,%ecx //ecx=z-y
8048c6b:89 cb mov %ecx,%ebx //ebx=z-y
8048c6d:c1 eb 1f shr $0x1f,%ebx //邏輯右移,ebx=(z-y)>>31,得到符號位s
8048c70:01 d9 add %ebx,%ecx //ecx=ebx+ecx=s+z-y
8048c72:d1 f9 sar %ecx //算術右移,ecx=(s+z-y)>>1
8048c74:8d 1c 11 lea (%ecx,%edx,1),%ebx //ebx=edx*1+ecx=y+ecx=y+(s+z-y)>>1
8048c77:39 c3 cmp %eax,%ebx //ebx-x,比較計算結果y+(s+z-y)>>1和x
8048c79:7e 17 jle 8048c92 //ebx<=eax
8048c7b:8d 4b ff lea -0x1(%ebx),%ecx //ecx=ebx-1
8048c7e:89 4c 24 08 mov %ecx,0x8(%esp) //esp+0x8=ecx=ebx-1
8048c82:89 54 24 04 mov %edx,0x4(%esp) //esp+0x4=edx=y
8048c86:89 04 24 mov %eax,(%esp) //esp=x
8048c89:e8 c2 ff ff ff call 8048c50 //遞歸調用函數func4
8048c8e:01 c3 add %eax,%ebx //ebx=ebx+eax
8048c90:eb 19 jmp 8048cab
8048c92:39 c3 cmp %eax,%ebx //ebx-eax
8048c94:7d 15 jge 8048cab //ebx>=eax
8048c96:89 74 24 08 mov %esi,0x8(%esp) //esp+0x8=esi=z
8048c9a:8d 53 01 lea 0x1(%ebx),%edx //edx=ebx+1
8048c9d:89 54 24 04 mov %edx,0x4(%esp) //esp+0x4=ebx+1
8048ca1:89 04 24 mov %eax,(%esp) //esp=eax=x
8048ca4:e8 a7 ff ff ff call 8048c50 //遞歸調用函數func4
8048ca9:01 c3 add %eax,%ebx //ebx=eax+ebx
8048cab:89 d8 mov %ebx,%eax //eax=ebx
8048cad:8b 5c 24 14 mov 0x14(%esp),%ebx
8048cb1:8b 74 24 18 mov 0x18(%esp),%esi
8048cb5:83 c4 1c add $0x1c,%esp
8048cb8:c3 ret
(5).現在重點就是分析func4函數,看到匯編代碼可以發現這是一個遞歸函數,關鍵在于找到遞歸的條件和退出函數的條件,以及遞歸之前函數的參數壓棧準備。根據這段匯編代碼寫出C語言代碼如下:
int func4(int x,int y,int z)
{
int t=(((z-y)+((z-y)>>31))>>1)+y;
if(t<=x)
{
if(t==x)
{
return t;
}
else return func4(x,t+1,z)+x;
}
else
{
return func4(x,y,t-1)+x;
}
}
(6).編寫測試函數如下
int main()
{
int i=0;
for(i=0;i<=14;i++)//每一種取值都進行測試
{
printf("func4(%d,0,14)=%d ",i,func4(i,0,14));
if((i+1)%5==0) printf("\n");
}
return 0;
}
運行代碼結果如下,可以發現返回值為0x7對應的x只有0x7一種值,故第4關通關輸入為7 7。
切換到之前測試用的終端,輸入7 7,終端顯示“So you got that one. Try this one.”,第四關順利通過。
phase_5: 序列的構造和數組尋址
分析
08048d28 :
8048d28:53 push %ebx
8048d29:83 ec 18 sub $0x18,%esp //開辟棧幀
8048d2c:8b 5c 24 20 mov 0x20(%esp),%ebx //參數1 x存儲在ebx
8048d30:89 1c 24 mov %ebx,(%esp)
8048d33:e8 53 02 00 00 call 8048f8b
8048d38:83 f8 06 cmp $0x6,%eax //eax-6(猜測eax是字符串的長度)
8048d3b:74 05 je 8048d42 //eax==6
8048d3d:e8 74 03 00 00 call 80490b6 //eax不是6,引爆
(1).注意到函數,結合后面eax和0x6比較,可知這一關輸入的參數是一個長度為6的字符串,需要我們去匹配出這個字符串。
8048d42:ba 00 00 00 00 mov $0x0,%edx //edx=0
8048d47:b8 00 00 00 00 mov $0x0,%eax //eax=0
//movsbl指令負責拷貝一個字節,并用源操作數的最高位填充其目的操作數中的其余各位,這種擴展方式叫“符號擴展”
8048d4c:0f be 0c 03 movsbl (%ebx,%eax,1),%ecx //ecx=(eax+ebx) 長度為6的字符串的第eax個字符
8048d50:83 e1 0f and $0xf,%ecx //f=00001111 取ecx的低4位
8048d53:03 14 8d 00 a2 04 08 add 0x804a200(,%ecx,4),%edx //edx=edx+4*ecx+0x804a200
8048d5a:83 c0 01 add $0x1,%eax //eax+1
8048d5d:83 f8 06 cmp $0x6,%eax //eax-6
8048d60:75 ea jne 8048d4c //eax!=6
8048d62:83 fa 2b cmp $0x2b,%edx //edx-0x2b
8048d65:74 05 je 8048d6c //相等
8048d67:e8 4a 03 00 00 call 80490b6 //爆炸
8048d6c:83 c4 18 add $0x18,%esp //結束
8048d6f:5b pop %ebx
8048d70:c3 ret
(2).接著edx,eax寄存器的值初始化為0,movsbl (%ebx,%eax,1),%ecx,這一句是表示將長度為6的字符串的第eax個字符存入ecx中。取ecx中的低4位,利用基址比例變址尋址的方式,到數組中找值,并累和在寄存器edx中。往下看,可以看到cmp $0x2b,%edx,如果不相等則爆炸,說明累和得到的值必須為0x2b。
(3).分析到此,關鍵在于找到數組中的值,由匯編我們知道數組首地址為0x804a200,進入GDB調試,設置斷點b phase_5,運行后查看其內容,從里邊找出一組和為0x2b的數字,這里取出一組 {2,a,6,1,c,c}。
對應下標為0,1,2,3,4,4,去ascii表中找低四位為0,1,2,3,4,4的字符,故得到其中一組解為pabcdd。
(4). 切換到之前測試用的終端,輸入pabcdd,終端顯示“Good work! On to the next…!”,第五關順利通過。
phase_6: 鏈表
分析
08048d71 :
8048d71:56 push %esi
8048d72:53 push %ebx
8048d73:83 ec 44 sub $0x44,%esp //開辟棧幀
8048d76:8d 44 24 10 lea 0x10(%esp),%eax
8048d7a:89 44 24 04 mov %eax,0x4(%esp) //參數2 y
8048d7e:8b 44 24 50 mov 0x50(%esp),%eax
8048d82:89 04 24 mov %eax,(%esp) //參數1 x
8048d85:e8 61 04 00 00 call 80491eb
(1).首先看到函數,根據之前的經驗,猜測這里也是讀取6個數字。
8048d8a:be 00 00 00 00 mov $0x0,%esi //esi=0
8048d8f:8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax //eax=(4*esi+y)
8048d93:83 e8 01 sub $0x1,%eax //eax--
8048d96:83 f8 05 cmp $0x5,%eax //eax-5
8048d99:76 05 jbe 8048da0 //無符號數<=
8048d9b:e8 16 03 00 00 call 80490b6 //引爆
8048da0:83 c6 01 add $0x1,%esi //esi+=1,,esi=1
8048da3:83 fe 06 cmp $0x6,%esi //esi-6
8048da6:74 33 je 8048ddb //等于跳轉
//以上代碼是確定6個數字的范圍。
8048da8:89 f3 mov %esi,%ebx //ebx=esi
8048daa:8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax //eax=(4*ebx+y)
8048dae:39 44 b4 0c cmp %eax,0xc(%esp,%esi,4) //(4*esi+esp+0xc)-eax
8048db2:75 05 jne 8048db9 //不相等
8048db4:e8 fd 02 00 00 call 80490b6 //相等,引爆
8048db9:83 c3 01 add $0x1,%ebx //ebx++
8048dbc:83 fb 05 cmp $0x5,%ebx //ebx-5
8048dbf:7e e9 jle 8048daa //小于等于跳轉
8048dc1:eb cc jmp 8048d8f
8048dc3:8b 52 08 mov 0x8(%edx),%edx //edx=(edx+0x8)=(0x804c13c+0x8)
8048dc6:83 c0 01 add $0x1,%eax //eax++
8048dc9:39 c8 cmp %ecx,%eax //eax-ecx
8048dcb:75 f6 jne 8048dc3 //不相等,繼續執行
8048dcd:89 54 b4 28 mov %edx,0x28(%esp,%esi,4) //4*esi+esp+0x28=edx
8048dd1:83 c3 01 add $0x1,%ebx //ebx++
8048dd4:83 fb 06 cmp $0x6,%ebx //ebx-6
8048dd7:75 07 jne 8048de0 //不相等
8048dd9:eb 1c jmp 8048df7
//以上代碼是判斷數字之間是否互不相等。
(2).這里的匯編代碼包括兩個循環,用來給出數字的取值范圍是屬于1-6,并且數字之間彼此兩兩不相等。
//鏈表初始化
8048df7:8b 5c 24 28 mov 0x28(%esp),%ebx /ebx=x1
8048dfb:8b 44 24 2c mov 0x2c(%esp),%eax //eax=x2
8048dff:89 43 08 mov %eax,0x8(%ebx) //x1->x2
8048e02:8b 54 24 30 mov 0x30(%esp),%edx //edx=x3
8048e06:89 50 08 mov %edx,0x8(%eax) //x1->x2->x3
8048e09:8b 44 24 34 mov 0x34(%esp),%eax //eax=x4
8048e0d:89 42 08 mov %eax,0x8(%edx) //x1->x2->x3->x4
8048e10:8b 54 24 38 mov 0x38(%esp),%edx //edx=x5
8048e14:89 50 08 mov %edx,0x8(%eax) //x1->x2->x3->x4->x5
8048e17:8b 44 24 3c mov 0x3c(%esp),%eax //eax=x6
8048e1b:89 42 08 mov %eax,0x8(%edx) //x1->x2->x3->x4->x5->x6
8048e1e:c7 40 08 00 00 00 00 movl $0x0,0x8(%eax) //鏈表尾
(3). 接下來對鏈表的結點進行了連接
(4).進入GDB調試,設置斷點b phase_6,往下分析代碼過程中看到一個立即數,查看其值,出現一個node1,猜測這是一個鏈表的數據結構。于是多查看幾個值,發現了其中的規律,每三個數為一組,第1個是數據域中的值,第2個是下標,第3個比較大,類似于地址,是指針域,然后對猜想進行驗證。調試后驗證猜測正確。根據之前對鏈表進行連接,順序為322->370->1c1->24b->0a1->192。
//遞增序列
8048e25:be 05 00 00 00 mov $0x5,%esi //esi=5
8048e2a:8b 43 08 mov 0x8(%ebx),%eax //eax=x2的地址
8048e2d:8b 10 mov (%eax),%edx //edx=x2
8048e2f:39 13 cmp %edx,(%ebx) //x1-x2
8048e31:7e 05 jle 8048e38 //x1<=x2
8048e33:e8 7e 02 00 00 call 80490b6
8048e38:8b 5b 08 mov 0x8(%ebx),%ebx //ebx=ebx+0x8,下一個數
8048e3b:83 ee 01 sub $0x1,%esi //esi--
8048e3e:75 ea jne 8048e2a
8048e40:83 c4 44 add $0x44,%esp
8048e43:5b pop %ebx
8048e44:5e pop %esi
8048e45:c3 ret
(5).ebx+0x8存儲的是第2個數字的地址,取出第2個數字x2,和edx存儲的第1個數字x1進行比較,需要滿足x1<=x2,接下來ebx地址加0x8,對下一個進行約束。由此可以看出這個序列是一個遞增序列。
(6).結合之前輸入的6個數字在1-6之間,且互不相同,這里應該是排序后遞增鏈表序列的下標。故得到下標為5 6 3 4 1 2。切換到之前測試用的終端,輸入5 6 3 4 1 2,終端顯示“Congratulations! You’ve defused the bomb!”,第六關順利通過。
secret_phase:二叉樹
做完前6關,看到提示信息的那一刻,我以為就這樣結束,但是聽說還有秘密關卡,仔細一看匯編代碼,果然有serect_phase的匯編代碼片段。起初沒有思路,不知道該從何入手,在網上參考了一些資料,結合bomb.c的代碼,發現每個階段函數結束后都調用了phase_defused()函數,猜測秘密關卡可能隱藏在某一關中。
分析如何找到秘密關卡
查看匯編代碼如下
0804923b :
804923b:81 ec 8c 00 00 00 sub $0x8c,%esp
8049241:65 a1 14 00 00 00 mov %gs:0x14,%eax
8049247:89 44 24 7c mov %eax,0x7c(%esp)
804924b:31 c0 xor %eax,%eax
804924d:83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
8049254:75 72 jne 80492c8
8049256:8d 44 24 2c lea 0x2c(%esp),%eax
804925a:89 44 24 10 mov %eax,0x10(%esp)
804925e:8d 44 24 28 lea 0x28(%esp),%eax
8049262:89 44 24 0c mov %eax,0xc(%esp)
8049266:8d 44 24 24 lea 0x24(%esp),%eax
804926a:89 44 24 08 mov %eax,0x8(%esp)
804926e:c7 44 24 04 a9 a3 04 movl $0x804a3a9,0x4(%esp)
8049275:08
8049276:c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
804927d:e8 ee f5 ff ff call 8048870 <__isoc99_sscanf>
8049282:83 f8 03 cmp $0x3,%eax
8049285:75 35 jne 80492bc
8049287:c7 44 24 04 b2 a3 04 movl $0x804a3b2,0x4(%esp)
804928e:08
804928f:8d 44 24 2c lea 0x2c(%esp),%eax
8049293:89 04 24 mov %eax,(%esp)
8049296:e8 09 fd ff ff call 8048fa4
804929b:85 c0 test %eax,%eax
804929d:75 1d jne 80492bc
804929f:c7 04 24 78 a2 04 08 movl $0x804a278,(%esp)
80492a6:e8 55 f5 ff ff call 8048800
80492ab:c7 04 24 a0 a2 04 08 movl $0x804a2a0,(%esp)
80492b2:e8 49 f5 ff ff call 8048800
80492b7:e8 db fb ff ff call 8048e97
80492bc:c7 04 24 d8 a2 04 08 movl $0x804a2d8,(%esp)
80492c3:e8 38 f5 ff ff call 8048800
80492c8:8b 44 24 7c mov 0x7c(%esp),%eax
80492cc:65 33 05 14 00 00 00 xor %gs:0x14,%eax
80492d3:74 05 je 80492da
80492d5:e8 f6 f4 ff ff call 80487d0 <__stack_chk_fail>
80492da:81 c4 8c 00 00 00 add $0x8c,%esp
80492e0:c3 ret
80492e1:90 nop
80492e2:90 nop
80492e3:90 nop
80492e4:90 nop
80492e5:90 nop
80492e6:90 nop
80492e7:90 nop
80492e8:90 nop
80492e9:90 nop
80492ea:90 nop
80492eb:90 nop
80492ec:90 nop
80492ed:90 nop
80492ee:90 nop
80492ef:90 nop
(1).結合前面關卡,發現這里有2個立即數,使用gdb調試查看其內容
發現提示輸入兩個數字和一個字符串,以及第4關的輸入。于是猜測秘密關卡藏在第4關,而這個字符串就是打開秘密關卡的“鑰匙”。
(2). 可以看到這里將0x804a3b2中存放的內容傳入函數strings_not_equal中進行比較,如果返回值不為0,就跳轉到0x80492bc,輸出0x804a2d8中的內容,否則就一次往下輸出0x804a278,0x804a2a0的信息,最后輸出0x804a2d8的信息。猜測:0x804a3b2就是進入秘密關卡的“鑰匙”,當“鑰匙”輸入錯誤就直接結束。
添加斷點,用gdb調試查看上述提到的4個地址中的內容,驗證了猜測的正確性。
分析如何通關
08048e97 :
8048e97:53 push %ebx
8048e98:83 ec 18 sub $0x18,%esp //開辟棧幀
8048e9b:e8 3d 02 00 00 call 80490dd
8048ea0:c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp) //參數3 z=a
8048ea7:00
8048ea8:c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) //參數2 y=0
8048eaf:00
8048eb0:89 04 24 mov %eax,(%esp) //參數1 x
8048eb3:e8 28 fa ff ff call 80488e0
8048eb8:89 c3 mov %eax,%ebx //ebx=x
8048eba:8d 40 ff lea -0x1(%eax),%eax //eax=x-1
8048ebd:3d e8 03 00 00 cmp $0x3e8,%eax //eax-0x3e8
8048ec2:76 05 jbe 8048ec9 無符號數比較,小于等于
8048ec4:e8 ed 01 00 00 call 80490b6
(1).這段代碼首先將三個參數放入棧幀中,接下來調用了strtol函數。mov %eax,%ebx;lea -0x1(%eax),%eax;cmp $0x3e8,%eax;jbe 8048ec9 ;這4句對參數x的范圍進行了限定,x的取值是1-0x3e9。
8048ec9:89 5c 24 04 mov %ebx,0x4(%esp) //fun7參數2 n=x
8048ecd:c7 04 24 88 c0 04 08 movl $0x804c088,(%esp) //fun7參數1 m=0x804c088
8048ed4:e8 6d ff ff ff call 8048e46 //調用fun7
8048ed9:85 c0 test %eax,%eax //eax and eax
8048edb:74 05 je 8048ee2 //等于0,不是0就爆炸
8048edd:e8 d4 01 00 00 call 80490b6
8048ee2:c7 04 24 b8 a1 04 08 movl $0x804a1b8,(%esp)
8048ee9:e8 12 f9 ff ff call 8048800
8048eee:e8 48 03 00 00 call 804923b
8048ef3:83 c4 18 add $0x18,%esp
8048ef6:5b pop %ebx
8048ef7:c3 ret
8048ef8:90 nop
8048ef9:90 nop
8048efa:90 nop
8048efb:90 nop
8048efc:90 nop
8048efd:90 nop
8048efe:90 nop
8048eff:90 nop
(2).這段代碼中主要調用了函數fun7,調用之前可以分析出參數,即fun7(地址,n),第1個參數為某個數組或者鏈表的首地址,第2個參數為1-1001之間的整數。通關的條件是fun7()的返回值為0。分析fun7中代碼,并結合返回值為0,故可以知道保證傳入fun7中兩個參數相等即可。
08048e46 :
8048e46:53 push %ebx
8048e47:83 ec 18 sub $0x18,%esp //開辟棧幀
8048e4a:8b 54 24 20 mov 0x20(%esp),%edx //參數1,edx=&x
8048e4e:8b 4c 24 24 mov 0x24(%esp),%ecx //參數2,ecx=y
8048e52:85 d2 test %edx,%edx //判斷edx是否為0
8048e54:74 37 je 8048e8d //等于0
8048e56:8b 1a mov (%edx),%ebx //取出地址中的值,給ebx,即ebx=x
8048e58:39 cb cmp %ecx,%ebx //x-y
8048e5a:7e 13 jle 8048e6f //x<=y
8048e5c:89 4c 24 04 mov %ecx,0x4(%esp) //esp+0x4=y
8048e60:8b 42 04 mov 0x4(%edx),%eax
8048e63:89 04 24 mov %eax,(%esp) //esp=edx+0x4
8048e66:e8 db ff ff ff call 8048e46 //調用函數fun7
8048e6b:01 c0 add %eax,%eax //eax=eax*2
8048e6d:eb 23 jmp 8048e92
8048e6f:b8 00 00 00 00 mov $0x0,%eax //eax=0
8048e74:39 cb cmp %ecx,%ebx //x-y
8048e76:74 1a je 8048e92 //如果x==y
8048e78:89 4c 24 04 mov %ecx,0x4(%esp) //esp+0x4=y
8048e7c:8b 42 08 mov 0x8(%edx),%eax
8048e7f:89 04 24 mov %eax,(%esp) //esp=edx+0x8
8048e82:e8 bf ff ff ff call 8048e46 //調用函數fun7
8048e87:8d 44 00 01 lea 0x1(%eax,%eax,1),%eax eax=2*eax+1
8048e8b:eb 05 jmp 8048e92
8048e8d:b8 ff ff ff ff mov $0xffffffff,%eax
8048e92:83 c4 18 add $0x18,%esp
8048e95:5b pop %ebx
8048e96:c3 ret
(3).根據fun7的匯編代碼寫出對應的C語言代碼
int fun7(int *x,int y)
{
if(x==0)
return 0xffffffff;
if(*x<=y)
{
if(*x==y) return 0;
x+=8;
return 2*fun7(x,y)+1;
}
else
{
x+=4;
return 2*fun7(x,y);
}
}
(4).根據前面的分析,要使炸彈不爆炸需要fun7返回值為0,從函數中可以看出,返回值為0,只需*x=y即可,于是查看傳入fun7中x中的內容即可。
8048ecd:c7 04 24 88 c0 04 08 movl $0x804c088,(%esp) //fun7參數1 m=0x804c088
8048ed4:e8 6d ff ff ff call 8048e46 //調用fun7
(5).切換到之前測試用的終端,運行./bomb,從第一關開始輸入,在第四關時,加上字符串“DrEvil”,最終輸入秘密關卡的36,終端顯示“Wow! You’ve defused the secret stage!Congratulations! You’ve defused the bomb!”。
(6).秘密關卡到這里就結束了,而對于這個關卡涉及到的結構可以進行進一步探索。查看0x804c088中內容發現有3個值,第1個是數值,第2,3個值比較大,是兩個地址,聯系所學,這兩個地址就是左,右子結點的地址。將整個結構用圖表示如下,可以看出這是一個典型的二叉樹結構。
實驗結果及分析
所有關卡通關密碼均已找出,執行結果如上。
對每一關進行簡單分析:第1關是確定輸入字符串;第2關是確定一個有規律的數列;第3關是switch語句的考察;第4關是函數過程調用的檢測;第5關是序列的構造和數組尋址方式;第6關是考察鏈表的相關知識;秘密關卡的尋找考察了考查Linux?Canary繞過技術及ROP(Return-Oriented-Programming)攻擊負載的構造(如何找到秘密和關卡的入口),秘密關卡則考察了二叉樹。
收獲與體會
1.本次實驗進一步熟悉了GDB調試工具,調試能力得到了一定的提高。
2.熟悉了linux中一些之前沒有見過的指令,如movsbl指令movsbl指令負責拷貝一個字節,并用源操作數的最高位填充其目的操作數中的其余各位,這種擴展方式叫符號擴展,對linux指令更加熟悉。
3.對過程調用和參數準備工作得到了進一步的理解。
4.學到了一些更為復雜的數據表示,比如鏈表,二叉樹的在計算機中的具體存儲,以及在底層的使用。
5.實驗中每個人的實驗的可執行文件都是不同,實驗過程中,也幫其他同學看過一些代碼,對于不同的反匯編代碼更加熟悉,也間接復習了之前計算機系統關于匯編語言的相關知識。
6.結合之前緩沖區溢出攻擊的學習,在實驗做完后,嘗試了用hexedit去直接修改可執行文件的某些執行,使得無論輸入什么,都不會跳轉到爆炸函數處,進一步理解了緩沖區溢出攻擊的原理。
標簽:24,計算機系統,00,esp,bomblab,mov,eax,實驗,ebx
來源: https://blog.csdn.net/weixin_44595362/article/details/106723509