Лаборатория TJCTF 2016 - µ'sic [forensics 200]

, 6 июня 2016

µ's Music Start!

I was solving this task for 3-4 days after TJCTF and yesterday finally solved it.

We are given a link to µ'sic.zip file.

Archive is protected with a password. I've brutted the pasword to the archive.
Password is "LL". Inside the archive a Welcome.txt file with the next message is stored:

Welcome to µ'sic!

The purpose of this challenge is to elucidate the applications of forensics and steganography techniques and the manner in which they may be solved. Our intent is that you will learn and develop a multitude of skills as you progress through this challenge.

For each "layer" of this challenge, we have included one of our favorite songs from Love Live! (now you know what LL stands for), along with the cover image that came with the CD for each song. Each layer involves a password protected zip file, and the password can be found hidden in the cover image.

If nothing else, please enjoy the µ'sic!


So we have several nested archives with songs, posters and some other files. 
Each level's cover image contains a hidden password to the next level's zip archive.

Level 0

Password is hidden as a plain text in the cover image:

Level 1

We have two cover images. We can find diff bytes using Python script:

with open('1.jpg', 'rb') as f:
    data1 = f.read()
with open('2.jpg', 'rb') as f:
    data2 = f.read()

out = ''
for i in xrange(len(data1)):
    if data1[i] != data2[i]:
        out += data1[i]
print out

And get a next data:
Which is base64 encoded next level's password:

Level 3

Inside this level's achive we have a PDF file.
Using the next script I've extracted and decompressed all stream data blocks from PDF.

import zlib

with open('1.pdf', 'rb') as f:
    data = f.read()

while True:
    start = data.find('\x0Astream\x0A')
    if start < 0:
    data = data[start + 8:]
    end = data.find('\x0Aendstream')
        decompressor = zlib.decompressobj()
        dec_data = decompressor.decompress(data[:end]) 
        with open('out.png', 'wb') as f:

Is is password written with Wingdings font.


Level 4

In this level's archive we have two PNG images. Using the next script I've extracted diff image with a password:

import operator
from PIL import Image

def diff(col1, col2):
    return map(abs, map(lambda x: operator.sub(*x), zip(col1, col2)))

img1 = Image.open('1.png')
pixels1 = img1.load()

img2 = Image.open('2.png')
pixels2 = img2.load()

w, h = img1.size

img3 = Image.new('RGB', (w, h))
pixels3 = img3.load()

for y in xrange(h):
    for x in xrange(w):
        v = tuple(diff(pixels1[x, y], pixels2[x, y]))
        if v == (1, 1, 1):
            pixels3[x, y] = (255, 255, 255)
            pixels3[x, y] = v



Level 5

We have a jpg file. I've noticed that inside of this file, zip achive is hidden. With the next script - I've extracted it.

with open('Cutie Panther.jpg', 'rb') as f:
    data = f.read()

pos = data.find('PK')

with open('out.zip', 'wb') as f:

Inside extracted archive - Cutie Panther.png image with QR codes... Lots of them...
Image size is 13500 x 13500 pixels. 900 QR codes.
So I've cutted the image into subimages with QR codes. And then decoded each of them.

import glob
import os
import re
import subprocess
import zipfile
from PIL import Image

def cut_qr_codes(img_path, out_dir, qr_left, qr_top, qr_w, qr_h, qr_step_x, qr_step_y):
    """ Cut off subimages from image """
    if not os.path.exists(out_dir):
    img = Image.open(img_path)
    img_w, img_h = img.size
    index = 0
    for j in xrange(qr_top, img_h - qr_top - qr_step_y, qr_h + qr_step_y):
        for i in xrange(qr_left, img_w - qr_left - qr_step_x, qr_w + qr_step_x):
            print i, j, index
            img.crop((i, j, i + qr_w, j + qr_h)).save(os.path.join(out_dir, '%003d.png' % index))
            index += 1

def decode_qr(path):
    """ Decode QR using zbar """
    res = subprocess.check_output('zbarimg %s -q' % path, shell=True)
    return re.findall('QR\-Code:(.*)\r', res)

def unzip_specific_file(zip_file, name, password):
    """ Unzip file from zip archive """
        zip_file.extract(name, pwd=password)
        return True
    return False

def main():
    # cut image into subimages with QR codes
    out_dir = './qr_codes/'
    cut_qr_codes('Cutie Panther.png', out_dir, 40, 40, 371, 371, 79, 79)

    # decode QR codes
    passwords = []
    for fn in glob.glob(os.path.join(out_dir, '*.png')):
        data = decode_qr(fn)

    # find the password
    filename = 'zip5.zip'
    zip_file = zipfile.ZipFile(filename)
    # get image's name in archive
    name = ''
    for name in zip_file.namelist():
        if name.endswith('.jpg'):
            print name

    for password in passwords:
        if unzip_specific_file(zip_file, name, password):
            print 'password is:', password

if __name__ == '__main__':

After running this script, I've found appropriate password.


Level 6

import itertools
from collections import Counter
from PIL import Image

img = Image.open('Octal.png')
w, h = img.size
pixels = img.load()

colors_data = []
for y in xrange(0, h, 20):
    for x in xrange(0, w, 20):
        col = pixels[x, y]

alphabet = set(colors_data)
alphabet_len = len(alphabet)

out_file = open('out.dat', 'wb')

colors = map(str, xrange(8))
for items in itertools.product(colors, repeat=alphabet_len):
    # get only possible variants without repetition
    if len(set(items)) == alphabet_len:
        alpha_to_item = dict(zip(alphabet, items))
        # get image as an octal number
        oct_number = ''.join(alpha_to_item.get(x) for x in colors_data)
        dec_number = int(oct_number, 8)
        # write all possible results
        if len(hex(dec_number)[2:-1]) % 2 != 0:
            out_file.write(('0' + hex(dec_number)[2:-1]).decode('hex') + '\n')


After running the script I've checked the file with huge number of possible results. And one of them was:

So a first part of this message is a password:

Level 7

On this level - a password was simply hidden in JPG file as plain text.

Level 8

In this level we have a PNG file. 
I've noticed a hint in PNG file after IEND block: r^g&b

And using the next script I've got a password to the next level:

from PIL import Image

img = Image.open('1.png')
w, h = img.size
pixels = img.load()
bits = ''

img_out = Image.new('RGB', (w, h))
img_out_pixels = img_out.load()

for j in xrange(h):
    for i in xrange(w):
        col = pixels[i, j]
        r, g, b = col
        bits += '1' if (r^g&b) & 0b1 else '0'
        img_out_pixels[i, j] = (255, 255, 255) if ((r ^ g & b) & 0b1) else (0, 0, 0)

bits = '0' + bits  # !!!!
out = ''
for i in xrange(0, len(bits), 8):
    out += chr(int(bits[i:i + 8], 2))
print `out`


Level 9

The last archive contained Flag.png file with part of QR code. Only a part :(

I've decoded QR code by hands :)
Here is the structure of QR code, I've filled each data blocks with color:

It is QR code version 4 (33x33). As you can see the mask (x % 3) is used here.
First block = 4 or byte encoding (8 bits per character).
Second block is a encoded message length = 58.
Next four blocks in the first column are t, j, c, t.
In such way all 58 bytes of data was extracted.

Flag is:

It was a really difficult task by Annabel Faylan. Thank you!
Inception! We need to go deeper! :)