358 words
2 minutes
CyCTF 2023 Reverse Engineering Writeup
2023-11-05

only the first three challenges :)

Init:#

It’s a pretty easy challenge, so I started by loading the file in jd-gui

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Init {
private static final String infoUrl = "https://ipinfo.io/country";
public static String xorDecrypt(byte[] input, int key) {
byte[] output = new byte[input.length];
for (int i = 0; i < input.length; i++) {
output[i] = (byte) (input[i] ^ key);
}
return new String(output);
}
public static void main(String[] args) {
System.out.println("Welcome to CyCTF 2023.");
if (args.length < 1 || !"flag".equals(args[0])) {
System.out.println("What do you want?");
} else {
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(infoUrl))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String countryCode = response.body().trim();
System.out.println("Your IP-based country code is: " + countryCode);
System.out.println("Decoding flag...");
int xorKey = (countryCode.charAt(0) + countryCode.charAt(1)) - 80;
byte[] encryptedFlag = {34, 56, 34, 53, 39, 58, 50, 113, 44, 114, 30, 45, 117, 15, 120, 20, 117,
120, 114, 18, 30, 2, 117, 47, 30, 121, 114, 30, 12, 113, 19, 114, 30, 34,
113, 15, 39, 52, 18, 112, 15, 120, 30, 118, 41, 117, 47, 30, 34, 113, 37,
114, 30, 113, 121, 7, 52, 18, 2, 117, 118, 112, 113, 15, 60};
System.out.println("Flag is: " + xorDecrypt(encryptedFlag, xorKey));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

It’s a simple code. It just gets a country code from ipinfo (and based on the language used, the country code is ‘CN’), and then xors the bytearray with it, which is the flag!

So the solution is to create a simple Java program that computes the xor with “CN” as the country code.

The flag is: cyctf{s0m3_l4N9U493S_C4n_83_M0R3_c0NfuS1N9_7h4n_c0d3_08FuSC4710N}

Spaghetti:#

It’s a pattern checker with an obfuscated javascript file.

Obfuscated JS code screenshot

This is our target function.

Target function screenshot

That function takes an array, which is the selected points on the pattern; if the selected first row is from left to right, that array will have [0, 1, 2] and when selected from right to left, it’ll be [2, 1, 0]

So after deobfuscating and refactoring, we get this:

function arrayCheck(inputArray) {
if (inputArray[6] + inputArray[2] + (inputArray[3] + inputArray[4]) != 15) { return false; }
if ((inputArray[5] + inputArray[6] ^ inputArray[2] + inputArray[5]) != 8) { return false; }
if ((inputArray[2] + inputArray[7]) * (inputArray[1] + inputArray[2]) != 165) { return false; }
if (inputArray[7] + inputArray[4] + (inputArray[0] ^ inputArray[5]) != 20) { return false; }
if ((inputArray[3] ^ inputArray[5]) * (inputArray[6] + inputArray[3]) != 4) { return false; }
if ((inputArray[1] + inputArray[3] ^ inputArray[7] + inputArray[3]) != 12) { return false; }
if (inputArray[0] + inputArray[3] + (inputArray[4] + inputArray[6]) != 9) { return false; }
if ((inputArray[3] + inputArray[0]) * (inputArray[5] * inputArray[1]) != 45) { return false; }
if ((inputArray[4] ^ inputArray[1]) + (inputArray[3] + inputArray[0]) != 8) { return false; }
if (inputArray[5] * inputArray[6] + inputArray[2] * inputArray[5] != 40) { return false; }
if (inputArray[2] * inputArray[7] + inputArray[1] * inputArray[2] != 80) { return false; }
if (inputArray[7] * inputArray[4] + inputArray[0] * inputArray[5] != 52) { return false; }
if (inputArray[3] * inputArray[5] + inputArray[6] * inputArray[3] != 5) { return false; }
if (inputArray[1] * inputArray[3] + inputArray[7] * inputArray[3] != 10) { return false; }
if (inputArray[0] * inputArray[3] + inputArray[4] * inputArray[6] != 2) { return false; }
if (inputArray[3] * inputArray[0] + inputArray[5] * inputArray[1] != 17) { return false; }
if (inputArray[4] * inputArray[1] + inputArray[3] * inputArray[0] != 20) { return false; }
return true;
}

It’s just a couple of checks to make sure the pattern selected is correct, and that pattern is used to decrypt the flag

With the help of a friend, I was able to create a checker to bruteforce this

import itertools
arr = list(itertools.permutations([0,1,2,3,4,5,6,7,8]))
def checker():
for i in arr:
if (i[6] + i[2] + i[3] + i[4]) != 15: continue
if (i[5] + i[6] ^ i[2] + i[5]) != 8: continue
if (i[2] + i[7]) * (i[1] + i[2]) != 165: continue
if i[7] + i[4] + (i[0] ^ i[5]) != 20: continue
if (i[3] ^ i[5]) * (i[6] + i[3]) != 4: continue
if (i[1] + i[3] ^ i[7] + i[3]) != 12: continue
if i[0] + i[3] + (i[4] + i[6]) != 9: continue
if (i[3] + i[0]) * (i[5] * i[1]) != 45: continue
if (i[4] ^ i[1]) + (i[3] + i[0]) != 8: continue
if i[5] * i[6] + i[2] * i[5] != 40: continue
if i[2] * i[7] + i[1] * i[2] != 80: continue
if i[7] * i[4] + i[0] * i[5] != 52: continue
if i[3] * i[5] + i[6] * i[3] != 5: continue
if i[1] * i[3] + i[7] * i[3] != 10: continue
if i[0] * i[3] + i[4] * i[6] != 2: continue
if i[3] * i[0] + i[5] * i[1] != 17: continue
if i[4] * i[1] + i[3] * i[0] != 20: continue
print(i)
checker()

And it prints the one pattern: (2, 3, 8, 1, 6, 5, 0, 7, 4)

And then we get our flag!

Flag result screenshot with star

Undercover:#

Based on the description and file structure, it looks like it’s JIT Compiled, but that can’t stop us!

Loading the undercover.dll through dnspy, we get this

dnSpy decompiled DLL screenshot

After renaming functions

dnSpy with renamed functions screenshot

To sum it up, it has encrypted resources and double decrypts them, but the AES key is stored in the JIT Compiled code, but with a simple breakpoint solved it.

AES key breakpoint screenshot

Key and IV screenshot

From that, I got the key and IV and the decrypted text.

Decrypted text screenshot

And since then, it’s a DLL. I make the executable load it.

The decrypted text with strings and this PowerShell code

Terminal window
$encodedScript = 'ZgB1AG4AYwB0A ... too long';
$decodedScript = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encodedScript));
Invoke-Expression $decodedScript

So, just decoding it in PowerShell, we get a script that does SHA256 hash checking of the input flag split into 2-character chunks.

SHA256 hash validation screenshot

The hashArray contains 37 SHA256 hashes, each corresponding to a 2-character chunk of the flag.

I just got the array and loaded it into a SHA256 reverse lookup site and got the flag:

cyctf{50_y0u_C4N_3X3CU73_p0W3r5h3Ll_W17h0u7_H4v1N9_70_3X3cu73_P0w3r5H3lL!}

I hope that my explanation is clear and I enjoyed this CTF. See you next time.

Share

If this article helped you, please share it with others!

CyCTF 2023 Reverse Engineering Writeup
https://titoot.dev/posts/cyctf-2023-reverse-engineering-writeup/
Author
Titoot
Published at
2023-11-05
License
CC BY-NC-SA 4.0

Some information may be outdated

Table of Contents