BUUCTF 编码与调制实战:从曼彻斯特到差分曼彻斯特的CTF信号解码

BUUCTF 编码与调制实战:从曼彻斯特到差分曼彻斯特的CTF信号解码

1. 曼彻斯特编码的CTF实战解码技巧

第一次在BUUCTF遇到曼彻斯特编码的题目时,我盯着那串"5555555595555A65556AA696AA6666666955"的十六进制数据发了半天呆。后来才发现,这类题目其实有规律可循,只要掌握几个关键点就能轻松破解。

曼彻斯特编码最让人头疼的就是它的两种不同约定标准。我在实际解题过程中发现,第一种标准(GE Thomas提出)将01对应0,10对应1;而第二种标准(IEEE 802.3/802.4使用)则正好相反,10对应0,01对应1。这个区别直接影响到最终解码结果是否正确。

这里分享一个我总结的快速识别技巧:当密文中只出现5、6、9、A这几个十六进制字符时,基本可以确定是曼彻斯特编码。因为:

  • 0101(二进制)对应十六进制5
  • 0110对应6
  • 1001对应9
  • 1010对应A

解码时我通常分三步走:

  1. 将每个十六进制字符转为4位二进制
  2. 每两位二进制按编码规则转换(01→1或10→1,取决于采用哪种标准)
  3. 将转换后的二进制数据按8位一组转为十六进制
txt = '5555555595555A65556AA696AA6666666955' plain = '' for i in txt: plain += bin(int(i,16))[2:].zfill(4) # 十六进制转4位二进制 temp = '' for i in range(0,len(plain),2): temp += plain[i+1] # 按第二种标准转换:取每对的第二位 flag = '' for i in range(0,len(temp),8): flag += hex(eval('0b'+temp[i:i+8][::-1]))[2:] # 8位一组转十六进制 print(flag.upper())

2. 差分曼彻斯特编码的识别与处理

差分曼彻斯特编码比普通曼彻斯特更复杂一些,但CTF题目中出现的频率也不低。我遇到的一道典型题目是"[BJDCTF2020]编码与调制",题目给出了NRZ、曼彻斯特和差分曼彻斯特三种编码的对比图。

差分曼彻斯特的特点是:

  • 每个比特周期开始时都会有一次电平跳变
  • 比特中间根据数值决定是否跳变:0有跳变,1无跳变
  • 解码时不依赖绝对电平,而是看跳变沿

实战中我发现一个快速判断方法:如果数据中出现大量连续的5或A,很可能是差分曼彻斯特编码。因为稳定的时钟信号会表现为规律的电平变化。

解码差分曼彻斯特时,我常用的Python脚本如下:

from Crypto.Util.number import * txt = '2559659965656A9A65656996696965A6695669A9695A699569666A5A6A6569666A59695A69AA696569666AA6' temp = '' for i in txt: pd = bin(int(i,16))[2:].zfill(4) temp += pd[0] + pd[2] # 特殊处理差分曼彻斯特 flag = '' for i in range(0,len(temp),8): flag += hex(eval('0b'+temp[i:i+8]))[2:] print(long_to_bytes(eval('0x'+flag)))

这个脚本的关键在于对每4位二进制数的特殊处理方式,通过取特定位来还原原始数据。在实际比赛中,如果一种解码方式不奏效,可以尝试将二进制数据反转后再解码,有时会有意外收获。

3. CTF中常见的编码混合题型

很多CTF题目不会直接告诉你使用哪种编码方式,而是将多种编码方式混合使用。我遇到过最棘手的一道题是将NRZ、曼彻斯特和ASCII编码混合在一起。

解题时我总结了一套流程:

  1. 先观察数据特征:长度、字符分布、特殊字符
  2. 尝试常见编码转换:hex、base64、ASCII
  3. 检查是否有规律的电平变化模式
  4. 用Python脚本批量尝试不同解码方式

例如下面这段数据:

48 65 6C 6C 6F 20 57 6F 72 6C 64 21 5A 5A 5A 5A A6 A6 A6 A6

前半部分明显是ASCII码,后半部分突然变成5A和A6交替,这就提示可能需要切换解码方式。

对于混合编码题目,我建议准备一个多功能的解码脚本:

import binascii from Crypto.Util.number import * def try_decodings(data): # 尝试ASCII try: print("ASCII:", bytes.fromhex(data).decode('ascii')) except: pass # 尝试Base64 try: print("Base64:", b64decode(bytes.fromhex(data)).decode()) except: pass # 尝试曼彻斯特解码 manchester_decoded = manchester_decode(data) print("Manchester:", manchester_decoded) # 其他解码方式...

4. 实战中的二进制处理技巧

在信号解码类题目中,二进制处理是最基础也是最重要的技能。我总结了几种常见场景的处理方法:

  1. 二进制反转问题: 很多传感器数据传输时会采用LSB-first或MSB-first的不同约定。如果解码后发现数据不对,可以尝试反转二进制位序:

    binary = '11001010' reversed_bin = binary[::-1] # 反转位序
  2. 二进制与十六进制转换: Python的bin()函数生成的二进制字符串会带'0b'前缀,记得去掉:

    hex_str = '5A' binary = bin(int(hex_str, 16))[2:].zfill(8) # 转8位二进制
  3. 处理不完整字节: 当二进制数据长度不是8的倍数时,需要特别处理:

    binary_data = '110101001' # 补零到完整字节 if len(binary_data) % 8 != 0: binary_data = binary_data.zfill((len(binary_data)//8 + 1)*8)
  4. 多进制转换技巧: 有时需要直接在十六进制、十进制和二进制间转换:

    # 十六进制转十进制 dec = int('FF', 16) # 十进制转二进制字符串 bin_str = format(255, '08b')

在实际比赛中,我通常会准备一个包含这些常用函数的工具脚本,遇到编码题目时快速尝试各种可能性。记住,信号解码类题目的关键就是多尝试、多验证,不要被表面的复杂度吓倒。