Hey everyone! I came back from a long 36-hour CSAW ctf finals competition, competing as dcua.
So I wrote a quick (long) reflection on what I thought about CSAW ctf. Here are some of the writeups that I have for CSAW CTF
Pwn Adventure: Sourcery
This was a fun game released by Vector35. My personal thanks to the team who wrote this game just for CSAW 2018!
To start off you had to write some assembly code to "cast" a fire spell, and
ebx
can be changed to modify the power level of the fire. So looks like they
have an x86 emulator within the game.
So they give you a "Spell Extractor" which you can use to extract "enchantment"
spells and code. This is also x86 code (32-bit). Interestingly, they use the
in
and out
family of instructions to communicate directly with certain I/O
ports (such as output and stuff like that).
They also give you a "Pwn Tool" that can send/read data to the console or the "enchanted" object via using I/O ports (you write to port 0 as user input, and read from port 1 to read code output, and write to port 2 to write debug data.)
Originally, I thought that you can use the in/out instructions to read from ports of the console/object, but quickly I realized that that is not the case.
Cheats
Before I start explaining some of the flags walkthrough, I want to divert a bit.
So basically all the flags here do NOT need you to cheat the game, i.e. all can
be solved in-game. However, we are hackers, and we want to go through the game
much more quickly and smoothly. Unfortunately, during the competition I was not
able to find a way to cheat the game (probably it was because I never done
web-assembly before). After the competition, I found that I can modify the
memory directly in javascript using the HASH##
global variables. I ended up
searching for the health/mana scores in the memory, changing it a bit, to find
the indicies... err... addresses of where these values reside in memory.
Unfortunately, I think the addresses are randomized each time, so I ended up
writing some code to do that for me. This code is a
userscript that runs on TamperMonkey whenever you load the game.
As part of the program, you press Ctrl+B twice (when you have different health/mana scores) and it will automatically search where these values are. After that the program will set your health and mana to max rapidly so that it appears that you have infinite health and mana. Here is a run through of my cheat (the glitches in the music are due the fact that the cheat program is trying to search through the same memory that is used by the pwnadventure game)
I had particular disliking towards the spiders during the competition as I had to manually play through everything :\
Jailbreak
The first "console" was a hard-coded passcode 5129
. Enter that and you pass.
The second "console" read in a line as a string, and compared it with some secure password that was read off of another I/O port. This was pretty evident that it was a simple buffer overflow issue. So we just overflowed the return address, and redirected it to the access granted code:
1 | mov ecx, exp_len - exp |
Zombie boss battle
Before we were able to get to ANY other challenge, we had to go to deadwood, (go in town and then kill the zombie boss).
This was particularly frustrating because we had to manually play through this game... (no cheats :/ ). Anyways after that battle, you get an exploding rod which you need to explode the stone in the mountain area. Then you go south to a desert area. In that area you will see two entrances, one leads to the 'Cave of Nope' (which I foolishly went into and wasted a bunch of time there), and the other (farther down) leads to a lab area. There are three lab segments, and all of them are reverse engineering problems
Lab 1
This time, we can only enter "printable" characters, which means we can't do a ROP exploit... we will have to enter a valid PIN id. Okay so we have this verification code: (full code here)
1 | verify_code: |
Okay so the first loop simply xors the 0th/8th input character, 1st/9th,
2nd/10th, etc... Then The check loop compares this xor result (addressed as
esi
, and stored on stack) with the valid
array, which is defined somewhere
else:
1 | valid: |
So basically we need to find two characters, when xor'ed will have the result
stored in the valid
array. The only catch is that it has to be printable. So
my answer was this 0@00@@@@9c67vxbl
.
We also get hover boots, which are needed to pass over the bridge to enter the swamp area. We will explore there some time later.
Lab 2
Onto lab 2! Our second lab is a little bit more difficult to complete... here is our verify code: (full code here)
1 | verify_code: |
So the full code is extremly long so I cut it out. But essentially, it you note
carefully, it is basically performing a very similar monotonous algorithm for
all the letters. The algorithm take the first few letters to initialize some
sort of state. Then with the next few letters, it will take the current state of
cl
and dl
, do some stuff with it, and then check that the user input
character is equal to that.
Most of the operations appear to only apply to the single character. There is
one exception to that rule: the rol
actually works with 32-bit values, so
eventually some of those values from previous rounds will begin to eventually
factor into the lower 8 bit in a rather unpredicatable manner. Because of this
complexity, I have decided to just simply copy the assembly code, and then have
it print out the PIN id, rather than checking the PIN id. Even with the
printable character requirement, I can actually choose any random initial state,
and it will generate a valid state because of the and cl, 15; add cl, ...
instructions, which guarenteed that the value we are comparing with is
printable, as long as the add offset is well within the printable range. This is
my solver code (which uses nasm+nasmx to work). The key that it generates
is DSFFdcChiFRoOeBf
. Okay cool we pass this lab.
Lab 3
Okay now onto lab 3! Okay when I opened this code, I was a bit in a shock to see how much code it has... (but most of it is very repetitive). Immediately, I went to the decipher function and ignored everything else for the moment (this will bite me later on...). Here's that code snippet, (and the full code):
1 | decipher: |
So first we note that the argument passed to decipher
is a pointer to some
buffer user input, and it is reading the first 8 bytes into edx
and esi
.
Then clearly we go through some sort of loop which puts this data into several
hundred rounds of encryption.
Then after decipher returns, the program goes into this code to check...
1
2
3
4
5
6
7
8cld
mov esi, v0
lodsd
cmp eax, 0x57415343 ; 'CSAW'
jne .fail
lodsd
cmp eax, 0x41484148 ; 'HAHA'
jne .fail
...that user input array now decrypts to two numbers 0x57415343
and
0x41484148
For this problem I decided to just simply assemble this code, and pop it into IDA Hexrays. We find that this serverely simplifies the code:
1 | void decipher(char *a1) { |
Now we can more easily sift through this code... taking into account that we are using 32-bit integers for these computations, we can simply represent each round as a recurance relationship of previous rounds:
1 | f(0) = a1[1] |
Okay so we know that the last two values that this recurance must end with, i.e.
f(n+k) = 0x57415343
and f(n+k-1) = 0x41484148
. So all we have to do is
extract the xor'd values, and we can solve the recurance relationship backwords!
Now the only think I completely blew past at first was how to format this answer. At this point, I was so happy I found the code breaker that I forgot that this thing is reading in hex numbers. If only I looked at the code more closely I would see a line that says:
1 | ; read in the input, expect "XXXXXXXX-XXXXXXXX" |
Facepalm... oh well. That took me a while to realize that. But after that I solve it. Here's my python code (without the xor values):
1 | from lab3_values import values |
And the correct PIN is: 9b916917-b6117336
. Okay I got all three labs, three
flags, and a key. Now onto the other stuff!
Swamp Maze
Okay so the premise of this game is you have a robot that you can program to do
whatever you want. To get here, you need to get hover boots from lab 1, and hop
over the broken bridge. You have to deal with some sharks, and then arrive at a
lab. The first challenge was getting the robot to move forward. This task is
done by using the SYS_WALK
syscall: (Note that we also can't have null bytes
in our code.
1 | mov esi, payload |
This code is used by the pwntool to write new code for the robot. When the robot executes this command, it will walk in the negative x direction.
Now then with this baby challenge out of the way, we see the next challenge is to guide the robot through a maze. Of course the (small) problem was that the robot only had 64 bytes of memory (not a lot!) to work with...
We didn't actually solve this problem during the competition, but afterwards, we asked team perfect blue, and they told us that they just had the robot follow whereever the character went (facepalm!). Anyway here's the code I wrote after the contest:
1 | mov esi, payload |
Yeah so that was that, and then we had to fight the boss battle, which was freaking annoying if you didn't have a cheat. The first time I tried solving through this challenge (after the competition) the boss somehow glitched and did not spawn any rats after some time.
Hacker Overlord
This one was the final boss challenge. Again we have a buffer overflow, but this time we also have "ASLR" and a stack canary, so not as easy to exploit. To be continued...