Write-up-AmateursCTF-2024
Easy to medium crypto challenges.
Aesy
Challenge decription:
Please aes-decrypt the flag for me.
1
2
key: 8e29bd9f7a4f50e2485acd455bd6595ee1c6d029c8b3ef82eba0f28e59afcf9f
ciphertext: abcdd57efb034baf82fc1920a618e6a7fa496e319b4db1746b7d7e3d1198f64f
Solution:
Using any decrypt tools to decrypt AES ECB, we will have the flag.
1
amateursCTF{w0w_3cb_a3s_1s_fun}
Unsuspicious-rsa
Challenge decription:
I need help factoring this modulus, it looks suspicious, but I can’t factor using any conventional methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
def nextPrime(p, n):
p += (n - p) % n
p += 1
iters = 0
while not isPrime(p):
p += n
return p
def factorial(n):
if n == 0:
return 1
return factorial(n-1) * n
flag = bytes_to_long(open('flag.txt', 'rb').read().strip())
p = getPrime(512)
q = nextPrime(p, factorial(90))
N = p * q
e = 65537
c = pow(flag, e, N)
print(N, e, c)
# N,e,c = 172391551927761576067659307357620721422739678820495774305873584621252712399496576196263035396006999836369799931266873378023097609967946749267124740589901094349829053978388042817025552765214268699484300142561454883219890142913389461801693414623922253012031301348707811702687094437054617108593289186399175149061 65537 128185847052386409377183184214572579042527531775256727031562496105460578259228314918798269412725873626743107842431605023962700973103340370786679287012472752872015208333991822872782385473020628386447897357839507808287989016150724816091476582807745318701830009449343823207792128099226593723498556813015444306241
Solution:
Skimming through the code, we see that $q$ is generated by using $p$ and $90!$. The generate function will make $q$ have the form $p + k\cdot 90! + 1 +(90! - p) \cdot 90! $ . $(90! - p) \cdot 90! $ will add to $p$ some amount to make $p = m\cdot 90!$. Then $q = (m+k)\cdot 90! +1$.
When I digging around which the source, I realize that $k$ is often around $[0,200]$ which is very small, so i decide to bruteforcing. Let $v = \dfrac{\sqrt{N}}{90!} \approx m$ and we will find $k$. The rest is normal RSA decryption.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from gmpy2 import iroot
from Crypto.Util.number import *
n = 172391551927761576067659307357620721422739678820495774305873584621252712399496576196263035396006999836369799931266873378023097609967946749267124740589901094349829053978388042817025552765214268699484300142561454883219890142913389461801693414623922253012031301348707811702687094437054617108593289186399175149061
e = 65537
c = 128185847052386409377183184214572579042527531775256727031562496105460578259228314918798269412725873626743107842431605023962700973103340370786679287012472752872015208333991822872782385473020628386447897357839507808287989016150724816091476582807745318701830009449343823207792128099226593723498556813015444306241
fac_90 = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000
t = iroot(n, 2)[0]
v = t // fac_90
while True:
q = v*fac_90 + 1
if n % q == 0:
p = n//q
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
m = pow(c,d,n)
print(long_to_bytes(m))
break
v += 1
#amateursCTF{here's_the_flag_you_requested.}
Faked-onion
Challenge decription:
Are you as fake as this onion?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/local/bin/python3
import hmac
from os import urandom
def strxor(a: bytes, b: bytes):
return bytes([x ^ y for x, y in zip(a, b)]) #xor
class Cipher:
def __init__(self, key: bytes):
self.key = key
self.block_size = 16
self.rounds = 1
def F(self, x: bytes):
return hmac.new(self.key, x, 'md5').digest()[:15]
def encrypt(self, plaintext: bytes):
plaintext = plaintext.ljust(self.block_size, b'\x00')
ciphertext = b''
for i in range(0, len(plaintext), self.block_size):
block = plaintext[i:i+self.block_size]
for _ in range(self.rounds):
L, R = block[:-1], block[-1:]
L, R = R, strxor(L, self.F(R))
block = L + R
ciphertext += block
return ciphertext
key = urandom(16)
cipher = Cipher(key)
flag = b'..'
print("faked onion")
while True:
choice = input("1. Encrypt a message\n2. Get encrypted flag\n3. Exit\n> ").strip()
if choice == '1':
pt = input("Enter your message in hex: ").strip()
pt = bytes.fromhex(pt)
print(cipher.encrypt(pt).hex())
elif choice == '2':
print(cipher.encrypt(flag).hex())
else:
break
print("Goodbye!")
Solution:
First, we look at the encryption function:
1
2
3
4
5
6
7
8
9
10
11
12
def encrypt(self, plaintext: bytes):
plaintext = plaintext.ljust(self.block_size, b'\x00')
ciphertext = b''
for i in range(0, len(plaintext), self.block_size):
block = plaintext[i:i+self.block_size]
for _ in range(self.rounds):
L, R = block[:-1], block[-1:]
L, R = R, strxor(L, self.F(R))
block = L + R
ciphertext += block
return ciphertext
#Note: block_size = 16, rounds = 1
The code will divide plaintext into block of 16 bytes. And then seperated into 2 part: $L$ and $R$. $L$ is 15 bytes from the beginning and $R$ is the last byte. Then $L = R$ and $R = xor(L,Hash(R))$ then it is appended in ciphertext.
We notice that we decrypt the ciphertext we just do the reverse. Take the first bytes of the block, hash it through the oracle and xor with 15 remaining bytes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
def strxor(a: bytes, b: bytes):
return bytes([x ^ y for x, y in zip(a, b)]) #xor
m = b''
conn = remote('chal.amt.rs', 1414)
conn.recvuntil('>')
conn.sendline('2')
ct = conn.recvline().strip().decode()
ct = bytes.fromhex(ct)
for i in range(0, len(ct), 16):
block = ct[i:i+16]
L = block[0].to_bytes(1, 'big')
R = block[1:]
conn.recvuntil('>')
conn.sendline('1')
conn.recvuntil(':')
payload = b'\x00'*15+L #skip ljust
conn.sendline(payload.hex())
heh = conn.recvline().strip().decode()
heh = bytes.fromhex(heh)
heh = heh[1:]
m = m + strxor(R, heh)
m = m + L
print(m)
#amateursCTF{oh_no_my_one_of_a_kind_err_sorry,_f4ked_on10n_cipher_got_ki11ed_730eb1c0}