Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
So with some experimentation we found the binary actually XORs the inputted plaintext with a one time pad. Just sub in the ciphertext right? Kind of?
If we sub in a plaintext we know and xor back to get the stream we get a bunch of numbers. With a bit of help from the author and some research we realised these numbers were a sequence of lychrel numbers. The first bit of the stream, 1997, is a lychrel number, specifically the seed stored inside of the binary which it then uses to generate the rest of the lychrel, converts to string and then XORs.
"the bad seed" implies this seed must be fixed.
Soooo i just found out the position in the binary the seed was stored(0x7c5b) and bruteforced the seed in that location. Grepping for rgb gives us the plaintext
He&jaeden created the Lich King ages ago from the spirit of the orc shaman Ner'zhul to raise an undead army to conquer Azeroth for the Burning Legion. rgbctf{the flag is just rgb lol} Initially trapped within the Frozen Throne with Frostmourne, the Lich King eventually betrayed Kil'jaeden and merged with the human Arthas Menethil. When Frostmourne was destroyed and Arthas perished, Bolvar Fordragon became the new Lich King, imprisoning the master of the Scourge within the Frozen Throne once more in order to protect the world from future threats.:
Which yields the flag.
So, the source is less obfuscated and more elongated. It's got a lot of bloat, but let's focus on the important things.
We've got a bunch of classes. Each class has a two letter name, and has a bunch of two letter name functions. Each of these functions returns a 2 letter string.
These essentially form a lookup table, pair of chars + pair of chars = another pair of chars
Our input is gotten, then the function executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut is run on the input. The output is compared to scanner.getClass().getPackageName().replace(".", "") scanner.getClass().getPackageName().replace(".", "") = javautil This means our target output is javautil.
Let's look at the actual function.
Bloated, I know, but we can split the logic up. It splits the input into chunks of 4. It iterates over these chunks of 4. For every chunk of 4, it splits it into 2 chunks of 2. NOTE: this is all done on the input after the "encryption" 1. Grab the class name associated with the first chunk of 2 2. Iterate to do this three times: a. Grab method of class that chunk 2 corresponds to. b. call function, set chunk 2 to the output 3. Append the final chunk 2 to the output So, say we sent kpta It would go to class kp, and search for a method called ta. It would call ta, then store the output, and try to find the method corresponding to this output... etc. etc. This means we want the first chunk of 4 to result in ja, the second to result in va, the third ut, the fourth il.
This means we will want to find a chain of functions within 4 classes. The chain would have
function that returns name of function that returns name of function that returns name of function that returns string we want
We can search manually for these chains.
So, the input, after the encryption is done, must be [class name][start of chain][class name][start of chain]
This means our input, when encrypted, must be glvgprpkqgamfggg
If we look carefully at the encryption, we find it's actually just an XOR. Now, you know XOR, encryption and decryption are the same operation. So we can just run the encryption function on glvgprpkqgamfggg
to get the flag, enterprisecodeee
i honestly dont know it was a mix of bruteforcing the first 32 bytes and then something something IDK OK DONT FUKCIANDOFAJSLDFASDJFL
So since the program will not accept input less than 32 I bruteforced the first 32 bytes And then i realised if i put the start of that at the end it gives stuff So my input became 32494328fdsajsfkl}rgbCTF{hopeful32494328fdsajsfkl}
And then i bruteforced from the start again until i got the flag.
House of force + tcache_perthread_struct overwrite/tcache poisoning
Let's break down the main execution of the program. You input the party size, and it mallocs the party size << 5. Essentially, the party size * 32.
This is so that it can allocate enough party member structs, where the party member struct looks like this
If we're "all alone"(party size less than 2) it reads a name into the malloc-ed chunk and initialises the drink to 0xffffffffffffffff
This is where the vulnerability lies.
If we give a party size of 0, malloc will return a 0x20 length chunk(that includes metadata). In essentiality, a party size of 0 will lead to
[0x18 data bytes allocated][top chunk]
This means that if we give a party size of 0, the top chunk's value will be set to 0xffffffffffffffff!
That allows us to carry out the house of force attack by requesting large chunks that nudge the top chunks into other places. But, before we get into that, let's look at the song choosing and singing.
We can sing a song. This causes it to print out the value inside of the chosen_song variable. This is useful for heap and binary base leaks.
The chosen_song variable at first contains a pointer to a string in the binary "Never Gonna Give You Up - Rick Astley", so singing the song gives us a binary base leak we can offset.
Now, we can choose a song. We get to give the song title size, then it malloc's that much, and calls fgets(malloc_ptr,size,stdin); It then sets chosen_song to this new pointer returned by malloc. So by singing a song again after choosing a song, we get a heap leak.
Ok, now what?
The house of force can let us allocate anywhere in memory, but we only know the whereabouts of the heap and binary. Full RELRO's on, so none of that is very useful. We need some way to get a libc leak. That's where the tcache comes in.
Now, you might be confused as of how we can control the tcache without using a single free, but that is how the tcache_perthread_struct becomes useful.
The only useful printing we can do is singing a song. This means in order to leak a libc pointer, we must make malloc return a libc pointer without knowing the libc pointer beforehand.
How?
Let's take a look at the tcache_perthread_struct, which is stored at the very beginning of the heap.
It contains the counts encoded in characters for all the 64 bins, as well as pointers to the start of the bins.
The wonderful thing about tcache is the lack of checks. Because all chunks in a bin are meant to be the same size, it won't check whether or not we are just pointing it to some random memory.
So, we can use the house of force(malloc-ing a chunk with a specific size that when added from the top chunk location it will cause integer overflow that makes malloc place the top chunk back at tcache_perthread_struct) to get the top chunk at the tcache perthread. At this point, the top chunk will have size 0x269. I found that allocating 0x230 was the highest I could get without malloc throwing a hissy fit.
So that's how we gain control of tcache perthread, which gives us full tcache control. What do we do with this?
What if we pointed a tcache bin at an address we know will contain a libc pointer, such that we can malloc once, get that libc pointer at the top of the tcache, then malloc again, returning the libc pointer?
The GOT.
Ok. Let's place a count of 2 inside of the 0x20 bin. Then we can point the bin at puts@got. Let's a choose a song of size 0. Fgets 0 will give no input.
This means the fact that the got isnt writeable will have no effect.
Then if we choose a song with size 0 again, the returned pointer will be puts@glibc, which we can then leak by singing a song.
Brilliant, a libc leak!
Now what?
At this point, the heap is fried. Any more allocations served from the top chunk will cause horrible consequences.
Let's go back to when we had full tcache_perthread_struct control. The 0x20 bin we've already dealt with, but let's abuse some other bins.
We can point the 0x30 bin and the 0x40 bin into a writeable segment inside of the binary.
Hmm, that's cool and all, but how do we arb write? We can't exactly house of force again.
... Or can we?
Well, we can, but I didn't. Instead, I pointed two bins to the exact same location. I gave the 0x30 bin count 1 and the 0x40 bin count 2, and pointed them to the same address
Tcache would be like so
Choose a song of size 0x20, will be served from the 0x30 bin.
0x40 bin -> writeable area -> 0x0
We now have full control over what the 0x40 bin will think is a tcache chunk. Let's write the malloc hook address to it.
0x40 bin -> writeable area -> malloc hook
Choose a song of size 0x30, will be served from 0x40 bin. Now malloc hook is at top of tcache! Choose a song of size 0x30 again, and we'll get an arbitrary write at malloc hook. Let's write system to malloc hook.
Ok, so now we want to call malloc on a /bin/sh pointer. How? We need to input size as a number.
Malloc will call malloc hook on the size.
What we can do is send a size number that is actually a disguised /bin/sh pointer. Malloc will then call system on this number, actually calling system("/bin/sh")
NOTE: The final step didn't work locally for me, but it works remotely. Summarised plan: 1. House of force to get heap chunk pointing to tcache_perthread_struct 2. Put count of 2 inside of 0x20 bin. Set the top of the bin to point to puts@got for example 3. Put count of 1 inside 0x30 bin and 2 inside the 0x40 bin. Point them towards the same place, some writeable segment inside of the binary. 3. Choose a song with length 0. It'll allocate in the 0x20 bin but also fgets 0 will give us no input so no sigsev should occur 4. Top of tcache will have pointer to puts@GLIBC 5. Choose another song with length 0. Returned pointer should be puts@GLIBC. Again, fgets 0 gives no input, no sigsev 6. Sing song, that should print the libc pointer 7. Create a 0x20 length song, will be allocated at the writeable segment. It will ALSO be at the top of the 0x40 tcache, allowing for poisoning. ( see step 3). Set next pointer to malloc hook 8. Create 0x30 length song. Create another. This will be at malloc hook, write system to it 9. Choose a song where the song title size represents the pointer to /bin/sh in libc
We can cat /pwn/flag.txt
to get the flag.
Script:
Decompiling the file, we find it runs the encryptFlag function on the first argument, then prints the output out as hex.
The encrypt flag function runs some complicated airthmetic thing, which doesn't really matter that much, or at all.
What's notable is that the encryption is kind of a rolled byte by byte. That is, the same byte preceded by the same text before it will encrypt to the same thing.
Knowing the flag format, rgbCTF{flag}, we can use a byte by byte bruteforce.
I recreated the function inside of python and attempted to run the bruteforce there, but I got non-preferable results. So, I did this again, this time recreating the code in c and compiling it, then created a python wrapper script to run the bruteforce.
I'm not sure exactly why, but I had to constantly switch between the two scripts, using one to brute the next part of the flag, subbing it into the other to brute the next part of the flag, subbing that in... etc.
Anyhow after all of my pain and a little trial and error i was able to create the final flag.
Python script with recreation:
Python script that used the binary I recompiled:
source of binary i recompiled: