➜ ~

Playing Hacks and Stuffs!


Project maintained by h4ckyou Hosted on GitHub Pages — Theme by mattgraham

PicoCTF 2025

Hi h4cky0u here, I participated with team M3V7R and we placed first on the Africa scoreboard image image

I tackled mostly the pwn and rev challenges and this writeup contains the solution to them all

Challenge Solved

Binary Exploitation

Reverse Engineering

Binary Exploitation

PIE Time 1

image

We are given the source code and binary

Reading the source code, in the main function image

This would:

The program has a win function which would print the flag image

We simply just need to jump to function

Here’s my solve script

image

Running it, we get the flag image

PIE Time 2

image

Same as the previous challenge we are also provided with the source code and binary

Starting from the main function we see it calls the call_functions function image

Here’s what the function does image

So first it:

This also has a win function image

Our goal is to jump there yet again

But this time we are not given any memory leaks, and checking the protection enabled on the binary we get this image

This means we need to leak some memory address

There’s an obvious format string bug since it uses printf on our controlled buffer without using a format specifier

We will leverage that to get memory leaks

To calculate the offset where a pointer to the elf section is on the stack i’ll use gdb

Using gdb-pwndbg and setting a breakpoint at b *call_functions+80 image

Here’s how the stack looks like image

We can see at offset 19 holds a pointer to the elf section image

Now we calculate the offset of that address to the pie base image

This means if we get the address at offset 19 and subtract it with 0x1441 that would give the pie base

And with that we can easily just jump to the win function address

Here’s my solve image

Running it works image

Hash Only 1

image

We are given an ssh instance to connect to and also an alternative command to copy the flaghasher binary from the remote host to our local host

Let us first take a look at what the binary does on the remote instance image

Running it we see it computes the md5 hash of the /root/flag.txt file

We obviously can’t derive the content of the flag from just the hash so we need to some how figure a way around this

To copy the binary to our host we use this command

scp -P 53610 ctf-player@shape-facility.picoctf.net:~/flaghasher .

After the transfer, checking the file type shows it’s a 64 bits binary image

Running strings on it we can infer it’s a c++ compiled binary image

Using IDA to decompile here’s the main function image image

The program first prints a message to the terminal and pauses for two seconds. It then defines a command string containing /bin/bash -c 'md5sum /root/flag.txt'. After that, it escalates privileges by setting both the group ID (gid) and user ID (uid) to 0. Finally, it executes the command using the system function.

This basically just calculates the md5sum value of the file /root/flag.txt

The issue here is the way the md5sum binary is being used, it doesn’t specify the full path thus we can hijack the binary so that it executes something else

The first step is to create a malicious md5sum binary, i just did something really easy image

Next we add our current directory to the environment PATH variable image

echo "cat /root/flag.txt > a.txt"  > md5sum
chmod +x md5sum
export PATH=/home/ctf-player:$PATH

We can see that on executing the flaghasher binary we get the flag!

Hash Only 2

image

Connecting to the ssh remote instance we get this image

Every thing looks all good until i tried finding the suid binary flaghasher and it shows this error:

-rbash: /dev/null: restricted: cannot redirect output

So it seems we are in a restricted shell called rbash

Looking it up for bypass we find that it can be easily bypassed using the -t "bash --noprofile" argument

Doing that works image

The flaghasher binary is still present but just in a different directory

Executing it shows the same thing as the previous one image

I just reused my previous solve for the path hijack and that worked image

Echo Valley

image

We’re given the source code and binary image

Here’s the main function

image

It just calls the echo_valley function which does this image

This simply just keeps receiving our input and printing it out back until we give it exit before it returns back to main

There’s also a win function image

So our goal is simply to somehow call that function

The bug is an obvious format string bug

Looking at the protection on the binary we get this image

We see that all protections are enabled

The first thing we would need are memory leaks

I leaked pie and stack

The reason i leaked a stack address is because i’ll be overwriting the saved rip of the echo_valley function to the print_flag function

Setting a breakpoint at echo_valley+218 here’s what is on the stack image

At offset 20 & 21 holds some stack and pie address image

And that’s what i leaked image

To calculate the saved rip address i set a breakpoint at echo_valley+249 then inputted exit image

Now we look at the stack frame image

Now we just subtract our leaked stack address with that of the saved rip image

It’s just 8

With this we will leverage the format string bug by gaining arb write and our goal is to overwrite the saved rip to the win function

Here’s my solve image

Running it works image

Handoff

image

We are given the source code and binary

First we’ll take a look at the protections on the binary image

The binary literally has no protection enabled, okay looks good!

Running it shows this image

It has just two main options we can choose from

Since we have the source code let’s take a look at it image

First we have a struct called entry_t that manages the name and message of the recipient

typedef struct entry {
	char name[8];
	char msg[64];
} entry_t;

I’ll look through just the important details

If we decide to add a new recipient it will read into entries[total_entries].name 32 bytes of data

If we decide to send a message it will receive the index of the the entries we want to store the message and read 64 bytes of data into the msg field of the entry_t struct

And finally when we choose exit it will ask for our feedback where it reads in up to 32 bytes of data to the feedback array, null terminates at index 7 and returns

Okay cool so now what’s the vulnerability?

Well there are some bugs here such as:

There’s also an overflow in add recipient but i wouldn’t consider it that much of a risk because even though it overwrites the msg field when we attempt to add a message it will replace what we overwrote with our new message

The out of bound read occurs in the add message function since it doesn’t check if index is less than 0 image

But this isn’t so much useful because there’s nothing really important before the entries as you can see from the stack view image

Now the main bug which we’ll exploiting is the overflow in feedback

We see it’s defined as a char array of 8 bytes but we’re reading in 32 bytes of data to the array

At first it might look like an easy win here? but if you take a look at the stack view you’ll see this image

-000000000000000C     char feedback[8];
-0000000000000004     _DWORD total_entries;
+0000000000000000     _QWORD __saved_registers;
+0000000000000008     _UNKNOWN *__return_address;

The offset from the feedback array to the return address is:

This means the first 20 bytes will first fill up the feedback array, the total_entries and then the saved rbp

That effectively leaves us with roughly 8 bytes for rip control

What???

How do we pwn this with just 8 bytes of rip control

Things began getting tough at this point

I neglected the fact that NX was disabled at first and that means the stack is rwx

During the time spent on trying to solve this challenge (about 5 hours or so) i attempted:

Then i decided to take a look at the rop gadgets again and got this image image

The one of interest is the jmp rax

Taking a look at the registers when it’s about to return shows this (set bp at vuln+485) image

We see that rax is a controllable buffer and that represents our feedback array!

This means we can place shellcode as the feedback then set rip to the jmp rax gadget effectively giving us shellcode execution

Phew! But now we know that, how can we spawn a shell?

Remember that the entries are actually stored on the stack! This means we can make rsp point to entries[0] which would hold our shellcode!

I calculated the offset between the current stack with where entries[0] is stored to be 0x2e4

So with a sub rsp instruction + a jmp rsp instruction i pivoted the stack to our execve shellcode and got code execution!

Here’s my solve image

Running it works image

Reverse Engineering

I’m yet to write about this but i solved all challenges in this category, I’m a bit tired but all my solves are here

The Binary Instrumentation 1 & 2 isn’t there because it’s windows rev which i solved by debugging on my windows host

But the idea behind the challenge 1 is that it will resolve some winapi functions and then executes a region of memory which holds some shellcode that later calls a function that is meant to print the flag

The issue is that it’s going to take a lot of time since it uses a sleep variant to make the program pause execution

Rather than patching the function call i just scrolled down a bit and saw the base64 encoded flag which is supposed to be printed to stdout after the sleep is done so i just decoded and i got the flag

This one really took me quite a lot of time (3 hours) the reason is i’m not so much familiar with windows reversing

So i really spent lot of time in the debugger

But the challenge itself basically also resolves some api and then executes some shellcode

The issue with this one is when it tries to perform some winapi call it fails for some reason :(

But before it exists i created a dump of the new memory region it creates

And decompiling it i got the flag, but you can as well just scroll down in the memory dump and you’ll see a suspicious base64 looking string

The other challenge which gave me issue was perplexed though to reverse it wasn’t so difficult but for some reason i wasn’t getting the last bytes to be printable

It really frustrated me and i ended up brute forcing using gdb scripting

This is my rant here: image image

And finally image

GG to my teammates!