TCP1PCTF2023 Crypto

Final Consensus

meet in middle
chall.py

from Crypto.Cipher import AES
import random
from Crypto.Util.Padding import pad

a = b""
b = b""
FLAG = b"TCP1P{nothing_ever_lasts_forever_everybody_wants_to_rule_the_world}"

def generateKey():
	global a, b
	a = (str(random.randint(0, 999999)).zfill(6)*4)[:16].encode()
	b = (str(random.randint(0, 999999)).zfill(6)*4)[:16].encode()

def encrypt(plaintext, a, b):
	cipher = AES.new(a, mode=AES.MODE_ECB)
	ct = cipher.encrypt(pad(plaintext, 16))
	cipher = AES.new(b, mode=AES.MODE_ECB)
	ct = cipher.encrypt(ct)
	return ct.hex()

def main():
	generateKey()
	print("Alice: My message", encrypt(FLAG, a, b))
	print("Alice: Now give me yours!")
	plain = input(">> ")
	print("Steve: ", encrypt(plain.encode(), a, b))
	print("Alice: Agree.")


if __name__ == '__main__':
	main()

题目的代码对cipher作了两次ECB加密 这种两次加密的都可以用
meet in middle 的思想
ENC(ENC(c)) = p
所以
ENC(c) = DEC(p)
对于这道题我们可以用一个另外的pt得到steve的ct
然后分别存储0~99999的a,b作为key的加解密值
用超级快的set.intersection求出same 找到对应的索引值就是a,b
最后解密即可
原版wp:

A meet in the middle (https://en.wikipedia.org/wiki/Meet-in-the-middle_attack) attack that require efficient problem solving instead of naive bruteforcing.

Summary:
The encryption mechanism involves encrypting a plaintext using AES mode ECB twice, both times using different keys. These two keys are derived from a set of 6 random digits that are repeated to make a length of 16. Example: 123456 -> 1234561234561234.

Attack idea:
1. Find a key pair by providing a sample plaintext and obtaining its corresponding sample ciphertext.
Generate all possible combinations of the first key ranging from 000000 to 999999. Use each key to manually encrypt the sample plaintext and store the results in an array.
2. Generate all possible combinations of the second key ranging from 000000 to 999999. Use each key to manually decrypt the sample ciphertext and store the results in an array.
3. Identify a value that is common in both the array of encryption results and the array of decryption results. The index of this common value in the encryption results array corresponds to the first key, while in the decryption results array, it corresponds to the second key. Accelerate this process using intersection.
4. Once both keys are identified, decrypt the flag using the second key first, followed by the first key. Flag!

最后的solve.py:

from Crypto.Cipher import AES
import random
from Crypto.Util.Padding import pad
from tqdm import tqdm

ciphertext = '9c200a71815f14d3f8c65385f9a9599e3c6d31ec0df2af0b1fd87ecd62c9c148cab8329220d8278a8d043bad06584bf7ddfcfe12a8770e6210db1850040a4656f72a9bc50186fe9fa4424d1817537587'
plaintextsample = b'a'
ciphertextsample = '6cdc1352fbe2ee2c6486a5fc4f28086b'

print('[+] encrpted start')
# encrypted
encrpt = []
for i in range(999999+1):
    key = (str(i).zfill(6)*4)[:16].encode()
    cipher = AES.new(key,mode=AES.MODE_ECB)
    ct = cipher.encrypt(pad(plaintextsample,16))
    encrpt.append(ct)
print('[+] encrpt finished')
# decrypted
print('[+] decrypted start')
decrpt = []
ciphertextsample = bytes.fromhex(ciphertextsample)
for i in range(999999+1):
    key = (str(i).zfill(6)*4)[:16].encode()
    cipher = AES.new(key,mode=AES.MODE_ECB)
    pt = cipher.decrypt(ciphertextsample)
    decrpt.append(pt)
print('[+] decrypt finished')

assert len(encrpt) == len(decrpt)

ciphertext = bytes.fromhex(ciphertext)
same = list(set(encrpt).intersection(set(decrpt)))[0]
index1 = encrpt.index(same)
index2 = decrpt.index(same)
key = (str(index2).zfill(6)*4)[:16].encode()
cipher = AES.new(key,mode=AES.MODE_ECB)
pt = cipher.decrypt(ciphertext)
key = (str(index1).zfill(6)*4)[:16].encode()
cipher = AES.new(key,mode=AES.MODE_ECB)
pt = cipher.decrypt(pt)
print(pt)

image

Cherry Leak

gcd
chall.py

from Crypto.Util.number import getPrime, bytes_to_long

p = getPrime(1024)
q = getPrime(512)
n = p * q
e = 65537

FLAG = b"TCP1P{in_life's_abundance_a_fragment_suffices}"
# FLAG = b"TCP1P{???????????????????????????????????????}"

lock = False
while True:
    print("1. Get new prime")
    print("2. Get leak")
    print("3. Get flag")
    print("4. Exit")
    print("> ", end="")
    try:
        choice = int(input())
    except:
        break
    if choice == 1:
        if lock:
            print("You can't do that anymore!")
            continue
        print("which prime? (p/q)")
        print("> ", end="")
        prime = input()
        if prime == "p":
            p = getPrime(1024)
        elif prime == "q":
            q = getPrime(512)
        else:
            print("What?")
            continue
        n = p * q
        lock = True
    elif choice == 2:
        print("choose leak p ? q (+-*/%)")
        print("> ", end="")
        leak = input()
        if leak == "+":
            print(f"p + q = {pow(p + q, e, n)}") # nuh uh
        elif leak == "-":
            print(f"{p - q = }")
        elif leak == "*":
            print(f"p * q = {pow(p * q, e, n)}") # nuh uh
        elif leak == "/":
            print(f"p // q = {pow(p // q, e, n)}") # nuh uh
        elif leak == "%":
            print(f"{p % q = }")
        else:
            print("What?")
    elif choice == 3:
        print(f"c = {pow(bytes_to_long(FLAG), e, n)}")
        lock = True
    elif choice == 4:
        break
    else:
        print("What?")

我们选择p作为change的对象 对于p1,p2我们能够得到p1-q,p1%q,p2-q,p2%q的值
suppose x = p - q and y = p % q
p = kq + y
x + q = kq + y
x - y = (k-1)q
所以对于两次的(k-1)q我们求一个gcd即可得到q(其实这里也有可能是q的倍数 而factor.db也分解不出来... 只能寄希望于刚好是q了)
solution.py

from Crypto.Util.number import *
from gmpy2 import *
from primefac import *
from sympy import *

c = 1328116538589715550433368447190529911812362534503784372639303465974999512046774519916750550052439612199689937635536599562413895928936783729519250829253609393894475689291703471936143690015959945752508354893570325332347853024637941932249131947866575151506545557166141529906704534286702974370021100084381729343036188010466262802829016724471115299893896706185483644841145815503234566953284362317889624564500746728409138493346936522245628796068879220118189988490794490
p1_q = 162603008487577728673759327616529950320223204634263140580713042593001826004999266902790586261444327651599473585461324987693739492437438218674329243170737304489631667246712345741760352381471831768431871830791489371090372349138381626374618506916470834418914303032286964269175623971137739214395471290469883948340
p2_q = 136398903713336226314523922270829075502393880676582458343859644648228651730471006485093627673544447857503144741416037966618120913002730388819468631203270902915435886420780189247810423602237175617308184905845074700103123614049455866263151459878696052734816792860572020632477541353985759687810933733014687393882
p1_mod_q = 4126078924843663792400164821030589881091275752356310387538125772670990236647906810668646357649534552484604758891618917933476755882541039330972605696251555
p2_mod_q = 6065945751500403651992113705085749325931999933013952750205379510750752989746861424433233644154271200813417486779227785855460877063312700039933099688171254
print(p1_q-p1_mod_q)
q = gcd(p1_q-p1_mod_q,p2_q-p2_mod_q)
q = 13393798105376255691517905243955898731664442878772384730265468347404590373037767868645627190607082349013231479390696838341602755302656803181138150844458409
p2 = 136398903713336226314523922270829075502393880676582458343859644648228651730471006485093627673544447857503144741416037966618120913002730388819468631203270916309233991797035880765715667558135907281751063678229804965571471018639828904031020105505886659817165806092051411329315882956741062344614114871165531852291
n = p2*q
phi = (p2-1)*(q-1)
e = 65537
d = modinv(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))

image

One Pad Time

题目代码:

import os
from pwn import xor
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

key = os.urandom(16)
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)

pt = open("flag.txt", "rb").read()
ct = pad(cipher.encrypt(pt), 16)
ct = xor(ct, key)
print(f"{iv = }")
print(f"{ct = }")

突破口在于这个pad
随便找一个pad(iv,16)
image
发现是在后面pad了16个'x\10'
有了这个隐含条件就好做了
因为我们知道xor(ct,key)的末16位和pad(ct,16)的末16位 再xor一次就可以得到key 然后再xor(ct,key)就得到原本的ct
然后AES解密即可

import os
from pwn import xor
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

iv = b'\xf5\x8e\x85ye\xc8j(%\xc4K\xc1g#\x86\x1a'
ct = b'h\x08\xafmDV\xaa\xcd\xea\xe9C\xdd7/\x1fF\xe2?\xcb\xb0\x1d F\xcc\xe5\xa6\x9dTJ\\\xd1\x90\xac\xe0\x1c\x891}\x83*\x86\xee\xc4~\xa0\x18\xa8\x06\xea"{|\x0b\x92[\x9a[\x91\xc8\x19\xb7FK\x01\xb5\xf98\x80\x9bR)2\x84`\xb3E\t\xd5\xe5\xf0[\x83\xc6\x19\x82\r\x7f\xfaGF\xdb\xcb\xab\xd5~\x95\t\xdd\xb5E>F\xdd\xa9\xa6\x82\x86\xee"\x99\xd9\xcc\xaf\xce\xf0\'\xb3\xf4~\xcf\xdb\xc8\xbd3\x01\xd0,}]\xd5V\xd3?\xb0\xe7\xb4[4\x8a\xa2[\xa1TV\xd16\x1f\xbd"\xc8\xa2\\K\x16I%\xdaL\xc6\xfb\xb7f.\x98\xc3\xf4J\x1b\xe9TT\x83-\x98BO\xb4\x00~\xb5w\xcf7m\xa1\xea\xa9\xf6\xa6\xee\x00Y\xdfE\x9c7\xe3\xa3\xa2\x1f=.\x85\x08l\xacN\xfb2\x89\x8bB\x7f\x94\x91p\x10ep\x9b\x06oz\x87&U]J\x019\x12W\xce<\xc8\xa8\xb4v\xaf,\xb1n\x8b\xf5\xfe\xf8\r\xa7:r\xe8\xe0fvKN\\\xea\xe0\xa1\xe3\x99\xcc\xfd\x1a\x99Q\x90\xdf}\xae\xad'
p_ad = b'\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
print(pad(iv,16))
key = xor(ct[-16:],p_ad)
print(key)
plain = AES.new(key, AES.MODE_CBC, iv)
print(plain.decrypt(xor(ct,key)))

最后得到超长flag
image

posted @ 2023-10-17 08:30  N0zoM1z0  阅读(55)  评论(0编辑  收藏  举报