I had some free time over the weekend and I decided to jump into a CTF competition to get some practice in. I haven’t heard about PWNSEC yet so I decided to take a look at their CTF and solve a few challenges.

Taco Shop was a simple reverse engineering challenge at PWNSEC CTF 2024. The challenge could be solved multiple ways and I decided to stick to a manual solution to learn as much from solving it as I can. In the end, I’ve collected some useful references and added a few tools under my belt

Initial analysis

Running the challenge will show a menu to choose from:

Running1

I tried playing around with it for a bit at first. Passing negative numbers as menu choices and aside from a few witty meals I quickly realized there isn’t anything to be gained from such blind tampering.

I decided to load it into Ghidra and while the analysis concluded I took a look at the exports in r2:

Ghidra1

Not very helpful either. I jumped into Ghidra and saw it couldn’t recover many functions either:

r2symbols

Guessing it might be compressed or packed somehow I used strings to possible get references and found what I was looking for:

strings1

According to their website:

UPX is a free, secure, portable, extendable, high-performance executable packer for several executable formats.

Since I wasn’t familiar with it, I looked up how unpacking is done and after a bit of gooooooogling I stumbed upon this article: https://dlnhxyz.medium.com/manually-unpacking-a-upx-packed-binary-with-radare2-part-1-7039317c2ed8

First round of unpacking

This was perfect as It didn’t just allow me to get some practice with manual binary analysis, but I had a chance to practice with radare2 as well! I’m a sucker for CLI goodies so I read through the article and tried to follow along.

I learned how to use the visual debugger interface. Stepping over and into functions and the dcs command to run until we encounter a syscall. I got to where I could dump all memory maps with the dmda command, reconstructed the unpacked elf file from these separate regions and I was successful.

runningagain

I checked the resulting binary with strings once again and found a bunch of interesting strings.

strings2

Unfortunately, all of this was just trolling:

fakeflags

I threw the entire thing into Ghidra once again but this time it was even worse:

ghidra2

So I thought I’d try going back to r2 and debug the binary but I got the following error:

oops

The binary detects the presence of the debugger by using ptrace. We can see this in the strace output from earlier:

ptrace

Looking at the definition of ptrace we can see this might happen if the binary is already being traced. Indeed it is as we’ve already attached GDB to it.

ptracedefinition

Naturally, we can simple patch this check and jump over it.

Second round of unpacking

In GDB, I would’ve used the catch syscall ptrace command to set a breakpoint at around ptrace and move my way up from there. But how could I do that with r2? I wanted to learn!

I didn’t find how to do this easily and the break command didn’t work for me so I came up with the following:

  1. Load the binary into r2
  2. List the mapped memory regions with dm
  3. Move the starting and ending addresses of the mapped, unpacked binary to dcu <starting address> <ending address> as these are where we’ll eventually find the relevant code
  4. Let it run until we’ve hit the segments where the relevant code is

I had to run this a few times as I wasn’t sure I was on the right track but then I noticed the following:

startmain

We can clearly see __libc_start_main being called. From here I only had to take a few steps and I got to Nirvana:

code

Reversing the final stage

As I was sure at this point the correct code was mapped to memory I did the trick of dumping the memory ranges with dmda again and after loading the resulting binary into Ghidra, creating a few functions and cleaning it up a bit I got the actual code in there:

ghidra3

From here it was fairly straightforward:

  1. The binary reads your choice
  2. It then checks your input against a “secret password”
  3. If it’s correct, you get the flag

The secret password based on the function is the base64 decoded value of RnIzM19QNGwzU3QxbjMK which is Fr33_P4l3St1n3

secretcheck

Passing the password to the program will give you the following:

flag

Not sure if I’m an idiot, but I didn’t realize the flag was PWNSEC{Fr33_P4l3St1n3} …. I thought there was another secret item to find in there. I even hit up an organizer to check if there’s something else I’ve missed but after a while I submitted the flag and got the points.


Interesting challenge. Learned a lot, practiced reversing, familiarized myself with r2 and got the flag. This was pretty cool!

~ r4bbit