NZCSC tends to repeat challenges or at-least the themes of challenges yearly yet there is a distinct lack of resources such as write-ups from previous years. This aims to solve that while also just being a nice reference for things.

“The S in NZCSC stands for stego”

  • HexF

The following write-ups are either nice, concise paths to the solution I’ve improved in post to remove extra fluff or the path I took to actually solve them including dead ends. Further to this, the official write-ups deviate from how I solved a few challenges and where applicable I have included comments discussing the differences under each challenge.

Challenge & Link Description
Challenge 1 HTML comment
Challenge 2 File headers
Challenge 3 Cookies
Challenge 4 Crypto
Challenge 5 Stego + Spectrogram’s
Challenge 6 Crypto
Challenge 7 Rev
Challenge 8 Pikalang
Challenge 9 Length extension attack
Challenge 10 Stego
Challenge 11 Baby’s first SQLi
Challenge 12 Rev + Crypto + Lucky guesses
Challenge 13 Packet capture + Base64
Challenge 14 Stego + DTMF
Challenge 15 Document modification
Challenge 16 Git + Stego

Challenge 1 Link to heading

We get given a web page with a lot of lorem ipsum text. Inspect the HTML and find the flag stored as a comment.

Challenge 2 Link to heading

We get given a PNG file that doesn’t appear to load correct.

file simple.png

Results in

simple.png: Zip archive data, at least v2.0 to extract, compression method=store

Which fingerprints it as a ZIP file although we note the IHDR header when inspecting the raw data:

00000000: 504b 0304 1400 0800 0000 000d 4948 4452  PK..........IHDR

Looks like all we need to do is fix the headers. Lets change them to valid PNG headers:

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR

Now we can open the image and get the flag.

Challenge 3 Link to heading

When navigating to the website we can see that the page sets two cookies, user and pass. Use these in the login form to get the flag.

Login form Cookies

Challenge 4 Link to heading

I did not personally solve this challenge, writeup is courtesy of HexF

We get given a text file with the following content as well as the hint โ€œThis AI was trained on shapes and math.โ€

MTczMjEyMTh7NTA2NTA4NTU4ODkxMTg1NTcwNDE5OTEzNjV9ICgxMiw0Nyw3MSw5MCk=

This is obviously base64, which when decoded gives us:

17321218{50650855889118557041991365} (12,47,71,90)

From this, we can also notice different parts of the message such as

  • ciphertext: 17321218{50650855889118557041991365}
  • a key: (12,47,71,90)

Looking more at the ciphertext, we find curly brackets, indicating the cipher is likely an alphabet-only cipher, containing no punctuation. This reduces our search for possible ciphers down even more.

Knowing the flag format as well, this gives us a good guess that 17321218 should map to flag. This gives 2 digits per character. The digits also appear to only go up to a maximum of 99. This is a clear indication of decimal numbers.

With a little bit of crude python code we almost get there doing:

CT  = [17, 32, 12, 18, 50, 65, 8, 55, 88, 91, 18, 55, 70, 41, 99, 13, 65]
KEY = [12, 47, 71, 90]

ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

for i, c in enumerate(CT):
    key = KEY[i % 4]
    print(ALPHABET[((c - key)) % len(ALPHABET)], end="")
    
#FLTGMSPRYSZRGUCBB

So, with this we are pretty confident the presence of only 26 letters in the alphabet.

Through more googling and fiddling around we come across the Mexican Army Cipher Wheel. This has a 4-number key, and has 99 different positions, just like we identified before. Plugging the key and the number pairs from the plaintext into the dcode page gives us the flag: FLAGDUWKYBGKZUJBU.

Challenge 5 Link to heading

We get given the following file:

Le file

The little bit of white which may be a flag in small text in the top right is a red herring.

Running exiftool over the file reveals this:

exiftool challenge5.jpg 
ExifTool Version Number         : 12.40
File Name                       : challenge5.jpg
Directory                       : .
File Size                       : 250 KiB
File Modification Date/Time     : 2023:06:03 18:23:31+12:00
File Access Date/Time           : 2023:06:03 18:23:32+12:00
File Inode Change Date/Time     : 2023:06:03 18:23:31+12:00
File Permissions                : -rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Artist                          : j8FlkmzERp$w^kBsjIv7w&ixI8HC58@fC7BLaFneq286#5h*p6
Y Cb Cr Positioning             : Centered
Image Width                     : 512
Image Height                    : 512
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 512x512
Megapixels                      : 0.262

Now the Artist value looks interesting but doesn’t appear to be some form of encoding, maybe we need it later?

After running further stego tools, I got to using binwalk on the file which reveals an embedded zip file which contains a password protected .wav file. Maybe that Artist value we discovered earlier is the password? After giving it a go it turns out that it was indeed the password and we now have the output wav file.

Listening to the audio reveals no obvious encodings so we open the file with Audacity. NZCSC has a habit for spectrogram based flags, lets look at that? Looking at the spectrogram reveals the flag and after a bit of playing around I managed to get the right value for the flag.

Spectrogram

That’s BsuDBpNhJJcPixb once you get past the illegibility.

Gotchas Link to heading
Guessing Link to heading

The spectrogram displays the characters pretty badly and even after tweaks it’s still required guessing some characters which isn’t ideal. (CTF’s shouldn’t be guessing challenges.)

Challenge 6 Link to heading

We get given this:

๐Ÿฏ๐ŸŽ๐Ÿ ๐Ÿฒ๐ŸŽฎ๐Ÿฌ๐Ÿฌ๐Ÿฌ๐ŸŽŸ๐ŸŽ๐ŸŽ๐ŸŒช๐ŸŒ›๐ŸŒ†๐Ÿฌ๐ŸŒ’๐ŸŒŒ๐ŸŒŒ๐Ÿฆ๐ŸŒž๐ŸŽด 

This is the code to get the flag:

flag = "๐Ÿฏ๐ŸŽ๐Ÿ ๐Ÿฒ๐ŸŽฎ๐Ÿฌ๐Ÿฌ๐Ÿฌ๐ŸŽŸ๐ŸŽ๐ŸŽ๐ŸŒช๐ŸŒ›๐ŸŒ†๐Ÿฌ๐ŸŒ’๐ŸŒŒ๐ŸŒŒ๐Ÿฆ๐ŸŒž๐ŸŽด"

for i, c in enumerate(flag):
    x = ord(c) - ord("๐Ÿฏ")
    x = int((x * 6) // 18)
    x += ord("f")
    print(chr(x), end="")

Explanation:

So we know the first characters are going to be flag, lets focus on the first two.

f and l are 6 characters apart, ๐Ÿฏ and ๐ŸŽ are 18 apart which implies the difference between consecutive emojis is going to be 1/3 (6/18) which is simply the difference between the letters. Given we know ๐Ÿฏ is f, we can remove that from all characters in order to make them relative. Finally, we add to underlying value of f to bring the final result into ascii range and thus we have the flag.

Challenge 7 Link to heading

We get given an exe, when decompiling in Ghidra I noticed the following:

Decompiled code

When we invoke the exe it prompts for user input. Having seen in Ghidra that it checks against the string __i686.get_pc_thunk.bx I simply passed that in and received the flag.

Getting the flag

Official write-up thoughts Link to heading

The official write-up suggests we were meant to conduct a buffer overflow style exploit against the binary, however, the write-up does fail to mention how someone may jump to that conclusion with the implication being that ‘The file expects data as input which … might have a buffer overflow exploit’.

If your asking me, that’s quite the logical jump to make, and it then also requires a user to figure out how to conduct a successful buffer overflow as well as guessing the correct length for the overflow. All in all, far more complicated than it actually was.

Challenge 8 Link to heading

Downloading the file reveals the following content:

pi pi pi pi pi pi pi pi pi pi pika pipi pi pipi pi pi pi pipi pi pi pi pi pi pi pi pipi pi pi pi pi pi pi pi pi pi pi pichu pichu pichu pichu ka chu pipi pipi pipi pipi pi pi pikachu pi pi pi pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pi pi pi pikachu pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pikachu pi pi pi pi pi pi pikachu pi pi pi pi pi pikachu pi pikachu pi pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pikachu ka ka ka ka ka pikachu ka ka ka ka ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pi pi pi pi pi pikachu pikachu pi pikachu pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pi pi pi pi pi pi pikachu pi pi pi pi pi pi pi pi pi pi pi pi pikachu 

Which is an esoteric cipher I recognize from years past. We can use dcode.fr to get the flag easily.

Challenge 9 Link to heading

We get given the following site:

The submission form

We can submit various form values to get a resulting URL. If the mac matches the params we get an image, however when trying to do 30 as the epoch value we note the following error:

Mac error

Seems like we can’t get the relevant data into the MAC via the underlying implementation, possibly due to the secret length wrapping when we add that extra param? I’m really not sure but it looks like it may be a length extension attack.

I originally looked at hashsoup as a tool to use but given the lackluster documentation I moved to hlextend.

For this we need to know the hashing algorithm so off to Hash Analyzer we go, after throwing in the mac parameter we find out it is SHA2-256 which is susceptible to this type of attack.

The following was my final code:

import hlextend

sha = hlextend.new("sha256")
out = sha.extend(
    b"&epoch=10",
    b"&model=default&epoch=10&epoch=10",
    17,
    "7e1fe32820b829e98c932bd38b1894ce7330b189a0eed09e859d2a65eaa4d885",
)
print(out)
print(sha.hexdigest())

This script provides us with the following output values:

b'&model=default&epoch=10&epoch=10\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x88&epoch=10'
cb8a6165f88212dad7f7da045f6eae449e50003e5ac5889202f80c980e0b6fc6

After we URL encode the bytes and change the mac we receive this as the final URL:

https://r0.nzcsc.org.nz/challenge9/artistintelligence.php?mac=cb8a6165f88212dad7f7da045f6eae449e50003e5ac5889202f80c980e0b6fc6&model=default&epoch=10&epoch=10%80%00%00%00%00%00%00%00%00%00%00%00%00%01%88&epoch=10

When navigating to that URL we receive the flag:

The flag

Gotchas Link to heading
URL encodings Link to heading

I threw \x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x88 into CyberChef’s URL encoder which produced the following output:

%5Cx80%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx00%5Cx01%5Cx88

It took waaay too long to notice it was only escaping the \ as a literal string and not the bytes themselves.

I.e. It was %5Cx80 when it should have been %80

Secret length Link to heading

Yea it was 17, not 18. Woo zero indexing in tooling

Challenge 10 Link to heading

When navigating to the challenge all we see is this:

Chal

Which indicates we should look at robots.txt, which in turn gives us the following:

User-agent: *
Disallow:/images/
Disallow:/images/rougerobots.png

As this is a PNG file I like to run zsteg over them with the -a option to retrieve misc output. Amongst the mess is actually two flags, one is a false flag for if you attempt to getting the data via different means. The following is the actual flag and the invocation used to get it.

zsteg -a rougerobots.png

Flag

Official write-up thoughts Link to heading

The official write-up calls this challenge 11 when it’s actually challenge 10.

Challenge 11 Link to heading

We get given another login form, as we’ve already solved the cookie based challenge this is likely to be SQLi so lets test for it.

Inputting a single ' to check to see if anything happens and we receive the following:

Warning: mysqli_num_rows() expects parameter 1 to be mysqli_result, bool given in /var/www/html/challenge11/submit.php on line 14

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/challenge11/submit.php:14) in /var/www/html/challenge11/submit.php on line 20

Yup, this is a SQLi challenge. I love me some SQLi so lets go (although it turned out to be extremely basic).

When using the following basic input:

' OR 1=1 -- comment

Which gives us the flag:

Flag

Gotchas Link to heading
Comment syntax Link to heading

Simply doing ' OR 1=1 -- doesn’t work as the comment is not evaluated and results in an error. You need something after the -- to have the comment be enforced.

Official write-up thoughts Link to heading

The official write-up states the following:

The challenger must enter the visible command, “’ OR 1=1 –”, on both the username field and password field.

The code checks for both entries if the fields are empty or not and will return an error message if you only entered one field.

However, the reason you need to enter both fields using the payload from the write-up is because the comment is not being fully evaluated. (See the gotcha section above)

When adding text after the --, you can insert the payload into a single field and receive the flag.

The write-up also implies that is the required payload, but I found ' OR 1=1 # worked fine as well if my memory serves me correctly.

The official write-up calls this challenge 10 when it’s actually challenge 11.

Challenge 12 Link to heading

We get given the following file

Disassembling we get the following functions of interest:

class std::string* func(class std::string* arg1, void* arg2, void* arg3, 
    int32_t arg4)
{
    void* fsbase;
    int64_t rax = *(fsbase + 0x28);
    void var_25;
    std::allocator<char>::allocator(&var_25);
    std::string::string(arg1, &data_3005);
    std::allocator<char>::~allocator(&var_25);
    for (int32_t var_24 = 0; var_24 < arg4; var_24 = (var_24 + 1))
    {
        std::string::operator+=(arg1, (*(arg3 + (var_24 << 2)) & *(arg2 + (var_24 << 2))));
    }
    if (rax == *(fsbase + 0x28))
    {
        return arg1;
    }
    __stack_chk_fail();
    /* no return */
}

Within main we see two calls to this func

void var_2d8;
func(&var_2d8, &var_258, &var_138, 0x21);
void var_2b8;
func(&var_2b8, &var_1c8, &var_a8, 0x21);

After using GDB we can dump the register values that the callee makes before reproducing func locally in order to offline generate the URL. After far too long and little to no output, changing the AND to other operators such as XOR resulted in an AWS lambda output.

https://p5jnncvx2abbts6cb3wreofg4u0cnimp.lambda-url.ap-southeast-2.on.aws

The lambda contains this:

"& .  . 8  &  + & 8 ( . 3 & 9 . 3 ,  + . * 1 )  4 +  ( 4 2 5 : 9 * 7  8 ( . * 3 ( *  9 - & 9  & . 2 8  9 4  ( 7 * & 9 *  . 3 9 * 1 1 . , * 3 9  2 & ( - . 3 * 8  ( & 5 & ' 1 *  4 +  5 * 7 + 4 7 2 . 3 ,  ( 4 2 5 1 * =  9 & 8 0 8  9 7 & ) . 9 . 4 3 & 1 1 >  & 8 8 4 ( . & 9 * )  < . 9 -  - : 2 & 3  . 3 9 * 1 1 . , * 3 ( *  8 : ( -  & 8  1 * & 7 3 . 3 ,  7 * & 8 4 3 . 3 ,  5 7 4 ' 1 * 2  8 4 1 ; . 3 ,  & 3 )  5 * 7 ( * 5 9 . 4 3  & .  . 3 ; 4 1 ; * 8  9 * ( - 3 . 6 : * 8  1 . 0 *  2 & ( - . 3 *  1 * & 7 3 . 3 ,  3 * : 7 & 1  3 * 9 < 4 7 0 8  3 & 9 : 7 & 1  1 & 3 , : & , *  5 7 4 ( * 8 8 . 3 ,  & 3 )  ( 4 2 5 : 9 * 7  ; . 8 . 4 3  . 9  - & 8  9 - *  5 4 < * 7  9 4  7 * ; 4 1 : 9 . 4 3 . ? *  8 4 ( . * 9 >  & 3 )  . 2 5 7 4 ; *  * + + . ( . * 3 ( >  & 3 )  & ( ( : 7 & ( >  & .  8 > 8 9 * 2 8  ( & 3  & 3 & 1 > ? *  ; & 8 9  & 2 4 : 3 9 8  4 +  ) & 9 &  ) * 9 * ( 9  5 & 9 9 * 7 3 8  & 3 )  2 & 0 *  & ( ( : 7 & 9 *  5 7 * ) . ( 9 . 4 3 8   9 - . 8  * 3 & ' 1 * 8  9 - *  ) * ; * 1 4 5 2 * 3 9  4 +  . 3 9 * 1 1 . , * 3 9  5 * 7 8 4 3 & 1  & 8 8 . 8 9 & 3 9 8  8 * 1 +  ) 7 . ; . 3 ,  ( & 7 8  & 3 )  & ) ; & 3 ( * )  + & ( . & 1  7 * ( 4 , 3 . 9 . 4 3  8 > 8 9 * 2 8  & .  & 1 8 4  5 7 * 8 * 3 9 8  * 9 - . ( & 1  ( 4 3 ( * 7 3 8  7 * , & 7 ) . 3 ,  5 7 . ; & ( >  / 4 '  ) . 8 5 1 & ( * 2 * 3 9  & 3 )  ' . & 8  7 * , : 1 & 9 . 4 3  & 3 )  7 * 8 5 4 3 8 . ' 1 *  ) * ; * 1 4 5 2 * 3 9  & 7 *  * 8 8 * 3 9 . & 1  9 4  & ) ) 7 * 8 8  9 - * 8 *  ( - & 1 1 * 3 , * 8  & . 8  . 2 5 & ( 9  4 3  8 4 ( . * 9 >  . 8  8 . , 3 . + . ( & 3 9  & 3 )  . 9  7 * 6 : . 7 * 8  ( & 7 * + : 1  ( 4 3 8 . ) * 7 & 9 . 4 3  & .  7 * 8 * & 7 ( -  & 3 )  ) * ; * 1 4 5 2 * 3 9  ( 4 3 9 . 3 : *  9 4  5 : 8 -  9 - *  ' 4 : 3 ) & 7 . * 8  4 +  < - & 9  . 8  5 4 8 8 . ' 1 *  . 3  9 - . 8  + . * 1 )  9 - *  + : 9 : 7 *  4 +  & .  . 8  5 7 4 2 . 8 . 3 ,  & 3 )  4 + + * 7 8  9 * ( - 3 4 1 4 , . ( & 1  & ) ; & 3 ( * 2 * 3 9 8  & 3 )  8 4 ( . * 9 & 1  5 7 4 , 7 * 8 8  & .  - & 8  ' 4 9 -  5 4 8 . 9 . ; *  & 3 )  3 * , & 9 . ; *  . 2 5 1 . ( & 9 . 4 3 8  & 3 )  8 9 7 . 0 . 3 ,  &  ' & 1 & 3 ( *  . 8  ( 7 : ( . & 1  & . 8  7 * 8 5 4 3 8 . ' 1 *  & 3 )  * 9 - . ( & 1  : 8 *  . 8  * 8 8 * 3 9 . & 1  + 4 7  . 9 8  8 : ( ( * 8 8 + : 1  . 3 9 * , 7 & 9 . 4 3  . 3 9 4  4 : 7  1 . ; * 8  . 3  8 : 2 2 & 7 >  & .  . 8  & 3  * ; * 7  * ; 4 1 ; . 3 ,  ) . 8 ( . 5 1 . 3 *  < . 9 -  9 - *  5 4 9 * 3 9 . & 1  9 4  9 7 & 3 8 + 4 7 2  2 : 1 9 . 5 1 *  & 8 5 * ( 9 8  4 +  8 4 ( . * 9 >  + 1 & , ` + + ) * & + + + + ' + + ( + + + * b  . 9  5 7 * 8 * 3 9 8  * = ( . 9 . 3 ,  5 4 8 8 . ' . 1 . 9 . * 8  & 3 )  ) * 2 & 3 ) 8  4 : 7  ( 4 1 1 * ( 9 . ; *  & 9 9 * 3 9 . 4 3  + 4 7  . 9 8  7 * 8 5 4 3 8 . ' 1 *  ) * 5 1 4 > 2 * 3 9  9 4  * 3 8 : 7 *  &  5 4 8 . 9 . ; *  & 3 )  . 3 ( 1 : 8 . ; *  + : 9 : 7 *"

When reading Esoteric langs it doesn’t appear to be anything listed which is a pain.

Maybe its some form of cipher? Let’s try make it nicer to work with.

text = "& .  . 8  &  + & 8 ( . 3 & 9 . 3 ,  + . * 1 )  4 +  ( 4 2 5 : 9 * 7  8 ( . * 3 ( *  9 - & 9  & . 2 8  9 4  ( 7 * & 9 *  . 3 9 * 1 1 . , * 3 9  2 & ( - . 3 * 8  ( & 5 & ' 1 *  4 +  5 * 7 + 4 7 2 . 3 ,  ( 4 2 5 1 * =  9 & 8 0 8  9 7 & ) . 9 . 4 3 & 1 1 >  & 8 8 4 ( . & 9 * )  < . 9 -  - : 2 & 3  . 3 9 * 1 1 . , * 3 ( *  8 : ( -  & 8  1 * & 7 3 . 3 ,  7 * & 8 4 3 . 3 ,  5 7 4 ' 1 * 2  8 4 1 ; . 3 ,  & 3 )  5 * 7 ( * 5 9 . 4 3  & .  . 3 ; 4 1 ; * 8  9 * ( - 3 . 6 : * 8  1 . 0 *  2 & ( - . 3 *  1 * & 7 3 . 3 ,  3 * : 7 & 1  3 * 9 < 4 7 0 8  3 & 9 : 7 & 1  1 & 3 , : & , *  5 7 4 ( * 8 8 . 3 ,  & 3 )  ( 4 2 5 : 9 * 7  ; . 8 . 4 3  . 9  - & 8  9 - *  5 4 < * 7  9 4  7 * ; 4 1 : 9 . 4 3 . ? *  8 4 ( . * 9 >  & 3 )  . 2 5 7 4 ; *  * + + . ( . * 3 ( >  & 3 )  & ( ( : 7 & ( >  & .  8 > 8 9 * 2 8  ( & 3  & 3 & 1 > ? *  ; & 8 9  & 2 4 : 3 9 8  4 +  ) & 9 &  ) * 9 * ( 9  5 & 9 9 * 7 3 8  & 3 )  2 & 0 *  & ( ( : 7 & 9 *  5 7 * ) . ( 9 . 4 3 8   9 - . 8  * 3 & ' 1 * 8  9 - *  ) * ; * 1 4 5 2 * 3 9  4 +  . 3 9 * 1 1 . , * 3 9  5 * 7 8 4 3 & 1  & 8 8 . 8 9 & 3 9 8  8 * 1 +  ) 7 . ; . 3 ,  ( & 7 8  & 3 )  & ) ; & 3 ( * )  + & ( . & 1  7 * ( 4 , 3 . 9 . 4 3  8 > 8 9 * 2 8  & .  & 1 8 4  5 7 * 8 * 3 9 8  * 9 - . ( & 1  ( 4 3 ( * 7 3 8  7 * , & 7 ) . 3 ,  5 7 . ; & ( >  / 4 '  ) . 8 5 1 & ( * 2 * 3 9  & 3 )  ' . & 8  7 * , : 1 & 9 . 4 3  & 3 )  7 * 8 5 4 3 8 . ' 1 *  ) * ; * 1 4 5 2 * 3 9  & 7 *  * 8 8 * 3 9 . & 1  9 4  & ) ) 7 * 8 8  9 - * 8 *  ( - & 1 1 * 3 , * 8  & . 8  . 2 5 & ( 9  4 3  8 4 ( . * 9 >  . 8  8 . , 3 . + . ( & 3 9  & 3 )  . 9  7 * 6 : . 7 * 8  ( & 7 * + : 1  ( 4 3 8 . ) * 7 & 9 . 4 3  & .  7 * 8 * & 7 ( -  & 3 )  ) * ; * 1 4 5 2 * 3 9  ( 4 3 9 . 3 : *  9 4  5 : 8 -  9 - *  ' 4 : 3 ) & 7 . * 8  4 +  < - & 9  . 8  5 4 8 8 . ' 1 *  . 3  9 - . 8  + . * 1 )  9 - *  + : 9 : 7 *  4 +  & .  . 8  5 7 4 2 . 8 . 3 ,  & 3 )  4 + + * 7 8  9 * ( - 3 4 1 4 , . ( & 1  & ) ; & 3 ( * 2 * 3 9 8  & 3 )  8 4 ( . * 9 & 1  5 7 4 , 7 * 8 8  & .  - & 8  ' 4 9 -  5 4 8 . 9 . ; *  & 3 )  3 * , & 9 . ; *  . 2 5 1 . ( & 9 . 4 3 8  & 3 )  8 9 7 . 0 . 3 ,  &  ' & 1 & 3 ( *  . 8  ( 7 : ( . & 1  & . 8  7 * 8 5 4 3 8 . ' 1 *  & 3 )  * 9 - . ( & 1  : 8 *  . 8  * 8 8 * 3 9 . & 1  + 4 7  . 9 8  8 : ( ( * 8 8 + : 1  . 3 9 * , 7 & 9 . 4 3  . 3 9 4  4 : 7  1 . ; * 8  . 3  8 : 2 2 & 7 >  & .  . 8  & 3  * ; * 7  * ; 4 1 ; . 3 ,  ) . 8 ( . 5 1 . 3 *  < . 9 -  9 - *  5 4 9 * 3 9 . & 1  9 4  9 7 & 3 8 + 4 7 2  2 : 1 9 . 5 1 *  & 8 5 * ( 9 8  4 +  8 4 ( . * 9 >  + 1 & , ` + + ) * & + + + + ' + + ( + + + * b  . 9  5 7 * 8 * 3 9 8  * = ( . 9 . 3 ,  5 4 8 8 . ' . 1 . 9 . * 8  & 3 )  ) * 2 & 3 ) 8  4 : 7  ( 4 1 1 * ( 9 . ; *  & 9 9 * 3 9 . 4 3  + 4 7  . 9 8  7 * 8 5 4 3 8 . ' 1 *  ) * 5 1 4 > 2 * 3 9  9 4  * 3 8 : 7 *  &  5 4 8 . 9 . ; *  & 3 )  . 3 ( 1 : 8 . ; *  + : 9 : 7 *"
text = text.replace(" ", "")
print(text)

Which gives us:

&..8&+&8(.3&9.3,+.*1)4+(425:9*78(.*3(*9-&9&.2894(7*&9*.39*11.,*392&(-.3*8(&5&'1*4+5*7+472.3,(4251*=9&80897&).9.43&11>&884(.&9*)<.9--:2&3.39*11.,*3(*8:(-&81*&73.3,7*&843.3,574'1*2841;.3,&3)5*7(*59.43&..3;41;*89*(-3.6:*81.0*2&(-.3*1*&73.3,3*:7&13*9<47083&9:7&11&3,:&,*574(*88.3,&3)(425:9*7;.8.43.9-&89-*54<*7947*;41:9.43.?*84(.*9>&3).2574;**++.(.*3(>&3)&((:7&(>&.8>89*28(&3&3&1>?*;&89&24:3984+)&9&)*9*(95&99*738&3)2&0*&((:7&9*57*).(9.4389-.8*3&'1*89-*)*;*1452*394+.39*11.,*395*7843&1&88.89&3988*1+)7.;.3,(&78&3)&);&3(*)+&(.&17*(4,3.9.438>89*28&.&18457*8*398*9-.(&1(43(*7387*,&7).3,57.;&(>/4').851&(*2*39&3)'.&87*,:1&9.43&3)7*85438.'1*)*;*1452*39&7**88*39.&194&))7*889-*8*(-&11*3,*8&.8.25&(94384(.*9>.88.,3.+.(&39&3).97*6:.7*8(&7*+:1(438.)*7&9.43&.7*8*&7(-&3))*;*1452*39(439.3:*945:8-9-*'4:3)&7.*84+<-&9.85488.'1*.39-.8+.*1)9-*+:9:7*4+&..85742.8.3,&3)4++*789*(-3414,.(&1&);&3(*2*398&3)84(.*9&1574,7*88&.-&8'49-548.9.;*&3)3*,&9.;*.251.(&9.438&3)897.0.3,&'&1&3(*.8(7:(.&1&.87*85438.'1*&3)*9-.(&1:8*.8*88*39.&1+47.988:((*88+:1.39*,7&9.43.3944:71.;*8.38:22&7>&..8&3*;*7*;41;.3,).8(.51.3*<.9-9-*549*39.&19497&38+4722:19.51*&85*(984+84(.*9>+1&,`++)*&++++'++(+++*b.957*8*398*=(.9.3,5488.'.1.9.*8&3))*2&3)84:7(411*(9.;*&99*39.43+47.987*85438.'1*)*514>2*3994*38:7*&548.9.;*&3).3(1:8.;*+:9:7*

Seems to be some esoteric things, lets try brute force some basic ciphers. First a ‘basic’ Caesar with key brute forcing:

text = "& .  . 8  &  + & 8 ( . 3 & 9 . 3 ,  + . * 1 )  4 +  ( 4 2 5 : 9 * 7  8 ( . * 3 ( *  9 - & 9  & . 2 8  9 4  ( 7 * & 9 *  . 3 9 * 1 1 . , * 3 9  2 & ( - . 3 * 8  ( & 5 & ' 1 *  4 +  5 * 7 + 4 7 2 . 3 ,  ( 4 2 5 1 * =  9 & 8 0 8  9 7 & ) . 9 . 4 3 & 1 1 >  & 8 8 4 ( . & 9 * )  < . 9 -  - : 2 & 3  . 3 9 * 1 1 . , * 3 ( *  8 : ( -  & 8  1 * & 7 3 . 3 ,  7 * & 8 4 3 . 3 ,  5 7 4 ' 1 * 2  8 4 1 ; . 3 ,  & 3 )  5 * 7 ( * 5 9 . 4 3  & .  . 3 ; 4 1 ; * 8  9 * ( - 3 . 6 : * 8  1 . 0 *  2 & ( - . 3 *  1 * & 7 3 . 3 ,  3 * : 7 & 1  3 * 9 < 4 7 0 8  3 & 9 : 7 & 1  1 & 3 , : & , *  5 7 4 ( * 8 8 . 3 ,  & 3 )  ( 4 2 5 : 9 * 7  ; . 8 . 4 3  . 9  - & 8  9 - *  5 4 < * 7  9 4  7 * ; 4 1 : 9 . 4 3 . ? *  8 4 ( . * 9 >  & 3 )  . 2 5 7 4 ; *  * + + . ( . * 3 ( >  & 3 )  & ( ( : 7 & ( >  & .  8 > 8 9 * 2 8  ( & 3  & 3 & 1 > ? *  ; & 8 9  & 2 4 : 3 9 8  4 +  ) & 9 &  ) * 9 * ( 9  5 & 9 9 * 7 3 8  & 3 )  2 & 0 *  & ( ( : 7 & 9 *  5 7 * ) . ( 9 . 4 3 8   9 - . 8  * 3 & ' 1 * 8  9 - *  ) * ; * 1 4 5 2 * 3 9  4 +  . 3 9 * 1 1 . , * 3 9  5 * 7 8 4 3 & 1  & 8 8 . 8 9 & 3 9 8  8 * 1 +  ) 7 . ; . 3 ,  ( & 7 8  & 3 )  & ) ; & 3 ( * )  + & ( . & 1  7 * ( 4 , 3 . 9 . 4 3  8 > 8 9 * 2 8  & .  & 1 8 4  5 7 * 8 * 3 9 8  * 9 - . ( & 1  ( 4 3 ( * 7 3 8  7 * , & 7 ) . 3 ,  5 7 . ; & ( >  / 4 '  ) . 8 5 1 & ( * 2 * 3 9  & 3 )  ' . & 8  7 * , : 1 & 9 . 4 3  & 3 )  7 * 8 5 4 3 8 . ' 1 *  ) * ; * 1 4 5 2 * 3 9  & 7 *  * 8 8 * 3 9 . & 1  9 4  & ) ) 7 * 8 8  9 - * 8 *  ( - & 1 1 * 3 , * 8  & . 8  . 2 5 & ( 9  4 3  8 4 ( . * 9 >  . 8  8 . , 3 . + . ( & 3 9  & 3 )  . 9  7 * 6 : . 7 * 8  ( & 7 * + : 1  ( 4 3 8 . ) * 7 & 9 . 4 3  & .  7 * 8 * & 7 ( -  & 3 )  ) * ; * 1 4 5 2 * 3 9  ( 4 3 9 . 3 : *  9 4  5 : 8 -  9 - *  ' 4 : 3 ) & 7 . * 8  4 +  < - & 9  . 8  5 4 8 8 . ' 1 *  . 3  9 - . 8  + . * 1 )  9 - *  + : 9 : 7 *  4 +  & .  . 8  5 7 4 2 . 8 . 3 ,  & 3 )  4 + + * 7 8  9 * ( - 3 4 1 4 , . ( & 1  & ) ; & 3 ( * 2 * 3 9 8  & 3 )  8 4 ( . * 9 & 1  5 7 4 , 7 * 8 8  & .  - & 8  ' 4 9 -  5 4 8 . 9 . ; *  & 3 )  3 * , & 9 . ; *  . 2 5 1 . ( & 9 . 4 3 8  & 3 )  8 9 7 . 0 . 3 ,  &  ' & 1 & 3 ( *  . 8  ( 7 : ( . & 1  & . 8  7 * 8 5 4 3 8 . ' 1 *  & 3 )  * 9 - . ( & 1  : 8 *  . 8  * 8 8 * 3 9 . & 1  + 4 7  . 9 8  8 : ( ( * 8 8 + : 1  . 3 9 * , 7 & 9 . 4 3  . 3 9 4  4 : 7  1 . ; * 8  . 3  8 : 2 2 & 7 >  & .  . 8  & 3  * ; * 7  * ; 4 1 ; . 3 ,  ) . 8 ( . 5 1 . 3 *  < . 9 -  9 - *  5 4 9 * 3 9 . & 1  9 4  9 7 & 3 8 + 4 7 2  2 : 1 9 . 5 1 *  & 8 5 * ( 9 8  4 +  8 4 ( . * 9 >  + 1 & , ` + + ) * & + + + + ' + + ( + + + * b  . 9  5 7 * 8 * 3 9 8  * = ( . 9 . 3 ,  5 4 8 8 . ' . 1 . 9 . * 8  & 3 )  ) * 2 & 3 ) 8  4 : 7  ( 4 1 1 * ( 9 . ; *  & 9 9 * 3 9 . 4 3  + 4 7  . 9 8  7 * 8 5 4 3 8 . ' 1 *  ) * 5 1 4 > 2 * 3 9  9 4  * 3 8 : 7 *  &  5 4 8 . 9 . ; *  & 3 )  . 3 ( 1 : 8 . ; *  + : 9 : 7 *"
text = text.replace(" ", "")

for i in range(1, 40):
    out = ""
    for item in text:
        out += chr(ord(item) + i)

    if "flag" in out.lower():
        print("Shift", i)
        print(out)

Ah cool that actually worked:

Shift 19
9AAK9>9K;AF9LAF?>A=D<G>;GEHML=JK;A=F;=L@9L9AEKLG;J=9L=AFL=DDA?=FLE9;@AF=K;9H9:D=G>H=J>GJEAF?;GEHD=PL9KCKLJ9<ALAGF9DDQ9KKG;A9L=<OAL@@ME9FAFL=DDA?=F;=KM;@9KD=9JFAF?J=9KGFAF?HJG:D=EKGDNAF?9F<H=J;=HLAGF9AAFNGDN=KL=;@FAIM=KDAC=E9;@AF=D=9JFAF?F=MJ9DF=LOGJCKF9LMJ9DD9F?M9?=HJG;=KKAF?9F<;GEHML=JNAKAGFAL@9KL@=HGO=JLGJ=NGDMLAGFAR=KG;A=LQ9F<AEHJGN==>>A;A=F;Q9F<9;;MJ9;Q9AKQKL=EK;9F9F9DQR=N9KL9EGMFLKG><9L9<=L=;LH9LL=JFK9F<E9C=9;;MJ9L=HJ=<A;LAGFKL@AK=F9:D=KL@=<=N=DGHE=FLG>AFL=DDA?=FLH=JKGF9D9KKAKL9FLKK=D><JANAF?;9JK9F<9<N9F;=<>9;A9DJ=;G?FALAGFKQKL=EK9A9DKGHJ=K=FLK=L@A;9D;GF;=JFKJ=?9J<AF?HJAN9;QBG:<AKHD9;=E=FL9F<:A9KJ=?MD9LAGF9F<J=KHGFKA:D=<=N=DGHE=FL9J==KK=FLA9DLG9<<J=KKL@=K=;@9DD=F?=K9AKAEH9;LGFKG;A=LQAKKA?FA>A;9FL9F<ALJ=IMAJ=K;9J=>MD;GFKA<=J9LAGF9AJ=K=9J;@9F<<=N=DGHE=FL;GFLAFM=LGHMK@L@=:GMF<9JA=KG>O@9LAKHGKKA:D=AFL@AK>A=D<L@=>MLMJ=G>9AAKHJGEAKAF?9F<G>>=JKL=;@FGDG?A;9D9<N9F;=E=FLK9F<KG;A=L9DHJG?J=KK9A@9K:GL@HGKALAN=9F<F=?9LAN=AEHDA;9LAGFK9F<KLJACAF?9:9D9F;=AK;JM;A9D9AKJ=KHGFKA:D=9F<=L@A;9DMK=AK=KK=FLA9D>GJALKKM;;=KK>MDAFL=?J9LAGFAFLGGMJDAN=KAFKMEE9JQ9AAK9F=N=J=NGDNAF?<AK;AHDAF=OAL@L@=HGL=FLA9DLGLJ9FK>GJEEMDLAHD=9KH=;LKG>KG;A=LQ>D9?s>><=9>>>>:>>;>>>=uALHJ=K=FLK=P;ALAF?HGKKA:ADALA=K9F<<=E9F<KGMJ;GDD=;LAN=9LL=FLAGF>GJALKJ=KHGFKA:D=<=HDGQE=FLLG=FKMJ=9HGKALAN=9F<AF;DMKAN=>MLMJ=
Shift 27
AIISAFASCINATINGFIELDOFCOMPUTERSCIENCETHATAIMSTOCREATEINTELLIGENTMACHINESCAPABLEOFPERFORMINGCOMPLEXTASKSTRADITIONALLYASSOCIATEDWITHHUMANINTELLIGENCESUCHASLEARNINGREASONINGPROBLEMSOLVINGANDPERCEPTIONAIINVOLVESTECHNIQUESLIKEMACHINELEARNINGNEURALNETWORKSNATURALLANGUAGEPROCESSINGANDCOMPUTERVISIONITHASTHEPOWERTOREVOLUTIONIZESOCIETYANDIMPROVEEFFICIENCYANDACCURACYAISYSTEMSCANANALYZEVASTAMOUNTSOFDATADETECTPATTERNSANDMAKEACCURATEPREDICTIONSTHISENABLESTHEDEVELOPMENTOFINTELLIGENTPERSONALASSISTANTSSELFDRIVINGCARSANDADVANCEDFACIALRECOGNITIONSYSTEMSAIALSOPRESENTSETHICALCONCERNSREGARDINGPRIVACYJOBDISPLACEMENTANDBIASREGULATIONANDRESPONSIBLEDEVELOPMENTAREESSENTIALTOADDRESSTHESECHALLENGESAISIMPACTONSOCIETYISSIGNIFICANTANDITREQUIRESCAREFULCONSIDERATIONAIRESEARCHANDDEVELOPMENTCONTINUETOPUSHTHEBOUNDARIESOFWHATISPOSSIBLEINTHISFIELDTHEFUTUREOFAIISPROMISINGANDOFFERSTECHNOLOGICALADVANCEMENTSANDSOCIETALPROGRESSAIHASBOTHPOSITIVEANDNEGATIVEIMPLICATIONSANDSTRIKINGABALANCEISCRUCIALAISRESPONSIBLEANDETHICALUSEISESSENTIALFORITSSUCCESSFULINTEGRATIONINTOOURLIVESINSUMMARYAIISANEVEREVOLVINGDISCIPLINEWITHTHEPOTENTIALTOTRANSFORMMULTIPLEASPECTSOFSOCIETYFLAG{FFDEAFFFFBFFCFFFE}ITPRESENTSEXCITINGPOSSIBILITIESANDDEMANDSOURCOLLECTIVEATTENTIONFORITSRESPONSIBLEDEPLOYMENTTOENSUREAPOSITIVEANDINCLUSIVEFUTURE

Getting the flags out of those outputs gives the following:

Shift 19: FLAG (With nothing else to indicate it's going to be the flag with some further work)
Shift 27: FLAG{FFDEAFFFFBFFCFFFE} (That'd be the one)

TL;DR:

  1. Reverse provided binary, discover func of interest (See what I did there?)
  2. Dump register values for offline reversing
  3. Decompile func, reproduce locally in standalone script
  4. Guess that the AND isn’t the correct operator, try every operator until it works
  5. Grab the data from the AWS Lambda
  6. Guess it’s a cipher, bruteforce/guess caesar cipher
  7. Bruteforce/guess the shift key
  8. Success!
Gotchas Link to heading
Guessing Link to heading

The fact that a big part of this challenge required guessing or brute forcing the correct thing in order to proceed slowed it down a lot. I spent more time on this challenge than I did on everything else combined. I didn’t track time but I’d easily say around 10 hours.

Challenge 13 Link to heading

Get given a rar file which contains a pcapng file, Wireshark time. Scrolling the data in wireshark we see the following:

wireshark encoded data

That looks like a base encoding to me, throw the output into CyberChef and tidy it up a bit results in the flag. We do need to recurse Base64 three times to get the actual flag but Magic handles that for us easily.

Flag

Challenge 14 Link to heading

Downloading the image and running standard stego gives little to no indication of anything. Running foremost -i conspicuous however outputs a wav file for us.

This audio file turns out to be a DTMF tone, which we can easily decode to data using this website.

This gives us the following number string:

1461541411471731431461661471421501621716364656466164162666766641641751212

With a bit of massaging in CyberChef, we can get the flag from this as the numbers are just Octal.

Challenge 15 Link to heading

This challenge gives us a .docx file, which when opened has a cropped image within it.

Docx file

After attempting to modify the image you can discover that the original image still exists despite the crop.

Docx

After un-doing the crop we find a URL, https://usaupload.com/6Y2H, which lets us download a file called celestial.bin.

Docx

After opening the downloaded file we can find the flag.

Flag

Official write-up thoughts Link to heading

The official write-up for this challenge, chal15_Deception.pdf, denotes a solve path completely different to this and results in a completely different flag. I have not validated that flag, however the one in the image above is the one which gave me the solve for the flag. Hopefully the underlying platform supports multiple flags, and CROW set up multiple flags for this challenge otherwise it’s a bit sad that the official write-up would have a different path/flag to the solution.

Challenge 16 Link to heading

I did not personally solve this challenge, writeup is courtesy of HexF

We get given this zip file which contains a single file flag.zip which is password protected. This extracted directory is a git repo however there does not appear to be any information stored within .git which is useful.

To open flag.zip, we are assuming the presence of an ASCII encoded password somewhere within the git repository.

Inspecting the git log we notice something weird:

$ git log | head -n 15
commit 8a21affbae96e7d4f81c7a9449353fa8d4b35b43
Author: nothinghere <[email protected]>
Date:   Sat Jan 1 02:22:00 2039 +1300

    Commit Number 150

commit a436ee9eb96c9191fddf4152b6eb5442b09d6b47
Author: nothinghere <[email protected]>
Date:   Fri Jan 1 07:06:00 2010 +1300

    Commit Number 149

commit 23eb6b0802fa6313df7e4b3e91b8085fd2718d96
Author: nothinghere <[email protected]>
Date:   Tue Jan 1 05:10:00 2013 +1300

Did you catch it? Let me remove some of the noise:

$ git log | grep Date | head -n 5
Date:   Sat Jan 1 02:22:00 2039 +1300
Date:   Fri Jan 1 07:06:00 2010 +1300
Date:   Tue Jan 1 05:10:00 2013 +1300
Date:   Sun Jan 1 01:44:00 2073 +1300
Date:   Wed Jan 1 02:06:00 2042 +1300

Commits should happen in sequence, applied one after the other, with incrementing time after each other. These commits, our latest commit was committed in 2039, with the previous in 2010, but previous to that in 2013? Looks like something funky is going on with the dates.

Looking at the full list of dates we find the only variance is between the hour, minute and year fields. Isolating these we get tuples such as:

# (year, hour, minute)
(39, 2, 22)
(10, 7, 6)
(13, 5, 10)
(73, 1, 44)
(42, 2, 6)
(44, 1, 31)

Interestingly, we notice an inverse relationship between year and hour, i.e. as one increases, the other decreases. Considering we are trying to find a combination of operators between these, we might try y*h + m. This gives us values which lie in the ASCII printable range, giving a string:

dLKuZKaRqcj$nL0sZo#JvV5016UGsS5hyQQu*9#IvqlX1UU85OdLKuZKaRqcj$nL0sZo#JvV5016UGsS5hyQQu*9#IvqlX1UU85OdLKuZKaRqcj$nL0sZo#JvV5016UGsS5hyQQu*9#IvqlX1UU85O

However, this doesn’t end up being the password. The first thing to notice is that the password is repeated 3 times. Cutting this down we get: dLKuZKaRqcj$nL0sZo#JvV5016UGsS5hyQQu*9#IvqlX1UU85O

This is still not the password, we have to flip the character order before we get the final password: O58UU1XlqvI#9*uQQyh5SsGU6105VvJ#oZs0Ln$jcqRaKZuKLd

Using this to extract the zip file we get

$ 7z x flag.zip 

--โœ‚๏ธ--
  
Enter password (will not be echoed):
Everything is Ok

--โœ‚๏ธ--

$ cat flag.txt
flag{dzWyVvFSqZgafzJ}

Some useful tools: Link to heading

Various useful tools in no particular order: