图源:@しぐれうい 《雨に恋う》 83975808
cap.pcapng用Wireshark打开,清晰可见TCP的三次握手和四次挥手:
于是需要仔细分析中间的数据传输过程。
注意到某些数据包内有“numpy”等字符串:
于是想到可能是某种兼容Python的序列化方法。
又注意到有固定的头部数据:
而pickle序列化时也有固定的头部数据(协议版本4.0):
于是尝试利用pickle进行反序列化。示例如下:
1 2 3 4 5 6 7 8 9 10 import pysharkimport picklecap = pyshark.FileCapture('cap.pcapng' ) test_pack = cap[3 ] data = test_pack.data.data.binary_value deserialized = pickle.loads(data) print (deserialized)
得到结果312。随后发现之后的数据包的长度有规律:即436-(ACK)-90-(ACK)-90-(ACK)五个一组,或436-(ACK)-81-(ACK)两种组合。分别拿一组包进行反序列化测试:
1 2 Pack :327 Data:[array([0 .70710678 +0 .j, 0 .70710678 +0 .j]), array([ 0 .70710678 -8 .65956056 e-17 j, -0 .70710678 +8 .65956056 e-17 j]), array([0 .+0 .j, 1 .+0 .j]), array([1 .+0 .j, 0 .+0 .j])]Pack :329 Data:
1 2 3 Pack :331 Data:[array([0 .70710678 +0 .j, 0 .70710678 +0 .j]), array([1 .+0 .j, 0 .+0 .j]), array([1 .+0 .j, 0 .+0 .j]), array([ 0 .70710678 -8 .65956056 e-17 j, -0 .70710678 +8 .65956056 e-17 j])]Pack :333 Data:[0 , 0 , 1 , 1 ]Pack :335 Data:[0 , 1 , 0 , 1 ]
又发现第1041个数据包处有疑似密文的数据:
题目中的“QKD”即量子密钥分发。常见的QKD协议有BB84、B92、E91等。结合前面的关于流程的分析(通过状态向量传递量子、两个数组先后传递Bob的测量基和Alice的判断结果)可以确定使用BB84协议进行的密钥分发。这就也能解释开头处传输的“312”:Alice和Bob需要提前约定好密钥长度。(严谨的BB84密钥交换协议中包括纠错、保密放大、认证等流程,此处略去)
同时注意到密文与密钥一样是312字节,考虑可能是流密码对每一位进行加密。
而如上述序号为329的数据包处的空数据,结合量子传输过程中不可直接窃听的特性,可以想到存在窃听者Eve,测量了量子后使Bob没有收到Alice传输的量子。
通过量子的状态向量和题目中给出的量子初始状态,已经可以判断出对量子进行操作的量子门及其顺序,因此就相当于获得了Alice传输的量子。这也是题目中“Debug info”的含意。
编写程序解密即可。
增补:为降低难度,量子的初状态已经已题目描述的方式给出。参考资料的不同可能导致对状态向量的理解上产生偏差。为降低难度,此题使用量子数学的表示方式,即:
$ \left| \phi \right> = \alpha \left| 0 \right> + \beta \left| 1 \right> $ 得到 $ [\alpha, \beta] $
为方便处理,过滤掉所有不包含数据的包,并存为新的文件:
随后编写脚本解密即可。示例脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import pysharkimport binasciiimport qiskitimport picklefrom bitstring import BitArrayQUANLENG = 4 key = "" cap = pyshark.FileCapture('cap2.pcapng' ) def decrypt_msg (enckey: BitArray, msg: BitArray ): res = BitArray() for i in range (msg.len ): tmp = enckey[i] ^ msg[i] res.append("0b" + str (int (tmp))) return res def recv_quantum (quantum_state: list ): quantum = [qiskit.QuantumCircuit(1 , 1 ) for _ in range (4 )] for i in range (4 ): real_part_a = quantum_state[i][0 ].real real_part_b = quantum_state[i][1 ].real if real_part_a == 1.0 and real_part_b == 0.0 : continue elif real_part_a == 0.0 and real_part_b == 1.0 : quantum[i].x(0 ) elif real_part_a > 0.7 and real_part_b > 0.7 : quantum[i].h(0 ) else : quantum[i].x(0 ) quantum[i].h(0 ) return quantum def measure (receiver_bases: list , quantum: list ): for i in range (4 ): if receiver_bases[i]: quantum[i].h(0 ) quantum[i].measure(0 , 0 ) else : quantum[i].measure(0 , 0 ) quantum[i].barrier() backend = qiskit.Aer.get_backend("statevector_simulator" ) result = qiskit.execute(quantum, backend).result().get_counts() return result def get_key (qubits: list , bases: list , compare_result: list ): measure_result = measure(bases, qubits) for i in range (4 ): if compare_result[i]: tmp_res = list (measure_result[i].keys()) global key key += str (tmp_res[0 ]) if __name__ == "__main__" : key_len = pickle.loads(cap[0 ].data.data.binary_value) i = 1 while i < 567 : if int (cap[i+1 ].data.len ) == 15 : i += 2 continue quantum_state = pickle.loads(cap[i].data.data.binary_value) quantum = recv_quantum(quantum_state) bob_bases = pickle.loads(cap[i+1 ].data.data.binary_value) alice_judge = pickle.loads(cap[i+2 ].data.data.binary_value) get_key(quantum, bob_bases, alice_judge) i += 3 key = key[:key_len] msg = BitArray(pikle.loads(cap[567 ].data.data.binary_value)) plaintext = decrypt_msg(BitArray("0b" +key), msg) print (plaintext.tobytes())
得到Flag:
1 d3ctf {y1DcuFuYwCgRfX33uT1lgSy27jYIsF4i}
当然,使用量子状态向量、Bob的测量基和测量结果的直接对应关系直接得出结果(即不需要模拟)也是可以的。这里不再赘述。