OverTheWire Advent Bonanza 2019: [Day 14] tiny runes

About

This post is part of a series in which I’ll post writeups for all challenges of the OverTheWire Advent Bonanza 2019 CTF. To see the intro, click here

Overview

One of Santa’s helpers received a wish for an unreleased game, and they only managed to gather fragments of the dialog system. One of them they didn’t manage to decode. When we download the archive, we get three known fragments, and one unknown.

Analyzing the format

The first fragment results in the following image:

The first known fragment

Let’s take a look at the strings:

$ strings lines1.bin
TiNyMeTa
*TxTr
IHDR
PLTE
IDATx
%RAd
jE)Q
jO#j4
h,;I
asWPG
KWhs
=L_0
#M2u
P#h64
mPu'	
z:!0
Tp31
2laZ
IEND
LiNe
LiNe

If you have a little experience, you may immediately notice the strings IHDR, IDATx and IEND, which are used in the .png format. Also the file begins with the string TiNyMeTa, and at the end we have the same amount of LiNe strings as there are lines. After some more analysis we get the following structure:

  • “TiNyMeTa”
  • Some garbage
  • A PNG image
  • Lines, consisting of:
    • “LiNe”
    • Characters, consisting of 2 bytes each

Let’s look into the image some more. After extracting it, we can have a look:

The mystery image

It looks like a tilemap! And two bytes… Could it be that the first one is the X coordinate, and the second is the Y coordinate? Each character is 8×8 pixels, so let’s try it:

Writing a parser

We need to write a parser! It’ll first extract the image, then it’ll go over each line, and create a image from them. Let’s try it by recreating the first fragment:

$ ./parser.py lines1.bin lines1_recreation.png
Our recreation of the first fragment

A perfect recreation! So now we can use it on the unknown one!

The unknown fragment

Let’s run our parser on the unknown fragment:

$ ./parser.py lines4.bin lines4.png

The flag is:

AOTW{wh4t_4_r0tt3n_fi13_f0rm4t}

Here’s my parser:

#!/usr/bin/python3

from PIL import Image
from io import BytesIO
from sys import argv, exit
from struct import unpack

u32 = lambda x: unpack(">I", x)[0]

if len(argv) < 3: exit()

with open(argv[1], "rb") as f:
    content = f.read()

if content[:8] != b"TiNyMeTa":
    print("Invalid file!")
    exit()

lines_begin = content.index(b"LiNe")

tiles = content[content.index(b"\x89PNG"):lines_begin]
tiles = Image.open(BytesIO(tiles))

lines = []
lines_d = content[lines_begin:]

while b"LiNe" in lines_d:
    line_i = lines_d.index(b"LiNe") + 4
    lines_d = lines_d[line_i:]
    
    chars = []
    size = u32(lines_d[0:4])
    
    for i in range(4, size+4, 2):
        x = lines_d[i]
        y = lines_d[i+1]
        
        chars.append((x, y))
    
    lines.append(chars)

width = max(map(len, lines)) * 8
height = len(lines) * 8

img = Image.new("RGB", (width, height))

for y in range(len(lines)):
    for x in range(len(lines[y])):
        tile_x, tile_y = lines[y][x]

        img.paste(tiles.crop((tile_x*8, tile_y*8, tile_x*8 + 8, tile_y*8 + 8)), (x*8, y*8))

img.save(argv[2])

2 thoughts on “OverTheWire Advent Bonanza 2019: [Day 14] tiny runes

Leave a Reply

Your email address will not be published. Required fields are marked *