tl;dr
- Passing corrupted ciphertext to get the symmetric key leak
- Fastbin link corruption
- Exploiting double free and UAF in the heap
Challenge points: 964
No. of solves: 10
Challenge author: rudyerudite
Challenge description
The description for this challenge says,
Sail through Hade’s abode with your double-edged sword!
Further, the challenge was developed and tested on an environment with libc-2.23.so
and OpenSSL1.0.2g
for encryption functionality. In the following writeup, we will reference the functions used for encryption as Enc(plaintext)
and decryption function as Dec(ciphertext)
.
Understanding functionality
Before getting our hands dirty, executing checksec
tells us about the mitigations enabled in the binary:
1 | Canary : Yes |
The challenge performs the function of encrypting the user’s input and storing the ciphertext in user-allocated heap chunks (option 1) and deletes them on the user’s request (option 3). A user can allocate a maximum of 9 chunks.
1 | IV:aed9e7cf03014684820330cd5099a511 |
The player can store a plaintext on the .bss section and modify it later by sending the ciphertext (option 2). The key and the IV used for AES-CBC encryption
are stored in the .bss section. The IV is generated randomly and a static key used (read from a file) in every session.
Exploitation
Deletion of a chunk causes a double-free
and also there exists a UAF
which can help us to get the libc leaks
easily by freeing, allocating, and reading the same Unsorted Bin chunk.
To overwrite the pointer the attacker must get the symmetric key
used in the block cipher otherwise the encryption functionality would corrupt the user’s payload when storing it in memory. For getting a way out of this we need symmetric key
leaks.
Now we will use option 3 here. As the binary prints the name we can leak the key too! For that we need to bypass the remove_padding
function. Why? Check out the snippet:
1 | if(end<=16) |
To avoid the replacement with NULL bytes, the attacker must craft a ciphertext payload such that the last byte of the decrypted ciphertext is a negative number. In doing so, the naive padding function does not operate correctly (retaining the padded bytes). The intended way for this was to use the CBC bit-flipping attack to modify the last byte of the initial ciphertext (Encrypted code). This leads to leaking out the symmetric key
as there is no null-byte termination for the naive printf()
.
After leaking our the key, the attacker must send the Dec(payload) and use the given IV and leaked key for the operation. As, Enc(Dec(PT)) == Dec(Enc(PT))
we can now craftily pass our input such that the intended payload is not corrupted by the encryption function.
After this, the player can use the double free and overwrite the forward pointer of the fastbin chunk with a pointer near __malloc_hook
such that the next pointer points to value 0x7F
(satisfying the size check of fastbin). A detailed explanation for the fastbin corruption attack can be found here. Mind you, the fake address or the attack payload must be sent to the binary as Dec(Fake_addr_payload)
. The content is then stored as Enc(Dec(Fake_addr_payload))
which is Fake_addr_payload
in the heap chunk.
We can get a chunk allocated at this address after invoking a couple of malloc()
functions. We can then overwrite __malloc_hook
with a gadget that executes system()
on the next allocation.
Exploit code
1 | from pwn import * |