diff --git a/2020/rgbctf/oop/README.md b/2020/rgbctf/oop/README.md new file mode 100644 index 0000000..7d33bbe --- /dev/null +++ b/2020/rgbctf/oop/README.md @@ -0,0 +1,116 @@ +# Object Oriented Programming + +writeup by [haskal](https://awoo.systems) for [BLĂ…HAJ](https://blahaj.awoo.systems) + +**Pwn/Rev** +**413 points** +**88 solves** + +>There's this small up and coming language called java I want to tell you about + +provided file: + +## 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. + +```java + 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). + +```java + 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 + +```python +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 :) + +```python +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 + +```python +data = bytearray(data.encode()) +for i in range(len(data)): + data[i] ^= 2 + +print(data) +``` diff --git a/2020/rgbctf/oop/decode.py b/2020/rgbctf/oop/decode.py new file mode 100644 index 0000000..596ae38 --- /dev/null +++ b/2020/rgbctf/oop/decode.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +import pathlib + +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 + +import pprint +pprint.pprint(mapping) + +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") + +data = bytearray(data.encode()) +for i in range(len(data)): + data[i] ^= 2 + +print(data) diff --git a/2020/rgbctf/oop/src.zip b/2020/rgbctf/oop/src.zip new file mode 100644 index 0000000..38e54a2 Binary files /dev/null and b/2020/rgbctf/oop/src.zip differ