Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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 http://www.gutenberg.org/files/45723/45723-h/45723-h.htm .
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!
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:
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
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:
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: