The goal of this challenge is to provide a basic introduction to RSA and ASCII encoding. Using the Wikipedia link provided in the hint, we may calculate and as the modular inverse of . Then we may find . Code below (py3.8 needed):
This program outputs 77829732531886017666421465369706368622254927240332446949265849761777437379574153694975519245766808162296991738636674224619780544798026515410227980157
. To convert this into the flag, we first convert it to hexadecimal, and then convert the hex string into ASCII. Below are two ways to do this in Python:
Both of these give us the flag: actf{old_but_still_good_well_at_least_until_quantum_computing}
.
This is a simple stream cipher created by XORing the plain text with a keystream. The weakness here is that the initial key is generated with key = os.urandom(2)
, which only provides 2 bytes = 16 bits of security. As such we only need to bruteforce keystreams, and on each iteration we check if the resulting plaintext contains actf{
. Code below (notice how it looks very similar to the source):
This outputs several lines, all of which contain the flag (crc32 is not very collision resistant). The one which is entirely ASCII is there are like 30 minutes left before the ctf starts so i have no idea what to put here other than the flag which is actf{low_entropy_keystream}
. And thus our flag is actf{low_entropy_keystream}
.
This source code implements a simple AES-CBC decryption oracle, where any instances of {}
are replaced with the flag. Let's look at the CBC decryption diagram from Wikipedia.
Note that although the IV is randomized, it only affects the first plaintext block, which means the rest of the oracle will give the same output for any input, which really sounds a lot like ECB.
Remember that the weakness in ECB is that the same input will always give the same output anywhere in the message, which means collisions in the output mean collisions in the input. Here, the weakness is similar – an output block is completely determined by two adjacent ciphertext blocks. Essentially, it's ECB with 32-byte blocks of input and 16-byte blocks of output (we'll ignore every other block starting from the second one).
From here, we can conduct a standard attack on an ECB oracle, where we bruteforce the flag one character at a time. Code below:
This outputs the flag: actf{cbc_more_like_ecb_c}
You can make encryption stop early by sending an invalid number such as a string or a list. This way, the timing differences caused by cache hits/misses become much more noticeable. We can leak some of the round keys by doing a byte-by-byte bruteforce against the server, and then a bit of bruteforcing to resolve any ambiguities. This is enough to recover the first 192 bits of the key. To recover the last 64 bits, we have to use the decrypt function.
At least two teams solved this with a cool method involving floats, which didn't use timing at all. By inputting a float and checking for errors (int ^ float
causes an error), you can tell with 100% certainly whether something is in the cache.
i'm too lazy to add details so have two unreadable scripts kthxbye
This script performs the timing attack and whatever.
This script recovers the key (or at least the first 24 bytes, last 8 bytes is left as exercise to the reader)
The big idea is to construct a BSON object that also doubles as a BMP file. Because loading images does not count as frame navigation, the request will not be blocked. Also, BMP is ideal because it does not do any compression, so we do not have to worry about fitting a specific compression format.
btw everyone who used the devtools thing is lame
BMP reference: http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm
BSON reference: http://bsonspec.org/spec.html
Note that the first four bytes of a BSON file represent the size. Also note that the first two bytes of a BMP file have to be b'BM'
, which means optimistically the minimum file size would have to be b'BM\x00\x00'
as an integer, or 19778
. Not too bad. The next four bytes after the header are supposed to be the file size, but Chrome doesn't check this so we can set it to whatever we want.
On the BSON side of things, we must now create an element. Since the rest of the BMP header is probably going to be nasty unprintable data, the binary
type (0x05
) is probably going to be best here. We'll use a simple key here such as AAAA
, and we'll need a 4-byte length which we'll determine later. We also need a subtype, but BSON doesn't care if it's invalid so we again can put whatever we want.
After the BMP file size, we have four bytes for a reserved value (which means we can put anything), and then four bytes for DataOffset
. The header will end up being 0x3e
bytes so that is what we will put here. This also coincides with the size of the current BSON element, so we'll need to make sure that works out. The next value is the size of the InfoHeader
, which comes out to 0x28
bytes. At this point, we can just fill in the rest of the header, and then some garbage until we reach the end of the BSON data.
After the end of the BSON binary data, we construct a string that will hold a bunch of dummy values that take us to the end of the file. Construction below:
At the end of the file we leave some space for the flag key that the server will add. We don't know the length of the flag, but this can be easily bruteforced. Since the flag is in the BMP data and we are using 1 bit per pixel, it will show up encoded as binary, with black and white pixels representing 0 and 1.
After bruteforcing flag length, we eventually find that a webpage with this content works:
Result:
At the very top you can see some data, here it is zoomed in:
We can clip this out with MS Paint or a similar tool and run a simple Python script to extract the flag from these pixels.
This outputs the flag: actf{a_cross_origin_catastrophe}
.