Introduction

Computas åpen fagkveld was IRL CTF hosted by Computas at DIGS in Trondheim. This was my first time playing in an IRL CTF and it was pretty exciting and I cannot wait to do another one!
Haven’t played CTFs in a while and this reminded me of the joy of solving infosec challenges which makes me realize how passionate I’m about this field and finding solutions to problems related to it.

Table of contents

Challenge Category Points
Hello World! Forensics 10
Stegg Forensics 50
Hidden in Plain Sight Forensics 200
Secrets in secrets Forensics 300
Dørvakt Reversing 50
Avengers Assemble Reversing 100
Dørvakt 2 Reversing 100
java-encoder Reversing 350
Still pretty basic Cryptography 50
Velkommen til kodeknekking! Cryptography 50
Låst ned Cryptography 200
Show the seed Cryptography 200
Enten Eller Cryptography 250
SQL-Injection 101 Web 50
Robots4ever Web 75
Cookie-monster Web 100

Write-up

Forensics

Hello World!

Velkommen til Capture The Flag! Her er et flagg for å starte :) ctf{v3lkommen_til_CTF!}

This one is pretty straight forward the flag is given on the challenge description :)
Flag: ctf{v3lkommen_til_CTF!}

Stegg

Et enkelt bilde. Eller er det?

On this one we are given a PNG image file. From the challenge name we could derive that it has something todo with stenography.
Before taking any other steps we could try to look for the flag by seeing which strings we could read on the PNG file.

[morim@blossomsec stegg]$ file egg.png
egg.png: PNG image data, 360 x 360, 4-bit colormap, non-interlaced
[morim@blossomsec stegg]$ strings egg.png  | grep ctf
ctf{sp3ilegg_er_best}

And there it is! We got the flag just by looking at the strings :)
Flag: ctf{sp3ilegg_er_best}

Hidden in Plain Sight

Vent litt…. Hvor er oppgaven?

Now on this one I’ve got to admit that I’ve got pretty lost and wasted too much time on it. I couldn’t solve it during the CTF but aftewards I reached out to the CTF creators so they could help me find the flag.

It turns out the flag was hidden in an image file that was embedded on the source code of the index page of the CTF.

Image alt

Once again we will try the easy way an search for the flag in the strings of the image file.

[morim@blossomsec hidden_in_plain_sight]$ file secret.png 
secret.png: PNG image data, 1482 x 343, 8-bit/color RGBA, non-interlaced
[morim@blossomsec hidden_in_plain_sight]$ strings secret.png | grep ctf
ctf{made_you_look_at_the_brand!}

It turns out the easy way got us the flag again!
Flag: ctf{made_you_look_at_the_brand!}

Secrets in secrets

To solve this challenge it was required to solve the Hidden in plain sight one.
By reading the name of it we will assume it is something about the secret.png file where we found the last flag. That being said we’ll in take a deep look in to the image file to find any other files that might be embedded there.

[morim@blossomsec secrets_in_secrets]$ exiftool secret.png 
ExifTool Version Number         : 12.50
File Name                       : secret.png
Directory                       : .
File Size                       : 37 kB
File Modification Date/Time     : 2023:04:15 23:48:34+02:00
File Access Date/Time           : 2023:04:15 23:48:38+02:00
File Inode Change Date/Time     : 2023:04:15 23:48:34+02:00
File Permissions                : -rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 1482
Image Height                    : 343
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 11811
Pixels Per Unit Y               : 11811
Pixel Units                     : meters
Warning                         : [minor] Trailer data after PNG IEND chunk
Image Size                      : 1482x343
Megapixels                      : 0.508

After checking the metadata we got a warning saying that there is some data appended to the end of the file. We can use xxd to perform a hexdump so we can check for unusual data.

[morim@blossomsec secrets_in_secrets]$  xxd secret.png | tail
00008e10: 2100 0000 2100 0000 0400 1800 0000 0000  !...!...........
00008e20: 0100 0000 a481 0000 0000 666c 6167 5554  ..........flagUT
00008e30: 0500 03d2 7835 6475 780b 0001 04e8 0300  ....x5dux.......
00008e40: 0004 e803 0000 504b 0102 1e03 1400 0000  ......PK........
00008e50: 0800 7d86 8b56 2ddc 84a4 e42d 0000 6a31  ..}..V-....-..j1
00008e60: 0000 0d00 1800 0000 0000 0000 0000 a481  ................
00008e70: 5f00 0000 746f 7073 6563 7265 742e 6a70  _...topsecret.jp
00008e80: 6755 5405 0003 0d74 3564 7578 0b00 0104  gUT....t5dux....
00008e90: e803 0000 04e8 0300 0050 4b05 0600 0000  .........PK.....
00008ea0: 0002 0002 009d 0000 008a 2e00 0000 00    ...............

It looks like there are a few zip files appended on the image and the next step here is to extract them.

[morim@blossomsec secrets_in_secrets]$ binwalk -e secret.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 1482 x 343, 8-bit/color RGBA, non-interlaced
24434         0x5F72          Zip archive data, at least v1.0 to extract, compressed size: 33, uncompressed size: 33, name: flag
24529         0x5FD1          Zip archive data, at least v2.0 to extract, compressed size: 11748, uncompressed size: 12650, name: topsecret.jpg
36505         0x8E99          End of Zip archive, footer length: 22

[morim@blossomsec secrets_in_secrets]$ ls
secret.png  _secret.png.extracted
[morim@blossomsec secrets_in_secrets]$ ls _secret.png.extracted/
5F72.zip  flag  topsecret.jpg

We successfuly extracted three files:

  • 5F72.zip : A zip archive containing the files flag and topsecret.jpg
  • flag : A text file with the flag of the previous challenge
  • topsecret.jpg : A jpeg file of an old Computas logo

Once again we will try to check for embedded files on the newly discovered image.

[morim@blossomsec secrets_in_secrets]$ exiftool topsecret.jpg 
ExifTool Version Number         : 12.50
File Name                       : topsecret.jpg
Directory                       : .
File Size                       : 13 kB
File Modification Date/Time     : 2023:04:11 16:51:57+02:00
File Access Date/Time           : 2023:04:11 16:54:32+02:00
File Inode Change Date/Time     : 2023:04:16 00:02:34+02:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Image Width                     : 512
Image Height                    : 171
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 512x171
Megapixels                      : 0.088
[morim@blossomsec secrets_in_secrets]$ jhead topsecret.jpg 
File name    : topsecret.jpg
File size    : 12650 bytes
File date    : 2023:04:11 16:51:57
Resolution   : 512 x 171
JPEG Quality : 84

This time there was nothing unusual on the image metadata so we will run binwalk and stegseek to look for any data that might be hidden.

[morim@blossomsec secrets_in_secrets]$ binwalk topsecret.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01

[morim@blossomsec secrets_in_secrets]$ stegseek --seed topsecret.jpg 
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found (possible) seed: "0aec9e93"            
	Plain size: 56.0 Byte(s) (compressed)
	Encryption Algorithm: rijndael-128
	Encryption Mode:      cbc

It seems that stegseek found a possible file that was hidden in the image using steghide. Since we don’t know the passphrase used to encrypt and hide the data on the file we are going to use stegseek to find the passphrase and extract the data for us.

[morim@blossomsec secrets_in_secrets]$ stegseek --crack topsecret.jpg ~/Documents/Wordlists/rockyou.txt 
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: "1985"
[i] Original filename: "flag".
[i] Extracting to "topsecret.jpg.out".

[morim@blossomsec secrets_in_secrets]$ cat topsecret.jpg.out 
ctf{w3lcome_to_c0mputas_41243}

Another flag secured!
ctf{w3lcome_to_c0mputas_41243}

Reversing

Dørvakt

Dørvakten slipper meg ikke inn! Kan du finne passordet?

We are given an ELF binary file and as we did on the forensics challenges above, before wasting any more time, we could search for the flag on the strings of the binary.

[morim@blossomsec dorvakt]$ file guard 
guard: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0fcc120ec49c276d18ad492cddcfccc87130fa6f, for GNU/Linux 3.2.0, not stripped
[morim@blossomsec dorvakt]$ strings guard | grep ctf
ctf{mellon_50423}

It seems like we did it again.
Flag: ctf{mellon_50423}

Avengers Assemble

Jeg har testet ut denne nye metoden for å beskytte nettsiden min med en PIN-kode…

We are given a simple web app page with a submission form in which we can submit a PIN code to get the flag.
Avengers Assemble web app
After checking the Javascript loaded on the page we can see that the PIN validation is made on the client side which is a big nono regarding appsecurity.

JS Embedded on the html:


import { checkPinCode } from "./build/debug.js";

function getFlag(pinCode) {
    let key = "flag{w4sm_is_the_futur3_" + pinCode.toString() + "}";
    return key;
}

function click() {
    let userProvidedPin = document.getElementById("key").value;

    let isPin = checkPinCode(userProvidedPin);
    if (isPin) {
        document.getElementById("flag").innerHTML = getFlag(userProvidedPin);
    } else {
        document.getElementById("flag").innerHTML = "That's not the right pin code :(";
    }
}

document.getElementById("generate").addEventListener("click", click);

debug.js:

async function instantiate(module, imports = {}) {
  const { exports } = await WebAssembly.instantiate(module, imports);
  const memory = exports.memory || imports.env.memory;
  const adaptedExports = Object.setPrototypeOf({
    checkPinCode(a) {
      // assembly/index/checkPinCode(i32) => bool
      return exports.checkPinCode(a) != 0;
    },
  }, exports);
  return adaptedExports;
}
export const {
  memory,
  checkPinCode
} = await (async url => instantiate(
  await (async () => {
    try { return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url)); }
    catch { return globalThis.WebAssembly.compile(await (await import("node:fs/promises")).readFile(url)); }
  })(), {
  }
))(new URL("debug.wasm", import.meta.url));

debug.wasm:

(module
  (table $0 (;0;) 1 1 funcref)
  (memory $memory (;0;) (export "memory") 0)
  (global $~lib/memory/__data_end (;0;) i32 (i32.const 8))
  (global $~lib/memory/__stack_pointer (;1;) (mut i32) (i32.const 32776))
  (global $~lib/memory/__heap_base (;2;) i32 (i32.const 32776))
  (elem $0 (;0;) (i32.const 1) funcref)
  (func $assembly/index/checkPinCode (;0;) (export "checkPinCode") (param $a (;0;) i32) (result i32)
    (local $b i32)
    (local $c i32)
    (local $d i32)
    local.get $a
    i32.const 7
    i32.div_s
    local.set $b
    local.get $b
    i32.const 30
    i32.mul
    local.set $c
    local.get $c
    i32.const 1
    i32.add
    local.set $d
    local.get $d
    i32.const 5851
    i32.eq
    if
      i32.const 1
      return
    else
      i32.const 0
      return
    end
    unreachable
  )
)

The algorithm used to check if the provided PIN code is correct is on the debug.wasm file, since we can view the source code it’s just a matter of reversing the algorithm to derive the PIN from the value used to check if it is correct.
The algorithm is pretty simple and we will resume it to this:
pin / 7 * 30 + 1 = 5851
So now we can get the pin by reverting order of the operations:
pin = (5851 - 1) / 30 * 7 = 1365

After submitting the value 1365 the web app generates the flag.
Flag: flag{w4sm_is_the_futur3_1365}

Dørvakt 2

Jeg fant en ny dørvakt, men denne gangen ser det ut som det blir litt vanskeligere…

This one was quite more challenging than the first two challenges, so searching in the strings will not get us the flag this time!

We are given a ELF binary which uses the first argument passed on the code to generate the flag.

[morim@blossomsec dorvakt2]$ ./nerdy_guard 
Please give a number as an argument
[morim@blossomsec dorvakt2]$ ./nerdy_guard 123
-7038
That wasn't the correct password :(
[morim@blossomsec dorvakt2]$

We are going to need to fire up Ghidra to help us solve this one. After disassemble and decompiling the binary we can view the algorithm used to check if the provided PIN code is correct. Disassembled and decompiled  code

It looks like a pretty simple algorithm and it won’t take too much time bruteforcing it, so to do that we’ll write a python script:

#!/usr/bin/python

wanted_key = 433244
for argument_1 in range(1,2000000):
    key = argument_1
    for i in range(0,10):
        key = key * 2 - (argument_1 + 7)
    if key == wanted_key:
        print(f"CRACKED_PIN: {argument_1}")
        exit(0)
print("Better luck bruteforcing next time!")
[morim@blossomsec dorvakt2]$ time ./crack.py 
CRACKED_PIN: 440405

real	0m0.770s
user	0m0.762s
sys	    0m0.007s
[morim@blossomsec dorvakt2]$ ./nerdy_guard 440405
433244
ctf{the_password_is_440405}
[morim@blossomsec dorvakt2]$

After running the script above we cracked the PIN and got the flag!
Flag: ctf{the_password_is_440405}

java-encoder

Enkoding er noe skikkelig dritt, forhåpentligvis blir alt bedre når man implemtenterer det i java. :)

Unfortunately I couldn’t finish this one before the CTF ended but still managed to solve it afterwards. We were given a java file which we are going to decompile so we can analyze our approach to getting the flag. To do this we are going to fire up jd-gui which will give us the code below:

class MyJavaEncoder {
    public static void main(String[] args) {
    	String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{~-:}";
    	Scanner input = new Scanner(System.in);
    	System.out.println("Type in your flag guess: ");
    	String userFlagGuess = input.nextLine();
    	String[] flagParts = userFlagGuess.split("_");

        if (flagParts.length==3){
        	Boolean split1 = part1(flagParts[Integer.parseInt("A74", 16)%14]);
        	Boolean split2 = part2(flagParts[(alphabet.length()+1)%4]);
        	Boolean split3 = part3(flagParts[67352854%19], alphabet,userFlagGuess.length()%50);
        	
        	if (split1 && split2 && split3){
        		System.out.println("Correct flag! :)");
        		return;
        	}
        }
        System.out.println("That's not it, try again");
    }
    
    
     public static boolean part1(String s) {
     	if (s.length()!=12){return false;}
     	if (!s.substring(0,8).equals("d0ne~:cl")){return false;}
        try { 
            MessageDigest md = MessageDigest.getInstance("MD5"); 
            byte[] digest = md.digest(s.getBytes()); 
            BigInteger no = new BigInteger(1, digest); 
            String hash = no.toString(16); 
            while (hash.length() < 32) { 
                hash = "0" + hash; 
            } 
            return hash.equals("7716715cf6f91f8b4b51c55aa3312169"); 
        }  
        catch (NoSuchAlgorithmException e) { 
            throw new RuntimeException(e); 
        } 
    } 
    public static boolean part2(String s) {
    	try {
            return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8.toString())).equals("Y3Rme0gwdH5kYW1u");        
        } catch(UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }
    public static boolean part3(String s, String alphabet, int rot) {
        String o = "";
        for (char c: s.toCharArray()) {
            int pos = alphabet.indexOf(c);
            o += alphabet.charAt((pos+rot)%alphabet.length());
        }
        return o.equals("{H}X{JXQX6Z16HH{16Z"); 
    }
}

After analyzing the code we can notice a few things:

  • We have to input the actual flag to the provided code
  • The flag is split up to three different sections and each section has a different validation algorithm
  • We’ll need to reverse the checking algorithms on the three different sections to get the flag

part1(): This function validates flagParts[2] and it does that by checking its length which needs to be bigger than 12, then it gives us part of the flag because it checks if its first 8 chars are equal to the substring d0ne~:cl, after these two checks it calculates the MD5 hash of the provided flagPart. Since we got most of the flag, the hashing algorithm is MD5 and we are only missing three letters from this flagPart we can bruteforce our way out of this.

part2(): This one is pretty straight forward, it checks if the string in flagParts[0] is equal to the decoded base64 value of Y3Rme0gwdH5kYW1u.

part3(): This is were flagParts[1] is going to be validated, the validation process used on this one is done by encoding the flagPart in a custom algorithm and check it against the value {H}X{JXQX6Z16HH{16Z. To get this part we’ll need to revert the algorithm to decode the final value and it can be done by reversing the math operations done to determine the letter on the alphabet value. Now it seems like we are missing the value for to variable rot which is the length of the full flag, but we can determine its value by solving adding the length of the string {H}X{JXQX6Z16HH{16Z plus the values of part1() and part2() plus the number two (which is the number of underscores used to split the flag).

Now that we know what the code does and what we need to do for solving this challenge we are going to write a script in Python to solve it:

#!/usr/bin/python

import hashlib
import itertools
import base64

alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{~-:}"

def part3():
    key = "d0ne~:cl"
    attempts = 0
    for guess in itertools.product(alphabet, repeat=3):
        attempts += 1
        guess = key+''.join(guess)+"}"
        if hashlib.md5(guess.encode('utf-8')).hexdigest()  == "7716715cf6f91f8b4b51c55aa3312169":
            return guess
    return ""

def part2(p1_p3_len):
    cipher = "{H}X{JXQX6Z16HH{16Z"
    rot = p1_p3_len + len(cipher) + 2
    guess = ""
    for l in cipher:
        guess += alphabet[(alphabet.index(l)-rot)%len(alphabet)]
    return guess

def part1():
    return base64.b64decode("Y3Rme0gwdH5kYW1u").decode("utf-8")

def main():
    part_1 = part1()
    part_3 = part3()
    part_2 = part2(len(part_1)+len(part_3))
    print(f"{part_1}_{part_2}_{part_3}")

if __name__ == '__main__':
    main()
[morim@blossomsec java-encoder]$ time ./solve.py 
ctf{H0t~damn_r3ver5e-engin33ring_d0ne~:clap:}

real	0m0.034s
user	0m0.031s
sys	    0m0.004s

Even thought it didn’t count to get us points on the scoreboard we’ve managed to get the flag.
Flag: ctf{H0t~damn_r3ver5e-engin33ring_d0ne~:clap:}

Cryptography

Still pretty basic

Vi brukte en litt nyere kode denne gangen: Y3Rme3ByZXR0eV9iYXNpY181MjI2OH0=

Straightforward challenge we just need to decode the provided base64.
Flag: ctf{pretty_basic_52268}

Velkommen til kodeknekking!

Den første oppgaven er å knekke denne koden: pgs{nyyebnqfyrnqgbebzr} Lykke til!

We were given the value pgs{nyyebnqfyrnqgbebzr} which looks like it was been passed through a ROT13 algorithm, so the get the flag we can use CyberChef to decode it.

Flag: ctf{allroadsleadtorome}

Låst ned

“Vi fanget denne filen som ble sendt av en etteretningsorganisasjon, men vi klarer ikke å åpne den. Kan du hjelpe oss?”

We were given a password protected zipfile and a hint mentioning john the reaper. Since the hint mentioned john we will fire it up and crack the password using the rockyou.txt wordlist.

[morim@blossomsec last_ned]$ zip2john topsecret.zip > topsecret.hashes 
topsecret.zip/topsecret/ is not encrypted!
ver 1.0 topsecret.zip/topsecret/ is not encrypted, or stored with non-handled compression type
ver 1.0 efh 5455 efh 7875 topsecret.zip/topsecret/flag PKZIP Encr: 2b chk, TS_chk, cmplen=37, decmplen=25, crc=E168A732

[morim@blossomsec last_ned]$ john --wordlist=~/Documents/Wordlists/rockyou.txt topsecret.hashes
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
JamesBond007     (topsecret.zip/topsecret/flag)
1g 0:00:00:00 DONE (2023-04-14 14:33) 25.00g/s 6348Kp/s 6348Kc/s 6348KC/s dolphins04..1badchick
Use the "--show" option to display all of the cracked passwords reliably
Session completed

[morim@blossomsec last_ned]$ unzip topsecret.zip && cat topsecret/flag
Archive:  topsecret.zip
[topsecret.zip] topsecret/flag password: 
 extracting: topsecret/flag          
ctf{positively_sh0cking}

Once again pretty straight forward and we got the flag. Flag: ctf{positively_sh0cking}

Sow the seed

En kollega begynte å bruke AES-kryptering for å large passordet sitt til jobben. De er 100% sikre på at det ikke går an å knekke, men jeg er ikke så sikker på det..
Jeg tror fremdeles vi har ett ess i ermet vårt fordi jeg så på kameraene og fant ut at han krypterte passordet sitt kl 15:01:40 den 28. februar i år.
Jeg stjal også koden hans, så ikke si noe er du grei…

On this task we were given a zip archive in which we can find a JSON file that contains the flag in the form of ciphertext and the code used to generate that file. The first thing we need to do is analyzing the code.

from dataclasses import dataclass
import json
import random
import base64

from Crypto.Cipher import AES

@dataclass
class EncryptedMessage:
    nonce: bytes
    tag: bytes
    ciphertext: bytes

def save(message: EncryptedMessage):
    with open("melding.json", "w", encoding="utf-8") as f:
        obj = {
            "nonce": base64.b64encode(message.nonce).decode("utf-8"),
            "tag": base64.b64encode(message.tag).decode("utf-8"),
            "ciphertext": base64.b64encode(message.ciphertext).decode("utf-8"),
        }
        json.dump(obj, f, indent=4)

def load() -> EncryptedMessage:
    with open("melding.json", "r", encoding="utf-8") as f:
        obj = json.load(f)
        nonce = base64.b64decode(obj["nonce"])
        tag = base64.b64decode(obj["tag"])
        ciphertext = base64.b64decode(obj["ciphertext"])
        return EncryptedMessage(nonce, tag, ciphertext)


def encrypt(key: bytes, data: bytes) -> EncryptedMessage:
    cipher = AES.new(key, AES.MODE_GCM)
    nonce = cipher.nonce
    ciphertext, tag = cipher.encrypt_and_digest(data)

    return EncryptedMessage(nonce, tag, ciphertext)

def decrypt(key: bytes, message: EncryptedMessage) -> str:
    cipher = AES.new(key, AES.MODE_GCM, nonce=message.nonce)
    plaintext = cipher.decrypt(message.ciphertext).decode("utf-8")
    
    try:
        cipher.verify(message.tag)
        print("The message is authentic!")
        print("Plaintext: ", plaintext)
        return plaintext
    except ValueError:
        print("Key incorrect or message corrupted")
        return None


def main():
    key = random.randbytes(16)

    with open("flag.txt", "r", encoding="utf-8") as f:
        data = f.read().encode("utf-8")

    msg = encrypt(key, data)
    #save(msg)

    #msg = load()
    #decrypt(key, msg)

if __name__ == "__main__":
    main()

After looking at the code we can determine that to get the flag we need to decrypt the ciphertext in melding.json. We’ve got the nonce and the tag to verify the validity of the decription, the only thing we are missing is the key. If we look at the main function we can see that the key was generated using python’s random.randbytes(16).
Python’s ‘random.randbytes’ uses time as a seed to its PRNG, so if we know the exact timestamp in which the key was generated we can reproduce the random sequence that was used as a key, this timestamp was given in the challenge description.
First we need to get the timestamp in the unix epoch format:

[morim@blossomsec stjalet-kode]$ python
Python 3.10.10 (main, Mar  5 2023, 22:26:53) [GCC 12.2.1 20230201] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> t=datetime.datetime(2023,2,28,15,1,40)
>>> t.timestamp()
1677592900.0

Then we need to modify the stolen code to decrypt the flag:

...
def main():
    random.seed(1677592900)
    key = random.randbytes(16)
    msg = load()
    decrypt(key, msg)
...

After changing the code we need to run the code to get the flag:

[morim@blossomsec stjalet-kode]$ cp ../melding.json .
[morim@blossomsec stjalet-kode]$ python -mvenv env && source env/bin/activate && pip install -r requirements.txt
Collecting pycryptodome
  Using cached pycryptodome-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
Installing collected packages: pycryptodome
Successfully installed pycryptodome-3.17

[notice] A new release of pip available: 22.3.1 -> 23.0.1
[notice] To update, run: pip install --upgrade pip
(env) [morim@blossomsec stjalet-kode]$ python main.py 
The message is authentic!
Plaintext:  b'ctf{d4t3tim3_f0r_crypt0_i5_n0t_pr3fer4ble}'

Flag: ctf{d4t3tim3_f0r_crypt0_i5_n0t_pr3fer4ble}

Enten Eller

Jeg lagde min egen kode denne gangen! Men jeg vet ikke hvordan man dekrypterer den… Kan du hjelpe meg?
Ciphertext: 524d5e4e49564a6a574d4f6a04000807020845
key: 1985

On this challenge we were given a ciphertext, a key and a hint saying it has something to do with XOR and Vignere. This time I decided to use dcode.fr to decode the ciphertext throught this decoder:
Decoded cipher

And we secured another flag. Flag: ctf{xor_ftw_590231}

Web

Unfortunately I couldn’t get the write-ups for this challenges before the CTF site was taken offline.

But I’ll do my best to recreate them from memory…

SQL-Injection 101

This challenge was a simple webapp login page where we could inject a ' OR '1'=1';-- to get the flag

Robots4ever

The name of this one was a hint for us to check for the robots.txt file which contained a path where we could find the flag.

On this challenge we need to create an account to access the webapp. After the account creation and login done, we needed to fiddle around with the cookies of the webapp to secure the flag. It was as simple as changing the user defined in the cookie to the value admin and there it was!

Conclusion

The CTF was great, had a good time playing, the hosts were amazing, and I’m definitely joining it again if it ever happens in the future.
Most of the challenges were entry level ones but since there was a time constraint of only a few hours they couldn’t be really hard ones or else we wouldn’t get a single point. It was a wise choice to make them entry level.
Great initiative on behalf of Computas for doing an event like this since security awareness plays a major role in these modern times.