出了一个题目
题目
#!/usr/bin/python
import socketserver
import random
import os
import string
import binascii
import hashlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Util.number import getPrime
from hashlib import sha256
from sec import FLAG
import gmpy2
def init():
q = getPrime(512)
p = getPrime(512)
e = getPrime(64)
n = q*p
phi = (q-1) * (p-1)
d = gmpy2.invert(e, phi)
hint = 2 * d + random.randint(0, 2**16) * e * phi
mac = random.randint(0, 2**64)
c = pow(mac, e, n)
counter = random.randint(0, 2**128)
key = os.urandom(16)
score = 0
return n, hint, c, counter, key, mac, score
class task(socketserver.BaseRequestHandler):
def POW(self):
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
result = hashlib.sha256(proof.encode('utf-8')).hexdigest()
self.request.sendall(("sha256(XXXX+%s) == %s\n" % (proof[4:],result)).encode())
self.request.sendall(b'Give me XXXX:\n')
x = self.recv()
if len(x) != 4 or hashlib.sha256((x+proof[4:].encode())).hexdigest() != result:
return False
return True
def recv(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def padding(self, msg):
return msg + chr((16 - len(msg)%16)).encode() * (16 - len(msg)%16)
def encrypt(self, msg):
msg = self.padding(msg)
if self.r != -1:
self.r += 1
aes = AES.new(self.key, AES.MODE_CTR, counter = Counter.new(128, initial_value=self.r))
return aes.encrypt(msg)
else:
return msg
def send(self, msg, enc=True):
print(msg, end= ' ')
if enc:
msg = self.encrypt(msg)
print(msg, self.r)
self.request.sendall(binascii.hexlify(msg) + b'\n')
def set_key(self, rec):
if self.mac == int(rec[8:]):
self.r = self.counter
def guess_num(self, rec):
num = random.randint(0, 2**128)
if num == int(rec[10:]):
self.send(b'right')
self.score += 1
else:
self.send(b'wrong')
def get_flag(self, rec):
assert self.r != -1
if self.score == 5:
self.send(flag, enc=False)
else:
self.send(os.urandom(32) + flag)
def handle(self):
self.r = -1
if not self.POW():
self.send(b'Error Hash!', enc= False)
return
self.n, self.hint, self.c ,self.counter, self.key, self.mac, self.score = init()
self.send(str(self.n).encode(), enc = False)
self.send(str(self.hint).encode(), enc = False)
self.send(str(self.c).encode(), enc = False)
for _ in range(6):
rec = self.recv()
if rec[:8] == b'set key:':
self.set_key(rec)
elif rec[:10] == b'guess num:':
self.guess_num(rec)
elif rec[:8] == b'get flag':
self.get_flag(rec)
else:
self.send(b'something wrong, check your input')
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
def main():
HOST, PORT = '127.0.0.1', 10086
server = ForkedServer((HOST, PORT), task)
server.allow_reuse_address = True
server.serve_forever()
if __name__ == '__main__':
main()
连上之后给了6次交互机会, 但其中最少有一次需要用来set key
有三个选项:
- set key : 初始化
aes-ctr
的counter - guess num: 每猜中一次随机数, 分数+1
- get flag : 当分数不为4的时候, 发送的为有填充的加密的flag, 分数为4的时候发送明文flag
思路
由于题目set key
没有校验次数, 可以多次重置密钥, 且密钥为每一次连接生成的随机值, 加上aes-ctr
的特性, 只需要获取到足够长的明文即可
在guess key
中, 猜对随机数服务端会发送填充加密后的right
, 猜错随机数会发送填充加密后wrong
, 实际上, 这里的明文都不够长,
于是这样会出现只能获取到一半flag的情况。
正确是思路的是故意输入不符合要求的命令, 由于self.send(b'something wrong, check your input')
, 填充和加密操作被内置到了send
方法里面, 所以这里会发送很长的密文, 重复三次, 去除重合的部分即可得到足够长的密钥流
于是6次机会 = 1次set key
初始化 + 3次报错guess num
获取密钥流 + 1次set key
重置密钥流 + 1次get flag
获取加密后的flag
本地解密即可
exp
#!/usr/bin/python
from pwn import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256
import string
import time
import binascii
context.log_level = 'debug'
r = remote('happi0.club', 10086)
def padding( msg):
return msg + chr((16 - len(msg)%16)).encode() * (16 - len(msg)%16)
def xor_bytes(var, key):
return bytes(a ^ b for a, b in zip(var, key))
def decrypt(ct):
msg = padding(b'something wrong, check your input')
pt = xor_bytes(msg, ct)
return pt
# pow
data = r.recvline()
print(data[12:28], data[33:97])
found = mbruteforce(lambda x:sha256(x.encode() + data[12:28]).hexdigest().encode() == data[33:97], string.ascii_letters+string.digits, 4)
r.sendline(found)
r.recvline()
# set key
n = int(binascii.unhexlify(r.recvline()[:-1]))
d = int(binascii.unhexlify(r.recvline()[:-1])) // 2
c = int(binascii.unhexlify(r.recvline()[:-1]))
m = pow(c,d,n)
r.sendline(b'set key:' + str(m).encode())
time.sleep(0.5)
# guess num
key_stream = b''
for i in range(3):
r.sendline(b'happi0')
time.sleep(0.5)
ct = binascii.unhexlify(r.recvline()[:-1])
pt = decrypt(ct)
if i != 2:
key_stream += pt[:16]
else:
key_stream += pt
print('pt:' + str(pt) + '\n' + 'length: ' + str(len(pt)))
print('key_stream:' + str(key_stream) + '\n' + 'length: ' + str(len(key_stream)) + '\n')
# reset key
r.sendline(b'set key:' + str(m).encode())
time.sleep(0.5)
r.sendline(b'get flag')
time.sleep(0.5)
# decrypt flag
flag = binascii.unhexlify(r.recvline()[:-1])
print(flag, type(flag), key_stream)
flag = xor_bytes(flag, key_stream)
print(flag)
#b'\x8a\xa8\x83\xed\xe9\xe0\xe5\x11\xf4\x9c\xcc\xb6K\x91\xbb\xa9\xf0\xd4\t\x15\x19r\xf5Z\x9d.\x9368\x90\xe8\xd5flag{c836b2abae33d2e5b9a0e50b28ba5e95}\n\n\n\n\n\n\n\n\n\n'