task.py

import socketserver
import os, sys, signal
import string, random, time, json
from hashlib import sha256
from secret import flag
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from ast import literal_eval as eval

key = os.urandom(32)

def decrypt(ciphertext:str,iv):
try:
iv = bytes.fromhex(iv)
ciphertext = bytes.fromhex(ciphertext)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ciphertext),16)
if not b'Admin' in decrypted:
return 'Permissino denied !'

except ValueError as e:
return str(e)

return decrypted


def encrypt(plaintext:str,admin=False):
try:
iv = os.urandom(16)
payload = {'permission':'Guest','Time':f'{time.time()}','data':plaintext}
print(payload)
payload = pad(json.dumps(payload).encode(),16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(payload)
except ValueError as e:
return str(e)

return iv.hex() + encrypted.hex()


class Task(socketserver.BaseRequestHandler):
def _recvall(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 send(self, msg, newline=True):
if type(msg) is str:
msg = msg.encode()
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()

def close(self):
self.send(b"Bye~")
self.request.close()

def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
x = self.recv(prompt=b'Give me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True

def handle(self):
if 0 and not self.proof_of_work():
return
menu = '''1. Encrypt data\n2. Decrypt data\n3. Get encrypted flag\n'''

while 1:
self.send('\n' + menu)
try:
r = int(self.recv())
except:
continue
if r == 3:
self.send(b'Encrypted flag:\n')
self.send(encrypt(flag.hex()))
elif r == 2:
iv,data = self.recv(prompt=b'Data to decrypt:').strip().split(b'||')
self.send(decrypt(data.decode(),iv.decode()))
elif r == 1:
data = self.recv(prompt=b'Data to encrypt:').strip()
self.send(encrypt(data.decode()))


self.close()

class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

class ForkedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

if __name__ == "__main__":

HOST, PORT = '0.0.0.0', 10000
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()


思路分析

对代码进行分析,发现代码当中主要有3个关键函数。

proof_of_work()这个函数在程序里没起到作用,不知道是不是复现的时候师傅们把这个功能修改了。

encrypt()这个函数会首先将我们传入的明文,修改成如下形式:{'permission':'Guest','Time':f'{time.time()}','data':plaintext}然后采用CBC的方式对修改后的payload进行加密,并且返回IV和密文。

decrypt()这个函数会根据我们传入的密文和IV对密文内容进行解密,但是这里要求我们传入密文解密后得到的明文一定满足'permission':'Admin'·

Encrypted flag会调用encrypt()这个函数对我们想要的flag进行加密。

经过对代码的分析,现在我们的思路很明确,考点就是CBC字节反转.

我们首先调用Encrypted flag得到加密后的密文,那么这时候的payload{'permission':'Guest','Time':f'{time.time()}','data':flag}

我们将这样加密后的密文,直接送去解密,肯定会返回错误,我们需要通过字节反转,将这里的Guest修改成Admin

解题过程

Encrypted flag:

6af6da83fa4b83f1e6dd21da8267c6f70b8c817ed8b15688ad751e1717c8596465bba256789656c4d2e7a9f37cec3fba3710d47c7af313979739347140aa2a080841ff385e49d82502bc6a153e2b7f7b161fc1d3732e4b7b509cc1be3928d9f107edd36b27f2319e7b72e35665d589ed689f3055740bf8ed5bcb6b5ca9f8e592ea1da515841fb19f597ba50e9eb239d6f14757df3d6e080317915da69b394e61d13353248bac03fd6efd68a09bb83ca3

IV=6af6da83fa4b83f1e6dd21da8267c6f7
C=0b8c817ed8b15688ad751e1717c8596465bba256789656c4d2e7a9f37cec3fba3710d47c7af313979739347140aa2a080841ff385e49d82502bc6a153e2b7f7b161fc1d3732e4b7b509cc1be3928d9f107edd36b27f2319e7b72e35665d589ed689f3055740bf8ed5bcb6b5ca9f8e592ea1da515841fb19f597ba50e9eb239d6f14757df3d6e080317915da69b394e61d13353248bac03fd6efd68a09bb83ca3

关于字节反转的详细思路,可以参考这篇cryptohack的博文,我们这里直接进行操作。

exp.py

from Crypto.Util.number import *
import binascii
from pwn import xor

admin = b'Admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
guest = b'Guest\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
c_hex = 'dbca2f27304d1311453f623acab96c56b75fad2e760031bb74943d2419a681d101bfb6ec5e6e141e1e2c3b8acc9456b9aacdcfded5049814e0abc2d112a46e5219ee30460564df68981d07ca744ff9601a50f2c331fc0023edb71ea80649daabc7bdf7e7264f13dfeffeadb037e4a33bf96eb2e524ea3f175ff3e2ee753b08e122cb7764dece1bf93319c206e58e94fb96f2239583fd1a82d9beb20f01b09aaada6f78da43d835e13b5934596f60abcf'

iv = c_hex[:32]
c_hex = c_hex[32:]
c = binascii.a2b_hex(c_hex)
fake_c = xor(admin, c[:16])
fake_c = xor(fake_c, guest)
fake_hex = str(binascii.b2a_hex(fake_c))[2:-1] + c_hex[32:]
print(iv + "||" + fake_hex)

flag = "4e53534354467b42724f6e59615f5a61596348694b5f42726f6e79615f42726f6e79615f42726f6e79617d"
print(long_to_bytes(int(flag, 16)))