117 lines
5.0 KiB
Markdown
117 lines
5.0 KiB
Markdown
# 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: <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.
|
|
|
|
```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)
|
|
```
|