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,查看機器代碼:
    shell

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

  • 查看咱們的shellcode:
    ubuntu

  • 接着,咱們看一個被攻擊的簡單代碼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:
    3d

  • 咱們先運行禁用了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:

相關文章
相關標籤/搜索