I had a short look at some challenges from ISITDTU CTF (2019 quals) over the weekend and managed to solve two crypto challs.
Challenges:
- Crypto
- decrypt to me
- Easy RSA 2
decrypt to me
Crypto - 395 points (solved)
decrypt to me?????
import binascii
def generate_prg_bit(n):
state = n
while True:
last_bit = state & 1
yield last_bit
middle_bit = state >> len(bin(n)[2:])//2 & 1
state = (state >> 1) | ((last_bit ^ middle_bit) << (len(bin(n)[2:])-1))
flag = '###########'
enc = "OKQI+f9R+tHEJJGcfko7Ahy2AuL9c8hgtYT2k9Ig0QyXUvsj1B9VIGUZVPAP2EVD8VmJBZbF9e17"
flag_bin_text = bin(int(binascii.hexlify(flag), 16))[2:]
prg = generate_prg_bit(len(flag_bin_text))
ctext = []
flag_bits = [int(i) for i in flag_bin_text]
for i in range(len(flag_bits)):
ctext.append(flag_bits[i] ^ next(prg))
ciphertext = '0b' + ''.join(map(str, ctext))
n = int(ciphertext, 2)
print binascii.unhexlify('%x' % n).encode('base64')
Solution
We are given a python program that uses a pseudorandom generator to XOR each bit of the flag with to give us the encrypted flag enc
.
The first thing we take note of is that we don't need to understand how the prg works at all. Since it is given to us, we just need to use it to XOR the ciphertext since
The second important thing to notice is that the seed being used for the prg is len(flag_bits))
, a value which is very feasible to brute force.
With this knowledge, all that's left is implementation:
import binascii
def generate_prg_bit(n):
state = n
while True:
last_bit = state & 1
yield last_bit
middle_bit = state >> len(bin(n)[2:])//2 & 1
state = (state >> 1) | ((last_bit ^ middle_bit) << (len(bin(n)[2:])-1))
enc = "OKQI+f9R+tHEJJGcfko7Ahy2AuL9c8hgtYT2k9Ig0QyXUvsj1B9VIGUZVPAP2EVD8VmJBZbF9e17"
ctext = '0' + bin(int(binascii.hexlify(enc.decode('base64')),16))[2:]
for k in range(7, 1000, 8):
try:
prg = generate_prg_bit(k)
f = ''
cbits = [int(i) for i in ctext]
for i in range(len(cbits)):
f += str(cbits[i] ^ next(prg))
f = binascii.unhexlify(hex(int(f,2))[2:-1])
if 'ISITDTU' in f:
print f, k
break
except:
pass
Also note that we prepend '0'
to the ctext
variable after reversing the encodings done by the encryption script. This is because bin(n, 2)[2:]
will not start with a '0'
. We could have also set the script to run through all possible flag lengths, but since the seed is the length of the binary representation of the text, we know it will be of the form since the first letter of the flag is I
which is equivalent to 10010010
(7 bits).
Running the script prints the flag.
Flag: ISITDTU{Encrypt_X0r_N0t_Us3_Pseud0_Rand0m_Generat0r!!!!!}
Easy RSA 2
Crypto - 919 points (solved)
Let's continue with RSA
from Crypto.Util.number import *
import gmpy2
flag = '##################################'
p1 = getPrime(512)
p2 = gmpy2.next_prime(p1)
q1 = getPrime(512)
q2 = gmpy2.next_prime(q1)
n = p1*p2*q1*q2
e = 65537
phi = (p1-1)*(p2-1)*(q1-1)*(q2-1)
d = gmpy2.invert(e,phi)
c = pow(bytes_to_long(flag),e,n)
print pow(p1+q2,65537,n)
#5043622010330564722783560796388733110223192234657313797979729183216316602247790170027393145104828283812158304519218370476380897023249898720267053051908498011845198383126598688185743313040451851234309071530873683667360872515868401870834371902623509762498919172464493397284930232415029297203698778851121422456149280629701148108649396642433199634388011535777204188207597427548981195309015900421249473588077922607729093939587454170211363784480831197764238579460361668878037335596700513382133341370374840639374005225742007557272153800433699784092511039693877686425832957477808359462507401596842526527374816943302475357302
print pow(p2+q1,65537,n)
#7919283184559406259028604751155413696993375814336862337694645459367829841130544291770103966362177145582007048754925168845793555136985754996486596987205043932984314934297789456823769422776642272151478021108135062833657996366160688598742804847633068533451034898357435150319123770512604358033881809960916484049603490477616900480883862825416570459592254659007024761917196293369565486538943942938968226701375668351560376904094935919442322484791587819687743780031411339960372463937311578960714219580981945254129150844798674023932645363519148439092971133029751088847668041720574694350298717079140377388740434213791727288722
print n
#8573641536164485111081609341110540574423426701587222458588002464807917555910942077276167528046769327390058096169685188870928286845342631974847171845103806710768418462668311621275704636581042137915505959767806384415314024549489538717607173007829067492516776714817262226691787436227002924225311861164296655909746846329870548266285498682510415418053656271623482202491805513797215793596385014264449282551352796096361524482384994633912515104414237252657058698433260597636367614328512751722615849959987780969423318207123668118325176544879335267439096589035064596631756303300860315257404427016819145298919974287174103934503
print c
#8436043641135865531308468859210199431445831063674810351906331674115825605849862045115409554309732867926457428348729196827592921108183774070414343257409618631078896543782150761081732376735501920417229787663210936174854000594130785353102718054331606096192133481536724402629697019651921188121029927710787682993814748802295545306899075962041017278877203965796981792702098381465051289581518257202127401748725944229037078896857591660248467597356051123218945757343652461844056927461929195427880969904210166880623689090977714615839624798930450630919330253477634839161931755642681718034910946900928731231093352169252474939674
Solution
This was an interesting RSA problem based around the modulus having multiple prime factors. The things that stand out immediately are the ways the primes p2
and q2
are generated. Since p2
is the next prime after p1
and q2
is the next prime after q1
we can write them as such:
For small integers and
From this, we can further observe that will be small since:
Thus, can be easily factorised into the two factors and . We can use Alpertron's Integer Factorization Calculator to do this for us in seconds. Then we get:
n1 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135377198629507140256023102013131493810077402487620597574150419252484604398426042348936643985371724093575122287222535733709825807203222224632574777263054682547849
n2 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135375408481531310003733935540422655297910944762679239039783193372674051452613244254750117673971732027355424665752849172881712190463569600293779394868475158854447
with n1 = p1*q2
and n2 = p2*q1
.
From these two equations, it turns out we can derive a simple quadratic equation in terms of , , and to solve for any one of , , or :
Combining these two results:
Now all that's left is to bruteforce solve for with small and . We should be able to comfortably find a solution for . We should also note that we require to be prime. Script to solve for :
n1 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135377198629507140256023102013131493810077402487620597574150419252484604398426042348936643985371724093575122287222535733709825807203222224632574777263054682547849
n2 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135375408481531310003733935540422655297910944762679239039783193372674051452613244254750117673971732027355424665752849172881712190463569600293779394868475158854447
from gmpy2 import iroot
from Crypto.Util.number import isPrime
def quadratic(a, b, c):
try:
(d, _) = iroot(b*b - (4*a*c),2)
return ((-b-d)//(2*a), (-b+d)//(2*a))
except:
return 0
for (e, d) in ((e, d) for e in range(1, 5000) for d in range(1, 5000)):
q1 = quadratic(e, e*d+n1-n2, -d*n2)
if q1 != 0:
q1 = q1[1]
res = q1*q1*e + q1*(e*d+n1-n2)-d*n2
if res == 0 and isPrime(q1):
print(q1, e, d)
Which after a couple of seconds after being executed, prints: 9171763103475238155920017905180080637957026348805819229558633463083231168099582814700421044911191638619215776932495855800737116252367554545053545527516011 386 528
which gives us the value for q1
.
Using this, we can calculate the rest of the primes that make up n
and proceed to decrypt RSA as usual.
from Crypto.Util.number import inverse, long_to_bytes
c = 8436043641135865531308468859210199431445831063674810351906331674115825605849862045115409554309732867926457428348729196827592921108183774070414343257409618631078896543782150761081732376735501920417229787663210936174854000594130785353102718054331606096192133481536724402629697019651921188121029927710787682993814748802295545306899075962041017278877203965796981792702098381465051289581518257202127401748725944229037078896857591660248467597356051123218945757343652461844056927461929195427880969904210166880623689090977714615839624798930450630919330253477634839161931755642681718034910946900928731231093352169252474939674
n1 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135377198629507140256023102013131493810077402487620597574150419252484604398426042348936643985371724093575122287222535733709825807203222224632574777263054682547849
n2 = 92593960581478990492272892082797582837598460722110672289472622026362327649808629839901870926342611079306340852878894165246912369940368199994436453135375408481531310003733935540422655297910944762679239039783193372674051452613244254750117673971732027355424665752849172881712190463569600293779394868475158854447
q1 = 9171763103475238155920017905180080637957026348805819229558633463083231168099582814700421044911191638619215776932495855800737116252367554545053545527516011
epsilon = 386
delta = 528
q2 = q1 + delta
p1 = n1//q2
p2 = p1 + epsilon
n = n1*n2
phi = (p1-1)*(p2-1)*(q1-1)*(q2-1)
d = inverse(0x10001, phi)
m = pow(c, d, n)
print(long_to_bytes(m).decode())
Which executing, prints the flag.
Flag: ISITDTU{C0ngratu1ati0ns_Attack_RSA_Multi_prim3!!!!}