Saturday, June 1, 2013

ebCTF - binary and web write-up

The first ever ebCTF (teaser) has just finished and I must say it was really well organized and the tasks were great too. The CTF lasted for 8 hours and there were 6 tasks in total. I only had a few hours to play but I managed to solve 3 of them with my one person team.

Dice Game

Dice Game was a binary challenge worth 100 points.  It turned out to be the easiest task, but I had fun cracking it. I may have taken an alternate route so it will be interesting to look at other write-ups. Let's see.

The program is a text-based game where the player has to roll a specific number on a dice.

Something tells me that solving this by pure luck would be unfeasible. Let's look at the binary in IDA. There are some interesting strings:

Rolling 7 on a 6 sided dice? I don't think so. But we can look at the routine that prints the flag. The block that prints the flag doesn't make much sense at first and I'm too lazy to reverse it so I decide to look for the parts where the rolled numbers are checked. They are pretty easy to find.

We could try to debug this step-by-step and modify the Z flag after the cmp instructions. But what worries me is that there are calls to a time function after each roll. This might mean that the binary tries to detect debugging and I don't want to waste time.

Now I could try debugging this with DarkOlly, it has pretty good techniques to evade detection. I decided not to use Olly however. Patching the binary seemed much more fun.

I don't have a version of IDA that is capable of patching so I did it manually. I decided to look for the critical cmp instructions and patch there. To do this, I turned on visible opcode bytes in IDA (Options/General/... Number of opcode bytes). This is what it looked like afterwards:

My idea was to change the jnz instruction into a jz, because _not_ rolling a specific number (especially 7) is much easier. I just had to make sure that "83 7d a4 03 75 5e" only exists once in the binary so I'm overwriting at the correct place.

Changing 75 to 74 (jz) did the trick. This had to be repeated 4 more times. The last two instructions were near jump opcodes (0f 85). In this case 85 had to be changed to 84.

After patching the binary I got the flag on my second try.

My team was the second to get any points so I guess this method wasn't too bad in terms of speed :)

Wooden Shoes

Wooden Shoes was the title of the web challenge. It was worth 200 points and it turned out to be the 3rd hardest task. After opening the site this is what we see:

Entering an apostrophe results in an error that gives us an important clue. 

When entering a well-formed search string we get a different url.

So the search string and the column that we sort by is somehow turned into a large hex value. Changing the hex value causes an internal server error, which is good. We can suspect that this hex value is somehow decrypted on the server, and then its contents are used for the query. We should hope that the restrictions are more lax after the value was decrypted. Let's figure out the format!

I made lots of queries and compared the 'what' part of the urls. Some of them are here:
a   - 074a9b5552ab43
b   - 044a9b5552ab43
c   - 054a9b5552ab43
aa  - 0721e15749a145d9
ab  - 0722e15749a145d9
ac  - 0723e15749a145d9
aaa - 07218a2d4bba4fdfdb
Looks like 1 character adds 1 byte to the hex value. When changing a single character only the corresponding hex byte changes. Changing the first character by 1 does not result in a change by 1 in the hex value, but it does with the second character. The first and only idea I had was some XOR encryption. It turned out to be right:
0x07 ^ 0x97 == 0x04 ^ 0x98 == 0x05 ^ 0x99
So I entered lots and lots of spaces to figure out the key. It was 32 characters long and kept repeating after that. Then I figured out the syntax of the encrypted data. First comes the search query then a newline character and then the sort-by column's name.
I wrote a tool that would encrypt any data for me. I tried putting SQLi payloads into the search query but that didn't work. All that was left was the order-by column part. This meant that the exploit will most likely be blind.
It was quite easy to confirm that there is an SQLi vulnerability but exploiting it seemed impossible. I couldn't access the information_schema and a query that returned 2 rows in the order by clause wouldn't result in an error.

After many tries I decided that this could not be MySQL and started looking at other possibilities. PostgreSQL - nope, Oracle - nope, MS SQL - nope. SQLite - yes! Yay and nay, because I didn't know a single thing about SQLite, especially about its SQLi specifics.
After a few google queries I found what I needed: sqlite_master is the table that contains all the metadata.
The solution was trivial from here. Here are 2 interesting queries that would do the trick with a python script:
case when (select 1 from (select sql from sqlite_master where type='table' and tbl_name='secret_flag' limit 0,1) t where substr(t.sql,%s,1)>='%s') then `price` else `stock` end--
case when (select 1 from (select flag from secret_flag limit 0,1) t where substr(t.flag,%s,1)>='%s') then `price` else `stock` end--
Here is the script in action:

No comments:

Post a Comment