密码学实战 - HTB Broken Decryptor

868 阅读3分钟

概述

Broken Decryptor是来自于HTB(hackthebox.com)的一个中级密码学挑战,完成该挑战所需要掌握的知识点在于使用CTR模式的AES算法。

题目分析

相关的任务文件包括一个challenge.py源代码以及一个在线运行环境。

challenge.py代码节选如下

import socketserver
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os

key = os.urandom(0x10).replace(b'\x00', b'\xff')
iv = os.urandom(0x10).replace(b'\x00', b'\xff')

def xor(a, b):
    return bytes([_a ^ _b for _a, _b in zip(a, b)])

def unhex(msg):
    return bytes.fromhex(msg)

def encrypt(data):
    ctr = Counter.new(128, initial_value=int(iv.hex(), 16))
    crypto = AES.new(key, AES.MODE_CTR, counter=ctr)
    if type(data) != bytes:
        data = data.encode()
    otp = os.urandom(len(data)).replace(b'\x00', b'\xff')
    return xor(crypto.encrypt(data), otp)

def decrypt(data):
    ctr = Counter.new(128, initial_value=int(iv.hex(), 16))
    crypto = AES.new(key, AES.MODE_CTR, counter=ctr)
    return crypto.decrypt(data.encode())

def get_flag():
    flag = open('flag.txt', 'r').read().strip()
    return encrypt(flag)

def send_msg(s, msg):
    s.send(msg.encode())

def get_input(s, msg):
    send_msg(s, msg)
    data = b''
    while (recv := s.recv(0x1000)) != b'':
        data += recv
        if data.endswith(b'\n'):
            break
    data = data.strip()
    return data.decode()

def main(s):
    while True:
        send_msg(s, '1) Get flag\n')
        send_msg(s, '2) Encrypt Message\n')
        send_msg(s, '3) Decrypt Message\n')
        try:
            opt = get_input(s, 'Your option: ')
            if opt == '1':
                send_msg(s, get_flag().hex()+'\n')
            elif opt == '2':
                pt = get_input(s, 'Enter plaintext: ')
                send_msg(s, encrypt(unhex(pt)).hex()+'\n')
            elif opt == '3':
                ct = get_input(s, 'Enter ciphertext: ')
                send_msg(s, decrypt(unhex(ct)).hex()+'\n')
            else:
                send_msg(s, 'Invalid option!\n')
        except:
            send_msg(s, 'An error occured.')
            return

以上代码提供了三个输入选项,选项1对flag进行加密并返回密文,选项2运行用户输入明文并返回相应的密文,选项3运行用户输入密文并返回相应的明文。 加密算法是使用CTR模式的AES算法。

运行以上代码可以发现选项3的实现有问题,始终换回错误信息。 另外可以可以看到CTR模式所用的keyIV都是定值。但其加密输出又与一个随机生成的OTP进行异或后生成最终的密文。

解题过程

整个解题过程分为两个步骤。

第一步在于获得flag的AES加密的输出t,也就是与OTP进行异或的输入。 我们知道t与OTP异或的结果c, 但OTP未知,因此我们只能采取暴力穷举。 对于OTP中的第j个字节而言,其可能的数值为1到255之间的某个数 (0被替换成255),因此,我们可以对c[j]与每个可能的数值进行异或,以得到所有可能的t[j], 使用选项1重复运行该步骤,可以减少t[j]的可能值数量,当t中的每一个字节都只有一个可能值时,我们就得到了整个t

获得了c之后,第二步就可以获得flag的明文。 其原理在于CTR模式下当keyIV是定值时,将密文再次加密就可以获得明文。因此使用与第一步相同的方法,但使用选项2并输入t, 穷举完成之后,就得到完整的明文。

代码如下:

#!/usr/bin/python3
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os
from Crypto.Util.number import bytes_to_long, long_to_bytes 

from pwn import remote

flag_length = 15

def encrypt1():
    conn.recvuntil("Your option:")
    conn.sendline("1")
    line = conn.recvline()
    line = line.decode().strip()
    
    return bytes.fromhex(line)
    
def encrypt2(data):
    conn.recvuntil("Your option:")
    conn.sendline("2")
    conn.recvuntil("Enter plaintext:")
    conn.sendline(data)
    line = conn.recvline()
    line = line.decode().strip()
    
    return bytes.fromhex(line)
        
def checkBruteForceResults(results):
  for i in range(flag_length):
    if (sum(results[i]) > 1):
      print("check byte #", i, " sum = ", sum(results[i]))
      return False
  
  return True

def bruteForceWithOTP(option, data = None):
    brute_force_results = []
    for _ in range(flag_length):
        brute_force_results.append([1] * 256)

    done = False
    while (not done):
        if (option == 1):
            c = encrypt1()
        else:
            c = encrypt2(data)
        
        for i in range(flag_length):
            possible_bytes = [0] * 256
            
            #穷举所有可能的OTP值
            for j in range(0, 256):
                if (j == 0):
                    j = 0xff      
                
                possible_byte = c[i] ^ j
                possible_bytes[possible_byte] = 1
          
            for j in range(256):
              if (possible_bytes[j] == 0):
                #剔除不可能的数值
                brute_force_results[i][j] = 0
                
        done = checkBruteForceResults(brute_force_results)
    
    #穷举完成, 获取结果
    result = bytearray(flag_length)

    for i in range(flag_length):
      for j in range(256):
        if (brute_force_results[i][j] == 1):
            result[i] = j
            break
    
    return result.hex()

conn = remote('139.59.172.163', 30827, level = 'error')

t = bruteForceWithOTP(1)
print("t =", t)
                     
flag = bruteForceWithOTP(2,  t)

print("flag =", bytes.fromhex(flag))