Web – 100pts


Foiled by gdpr again…


Opening the challenge page we get a Privacy notice.

Taking a look in Burp we can see that it has discovered a endpoint named flag_policy.

Passing the request to Repeater and sending the request returns the flag.



Reverse Engineering – 218pts


Can you crack the code before you get demoted to weenie hut junior?


Opening the binary in IDA we find the flag routine in the main function.

So it generates a random number, XOR:s this with our input and then with 0x3597B741. The result is then compared with another random number and if they match we get the “Password accepted” message. Lets take a look at the _revvy function.

Now we have the initial seed for the PRNG, so we can retrieve the generated random numbers and finally get the correct password. To get the random numbers we can write a small C program.

#include <stdio.h>
#include <stdlib.h>
int main(void)
    for(int i = 0; i<2; i++)
        printf(" %d ", rand());
    return 0;

When we run this we get the following numbers.


Using those we can write a small python script to reverse the password.

#!/usr/bin/env python3

rand1 = 1915766271
rand2 = 318420489

xor_val = 0x3597B741

res = rand2 ^ xor_val
res = res ^ rand1


Running this returns the flag.



Reverse Engineering – 341pts


Should we make this one swirling-as-a-service?


Attached is a image called flag.png.

The website for the challenge lets you upload a image which it then swirls.

Mirroring the page we can start to analyze and change the code. In the JavaScript file we can find the swirling routine.

// Fragment shader program

  const fsSource = `
    precision highp float;

    varying vec2 vPos;

    uniform sampler2D uSampler;
    uniform vec2 uResolution;
    uniform float uTime;

    uniform float uRadius;
    uniform float uSwirlFactor;

    void main(void) {
      vec2 uv = gl_FragCoord.xy / uResolution.xy;

      float dist = distance(uv, vec2(0.5));
      mat2 rotmat;
      if (dist < uRadius) {
          float percent = (uRadius - dist) / uRadius;
          float angle = percent * percent * uSwirlFactor * uTime;
          float sina = sin(angle);
          float cosa = cos(angle);
          rotmat = mat2(cosa, sina, -sina, cosa);
      } else {
          rotmat = mat2(1, 0, 0, 1);

      vec2 texCoord = rotmat * (uv - vec2(0.5)) + vec2(0.5);
      vec4 diffuse = texture2D(uSampler, texCoord);
      gl_FragColor = diffuse;

If we change float angle = percent * percent * uSwirlFactor * uTime; to float angle = percent * percent * -uSwirlFactor * uTime; we should be able to reverse the swirl-effect. When uploading the flag image we get the unswirled version.

Reading the QR code we get a link to a gist containing the flag.



Reverse Engineering – 491pts


Akhbaar has opened an online casino to pass time during the pandemic. Come play at the roulette wheel.


Opening the binary in IDA and taking a look at the strings we can find some interesting strings.

Taking a look at the usage of the format string we get a bunch of values.

Extracting the values we get a Base32 encoded string. Using the following script we can decode the flag.

#!/usr/bin/env python3

import struct
import base64

data = struct.pack('>IIIIIIIIII', 0x4D4A5258, 0x495A5433, 0x47525657, 0x51595255, 0x47525A44, 0x434E4A55, 0x4E555948, 0x454D444F, 0x50554641, 0x3D3D3D3D)



Reverse Engineering – 492pts


Just an image file, nothing to see here.


All we get here is a PNG image.

Zooming in on the text we can see that it’s some hex values.

In the metadata we find a Google drive link and the same hex-data in the User Comment field.

User Comment                    :  0x00, 0x62, 0x00, 0x63, 0x00, 0x74, 0x00, 0x66, 0x00, 0x7B, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x07, 0x6E, 0x00, 0x73, 0x06, 0x6F, 0x08, 0x64, 0x0B, 0x6F, 0x00, 0x6E, 0x0A, 0x77, 0x07, 0x72, 0x09, 0x73, 0x0C, 0x72, 0x12, 0x77, 0x11, 0x64, 0x0B, 0x7D

The link contains Pico-8 for various platforms, after downloading the specific version we can open the PNG file as a Pico-8 program. To do this we need to rename the file to 1000words.p8.png and put it in the carts directory.

Now we are able to load the program.

When running the program we get a demo with the some other hex values.

Ok, lets take a look at the code, to exit the demo we press ESC, and then in the prompt we press ESC again to enter the editor. Looking around the code we find an assignment to the msg variable.

Taking a look at the lz function we can find the algorithm for the generation of the values seen in the demo and the values found in the original image.

So we have a LZ like compression algorithm. Lets write a decompression routine using the hex-data found in the original image.

#!/usr/bin/env python3

cb = {

input = open('dump.bin', 'rb').read()

code = 1
decoded = ''

for idx in range(0, len(input), 2):
    if input[idx] != 0:
        cb[code] = cb[input[idx]] + chr(input[idx+1])
        decoded += cb[code]
        code += 1
        decoded += chr(input[idx+1])
        cb[code] = chr(input[idx+1])
        code += 1


Running this we get the flag.



Misc – 490pts


I wrote a cool new Minecraft datapack to find ELVES!! Well, actually, just one…


Attached is a zip containing a Minecraft datapack containing a bunch of functions. Checking the initchecks.mfunction we can see a lot of calls to all the checkN functions.

scoreboard players set @a checksPassed 0
scoreboard players set @a search 1
scoreboard objectives setdisplay sidebar checksPassed
tellraw @a "Looking for an elf.."
function elfcraft:checks/check0
function elfcraft:checks/check1
function elfcraft:checks/check2
function elfcraft:checks/check225
function elfcraft:checks/check226
function elfcraft:checks/check227
execute as @a[scores={search=1}] if score @s checksPassed matches 228 run tellraw @a "found one!"
scoreboard players set @a search 0

Taking a look at the check functions we can see that all of them are checking if a certain block is located at a certain position in the world.

scoreboard players set @a localChecks 0

execute as @a[scores={search=1}] if block ~0 ~-1 ~0 minecraft:white_concrete run scoreboard players add @a localChecks 1
execute as @a[scores={search=1}] if block ~1 ~-1 ~0 minecraft:white_concrete run scoreboard players add @a localChecks 1
execute as @a[scores={search=1}] if block ~2 ~-1 ~0 minecraft:white_concrete run scoreboard players add @a localChecks 1
execute as @a[scores={search=1}] if block ~14 ~-1 ~2 minecraft:white_concrete run scoreboard players add @a localChecks 1
execute as @a[scores={search=1}] if block ~12 ~-1 ~3 minecraft:white_concrete run scoreboard players add @a localChecks 1
execute as @a[scores={search=1}] if block ~12 ~-1 ~4 minecraft:white_concrete run scoreboard players add @a localChecks 1

execute as @a[scores={search=1}] if score @s localChecks matches 33 run scoreboard players add @s checksPassed 1

We can also see that the z-coordinate is the same across all the functions, so in reality it’s not a 3d axis but we should be able to recreate the positions using a 2d plane.

Using this we can write a script that parses all the mfunction files, take the x and y coordinates and use the coordinates to paint a pixel on a image to see what we end up with.

#!/usr/bin/env python3

import os
import sys
from PIL import Image, ImageDraw

img ="RGB", (50,1400), "white")
draw = ImageDraw.Draw(img)

coords = []
data_path = 'elfcraft/data/elfcraft/functions/checks/'

for file in os.listdir(data_path):
    current = os.path.join(data_path, file)
    if os.path.isfile(current):
        indata = open(current)
        data = indata.readlines()
        for line in data:
            if 'block' in line:
                arr = line.replace('~', '').split(' ')
                coords.append((int(arr[5]), int(arr[7])))

for coord in coords:
    img.putpixel(coord, 0xff)'out.png')

After running this script we end up with an image containing a bunch of hex-values, after dumping all values to a file we end up with an ELF file.

Opening the ELF in IDA we see the following.

So all the program does is reading a char from input, XOR:ing each byte of a byte array with the input and prints the result.

Extracting the byte array from byte_80480CB we get this.

00 01 16 04 19 0F 53 0C 51 01 10 03 56 04 16 3D 27 2E 24 01 10 56 04 16 1F

Since the first byte is 0x00 we can assume that the value we need to XOR the data with is b, since we know the start of the flag is bctf{. XOR:ing the bytes with b we get the flag.