❮ 2024-03-15
Nullcon CTF Writeups
CTF Writeups for Nullcon CTF
Jeopardy CTF with CTF0.
Challenges I solved
Misc - Timecode
Java Challenge, Code provided.
The Challenge prints 6 numbers and wants you to input these 6 numbers again, but when you do that it "misinterprets" them in a weird way, and tells you, that you gave back the "wrong" numbers ... kinda weird.
> nc
)
Looking at the source code, the players input gets handled like that, it first parses the user input with "parseInt" and then converts it to an Integer-Object via Casting (Instead of just using Integer.valueOf(n))
After that it compares the input to the sent output, if its the same you pass the challenge, if not the connection closes. You have to survive 10 challenges in succession.
1 for 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
After debugging it some REALLY weird happens at line 7, after casting the int primitive to an Integer, the Integer holds an entirely different value, leading the following comparison to always fail.
Weird casting behaviorAfter double checking, if thats some weird casting behavior, I got reminded how Autoboxing in Java works, at least for Strings. To save resources, Java has a so called "String-Pool".
Java String and Integer Pool
Every time you create a String like this:
String foo
Java does a lookup into its String-Pool. If the same String is already present inside, just a reference to this one gets passed back.
String Pool in JavaThat saves some heap space, and all Strings inside the String-Pool can be compared with "==", because all share the same reference.
To force Java to not place a String on the String-Pool, just instantiate the String with the "new" Keyword:
String foo
Apparently Java has the same mechanism for Integers as well, every time you do:
Integer test
Between -128 and 127, Java references the Integer reference to an Object in a so called "Integer Cache".
IntegerCacheThe objects reference is received via a simple Lookup:
public static Integer
For Example: To get the Integer-Object for the int "0", the 128th Entry inside the Array gets returned.
That exact lookup table got sneakily changed when setting up the challenges, thus resulting in getting arbitrary numbers when autoboxing ints to Integers:
Tampering the IntegerCache
At first the IntegerCache needs to be Public, thats achieved with some reflection:
//Makes Integer cache public
private static Integer[] throws Exception
Afterwards the Lookup-Array-Content gets overwritten with a previously generated Array with pseudo random numbers based on a certain timestamp:
//Overwrites data in integer value cache from 128 to 255 with content of "second" from 0 to 255
for
After doing that the user-input-parsing logic starts ... resulting in wrong numbers due to the cast from int to Integer.
For Example: User Inputs 10, now Java looks up the value in the 138th. Array-Position (128+10) and finds: 112. So your 10 just became a 112 and the comparison afterwards fails.
Loopup Table with random inputGetting the Flag
The Integer-Array that gets passed to the Integer-Cache is not entirely random, the logic uses the current unix timestamp as seed and print the one we need to use inside the nc session:
Challenge OutputWith that timestamp you can call the "get_mapping" function locally to generate the same Array the server generated too, and so be able to lookup what numbers you have to type into the challenge prompt to get the "correct" number after autoboxing, and so be able to pass the check:
Index LookupI wrote a simple unittest for this and just copy pasted the challenge input/output 10 times:
public void
Flag:
Challenge OutputFailed attempt
I was blind and haven't seen that the timestamp was provided via challenge prompt, so I thought I needed to time my request, because at certain times (every 64 Milliseconds), the Integer array got correctly ordered by the algorithm:
1 public static Pair 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Every time 'time % 128' was 64, the underlying code would generate an ordered Integer array
I tried to time every "sendline()" prompt in pwntools, but sub 1ms packet travel without jitter to survive the 10 challenges is kinda impossible.
I was close giving up, but after seeing the timestamp in the nc prompt once again it make "click", and I had to slam my head onto my desk a few times 🫠 .