CLB An toàn Thông tin Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com hoặc inseclab@uit.edu.vn và thông qua fanpage: fb.com/inseclab.
lttn
Symbol
Ta dễ dàng thấy đây là 1 dãy các kí hiệu toán học được đại diện cho các kí tự của flag
Sau 1 lúc google mò mẫm thì mình thấy các kí tự cuối của flag là LaTeX. Nên mình nghĩ các kí tự này có liên quan đến LaTeX.Mình thấy các kí hiệu được biểu diễn bằng LaTeX với công thức lần lượt là
\Cap
\Cap
`
`\Theta
`
`\Findv
`
`\Pi
`
`\ltime
`
`\alept
`
`y
` kiếm không ra :(
`\wp
`
`\infty
`
`\therefore
`
`\heart
`
`\Lsh
`
`\alepth
`
`\Theta
`
`\eth
`
`\Xi
Ghép các chữ cái đầu lài ta được : CCTF{Play_with_LaTeX} :v
Farm
#!/usr/bin/env sagefrom sage.all import *import string, base64, mathfrom flag import flagALPHABET = string.printable[:62] + '\\='F = list(GF(64))def keygen(l): key = [F[randint(1, 63)] for _ in range(l)] key = math.prod(key) # Optimization the key length :D return keydef maptofarm(c): assert c in ALPHABET return F[ALPHABET.index(c)]def encrypt(msg, key): m64 = base64.b64encode(msg) enc, pkey = '', key**5 + key**3 + key**2 + 1 for m in m64: enc += ALPHABET[F.index(pkey * maptofarm(chr(m)))] return enc# KEEP IT SECRET key = keygen(14) # I think 64**14 > 2**64 is not brute-forcible :Penc = encrypt(flag, key)print(f'enc = {enc}')
Để tạo key thì đầu tiên chương trình lấy ngẫu nhiên l phần tư trong GF(64)
` và nhân tất cả chúng lại với nhau được 1 key, cuồi cùng lấy
`key = key^5 +key^3 +key^2 +1
Do tính chất khép kín nên khi trải qua nhìu bước làm phía trên thì key cũng sẽ chỉ nằm trong GF(64) -> Vậy chỉ chó 64 key tất cả có thẻ gen ra nên ta có thể brute force để tìm
Đã có key thì ta hoàn toàn có thể đảo ngược lại code để tìm flag.
Hoặc nếu lười như mình thì có thể brute force tiếp để tìm m
` sao cho thỏa
`enc = ALPHABET[F.index(pkey * maptofarm(chr(m)))]
` với mỗi vị trí của
`enc
` và
`m
Solution của mình
from sage.all import *import string, base64, mathfrom base64 import *ALPHABET = string.printable[:62] + '\\='F = list(GF(64))def maptofarm(c): assert c in ALPHABET return F[ALPHABET.index(c)]def decrypt(key,c): flag="" for i in c: for m in ALPHABET.encode(): if ALPHABET[F.index(key * maptofarm(chr(m)))]==i: flag+=chr(m) try: flag=b64decode(flag) return flag except: return flag.encode()for key in F: flag=decrypt(key,'805c9GMYuD5RefTmabUNfS9N9YrkwbAbdZE0df91uCEytcoy9FDSbZ8Ay8jj') if b'CCTF' in flag: print(flag)
4rtist
Keybase
#!/usr/bin/env python3from Crypto.Util import numberfrom Crypto.Cipher import AESimport os, sys, randomfrom flag import flagdef keygen(): iv, key = [os.urandom(16) for _ in '01'] return iv, keydef encrypt(msg, iv, key): aes = AES.new(key, AES.MODE_CBC, iv) return aes.encrypt(msg)def decrypt(enc, iv, key): aes = AES.new(key, AES.MODE_CBC, iv) return aes.decrypt(enc)def die(*args): pr(*args) quit()def pr(*args): s = " ".join(map(str, args)) sys.stdout.write(s + "\n") sys.stdout.flush()def sc(): return sys.stdin.readline().strip()def main(): border = "+" pr(border*72) pr(border, " hi all, welcome to the simple KEYBASE cryptography task, try to ", border) pr(border, " decrypt the encrypted message and get the flag as a nice prize! ", border) pr(border*72) iv, key = keygen() flag_enc = encrypt(flag, iv, key).hex() while True: pr("| Options: \n|\t[G]et the encrypted flag \n|\t[T]est the encryption \n|\t[Q]uit") ans = sc().lower() if ans == 'g': pr("| encrypt(flag) =", flag_enc) elif ans == 't': pr("| Please send your 32 bytes message to encrypt: ") msg_inp = sc() if len(msg_inp) == 32: enc = encrypt(msg_inp, iv, key).hex() r = random.randint(0, 4) s = 4 - r mask_key = key[:-2].hex() + '*' * 4 mask_enc = enc[:r] + '*' * 28 + enc[32-s:] pr("| enc =", mask_enc) pr("| key =", mask_key) else: die("| SEND 32 BYTES MESSAGE :X") elif ans == 'q': die("Quitting ...") else: die("Bye ...")if __name__ == '__main__': main()
Connect to the server and get the encrypted flag and request server to encrypt the string The message is protected by AES!
We've know:
+ Plaintext + The encryption algorithm (AES CBC with block size 16)+ first 14 characters of the 16 character key+ The complete second block and parts of the first ciphertext block
In the Cipher Block Chaining (CBC) mode of operation, each plaintext block is XORed with the previous ciphertext block before being encrypted.
If the encryption function is Eₖ, then we have the following recurrence relation:
The decrypted result is XORed with the previous ciphertext block.It follows that if the decryption function is Dₖ, then the decryption is:
Brute force 2 last characters of key by decrypt the 2nd block with the IV is 1st block. If the plaintext's started with r
and end with S!
, accept the key.
Scince xor is revertable a ⊕ b = c ⇔ c ⊕ b = a and we now know the key, we can simply change the role of the first cipher text block and second plaintext block and do AES decryption on the second ciphertext block with the second plaintext block as IV (instead of the first cipher text block), which leads to the first ciphertext block as "decrypted paintext".
Do it again but this time we change the role of first plaintext block and IV when doing decryption on the first ciphertext block, which leads to the IV as decrypted plaintext.
Finally have key, IV, encrypted flag => decrypt to see flag.
from Crypto.Util import numberfrom Crypto.Cipher import AESfrom operator import xorimport binascii, sysALPHABET ='0123456789abcdef'KEY_first = "3247c8d03aa36ca1270aee48458c"cipher1 = "67390000000000000000000000006660" cipher2 = "2e51675f978784997032ffffe2b6bbfd"plain1 = "The message is p"plain2 = "rotected by AES!"realkey = ''def decrypt(cipher, passphrase): aes = AES.new(passphrase, AES.MODE_CBC, binascii.unhexlify(cipher1)) return aes.decrypt(cipher)def decrypt2(enc, iv, key): aes = AES.new(key, AES.MODE_CBC, iv) return aes.decrypt(enc)# iterate through relavent ascii rangefor i in ALPHABET: for j in ALPHABET: for k in ALPHABET: for l in ALPHABET: key = KEY_first + i + j + k + l keyed = binascii.unhexlify(key) dec_plain2 = decrypt(binascii.unhexlify(cipher2), keyed) if "S!" in str(dec_plain2) and "r" in str(dec_plain2): print("decrypted plain2: " + str(dec_plain2) + " with key: " + str(keyed)) realkey = keyedprint(realkey)realcipher1 = decrypt2(binascii.unhexlify(cipher2),plain2.encode(),realkey)print(realcipher1.hex())assert md5(salt).hexdigest() == '5f72c4360a2287bc269e0ccba6fc24ba'assert sha1(pepper).hexdigest() == '3e0d000a4b0bd712999d730bc331f400221008e0'IV = decrypt2(realcipher1,plain1.encode(),realkey)print(IV)ct = "2b98362a49d61c9438d4c889a1c2bd23142cf196b84e57cf886682c0165a3e7d"print(decrypt2(binascii.unhexlify(ct),IV,realkey))
Flag: CCTF{h0W_R3cOVER_7He_5eCrET_1V?}
Salt pepper
#!/usr/bin/env python3from hashlib import md5, sha1import sysfrom secret import salt, pepperfrom flag import flagassert len(salt) == len(pepper) == 19assert md5(salt).hexdigest() == '5f72c4360a2287bc269e0ccba6fc24ba'assert sha1(pepper).hexdigest() == '3e0d000a4b0bd712999d730bc331f400221008e0'def auth_check(salt, pepper, username, password, h): return sha1(pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest() == hdef die(*args): pr(*args) quit()def pr(*args): s = " ".join(map(str, args)) sys.stdout.write(s + "\n") sys.stdout.flush()def sc(): return sys.stdin.readline().strip()def main(): border = "+" pr(border*72) pr(border, " welcome to hash killers battle, your mission is to login into the ", border) pr(border, " ultra secure authentication server with provided information!! ", border) pr(border*72) USERNAME = b'n3T4Dm1n' PASSWORD = b'P4s5W0rd' while True: pr("| Options: \n|\t[L]ogin to server \n|\t[Q]uit") ans = sc().lower() if ans == 'l': pr('| send your username, password as hex string separated with comma: ') inp = sc() try: inp_username, inp_password = [bytes.fromhex(s) for s in inp.split(',')] except: die('| your input is not valid, bye!!') pr('| send your authentication hash: ') inp_hash = sc() if USERNAME in inp_username and PASSWORD in inp_password: if auth_check(salt, pepper, inp_username, inp_password, inp_hash): die(f'| Congrats, you are master in hash killing, and it is the flag: {flag}') else: die('| your credential is not valid, Bye!!!') else: die('| Kidding me?! Bye!!!') elif ans == 'q': die("Quitting ...") else: die("Bye ...")if __name__ == '__main__': main()
This challenge request to sign in, and send the authentication hash.
In source code I see:
assert len(salt) == len(pepper) == 19assert md5(salt).hexdigest() == '5f72c4360a2287bc269e0ccba6fc24ba'assert sha1(pepper).hexdigest() == '3e0d000a4b0bd712999d730bc331f400221008e0'def auth_check(salt, pepper, username, password, h): return sha1(pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest() == h
So they give length of messenger and 2 hash. Searching GG I see that i could use hashlength extension attack. My pro brother support me hash_extender to solve this.
First, I solve md5(salt + username).hexdigest()
.
Now my new username string is: 8000000000000000000000000000000000000000000000000000000000000000000000000098000000000000006e335434446d316e
After that, I solve sha1(pepper + password).hexdigest()
to get new password.
My new password: 8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000985034733557307264
Finally solve (pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest()
to get the hash.
Send them to server and get the flag.
Flag: CCTF{Hunters_Killed_82%_More_Wolves_Than_Quota_Allowed_in_Wisconsin}
4rtist ft dunglq
Tuti
#!/usr/bin/env python3from Crypto.Util.number import *from flag import flagl = len(flag)m_1, m_2 = flag[: l // 2], flag[l // 2:]x, y = bytes_to_long(m_1), bytes_to_long(m_2)k = '''000bfdc32162934ad6a054b4b3db8578674e27a165113f8ed018cbe91124fbd63144ab6923d107eee2bc0712fcbdb50d96fdf04dd1ba1b69cb1efe71af7ca08ddc7cc2d3dfb9080ae56861d952e8d5ec0ba0d3dfdf2d12764'''.replace('\n', '')assert((x**2 + 1)*(y**2 + 1) - 2*(x - y)*(x*y - 1) == 4*(int(k, 16) + x*y))
Solving ((x**2 + 1)*(y**2 + 1) - 2*(x - y)*(x*y - 1) == 4*(int(k, 16) + x*y))
we have (x + 1) * (y - 1) = k2
with k2 = sqrt(4 * k)
. I brute force (x + 1) and convert to bytes. If CCTF{
in the result then calculate (y - 1).
from Crypto.Util.number import *from sage.all import *k = 992752253935874779143952218845275961347009322164731344882417010624071055636710540798045985678351986133612b=divisors(k)for i in b: ct1 =long_to_bytes(i-1) if b'CCTF' in ct1: ct2 =(k//i) + 1 print(i - 1, ct2) print(ct1, long_to_bytes(ct2))
Flag: CCTF{S1mPL3_4Nd_N!cE_Diophantine_EqUa7I0nS!}
Ở đây và là nửa đầu và nửa sau của flag, là một số cho trước thỏa mãn . Biến đổi một tí mình có
Như vậy mà nên mình chỉ cần factor số này là tìm được và . Do có thể có nhiều cách chọn nên mình tìm cái "hợp lí" nhất
dunglq
Rima
#!/usr/bin/env pythonfrom Crypto.Util.number import *from flag import FLAGdef nextPrime(n): while True: n += (n % 2) + 1 if isPrime(n): return nf = [int(x) for x in bin(int(FLAG.hex(), 16))[2:]]f.insert(0, 0)for i in range(len(f)-1): f[i] += f[i+1]a = nextPrime(len(f))b = nextPrime(a)g, h = [[_ for i in range(x) for _ in f] for x in [a, b]]c = nextPrime(len(f) >> 2)for _ in [g, h]: for __ in range(c): _.insert(0, 0) for i in range(len(_) - c): _[i] += _[i+c]g, h = [int(''.join([str(_) for _ in __]), 5) for __ in [g, h]]for _ in [g, h]: if _ == g: fname = 'g' else: fname = 'h' of = open(f'{fname}.enc', 'wb') of.write(long_to_bytes(_)) of.close()
Bài này không dùng biến chữ để chạy loop và dùng dấu gạch dưới của python nên lúc đầu mình thấy hơi rắc rối.
Đầu tiên flag được chuyển sang dạng nhị phân và thêm 1 bit “0” ở đầu được dãy . Kế tiếp với mỗi ⋯ thì .
Kế tiếp 2 số và được tạo là 2 số nguyên tố kế tiếp tính từ là độ dài . và là 2 list tạo ra từ việc lặp và lần. Như vậy độ dài của là và độ dài của là
Kế tiếp là số nguyên tố kế tiếp tính từ . Thêm bit “0” vào đầu và thực hiện với ⋯ . Làm tương tự với
Cuối cùng là chuyển và sang số int base 5 và viết lên file dưới dạng byte. Nên đầu tiên mình sẽ làm ngược lại và tìm được và , sau đó mình bruteforce để tìm , và và xem thử bộ nào thỏa và .
Sau đó là làm ngược lại quá trình, với thì . Tương tự với . Có thể kiểm chứng cách đúng nếu đầu có đúng số 0 :))
Giờ thì, lấy số đầu của và tiếp tục làm ngược lại sẽ ra các bit của flag.
Flag: CCTF{_how_finD_7h1s_1z_s3cr3T?!}
Maid
#!/usr/bin/python3from Crypto.Util.number import *from gmpy2 import *from secret import *from flag import flagglobal nbitnbit = 1024def keygen(nbit): while True: p, q = [getStrongPrime(nbit) for _ in '01'] if p % 4 == q % 4 == 3: return (p**2)*q, pdef encrypt(m, pubkey): if GCD(m, pubkey) != 1 or m >= 2**(2*nbit - 2): return None return pow(m, 2, pubkey)def flag_encrypt(flag, p, q): m = bytes_to_long(flag) assert m < p * q return pow(m, 65537, p * q)def die(*args): pr(*args) quit()def pr(*args): s = " ".join(map(str, args)) sys.stdout.write(s + "\n") sys.stdout.flush()def sc(): return sys.stdin.readline().strip()def main(): border = "+" pr(border*72) pr(border, " hi all, welcome to Rooney Oracle, you can encrypt and decrypt any ", border) pr(border, " message in this oracle, but the flag is still encrypted, Rooney ", border) pr(border, " asked me to find the encrypted flag, I'm trying now, please help! ", border) pr(border*72) pubkey, privkey = keygen(nbit) p, q = privkey, pubkey // (privkey ** 2) while True: pr("| Options: \n|\t[E]ncrypt message \n|\t[D]ecrypt ciphertext \n|\t[S]how encrypted flag \n|\t[Q]uit") ans = sc().lower() if ans == 'e': pr("| Send the message to encrypt: ") msg = sc() try: msg = int(msg) except: die("| your message is not integer!!") pr(f"| encrypt(msg, pubkey) = {encrypt(msg, pubkey)} ") elif ans == 'd': pr("| Send the ciphertext to decrypt: ") enc = sc() try: enc = int(enc) except: die("| your message is not integer!!") pr(f"| decrypt(enc, privkey) = {decrypt(enc, privkey)} ") elif ans == 's': pr(f'| enc = {flag_encrypt(flag, p, q)}') elif ans == 'q': die("Quitting ...") else: die("Bye ...")if __name__ == '__main__': main()
Ở bài này server cung cấp cho mình các chứng năng sau:
- Encrypt 1 số bất kì không vượt quá bit
- Decrypt 1 số bất kì (hàm decrypt bị giấu đi)
- Lấy flag bị mã hóa
Key là 1 cặp khóa công khai-bí mật (pubkey,privkey), trong đó còn với và là 2 số nguyên tố 1024 bit và đồng dư 3 modulo 4. Hàm encrypt thực hiện mã hóa số bằng cách trả về . Còn hàm decrypt thực hiện giải mã chỉ cần privkey.
Kì cục …………..
Thế quái nào …………..
Mà encrypt cần cả và còn decrypt chỉ cần ?
Thật ra là vì nếu thì , như vậy giải thích cho việc không được vượt quá 2048-2 bit và việc giải mã chỉ cần . Như vậy cách attack của mình như sau:
- Chọn ngẫu nhiên các ciphertext, gửi lên để server decrypt và nhận lại các plaintext tương ứng. Ta biết rằng nên , từ đó lấy căn bậc 2 là có .
- Tiếp theo, chọn ngẫu nhiên các plaintext, gửi lên server encrypt và nhận lại ciphertext tương ứng. Do , khi đó . Việc này ngược lại quá trình trên vì như nãy mình đã nói, encrypt sử dụng còn decrypt thì chỉ cần .
- Và bây giờ và đã có đủ, ta decrypt và có flag thôi
Flag: CCTF{___Ra8!N_H_Cryp705YsT3M__\}
Improve
#!/usr/bin/env python3from Crypto.Util.number import *from gmpy2 import gcdfrom random import randintimport sys, hashlibfrom flag import flagdef lcm(a, b): return (a * b) // gcd(a,b)def gen_params(nbit): p, q = [getPrime(nbit) for _ in range(2)] n, f, g = p * q, lcm(p-1, q-1), p + q e = pow(g, f, n**2) u = divmod(e-1, n)[0] v = inverse(u, n) params = int(n), int(f), int(v) return paramsdef improved(m, params): n, f, v = params if 1 < m < n**2 - 1: e = pow(m, f, n**2) u = divmod(e-1, n)[0] L = divmod(u*v, n)[1] H = hashlib.sha1(str(L).encode('utf-8')).hexdigest() return Hdef die(*args): pr(*args) quit()def pr(*args): s = " ".join(map(str, args)) sys.stdout.write(s + "\n") sys.stdout.flush()def sc(): return sys.stdin.readline().strip()def main(): border = "+" pr(border*72) pr(border, " hi talented cryptographers! Your mission is to find hash collision ", border) pr(border, " in the given hash function based on famous cryptographic algorithm ", border) pr(border, " see the source code and get the flag! Its improved version :) ", border) pr(border*72) nbit = 512 params = gen_params(nbit) n = params[0] while True: pr("| Options: \n|\t[R]eport collision! \n|\t[T]ry hash \n|\t[G]et parameters \n|\t[Q]uit") ans = sc().lower() if ans == 'r': pr("| please send the messages split by comma: ") m = sc() try: m_1, m_2 = m.split(',') m_1, m_2 = int(m_1), int(m_2) except: die("| Sorry! your input is invalid, Bye!!") # fix the bug :P if m_1 % n != 0 and m_2 % n != 0 and m_1 != m_2 and 1 < m_1 < n**2-1 and 1 < m_2 < n**2-1 and improved(m_1, params) == improved(m_2, params): die(f"| Congrats! You find the collision!! the flag is: {flag}") else: die("| Sorry! your input is not correct!!") elif ans == 't': pr("| Please send your message to get the hash: ") m = sc() try: m = int(m) pr(f"improved(m) = {improved(m, params)}") except: die("| Sorry! your input is invalid, Bye!!") elif ans == 'g': pr('| Parameters =', params) elif ans == 'q': die("Quitting ...") else: die("Bye ...")if __name__ == '__main__': main()
Ở bài này khá có khá nhiều thứ linh tinh nhưng chung quy lại là mình cần nhập vào 2 số và sao cho kết quả hàm improve với 2 số này là giống nhau.
Các tham số sẽ là làm chặn trên cho 2 số nhập vào (không vượt quá , có tính chất quan trọng là luôn chẵn, đây là tiền đề để mình giải bài này.
Hàm improve mình để ý rằng được tạo ra sau vài biến đổi từ , mà mình cần 2 số cho cùng , vậy chỉ cần cho ra cùng là xong. Mà như hồi nãy mình có đề cập là luôn chẵn, vậy chỉ cần chọn và là xong :))
Flag: CCTF{Phillip_N0W_4pr0b4b1liStiC__aSymM3Tr1C\_AlGOrithM!!}
Onlude
#!/usr/bin/env sagefrom sage.all import *from flag import flagglobal p, alphabetp = 71alphabet = '=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$!?_{}<>'flag = flag.lstrip('CCTF{').rstrip('}')assert len(flag) == 24def cross(m): return alphabet.index(m)def prepare(msg): A = zero_matrix(GF(p), 11, 11) for k in range(len(msg)): i, j = 5*k // 11, 5*k % 11 A[i, j] = cross(msg[k]) return Adef keygen(): R = random_matrix(GF(p), 11, 11) while True: S = random_matrix(GF(p), 11, 11) if S.rank() == 11: _, L, U = S.LU() return R, L, Udef encrypt(A, key): R, L, U = key S = L * U X = A + R Y = S * X E = L.inverse() * Y return EA = prepare(flag)key = keygen()R, L, U = keyS = L * UE = encrypt(A, key)print(f'E = \n{E}')print(f'L * U * L = \n{L * U * L}')print(f'L^(-1) * S^2 * L = \n{L.inverse() * S**2 * L}')print(f'R^(-1) * S^8 = \n{R.inverse() * S**8}')
Bài này mình giải trước khi hết thời gian 1 tiếng và là bài cuối cùng mình giải được. Hàm prepare chuyển flag thành ma trận , là bộ 3 ma trận , và .
Việc mã hóa như sau:
- Với , và như trên,
- , và ciphertext là
Đề cho mình 4 ma trận:
- là ciphertext, với 1 chút biến đổi mình có
- Ma trận
- Ma trận , mình đặt là
- Ma trận , mình đặt là
Lấy nhân với mình có . Khi đó . Vậy là mình có rồi :))
Quay lại , khi đó , nhân bên phải của 2 vế với thì
Quay lại 1 chút, , suy ra
Từ đó mình dễ dàng tìm lại được
Thực hiện tương tự hàm prepare mình có được flag
Flag: CCTF{LU__D3c0mpO517Ion__4L90?}
Writeup đến đây là hết, cám ơn các bạn đã đọc. Source code của mình ở đây