64位Ubuntu系統下ROP攻擊


64位Ubuntu系統下ROP攻擊

基礎知識

ROP攻擊

  • ROP全稱為Retrun-oriented Programmming(面向返回的編程)是一種新型的基於代碼復用技術的攻擊,攻擊者從已有的庫或可執行文件中提取指令片段,構建惡意代碼。
  • ROP攻擊同緩沖區溢出攻擊,格式化字符串漏洞攻擊不同,是一種全新的攻擊方式,它利用代碼復用技術。
  • ROP的核心思想:攻擊者掃描已有的動態鏈接庫和可執行文件,提取出可以利用的指令片段(gadget),這些指令片段均以ret指令結尾,即用ret指令實現指令片段執行流的銜接。操作系統通過棧來進行函數的調用和返回。函數的調用和返回就是通過壓棧和出棧來實現的。每個程序都會維護一個程序運行棧,棧為所有函數共享,每次函數調用,系統會分配一個棧楨給當前被調用函數,用於參數的傳遞、局部變量的維護、返回地址的填入等。棧幀是程序運行棧的一部分 ,在Linux中 ,通過%esp和 %ebp寄存器維護棧頂指針和棧幀的起始地址 ,%eip是程序計數器寄存器。而ROP攻擊則是利用以ret結尾的程序片段 ,操作這些棧相關寄存器,控制程的流程,執行相應的gadget,實施攻擊者預設目標 。ROP不同於retum-to-libc攻擊之處在於,R0P攻擊以ret指令結尾的函數代碼片段 ,而不是整個函數本身去完成預定的操作。從廣義角度講 ,return-to-libc攻擊是ROP攻的特例。最初ROP攻擊實現在x86體系結構下,隨后擴展到各種體系結構.。與以往攻擊技術不同的是,ROP惡意代碼不包含任何指令,將自己的惡意代碼隱藏在正常代碼中。因而,它可以繞過W⊕X的防御技術。

實驗環境

  • 虛擬機系統:Ubuntu 12.04(64位)

前期准備

  • 根據X86_64 ABI的調用約定,函數間傳遞參數不再以壓棧的方式,而是以寄存器方式傳遞參數,前面6個參數依次以rdi, rsi, rdx, rcx, r8和r9寄存來傳遞。在Linux系統,64位架構只使用48位的虛擬地址空間,也即每個地址高16位全部為0,因此在64位系統上,地址已天然零化,ret2libc攻擊似乎沒有了用武之地,在ret2libc的基礎上我們采用ROP攻擊方法。
  • 我們現在先假設棧沒有可執行屬性,那么需要自己編寫一個shell來執行,考慮通過execve系統調用來實現。我們直接將匯編代碼放在shell.c文件中:
int main() {
  asm("\
needle0: jmp there\n\
here:    pop %rdi\n\
         xor %rax, %rax\n\
         movb $0x3b, %al\n\
         xor %rsi, %rsi\n\
         xor %rdx, %rdx\n\
         syscall\n\
there:   call here\n\
.string \"/bin/sh\"\n\
needle1: .octa 0xdeadbeef\n\
  ");
}
  • 無論我們的代碼在內存中的哪個地方結束,call-pop指令都將使用“/bin/sh\”字符串的地址加載rdi寄存器,接下來編譯運行shell.c文件,成功獲得了一個shell:

實踐過程

  • 我們先提取要注入的payload,查看機器代碼:

  • 在64位系統上,代碼段通常位於0x400000,在該二進制文件中,我們的代碼位於0x4b8的偏移量處,並在偏移量0x4d5之前完成,共有29個字節:

  • 查看我們的shellcode:

  • 接着,我們看一個被攻擊的簡單代碼victim.c:

#include <stdio.h>
int main() {
  char name[64];
  puts("What's your name?");
  gets(name);
  printf("Hello, %s!\n", name);
  return 0;
}
  • 在Ubuntu系統上,有三種對策來保護堆棧,第一種是SSP,又名ProPolice,編譯器重新排列堆棧布局,使緩沖區溢出不太危險,並插入運行時堆棧完整性檢查;第二種是可執行空間保護(NX),當嘗試在堆棧中執行代碼時會導致分段錯誤;第三種地址空間布局隨機化(ASLR),堆棧的位置每次運行都是隨機的,所以即使我們可以覆蓋返回地址,也不知道該放在哪里。

三種攻擊嘗試

第一種嘗試

  • 我們可以想辦法繞過這些方法,使用gcc的-fno-stack-protector選項禁用堆棧保護,編譯victim,然后使用execstack -s指令禁用可執行文件空間保護,發現提示沒有安裝:

  • 安裝完成之后,禁用可執行文件空間保護,然后在運行文件時禁用ASLR:

  • 我們再在原victim.c代碼的基礎上加上一句printf("%p\n", name);,打印緩沖區的位置,然后再編譯運行:

  • 后面的過程中應該也會出現該緩沖區的地址,我們讓它以小端法顯示:

  • 這個時候我們攻擊我們的victim.c程序,可以看到攻擊成功:

第二種嘗試

  • 接着,我們再通過一個例子來看看打補丁的重要性,在以前,我們可以通過查看/proc/pid/stat來讀取任何進程的ESP注冊表,但是這種漏洞很早就被修復了,我們假裝目前在沒有打補丁的系統上,先查看所有進程的esp:

  • 我們先運行禁用了ASLR的victim程序:

  • 再在另一個終端窗口查看victim程序的esp:

  • 因此,當程序正在等待用戶輸入時,它的堆棧指針0x7fffffece8,我們計算從這個指針到名稱緩沖區的距離:

  • 現在重新運行啟用了ASLR的victim程序:

  • 我們查找victim進程的esp指針,然后添加上偏移量,就是上一步中運行victim程序的緩沖區地址:

  • 接着用管道命令來進行演示:

  • 在另一個終端中輸入如下命令:

  • 可以看到攻擊成功,獲取到了shell:

第三種嘗試

  • 先通過運行execstack -c指令重新啟動可執行空間保護,此外,在ROP攻擊中代碼片段從可執行內存中挑選出來,例如,它們可能是libc的片段,所以我們還要通過locate指令找到libc的位置,如圖所示,我們選擇第一個:

  • 我們希望執行pop %rdi retq,而指向“/bin/sh”的指針位於堆棧的頂部。這將在推進堆棧指針之前將指針分配給rdi,相應的機器代碼是兩個字節的序列0x5f 0xc3,它應該在libc的某處發生。但是沒有Linux工具能夠直接在文件中搜索給定的字節序列,所以我們可以用下面的grep指令來實現檢索:

  • 在ROP中,以RET結尾的一系列指令稱為gadget,如果我們用以下順序覆蓋返回地址:libc的地址+ 0x22a12;“/bin/sh”的地址;libc的system()函數的地址,然后在執行下一個ret指令時,程序將彈出“/bin/sh”的地址到rdi,然后跳轉到系統函數,我們就可以達到我們的目的。先輸入如下指令運行禁用了ASLR的victim程序:

  • 再在另一個終端中輸入如下指令:

  • 我們可以看到,libc被加載到從0x7ffff7a1a000開始的內存中,因此gadget地址是0x7ffff7a1a000 + 0x22a12,“/bin/sh”的地址我們之前已經找到了,是0x7fffffffed40,最后我們通過下面的指令來找libc的system()函數的地址:

  • 我們可以得到libc的system()函數的地址是0x7ffff7a1a000 + 0x45730,最后將它們放到一起,成功得到了shell:


免責聲明!

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



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