Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
we see a .git
directory, so we navigate to it, and check out the objects we can decompress all the zlib files with
one of them has a weird looking string in it: blob 149YXMgeW9kYSBvbmNlIHRvbGQgbWUgInJld2FyZCB5b3UgaSBtdXN0IgphbmQgdGhlbiBoZSBnYXZlIG1lIHRoaXMgLS0tLQpyZ2JjdGZ7ZDRuZ2wxbmdfYzBtbTE3c180cjNfdU5mMHI3dW40NzN9
removing the blob number gets us a b64decodable string, which gets us the flag:
These are all the writeups for rgbCTF 2020 where we came 3rd. It was an amazing ctf with some great challenges.
So basically just steal this code go brrr and remove the last 4 checks (since we dont want diagonals).
https://www.geeksforgeeks.org/find-the-number-of-islands-set-2-using-disjoint-set/?ref=rp
A Java file where some of the bytes have been corrupted to non-printable values. I went through and corrected it:
Then used:
This takes the difference between any non-matching bytes and prints them as ascii
rgbCTF{tr1pl3_m34n1ng}
Similar to the original adventure, the flag is hidden in the easter egg thing of adventure
woa decided to diff the original and the given file, and found that 0xD80 to 0xDD0 changed
I went one step further and found an asm breakdown of the game https://github.com/johnidm/asm-atari-2600/blob/master/adventure.asm
Which shows the easter egg graphic being hidden here
The easter egg is hidden with the byte being converted to binary, and then being shown with a lit pixel as a 1 and an unlit as a 0
We can then take the differenced bytes and do the same to display the flag
I dont know what the flag is but uhhhhhhhh yeah i read it as
rgbCTF{b4c0n_7nd_3665}
Script below:
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
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.
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:
I had no clue, so thought of a ton of things, eventually coming back to some physics lessons with waves.
We can use a spectrum analyser to see "the magnitude of an input signal vs frequency within the full frequency range of the instrument". I used spek (spek.cc) to do that, and it loads in the flag.
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.
Using the same method as 1 and 2 we use spectrogram stuffs to get chunks of text in each audio clip using briefing hints (and guessing) we sort these by decreasing time length to get a string: janOHS{ahse0n1yz_k4f73p}
We then guess vigenere and uh after some trial and error (and using the flag format as a guide) i got a partial key of "summon", so i just guessed the key was summoningsalt
by the theme and also the briefing to get the flag.
We get two audio tracks.
One of them has a message about turntables in base64 in the spectogram, which seems kinda useless.
So to get the difference between the tracks, I inverted track one, then used audacity to mix and render the two to a new track (effectively subtracting track1 from track2) giving us the flag in a spectogram.
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:
and then pyautogui to type it
The file is a raw hexdump. De-hexdump-ing this and checking the filetype in cyberchef shows it is an xz compressed file.
Using an online tool, https://extract.me/, you can extract a file from it.
This gives us the HTML for an SVG.
This SVG will form a QR code, but it's much easier to notice by replacing the #fffff6` colour with
#000000 using` find and replace.
Scanning this QR code gives some base64 encoded data. Decompress this, and you get another xz file. Extracting this gives an ASCII art for a QR code - in inverted colours. Swap the colours and scan for the flag.
So after beating the ai a tonne of times i realised they werent going to implement a win method for you to get the flag <:rolf:683071291347042408>
So i thought abouut global events but gave up but then found a b64 string in one of the lines of obfuscated code. i got...
rgbCTF{h4h4_j4v42cr1p7_ev3n72_AR3_c00L}
First things first, we saved the phone number, and saw if it synced with any social medias. It gave an instagram (). The first thing to do: check the highlights. The highlights reveal to us a few things, his full name and his city.
He also left a boarding pass on there, with him flying into Amsterdam on Friday 17th, at 1:45 pm. Using my aviation nerd skills, I searched up flights arriving into schipol at 1.45 pm, and i got the flight number.
Slapping together everything in a flag form, we get
Open the pcap, add data to the columns by clicking on the packet bytes and right click > Apply to column where it it highlights. Remove the other columns then go to File > Export Packet > Dissections > As CSV. Remove the odd data such as Value, the white space and the odd data parts. Remove the speech marks with 'sed -i 's/"//g' . Then run this script on it
The output will have the number.
Chuck it into https://incoherency.co.uk/image-steganography/#unhide
We then get an image revealed which contained the flag inside.
volatility image, use hashdump
Use crackstation to crack them, then cyberchef to hash them
Again, chuck it into same link which gets you a load of qr codes in one image Open it in an online qr code reader link Load of noise, but one pops out
As the title suggested, I grabbed my Jisho (jisho.org) and pasted the first "word" in.
In the side panel I saw 20-8-5 for the stroke numbers, which I immidiately recognised as A1Z26 cipher for 'THE'.
The characters were encoded into kanji with an equivalent stroke number.
I continued manual decryption until I identified the plaintext as .
At this point I wrote a quick script to match kanji to letters 1:1 in one paragraph, and use replace on the whole text.
This was much faster, and once I had decrypted 10 or so paragraphs like this grepping for rgb
yielded the encrypted flag - 璧技丩忄鰏叒讞鸞鸚鱺鑱磒夢洶飳勊淩鼱殆鸝钁厵鬱
. However, some kanji had values above 26.
I struggled with this for a while before trying to just extend them on the ASCII table, which worked!
Looking at the code, we can see it uses random.randint
to generate the one time pad.
The thing that is most of note is that it seeds python's random generator using the current time.
Not only this, it prints out the first 10 random numbers outputted by the generator by doing random.randint(5, 10000)
and printing the output 10 times.
We can record the time shortly after we connect to the server.
Chances are, we won't have the exact right value.
However, we can use the 10 random numbers as a form of "check" - essentially, we can bruteforce the time based seed within a sensible range, test the seed against the 10 random numbers we know come from the generator, and find the correct one.
Once we find the correct one, it's trivial to generate the byte array and XOR back to get the flag.
script below:
Let's break down the encryption step by step.
It splits the plaintext into padded blocks of 8. It also expands the key using a key expansion function so that the key is as long as the amount of blocks. The key expansion function causes a drastic loss in size of the keyspace by using modular exponentiation that is not with primitive roots and is modulus a composite(256), which would allow for more optimised bruteforce, but I didn't use this in the end. For each block, it goes through 8 rounds. In each round: 1. Take the current ciphertext. Put it through the sbox(basically a substitution table) 2. Then, put it through the pbox, essentially shuffling the binary based on a pre-determined pbox of indexes. 3. Xor every byte of the current block with the current byte of the expanded key
The sbox and the pbox are things we can easily invert. The only real "encryption" step is the xor bit.
The problem is that unlike normal AES where the key is expanded into 11 keys of the same length of the blocks, in here the key is expanded into essentially 1 byte keys. For each block, for each round, it will keep XORing with the same current byte of the key.
Essentially, we can bruteforce each byte of the key until we get printable values, as the encryption reduces the key in each block to one byte, and this one byte can be bruteforced.
I wrote inversion functions to handle the sbox and pbox, then simply bruted the one byte expanded key of each block until I got a printable value. We can concatenate the decrypted blocks to get the flag,
Script:
im aware there is a module for this but: 1. idk how to use it 2. i dont want my effort to go to waste (since i made my own cube manipulator) 3. i dont like installing one time modules cus they clog up space
since it was just cbc i just had to map it to a cube and reverse the scramble then xorred it to get a pdf file
i spent too long fixing bugs with the xor process and how bytes are handled i also spent too long fixing bugs of my cube program
found a qr code in pdf and...
Script:
So, at first this seems literally impossible.
The rand_block function takes a random 1 byte seed(initialising it to the output of os.urandom(1)
if the argument isnt given) and then generates a 16-byte random block from this.
The encryption is simple - given a stream of seed bytes and a plaintext, iterate through the seed bytes.
On each byte, set the current ciphertext variable to the current ciphertext variable encrypted with the key generated by the random block that is create using rand_block(cur_byte_of_seed)
Finally, the challenge is generated like so. A random base64 string is generated using urandom, then encrypted in the algorithm. We get the base64 of the encryption.
So, what's so fishy about this?
Notice no argument is passed to rand_block, it simply gets a rand block and encrypts 128 times.
The argument is supposed to be os.urandom(1)
by default, right? The problem is, everytime you call the function with no argument, the os.urandom(1) isnt regenerated. Instead, the value is first generated when the function is defined and then set to that again and again and again.
Long story short - chall is generated with the same key 128 times. Since it's os.urandom(1), we'll only have to bruteforce 256 possible keys. That can be done easily, and we can check if we've decrypted correctly by checking if the padding matches.
From there, we can simply enter the decrypted ciphertext.
^ script prints out the needed base64, you'll have to manually enter it into the prompt to get the flag from there