wannaShare | Writeup Crypto CTF 2021

PHAPHA_JIàN
1:17 02/08/2021

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``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

Source

#!/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.

CBC

If the encryption function is Eₖ, then we have the following recurrence relation:

CBC1

The decrypted result is XORed with the previous ciphertext block.It follows that if the decryption function is Dₖ, then the decryption is:

CBC2

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

Source

#!/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().

HSH

Now my new username string is: 8000000000000000000000000000000000000000000000000000000000000000000000000098000000000000006e335434446d316e

After that, I solve sha1(pepper + password).hexdigest() to get new password.

HSH2

My new password: 8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000985034733557307264

Finally solve (pepper + password + md5(salt + username).hexdigest().encode('utf-8')).hexdigest() to get the hash.

HSH3

Send them to server and get the flag.

HSH4

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 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 nên mình chỉ cần factor số này là tìm được . 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ố được tạo là 2 số nguyên tố kế tiếp tính từ là độ dài . là 2 list tạo ra từ việc lặp lần. Như vậy độ dài của và độ dài của

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 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 , sau đó mình bruteforce để tìm , và xem thử bộ nào thỏa .

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:

Key là 1 cặp khóa công khai-bí mật (pubkey,privkey), trong đó còn với 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 …………..

encrypt cần cả 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:

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ố 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 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 , .

Việc mã hóa như sau:

Đề cho mình 4 ma trận:

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

TIN LIÊN QUAN
Sau 4 buổi học với đa dạng góc nhìn về đề tài, gợi mở nhiều kiến thức liên quan đến An toàn thông tin (ATTT), khoá huấn luyện WannaQuest.Q2023.02 đã tạo tiền đề cho nhiều sinh viên tham gia hiểu rõ hơn tầm quan trọng của việc tham gia nghiên...
Thông qua nhiều sự kiện, hoạt động học thuật sôi nổi, WannaQuest được đánh giá cao khi trở lại với mùa thứ 2 với khóa huấn luyện WannaQuest.Q2023.02, một nơi đã trở thành nơi kết nối, giao lưu, tiếp nhận kiến thức An toàn thông tin (ATTT) theo cách cởi...
WannaGame Weekly UTCTF, ångstromCTF, Grey Cat The Flag, ImaginaryCTF, SekaiCTF, Downunder CTF, TeamItaly CTF, CTFZone, Asis Final, SEETF, Bauhinia... UIT Honor dice, Real World, bi0s, Seccon, pbctf, Kalmarctf, hxp, Plaid, m-leCon, HackTM, p4ctf, justCTF, codegate, Google, zer0pts, Defcon, HITCON, Hack.lu, N1CTF, Brics+, 0CTF/TCTF, Balsn, RuCTF (AD), FAUST (AD), saarCTF (AD)......