This past weekend, I had the honor of running the DEFCON 25 CTF Qualifiers with LegitBS. Since I rarely ever see CTF write-ups from organizers, and I got very little response on my CSAW Qualifier one, I decided to give it a shot again. My intent here is to share what I created and why I created it, rather than providing a solution. For that, I'm hoping the wider CTF community will provide its usual awesome write-ups.
From my perspective, the game went very smoothly and was perhaps the most difficult qualifier yet. We started on-time, there were precious few problems with challenge infrastructure, and the few problems we had were easily addressed. We had 27 challenges mostly covering the categories of reverse engineering and binary exploitation (since those are the skills required by DEFCON Finals), of which 3 actually remained unsolved at the end of the game. Of those 27 challenges, 3 were mine!
When designing my challenges, I tried to keep in mind the following goals:
- Challenges should be as straight-forward as possible - no silly guessing games or frustrating flag encoding
- Challenges should test skills that will be required by our final event
- Custom/unusual architectures due to our announcement that we'll be using a custom architecture for DEFCON Finals
- Heavy emphasis on binary skills due to the current design of DEFCON Finals
I would also say that, relative to other members of the team, I was expected to fill in some of the easier challenges. Lightning, SirGoon, and HJ had already planned some fairly hard/time-intensive challenges. So, we needed a few that would be smaller and less time-intensive to balance those out.
Floater
My first challenge was floater
, which was my entry into our "Baby's First" category intended for
newer/less-experienced players. This combined three different shellcoding problems I've encountered over the years:
- Your shellcode must be represented by a floating point number
- You don't control all the bytes of the buffer you are executing out of
- The buffer you are executing out of does not have write permissions
Originally, I had implemented this by reading in floating-point numbers, rounding them, and then placing them into
an array of double
s. Unfortunately, I forgot that a double
has an exponent field that is 3 bits larger than a
float
's, which killed all of the solutions I was looking for. The deployed challenge places the float
s into an
array of float
s at every other index instead. The rounding wound up being an unnecessary constraint, but I left it
in anyway.
Source code can be found on GitHub in a week or two.
Pepperidge Farm
My second challenge was pepperidge_farm
, which was my reverse engineering challenge. Since we'll be running DEFCON
Finals on a custom architecture this year, it's important that we test players' ability to handle an architecture
they haven't seen before. I also felt it was important to recognize
Jonathan Salwan and Nuit du Hack as being the
first group to do this with the NDH architecture.
Naturally, the solution was to write a crackme using NDH!
The challenge itself is fairly standard CTF fare - I check 32 characters against 16 16-bit values hard-coded into the challenge after applying a number of different transformations to them. I did this by creating a Z3 solver script and messing with random values until I got something I could solve. Then, I implemented the challenge in NDH.
Actually writing the challenge went pretty smoothly. NDH is designed very well and, although the VM's debugging facilities are a little spartan, I was able to get everything working. The thing I spent the most time on was a poor assumption that caused me to have to re-write half the challenge. As far as I can tell, it is impossible to get more than an 8-bit value out of a memory location. You can do 16-bit operations when both operands are in registers, but you can't do them when one is in memory. I hadn't realized this at first, so I spent a lot of time re-writing code I had assumed would work initially.
Source code can be found on GitHub in a week or two.
Faggin
For my third challenge, I had planned to re-use my Ghost in the Shellcode 2011 black-box programming challenge
"In Memory" as an exploitation challenge. The original presented you with an
MCS-4 emulator you could give a ROM to. The flag was stored piecemeal
in memory and required players to use the Intel 4004 DCL
instruction
to switch RAM banks in order to get the whole thing out. Since it was not an exploitation challenge and was
not intended to be reverse engineered, we never gave out the executable, which meant I could re-use the code.
To design the MCS-4 emulator, I used the following as references:
- The Intel 4004 Datasheet
- The MCS-4 Manual
Unfortunately, my original implementation of the emulator not only cut a lot of corners (and made some bad assumptions), but also did not afford me the memory access players would need to successfully exploit the service. As a result, it turned out I had to re-write most of the code anyway.
The most significant time investment was needing to pack all of the 4-bit and 12-bit values that the emulator operates on. This took a lot of time to get right since I had to modify every instruction that used each of these values. In the end, I moved most of this functionality into helper functions that would read/write the packed data for me.
My original idea for making the service vulnerable was to have two bugs:
- A memory leak via being able to read memory out-of-bounds off of non-existent Intel 4002 RAM chips
- A pointer overwrite via a missing maximum bound on writes to the PC registers
Implementing the first vulnerability was easy: I simply reduced the number of Intel 4002 RAM chips I included in my MCS-4 "object" by 8 while leaving in all of the existing bank-switching functionality. This enabled people to read and write to memory outside of what was intended for the array that implemented the RAM chips. Unfortunately, while I was testing this out, I realized that ROP was going to be quite difficult for a few reasons:
- I used absolutely no
libc
functions anywhere in my code. As a result, there would be no way to get a pointer leak forlibc
to gain access to good ROP gadgets. - Since I wrote the code in an object-oriented style, all of my functions required the first argument to be a structure pointer. This meant my own code was pretty bad for ROP as it often imposed difficult constraints.
This being DEFCON, finding out your challenge was harder than you'd intended is actually a feature, not a bug.
Unfortunately, I've had a rough few months that left me without the time necessary to work out a solution myself.
In the end, I decided to release a version on Sunday morning that simply used the first bug to both read and write
memory. Hilariously, even after compiling the executable with -static
(to avoid the need for a libc
leak), I
still couldn't find a good stack pivot. Lightning had me re-compile the service with -O1
instead, though, which
produced usable gadgets. My solution did require me to execute through a few of the gadgets in my ROP
chain as valid Intel 4004 instructions, though, which was still kinda cool.
Ultimately, I'm pretty disappointed with how this one turned out. Since you could write out-of-bounds very easily, I believe many people found the bug they needed with simple fuzzing. The intended bug for writing would have hidden this a little better and would have made for a more interesting challenge and required more reverse engineering as I'd intended. Hopefully the few people that solved it still found it fun and moderately novel.
Source code can be found on GitHub in a week or two.