CTF
Search…
SVATTT 2019 Qualification
Playing CTF is fun, playing CTF with friends is even better

Foreword

First of all, I want to give kudos to all my team-mates who have worked hard with me during this CTF. Thanks guys!
Next, I would like to give a special thanks to our teacher, Mr Le Dinh Thanh, who have been led UET's CTF club to many achievements for years
Finally, I want to thank VNISA, VNSecurity, Viettel and KMA for their efforts to organize this CTF contest.
Since I mostly focused on solving crypto and reverse engineer challenges during this CTF, so this write up is all about crypto and re challs

Crypto01

1
#!/usr/bin/env python3
2
from Crypto.Util.number import getPrime, inverse
3
SUFFIX = bytes.fromhex('2019')
4
5
def sign(message, private_key):
6
m = int.from_bytes(message + SUFFIX, 'big')
7
s = pow(m, private_key['d'], private_key['n'])
8
return s.to_bytes((private_key['n'].bit_length()+7)//8, 'big')
9
10
def verify(signature, message, public_key):
11
m = int.from_bytes(message + SUFFIX, 'big')
12
s = int.from_bytes(signature, 'big')
13
return pow(s, public_key['e'], public_key['n']) == m
14
15
16
if __name__ == '__main__':
17
p = getPrime(512)
18
q = getPrime(512)
19
n = p*q
20
e = 65537
21
d = inverse(e, (p-1)*(q-1))
22
private_key = {'n': n, 'e': e, 'd': d}
23
public_key = {'n': n, 'e': e}
24
print(public_key)
25
message = bytes.fromhex(input())
26
if b'cat flag' != message:
27
signature = sign(message, private_key)
28
print(signature.hex())
29
else:
30
print('Nope!')
31
32
message = bytes.fromhex(input())
33
signature = bytes.fromhex(input())
34
35
# is this possible?
36
if verify(signature, message, public_key) and message == b'cat flag':
37
print(open('flag.txt').read())
Copied!
So basically the server allow us to sign and get signature of a message if it's not "cat flag"
But still, we need signature of a message when it's "cat flag". To do so, we send to the server
1
m = 0x63617420666c6167 + n
Copied!
Which is not 0x63617420666c6167 but after passed to sign() function, it becomes as same as if we pass m itself alone to sign() function
(m+n)dmodn=(mdmodn+ndmodn)modn=mdmodn(m + n)^d \bmod n = (m^d \bmod n + n^d \bmod n ) \bmod n= m^d \bmod n
Since we got the signature that we want, just submit it and get the flag
1
C:\Users\ADMIN\Downloads>python3 weird.py
2
{'n': 67761998655885843590105879625321473468665366163122885232269853261794206115615993959728990876618395362891147295986525580673528819935287958007937223416079475673811763389010403429908730637246302717985068391502324114671797895969701496401082277405620402589075870672773107039203959453532840668196065255024387368989, 'e': 65537}
3
607f0cce6711fdc5eca52e50b42494ab7073fe40316dfd8fd9512e7cf3eb585e4516c2331bb6461d7ebe58c968a8a7ae3852b830cd1a9795d2c76cc7cd575fe6a91b5e2fc96315f4f836d9361f930b9fca410804f2cb27837b0202313df760196c61a1434ace9dc1c799b7b0dea61292d7fa6740962c360d8c854a8a74e78984
4
26919cbf2c988f600a6129c2f47ad4cd07503dec89b02486e01bf4a04f8ef210a5b10db8e5c959c6fbf0fccff074243a244dbf081546e21db3b2944275b4037988077bcb629e79f3d1b2c6997df56ef5d143b8698642f3270d3ace83037dfdf6508eadf78490ffcad6db103b1e686d68072b6d74fdcf41134712da1c86a6a73f
5
63617420666c6167
6
26919cbf2c988f600a6129c2f47ad4cd07503dec89b02486e01bf4a04f8ef210a5b10db8e5c959c6fbf0fccff074243a244dbf081546e21db3b2944275b4037988077bcb629e79f3d1b2c6997df56ef5d143b8698642f3270d3ace83037dfdf6508eadf78490ffcad6db103b1e686d68072b6d74fdcf41134712da1c86a6a73f
7
"SVATTT{test_flag}"
Copied!

Crypto02

1
#!/usr/bin/env python3
2
from Crypto.Util.number import getPrime, inverse
3
import random, math
4
if __name__ == '__main__':
5
p = getPrime(1024)
6
q = getPrime(1024)
7
n = p*q
8
phi_n = (p-1)*(q-1)
9
e = 65537
10
d = inverse(e, phi_n)
11
print(n)
12
secret = random.randint(0, n)
13
print(pow(secret, e, n))
14
# can you exploit this least significant base2019 digit oracle?
15
rounds = int(2048 // math.log2(2019)) + 1
16
for _ in range(rounds):
17
c = int(input())
18
print(pow(c,d,n) % 2019)
19
m = int(input())
20
if m == secret:
21
print(open('flag.txt').read())
Copied!
It's same as @ndh's write up
1
from pwn import *
2
from Crypto.Util.number import getPrime, inverse
3
import random, math
4
io = remote("ip",port)
5
n = int(io.recvline().strip())
6
e = 65537
7
cipher = int(io.recvline().strip())
8
rounds = int(2048 // math.log2(2019)) + 1
9
m = 0
10
x = []
11
for i in range(rounds):
12
c = cipher*inverse(pow(2019**i,e,n),n)
13
io.sendline(str(c))
14
loop = 0
15
maxlen = len(x)
16
for j in range(len(x)):
17
loop += x[j]*inverse(2019**(maxlen-j),n)%n
18
loop = loop %n% 2019
19
m0 = int(io.recvline().strip())
20
#m0 = pow(c,d,n)%2019
21
x.append((m0-loop)%2019)
22
m += ((m0-loop)%2019)*pow(2019,i)
23
io.sendline(str(m%n))
24
io.recvline()
25
io.recvline()
Copied!

RE02

Most of the re part was done by @kungfulon. Kudos to him.
Basically, the program was worked as following :
1
1. Generate a random 32 bytes key
2
2. Encrypt the key with RSA hardcoded public key
3
3. Use the genarated key, hardcoded IV "0123456789012345" with AES-256-CBC to encrypt the file data
4
5
The file structure is as following :
6
1. First 5 bytes is "CRYPT"
7
2. Next 4 bytes is the size of rsa encrypted 32 bytes key, let call it enc_key
8
3. Next is enc_key
9
4. Next 4 bytes is the size of aes-256-cbc encrypted data, let call it enc_data
10
5. Next is enc_data
Copied!
Script to decrypt the file
1
#Kudos to @kungfulon (Bien Pham)
2
from Crypto.Cipher import AES
3
from Crypto.PublicKey import RSA
4
from Crypto.Cipher import PKCS1_OAEP
5
6
import struct
7
def file_parse(content):
8
str_crypt = content[0:5]
9
key_size = int(content[5:9][::-1].encode('hex'), 16)
10
key_enc = content[9:9+key_size]
11
data_enc_size = int(content[9+key_size:9+key_size+4][::-1].encode('hex'), 16)
12
data_enc = content[9+key_size+4:9+key_size+4+data_enc_size]
13
return (key_enc, data_enc)
14
15
f = open('flag.txt.rsacrypted', 'rb') #put your file here
16
content = f.read()
17
f.close()
18
19
rsa_key = RSA.importKey('''-----BEGIN RSA PRIVATE KEY-----
20
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
66
SvfBX0F+26VR
67
-----END RSA PRIVATE KEY-----
68
''')
69
cipher = PKCS1_OAEP.new(rsa_key)
70
71
key_enc, data_enc = file_parse(content)
72
73
key = cipher.decrypt(key_enc)
74
75
iv = '0123456789012345'
76
aes = AES.new(key, AES.MODE_CBC, iv)
77
decd = aes.decrypt(data_enc)
78
79
#remember to remove padding
80
f = open('out.dec', 'wb')
81
f.write(decd)
82
f.close()
Copied!
Finally, the flag is
1
SVATTT{In_Wiener_we_trust}
Copied!

End game

Playing CTF is fun, playing CTF with friends is even better
Last modified 2yr ago