0x01 babyrsa
KEY
题目有三个环节:
第一个环节是 PoW,爆破前四位即可
第二个环节是小质数 $n$,直接 factordb 分解得出 $p$ 和 $q$ 即可
第三个环节给出的 $n$ 非常大,无法使用 factordb,但观察源码可以发现 $p$ 和 $q$ 是两个相邻的质数,所以可以在这个基础上使用费马分解进行爆破
题目
#!/usr/bin/env python3 import random import signal import socketserver import string from hashlib import sha256 from os import urandom from Crypto.Util.number import * from gmpy2 import next_prime flag = 'flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}' class Task(socketserver.BaseRequestHandler): def __init__(self, *args, **kargs): super().__init__(*args, **kargs) def proof_of_work(self): random.seed(urandom(8)) proof = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(16)]) digest = sha256(proof.encode()).hexdigest() self.dosend('sha256(XXXX + {}) == {}'.format(proof[4:], digest) + "\n" + '@ XXXX=') x = self.request.recv(10) x = (x.strip()).decode('utf-8') if len(x) != 4 or sha256((x + proof[4:]).encode()).hexdigest() != digest: return False return True def dosend(self, msg): try: self.request.sendall(msg.encode('latin-1') + b'\n') except: pass def timeout_handler(self, signum, frame): raise TimeoutError def error(self): self.dosend("sorry!") self.request.close() def game1(self): p = getPrime(20) q = getPrime(20) e = 0x10001 m = random.randint(0x10000, 0xfffff) c = pow(m, e, p * q) self.dosend("Welcome to game1" + "\n" + "The answer submitted should be hexadecimal strings like 0x1a23\n" + "# n=" + hex(p * q) + "\n" + "# e=" + hex(e) + "\n" + "# c=" + hex( c) + "\n" + "@ m=") rec_m = self.request.recv(1024).strip().decode('utf-8') if rec_m != hex(m): self.error() def game3(self): self.dosend("Welcome to game3. In this game should try 30 times and solve them") for i in range(30): p = getPrime(512) q = next_prime(p) e = 0x10001 m = random.randint(0x100000000000, 0xffffffffffff) c = pow(m, e, p * q) self.dosend( "{} try".format(i + 1) + "\n" + "# n=" + hex(p * q) + "\n" + "# e=" + hex(e) + "\n" + "# c=" + hex( c) + "\n" + "@ m=") rec_m = self.request.recv(1024).strip().decode() if rec_m == hex(m): continue else: self.error() def handle(self): try: signal.signal(signal.SIGALRM, self.timeout_handler) signal.alarm(50) if not self.proof_of_work(): self.dosend('You must pass the PoW!') self.request.close() signal.alarm(300) self.game1() self.game3() self.dosend(flag) except TimeoutError: self.dosend('Timeout!') self.request.close() except: self.dosend('Wtf?') self.request.close() class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass if __name__ == "__main__": HOST, PORT = '0.0.0.0', 10001 server = ThreadedServer((HOST, PORT), Task) server.allow_reuse_address = True server.serve_forever()
EXP
from factordb.factordb import FactorDB from pwn import * import itertools import gmpy2 import hashlib import string context.log_level = 'debug' conn = remote('xx.xx.xx.xx', xx) pattern = re.compile(r'[a-zA-Z\d]+') # PoW text = conn.recvline(keepends=True).decode('utf-8') texts = pattern.findall(text) right = texts[2] ans = texts[3] for i in string.ascii_letters + string.digits: for j in string.ascii_letters + string.digits: for k in string.ascii_letters + string.digits: for l in string.ascii_letters + string.digits: left = i + j + k + l data = left + right if hashlib.sha256(data.encode('utf-8')).hexdigest() == ans: conn.sendline(str.encode(left)) # game1 conn.recvline(keepends=True) # conn.recvline(keepends=True) # Welcome to game1 conn.recvline(keepends=True) # The answer submitted should be hexadecimal strings like 0x1a23 nText1 = conn.recvline(keepends=True).decode('utf-8') eText1 = conn.recvline(keepends=True).decode('utf-8') cText1 = conn.recvline(keepends=True).decode('utf-8') conn.recvline(keepends=True) # m= # 正则提取 n = int(pattern.findall(nText1)[1], base=16) e = int(pattern.findall(eText1)[1], base=16) c = int(pattern.findall(cText1)[1], base=16) # factordb分解质因数 f = FactorDB(n) f.connect() pq = f.get_factor_list() p = pq[0] q = pq[1] d = gmpy2.invert(e,(p-1)*(q-1)) m = pow(c,d,n) conn.sendline(str.encode(hex(m))) # game3 conn.recvline(keepends=True) # Welcome to game3. In this game should try 30 times and solve them # 连解三十道题 for _ in range(30): conn.recvline(keepends=True) # try nText = conn.recvline(keepends=True).decode('utf-8') eText = conn.recvline(keepends=True).decode('utf-8') cText = conn.recvline(keepends=True).decode('utf-8') conn.recvline(keepends=True) # m= n = int(pattern.findall(nText)[1], base=16) c = int(pattern.findall(cText)[1], base=16) # 爆破相邻的p和q for q in itertools.count(gmpy2.iroot(n, 2)[0]-1): if n % q == 0: p = n // q break d = gmpy2.invert(e,(p-1)*(q-1)) m = pow(c,d,n) conn.sendline(str.encode(hex(m))) # 打印flag flag = conn.recvline(keepends=True) print(flag) # flag{you_must_know_1nteractive_question}
0x02 babyblock
KEY
题目考察的是对 AES-CBC 的理解
由源码可知在加密时每 16 字节分为一块,然后使用随机生成的密钥 $k$ 进行 AES 加密后与 $IV$(Initial Vector)进行异或,但题目使用的 $IV$ 是固定的,这就给了我们获取 $IV$ 的机会
所以我们构造一段密文 $C$,将其分为三块,$P$ 为每一块的输出
$$
\begin{aligned}P_0 = Dec_k(C_0)\oplus IV\\ P_1 = Dec_k(C_1)\oplus C_0\\ P_2 = Dec_k(C_2)\oplus C_1\end{aligned}
$$
将 $P_0$ 和 $P_1$ 异或可得
$$
\begin{equation}P_0\oplus P_1 = (Dec_k(C_0)\oplus IV)\oplus Dec_k(C_1)\oplus C_1\end{equation}
$$
如何构造密文 $C$ 使得我们可以从式 $(1)$ 中得到 $IV$ 呢?
答案就是令 $C=0000000000000000\ 0000000000000000\ 0000000000000000$
(为方便辨识,每 16 个 0 加了一个空格)
我们知道,任何数与 0 异或都是它本身,那么化简可得
$$
\begin{aligned}P_0\oplus P_1 &= (Dec_k(C_0)\oplus IV)\oplus (Dec_k(C_1)\oplus C_0)\\&= (Dec_k(C_0)\oplus IV)\oplus Dec_k(C_1)\\&=IV\oplus Dec_k(C_0)\oplus Dec_k(C_1)\\&=IV\oplus 0000000000000000\\&=IV\end{aligned}
$$
所以,我们只需要将 $0000000000000000\ 0000000000000000\ 0000000000000000$ 作为密文提供给程序解密,得到的明文会包括三块,由于 $C_0$ 和 $C_1$ 相同,所以明文的第二块和第三块相同,我们只需要将第一块和第二块异或即可得到 $IV$
题目
#!/usr/bin/env python3 import random import signal import socketserver import string from hashlib import sha256 from os import urandom from Crypto.Cipher import AES flag = b'flag{xxxxxxxxxxxxxxxxxxxxxxxxxx}' BLOCK_SIZE = 16 def pad(m): if len(m) % BLOCK_SIZE != 0: m += b'\x00' * (BLOCK_SIZE - len(m) % BLOCK_SIZE) return m pad(flag) part_flag = [flag[i:i + BLOCK_SIZE] for i in range(0, len(flag), BLOCK_SIZE)] class Task(socketserver.BaseRequestHandler): def __init__(self, *args, **kargs): super().__init__(*args, **kargs) def proof_of_work(self): random.seed(urandom(8)) proof = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(16)]) digest = sha256(proof.encode()).hexdigest() self.dosend('sha256(XXXX + {}) == {}'.format(proof[4:], digest) + "\n" + '@ XXXX=') x = self.request.recv(10) x = (x.strip()).decode('utf-8') if len(x) != 4 or sha256((x + proof[4:]).encode()).hexdigest() != digest: return False return True def dosend(self, msg, newline=True): try: string = msg.encode('latin-1') if newline: string = string + b"\n" self.request.sendall(string) except: pass def receive(self, prompt=">> "): self.dosend(prompt, newline=False) return self.request.recv(2048).strip() def timeout_handler(self, signum, frame): raise TimeoutError def error(self): self.dosend("sorry!") self.request.close() def keyGen(self): key = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(16)]) return key.encode() def encrypt(self): self.dosend("Enter HEXADECIMAL message:") m = self.receive().decode().strip() m = pad(bytes.fromhex(m)) cipher = AES.new(self.key, AES.MODE_CBC, iv=self.IV) c = cipher.encrypt(m) self.dosend("This is your cipher") self.dosend(c.hex()) def decrypt(self): self.dosend("Enter HEXADECIMAL cipher:") c = self.receive().decode().strip() c = pad(bytes.fromhex(c)) cipher = AES.new(self.key, AES.MODE_CBC, iv=self.IV) m = cipher.decrypt(c) self.dosend("This is your message") self.dosend(m.hex()) def selectIV(self): while True: self.dosend("Which part of flag do you want to choose?") part = self.receive().decode().strip() try: part = int(part) assert 0 <= part < len(part_flag) return part_flag[part] except: continue def handle(self): try: signal.signal(signal.SIGALRM, self.timeout_handler) signal.alarm(50) if not self.proof_of_work(): self.dosend('You must pass the PoW!') self.request.close() signal.alarm(300) self.key = self.keyGen() self.IV = self.selectIV() while True: self.dosend("1: Encrypt") self.dosend("2: Decrypt") self.dosend("3: ChangeIV") self.dosend("4: Give up") choice = self.receive().decode() if choice == "1": self.encrypt() elif choice == "2": self.decrypt() elif choice == "3": self.IV = self.selectIV() elif choice == "4": self.dosend("88") self.request.close() except TimeoutError: self.dosend('\nTimeout!') self.request.close() except: self.dosend('Wtf?') self.request.close() class ThreadedServer(socketserver.ForkingMixIn, socketserver.TCPServer): pass if __name__ == "__main__": HOST, PORT = '0.0.0.0', 10002 server = ThreadedServer((HOST, PORT), Task) server.allow_reuse_address = True server.serve_forever()
EXP
from pwn import * import hashlib import string context.log_level = 'debug' conn = remote('xx.xx.xx.xx', xx) pattern = re.compile(r'[a-zA-Z\d]+') # PoW text = conn.recvline(keepends=True).decode('utf-8') texts = pattern.findall(text) right = texts[2] ans = texts[3] for i in string.ascii_letters + string.digits: for j in string.ascii_letters + string.digits: for k in string.ascii_letters + string.digits: for l in string.ascii_letters + string.digits: left = i + j + k + l data = left + right if hashlib.sha256(data.encode('utf-8')).hexdigest() == ans: conn.sendline(str.encode(left)) conn.recvline(keepends=True) # Which part of flag do you want to choose? conn.recvline(keepends=True) conn.sendline('0'.encode('utf-8')) conn.recvline(keepends=True) # 1: Encrypt conn.recvline(keepends=True) # 2: Decrypt conn.recvline(keepends=True) # 3: ChangeIV conn.recvline(keepends=True) # 4: Give up conn.sendline('2'.encode('utf-8')) conn.recvline(keepends=True) # Enter HEXADECIMAL cipher: conn.sendline('00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000'.encode('utf-8')) conn.recvline(keepends=True) # This is your message msg = conn.recvline() conn.recvline(keepends=True) # 1: Encrypt conn.recvline(keepends=True) # 2: Decrypt conn.recvline(keepends=True) # 3: ChangeIV conn.recvline(keepends=True) # 4: Give up o1 = msg[0:32] o2 = msg[32:64] flag_part1 = '' for i in range(0,32,2): flag_part1 += chr(int(o1[i:i+2],16)^int(o2[i:i+2],16)) # 每个字节进行异或 conn.sendline('3'.encode('utf-8')) conn.recvline(keepends=True) # Which part of flag do you want to choose? conn.sendline('1'.encode('utf-8')) conn.recvline(keepends=True) # 1: Encrypt conn.recvline(keepends=True) # 2: Decrypt conn.recvline(keepends=True) # 3: ChangeIV conn.recvline(keepends=True) # 4: Give up conn.sendline('2'.encode('utf-8')) conn.recvline(keepends=True) # Enter HEXADECIMAL cipher: conn.sendline('00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000'.encode('utf-8')) conn.recvline(keepends=True) # This is your message msg = conn.recvline() conn.recvline(keepends=True) # 1: Encrypt conn.recvline(keepends=True) # 2: Decrypt conn.recvline(keepends=True) # 3: ChangeIV conn.recvline(keepends=True) # 4: Give up o1 = msg[0:32] o2 = msg[32:64] flag_part2 = '' for i in range(0,32,2): flag_part2 += chr(int(o1[i:i+2],16)^int(o2[i:i+2],16)) flag = flag_part1 + flag_part2 print(flag) # flag{finnalyrc4C0irWZWWZLnVdysE}