writeups/2020/rgbctf/oop/README.md

5.0 KiB

Object Oriented Programming

writeup by haskal for BLÅHAJ

Pwn/Rev 413 points 88 solves

There's this small up and coming language called java I want to tell you about

provided file: <src.zip>

writeup

we are given a zip file with java source. there's a Main.java and a bunch of 2-character source files, with classes that contain a bunch of 2-character methods that return 2-character strings. examining Main, we see it uses reflection to call into these classes and methods to map the input string to the output.

		String userInput = getUserInputMethodFromScannerAndInvokeAndReturnOutput(scanner);
		if (userInput.length() != SIXTEEN)
			System.exit(0);
		
		if (executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut(userInput).equals(scanner.getClass().getPackageName().replace(".", ""))) {
			invokePrintLineMethodForOutputPrintStream(outputPrintStream, printLineMethod, "Nice. Flag: rgbCTF{" + userInput + "}");
		} else {
			invokePrintLineMethodForOutputPrintStream(outputPrintStream, printLineMethod, "Try again.");
		}

this code shows us the user input must be 16 chars exactly, and then a transformation function is called (with the parameter javautil, the package of Scanner without periods).

	public static String executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut(String stringToExecuteAforementionedCodeOn) throws Exception {
		String encryptedString = reallyBasicQuoteUnquoteEncryptionFunctionThatWillOnlyTakeTimeToFigureOutIfYouKeepReadingTheseRidiculouslyLongMethodNames(stringToExecuteAforementionedCodeOn);
		String returnValueOfThisFunction = new String();
		String[] chunksOfEncryptedStringOfLengthFour = splitStringIntoChunksOfLength(encryptedString, FOUR);
		for (String chunkOfEncryptedStringOfLengthFour : chunksOfEncryptedStringOfLengthFour) {
			String[] chunksOfChunkOfEncryptedStringOfLengthFourOfLengthTwo = splitStringIntoChunksOfLength(chunkOfEncryptedStringOfLengthFour, TWO);
			String firstChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo = chunksOfChunkOfEncryptedStringOfLengthFourOfLengthTwo[0];
			String secondChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo = chunksOfChunkOfEncryptedStringOfLengthFourOfLengthTwo[1];
			Class<?> classAndExtraCharactersSoItsNotAKeyword = Class.forName(firstChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo);
			Object object = classAndExtraCharactersSoItsNotAKeyword.getConstructors()[ZERO].newInstance();
			for (int loopArbitraryCounterIterator = 0; loopArbitraryCounterIterator < THREE; loopArbitraryCounterIterator++) {
				Method method = classAndExtraCharactersSoItsNotAKeyword.getMethod(secondChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo);
				secondChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo = (String)method.invoke(object);
			}
			returnValueOfThisFunction = new String(returnValueOfThisFunction + secondChunkOfChunkOfEncryptedStringOfLengthFourOfLengthTwo);
		}
		return returnValueOfThisFunction;
	}

looking at the transformation function, we find it calls a method that XORs all the characters in the input with a key, which turns out to be 2 (found by modifying the code to print it out). then, it splits the input into chunks of 4, uses the first 2 chars as a class name, and iterates 3 times to transform the second 2 chars by calling the method in the class. the result is concatenated together. this can be solved with a simple script to parse the java files to map the transformations, then reversing the transformation by doing the opposite of this operation.

first, parse the java. it's very regular so this isn't too hard

mapping = {}

for file in pathlib.Path("src").iterdir():
    if "Main" in file.name:
        continue
    name = file.name.replace(".java", "")
    with file.open("r") as f:
        data = f.read()
    thisfile = {}
    mname = None
    for line in data.split("\n"):
        if mname is None:
            if "public String" in line:
                mname = line.strip().split(" ")[2].replace("()", "")
        else:
            val = line.strip().split(" ")[1].replace('"', "").replace(";", "")
            thisfile[mname] = val
            mname = None
    mapping[name] = thisfile

next, go through backwards to find which class has 3 methods that can be chained to produce the required output. (implemented this with horrible CTF spaghetti code, please don't tell my fundies professor :)

start = "javautil"

def rfind(what):
    for file,data in mapping.items():
        for k,v in data.items():
            if v == what and k in data.values():
                for k2, v2 in data.items():
                    if v2 == k and k2 in data.values():
                        for k3, v3 in data.items():
                            if v3 == k2:
                                return file + k3

data = rfind("ja") + rfind("va") + rfind("ut") + rfind("il")

finally compute XOR for the flag

data = bytearray(data.encode())
for i in range(len(data)):
    data[i] ^= 2

print(data)