Binary Exploitation: HTB Bat Computer Walkthrough
In the next installment of the binary exploitation series we will go over the Bat Computer Pwn challenge from Hack the Box. I found this challenge to be really fun as it shows the consequences of not practicing simple exploit mitigation techniques.
We start off by running checksec
on the binary. Checksec is a tool used to check the properties of a Linux executable such as Relocation Read-Only (RELRO), Non-executable stack (NX), and stack canaries.
We can see that the binary only has Partial RELRO and Position Independent Executable (PIE) enabled. No stack canaries were found and Non-Executable (NX) is disabled. We will not go into detail about these mitigations as they deserve their own post in the future. You can find their basic definitions below:
- Position Independent Executable (PIE) — a binary and all of its dependencies are loaded into random locations within virtual memory each time the application is executed
- Partial RELRO — some sections of the binary are read-only, preventing them from being modified
- Stack Canary — a value written on the stack which is later checked to ensure it has not been overwritten; used to detect buffer overflows
- Non-Executable Stack (NX) — a memory protection mechanism used to prevent shell code located within the stack from being executed
Running the file
command on the binary shows that it is stripped, meaning there are no debug symbols present. A stripped binary makes it more difficult to reverse engineer and the main
function will be harder to find.
Let us now run the binary to see what it does.
We are given two options, the first option gives us a message along with a memory address. The second options prompts us a for a password and exits if its incorrect. For a better picture lets decompile the binary using Ghidra.
Code Analysis
Because the binary is stripped we cant simply go to the main
function. A simple work around is to look at the strings to see if we have encountered them before. In Ghidra, the strings window can be displayed by going to Window > Defined Strings. The screenshot above shows the string “Welcome to your Bat Computer.” selected. Following this string we land on what looks like our main
function, FUN_001011ec (this may be different on your computer)
In order to make analysis easier, the variables have been renamed to better reflect their purpose. One of these variables is user_choice
whose value determines which message/prompt to show the user. On line 18 user_choice is compared with integer 1, if equal, the memory address of the nav_command
buffer is printed using printf. If instead user_choice is 2, a password is requested and stored into user_pass
using scanf. Line 24 clearly shows the password being compared to the string b4tp@$$w0rd!
. If the passwords do not match the program terminates with exit(). If the passwords do match, however, the user is asked to provide their commands which are then stored within the nav_commands buffer, the same buffer whose address was printed in option 1. The string “Roger that!” is printed and the function loops back to the beginning. If user_choice are neither 1 nor 2, the binary prints a “Too bad” message and returns 0, finishing execution.
There is buffer overflow vulnerability within the read call on line 32. 0x89 (137) bytes are read from standard input into a buffer that is only allocated 76 bytes. This gives us enough space to overwrite the return address. We have to figure out the number of bytes between the start of our navigation commands input and the return address. We can do this using GDB.
Debugging
Because the binary is stripped we cant set a breakpoint on the main function with the command b *main
. We first have to run the binary then abort execution with Ctrl+C
. At the bottom of the gdb output above we can see the following line.
[#6] 0x7ffff7e02d0a → __libc_start_main(main=0x5555555551ec, argc=0x1, argv=0x7fffffffe088, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe078)
Execution was aborted in the function located at 0x5555555551ec
; this is our main function. Lets set the breakpoint at this address and run the binary again.
After setting a breakpoint on 0x5555555551ec
and executing the binary again we reach the beginning of main. We then show the next 70 instructions with the command x/70i $pc
. This helps us find the memory location after the call to read
that stores our input into nav_commands
. According to the gdb output above, we want to set the breakpoint at this instruction:
0x5555555552fc: lea rdi,[rip+0xe5e] # 0x555555556161
Once we reach this breakpoint the number of bytes between the our input and the return address can be calculated.
After setting a breakpoint at 0x5555555552fc
we restart execution. When the menu is displayed option 2 is chosen and the correct password of b4tp@$$w0rd!
is provided. We are then given a prompt to enter our commands; a random command of foobar
is entered. After supplying the password we reach our breakpoint. The navigation command foobar
is searched in memory with the command search-pattern foobar
; this gives the beginning of our input, 0x7fffffffdf44
. We then get the return address with the command info frames
or i f
for short. The return address is stored in register rip
whose value is 0x7fffffffdf44
. The number of bytes between the input and return address is 0x7fffffffdf98b – 0x7fffffffdf44 = 0x54
or 84
bytes.
Exploitation
Next order of business is to decide what to overflow the return address with. Remember how the output of checksec
said Non-Executable Stack (NX) was disabled? Well, this is our way forward. Because the stack is executable, this allows us to execute whatever code we provide in the buffer. But wait, how do we redirect execution to the start of our buffer?
At the beginning of this post, the binary gave us a memory address after choosing option 1. Going back to the Code Analysis section, it was deduced that this address is the beginning of our input ! This is what the return address will be overflowed with.
Next, we need our shellcode. We can use shellcode provided by shell-storm. We will choose one that executes the command /bin/sh
, giving us a shell:
Lets look at the decompiled code one more time…
After supplying the correct password and giving our input, the binary exits with a call to exit
at line 28. This means the return address will not be reached and therefore the shellcode will not execute. We need to take advantage of the loop and provide an incorrect option when the menu is shown again. This ensures that we reach the return
at line 35.
To write the exploit, I will use Python with Pwntools, an exploit development framework.
Of course, [IP ADDRESS]
and [PORT]
will be replaced with whatever address and port HackTheBox gives us when we start an instance. Lets run our exploit:
We successfully exploited the buffer overflow and got code execution!
Thank you for reading and I hope you learned something new, I know I did! Stayed tuned for more.