“It is my experience that proofs involving matrices can be shortened by 50% if one throws the matrices out.”
Emil Artin
ncat --ssl determined.chal.uiuc.tf 1337
Here’s gen.py:
1 | from SECRET import FLAG, p, q, r |
Here’s server.py:
1 | from Crypto.Util.number import bytes_to_long, long_to_bytes |
gen.txt doesn’t provide any particularly revealing information – the main challenge lies in breaking server.py, not the RSA part, as gen.py is literally just standard RSA encryption.
server.py analysis
We are allowed to fill in particular parts of the matrix M
. I’ll denote user-controlled inputs with an X
.
1 | [ |
p, q, and… r?
Importantly, p
, q
, and r
are part of the matrix. But r
isn’t part of the RSA encryption process at all…? Hmmm. That’s odd. Definitely something to note.
It then calls fun(M)
. Notice that fun()
is actually very similar to the check()
function from the crypto challenge Without a Trace.
Let’s ignore sign()
for now, and focus on the part after that:
1 | res = 0 |
Basically, this iterates through all the possible choices of cells of the matrix such that there is only one selected cell in each row and column (think Sudoku) and multiplies those cells, as well as the result of sign(sigma)
, and adds that to res
. We are then given res
.
Importantly, for a specific permutation of [0,1,2,3,4]
, if any of the selected cells are 0, it will add nothing to res
. This is very interesting, because if we can essentially isolate a single permutation, making sure all the other ones add nothing to res
, res
will be the product of sign(the isolated permutation) * (product of selected cells)
.
sign()…?
sign()
doesn’t actually matter. If you take a look at the return value, it will always return 1
or -1
. Since we’re going to be trying to isolate a single permutation, it won’t matter – we can just take the absolute value of the result to get the product of the selected cells.
how to matrix
Well, how do we set up our matrix such that we isolate a single permutation and crack the RSA encryption?
Turns out, this one works:
1 | [ |
Let me first explain how this isolates a single permutation.
Remember that fun()
essentially iterates through all combinations of five cells such that each row and column contain one cell each. In this matrix, only one combination works such that no cell contains 0. Confirm for yourself that it is the following combination cells:
(0,0), (1,1), (2,4), (3,3), (4,2)
.
And the corresponding numbers are:
p 1 1 r 1
.
So the absolute value of the number the server spits back to us should be $$p \cdot 1 \cdot 1 \cdot r \cdot 1=pr$$
but why pr???
Well, remember that r
was included in this matrix but not the RSA encryption process? Yeah… that’s really weird.
Well, in order to crack the RSA encryption, we basically want to factor the modulus n
, which is the product of p
and q
. Well, what if we found something like, say, pr
. Then, we can take the GCD of pr
and pq
, which is known to be much faster than prime factorizing a number, and get p
.
And that’s that! Send in the correct input numbers to fill in the matrix like listed above, get pr
, take the GCD of pr
and pq
, and get the flag!
1 | from Crypto.Util.number import * |
uiuctf{h4rd_w0rk_&&_d3t3rm1n4t10n}