Scrambled: RSA
Last updated
Was this helpful?
Last updated
Was this helpful?
Hmmm I wonder if you have learned your lesson... Let's see if you understand RSA and how the encryption works. Connect with
nc mercury.picoctf.net 4572
.
Connecting to the service outputs some ciphertext, the public modulus, and the exponent:
The service will also encrypt any input we give it and show the corresponding ciphertext.
In textbook RSA, a 1
should encrypt to 1
. But, when we try encrypting a 1
we get 13589787814203979735997344432080802832776008984701751529073256623683835560426019268055662727723878817162581242623884397426411415725347741008962849580645219519573984615300484601979495080932024787662131004577978449903745252490885056468740446181188019506761784393355054186218263954234491000582090477859332982836
.
Encrypting some input (aaa
) multiple times produces different outputs, which means the algorithm is not deterministic. Strangely, encrypting a
multiple times produces the same output. Essentially, the number of possible encrypted outputs is equal to the length of the input text. For example, there is only one character in 1
so there is only one ciphertext while there are three characters in aaa
and thus three valid ciphertexts exist.
Encrypting aa
produces 1358978781420397973599734443208080283277600898470175152907325662368383556042601926805566272772387881716258124262388439742641141572534774100896284958064521951957398461530048460197949508093202478766213100457797844990374525249088505646874044618118801950676178439335505418621826395423449100058209047785933298283673561024996000428515785201807310147438746525988645885310370799314417341170352572980373936820027773998706009446119059121725998847299152732140652261734547503673358211949690790453350635511909487532110430283112659904909965978120113482426195268318329751292020654211499572883335268473019408057847129211754745164317
, which is 616 characters long. Let's use E
to denote the encryption function and E1(m)
to denote the first ciphertext of message m
. The E1('a')
from before is 308 characters long. This is exactly double which indicates that the encryption algorithm uses some kind of concatenation. Reconnecting and sending a
and aa
again does not produce ciphertexts where the length is exactly double, but the length of E1('aa')
is always close to double that of E1('a')
.
After looking at E1('a')
and E1('aa')
, I realized that E1('aa')
includes E1('a')
. Encrypting abc
also includes E1('a')
. In other words, a
's ciphertext is in abc
's ciphertext. However, b
's ciphertext is not in abc
's ciphertext. Despite this, E1('ab')
is in E1('abc')
. The encryption algorithm appears to randomize the order of the encrypted letters. In E1('abc')
, E1('a')
is sometimes first, sometimes second, and sometimes last.
Taking all of this information into account, E1('a')
and some value that represents E1('b')
are always in E1('abc')
. This also applies to the flag ciphertext. Since the flag format begins with picoCTF{
, E1('p')
is in E1(flag)
and a representation of E1('i')
is also in the flag. We can encrypt p
and then encrypt pi
. We can then remove E1('p')
from E1('pi')
to obtain the representation of E1('i')
that is in the encrypted flag. We repeat this process until the {
character when we don't know the next character. At this point we can loop through every printable ascii character, c
, and encrypt 'picoCTF{'+c
.
So, in summary, our exploit does the following:
Create a string, decoded_flag
, to hold the flag's characters as the loop runs.
While decoded_flag[-1]
is not }
then for each printable ascii character, c
:
Encrypt decoded_flag+c
and store to current_encrypt_test
.
Remove the previous encrypted input (previous_encrypt_test
) from current_encrypt_test
to obtain the representation of E(c)
and save to current_char_rep
.
If current_char_rep
is in E1(flag)
then append c
to decoded_flag
and break to the next iteration.
Set previous_encrypt_test
to current_encrypt_test
.
Running the will print the flag character by character.
picoCTF{bad_1d3a5_3525501}