neverlanCTF 2020 - [Forensic] BitsnBytes (200pts)

Written by Maltemo, member of team SinHack.

Statement of the challenge

Description

https://challenges.neverlanctf.com:1150

Analyze

The webiste consisted of one image of a strange square with two different colors.

The image is in the svg format (Scalable Vector Graphics).

<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://wwww.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 288 288" width="288" height="288"> <rect x='0' y='0' width='12' height='12' style='fill:#00ff00' id='0'/><rect x='12' y='0' width='12' height='12' style='fill:#333136' id='1'/><rect x='24' y='0' width='12' height='12' style='fill:#333136' id='2'/>[...]<rect x='288' y='264' width='12' height='12' style='fill:#333136' id='574'/></svg>

It was refreshed every minutes.

Hypothesis 1 (picture oriented)

Two colors often means boolean values and/or binary message, I started with this hypothesis:

Maybe one inner square is equal to one bit ?

I started to code and got this draft script:

#!/usr/bin/python3 import requests from PIL import Image from cairosvg import svg2png import time import binascii def decode_binary_string(s): return ''.join(chr(int(s[i*8:i*8+8],2)) for i in range(len(s)//8)) try: while True: r = requests.get("https://challenges.neverlanctf.com:1150/svg.php") svg2png(bytestring=r.text,write_to='svg.png') im = Image.open("svg.png") l,h = im.size px = im.load() bin_msg = "" bin_msg_inverted = "" picture_size = 0 square_size = (l/24)-1 #-1 because convertion to png add a transparent line print("square size : {}".format(square_size)) for y in range(h): for x in range(l): if (x % square_size) == 0 and (y % square_size) == 0: if px[x,y] == (0, 255, 0, 255): bin_msg += "0" bin_msg_inverted += "1" elif px[x,y] == (51, 49, 54, 255): bin_msg += "1" bin_msg_inverted += "0" else: pass print("") print("") print(decode_binary_string(bin_msg))#text_from_bits(bin_msg)) print("") print("") print("---------------------------------") print("") print("") print(decode_binary_string(bin_msg_inverted))#text_from_bits(bin_msg_inverted)) print("") print("") print("=================================") time.sleep(60) except KeyboardInterrupt: exit()

With this approach, I didn’t thought of using the svg format (which is XML) and tried to convert svg to png with cairosvg library.

After getting my .png image, I started analyzing it with the pillow library (line 4).

I detected the size of the square for the image (because the size of the square was changing between every refresh).

Then I was decoding the picture square by square to get a binary string.
I was registering both possibilities in order to not miss the flag if I got the wrong correspondance color => value in my program.

I put a lot of spaces between answers because I had strings that overwhelming the screen (weird ascii characters).

Both of the message binary and inverted binary were wrong.

I concluded this hypothesis seemed to be false.

Hypothesis 2 (svg/xml oriented)

I started with a new hypothesis :

What if the SVG contained the information ?

After looking more in depth on the svg format, I started to realize that maybe python could parse and browse some xml.

After a quick search in the python3 documentation, I found all I needed to analyze the svg.

I decided to check directly the inner childs of the root tag <svg> and to get their colors.

I kept the hypothesis about the association of binary with colors.

Here is my script documented (I removed the inversed binary message for a cleaner code):

#!/usr/bin/python3 import requests # Library to make http requests import time # Library that we are using for the sleep function import xml.etree.ElementTree as ET # Library to parse and go through xml ''' Decode a binary already in string to ascii characters ''' def decode_binary_string(s): return ''.join(chr(int(s[i*8:i*8+8],2)) for i in range(len(s)//8)) try: # Infinity loop while True: #Gets the svg image r = requests.get("https://challenges.neverlanctf.com:1150/svg.php") #Parses the svg svg = ET.fromstring(r.text) bin_msg = "" #For every rectangle in the svg tag for rectangle in svg: #We get the color value, without caring of the size color = rectangle.get("style")[5:] #If the color is green if color == "#00ff00": bin_msg += "0" #If the color is dark grey elif color == "#333136": bin_msg += "1" #Prints the decoded message print(decode_binary_string(bin_msg)) time.sleep(60) #Waiting Ctrl + c to make a clean exit except KeyboardInterrupt: exit()

After the first run, I had a readable message, a time hash.

I decided to let my script run and make some researches about the time hash, all I got was a github repository.

After running the script for approximatively 40 minutes, I finaly got the flag !

Here are the logs:

./bitsandbytes.py time hash:9bc8397450fa1bf2388876d96f63ebda55cc3e25ef0270c5e287d8de30979 time hash:7c2d4640bdb50a06898b232030a1b316dc517d0afa1e17cca81dd1c966f16 time hash:6a079cf676a4b61a55bf97d86429614e4afb3e08b43500cf5c1e1a18132a0 time hash:068e7600cd84847912f92c7ba1fc07be0c648c7089393241a4fef85287a63 time hash:449018608af0d0132561eb6c2ca828197092ea429c4e49329b6819ba8121a time hash:60d63cd7f56d2837db1cbedb054a2ffb315d70bd7c3c14c8b68db9e612f44 time hash:ef6e362de79b8d5b797d34b36aec7eeae5ddc2d28216cc46a12eeab225101 time hash:6852a0351398d71a9046f8fe3291cede185b892359a33bf496bfaf74bbd3a time hash:da5125157687a76ff3a1404a8eccc38a208d7ccfe064f58a8e6730f30dae6 time hash:52828397d5b13524284f294f87e911366ea131ffea63ab06197d73bc6316e time hash:f30621a4268d4f6269a1511570360e8d3b43f1b5b67af8921765a736cdfc3 time hash:4335a95c97c5fc5ed17683576fb5f2352c80cc2103fc86d81bfb51aed2a4f time hash:cf8ade5e015fe21171ea816a1d3f17a187399e1292501d465b425dea4a42a time hash:f8637ceaa08ac2f8c428715fe467f3058096da3bdf7db3abe0aad4bcb7f9b time hash:bfc4b58a3d04282a27ffa78a33ef1b00e0063feb100606cf876ca93baaa35 time hash:ac79af5818db291f0805159c52f3dfba5a354c0d555bcdaa19640b450dd2b time hash:276cc024af04aea6b00fd8b6bbbb5f29979e2b6892b6aafe9b89122c9f322 time hash:02c78d9b7fc41740ab3f0383867e884927a25d868d41b76f44bac4d8c4025 time hash:8e7272eae9abebe256c5d0a73ea9b511e17a9d38cb0bf4ce60f50e6c33168 time hash:d17514ae6d9d33126471b0b50712a374c96e9bf9b2038486a12978a86c9dc time hash:17ae4b44f07d5e0456681db0fa9c660302f0b485301aa597f280dc9a8de7b time hash:ff250e3e5595f6ffef1c328e72665d8fd638cfb723b710cda01ced252489c time hash:3b7b05778852c9b096e05f079c629e3ae05989216f17ac08082c4f8068847 time hash:fa4523bab07f1b2ef6e6fad32c3d23d16459fdb4883a9780abdc8a84a4e6e time hash:f3d7d6d18a7c5df95b6ef516543a5ed7a5a4dfdb7c287015babf999915f7e time hash:4d64df2685939afe1fa5ecb8441427f20552bad548f195e5140068e40a1d3 time hash:ed5adbd515b29035a452a25e79ad4274381e27048fe6c6b08c2dfb0aefeba time hash:2f84ee67d9d65cd930bb541480ebf4e585d84704d823dfb09f8d7212fe3d7 time hash:043e2069b642f8eb6d18d0d712fff2125a046ce96daf403570fea400d2102 time hash:dffdff53a89ae1c821f453926f3cd6390e86d4005f922bbf8aeaf58766d9c time hash:a4a41d20339209ad58a6faf307e42e4e9981a2fbee3bae035621b5c9a111a time hash:924c132bbaef9b05618fb8e583cf3ed224b06bf1d4c1a0fc21fdfaa31e1ac time hash:04162e56dc70f6791b85e6ae370c54cf27f8d83dcf08ae823f0ed552feff8 time hash:e753409383e86469e338f8a6a5728f6d7b297f90a112738c4def97532c808 time hash:3a6806716c3ba863d0152ca30f92096eb0bdcd25a00c8eb56af004966aa8b time hash:52d10a32123140fc2201547c20e180d20a5710cd1bdb131fbba96564e85f8 time hash:7043d410e552f0081e2242b1f4e661df7b01c69b6142971a4beae1945f2e8 time hash:776b13d170b267e3bf8a77b3c2ad14afa2b9f36d9a6a9be310d8c9720a425 Now you've got it! Here's your flag: flag{its_all_ab0ut_timing}

TL;DR

The website was loading a new svg picture every minutes, that could contain the flag in binary within the svg xml <rectangles> tags. The aim was to automate this process with a program.

Flag

The flag is flag{its_all_ab0ut_timing}


Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.