通过分析逻辑分析仪的波形图,识别出波形组成的文字,得到 flag
解析 Gerber 文件,找到隐藏的字符串,拼接成 flag
通过分析 UART 通信的波特率,解码得到 flag
分析 Verilog 代码,对干扰的数据进行汉明码解码,通过统计分析得到 flag
结合逻辑分析仪记录和 Gerber 文件,分析数码管的显示内容,通过通道与数码管的连接关系,还原出数码管显示的字符,最终得到 flag
下载附件得到一个 sal 文件,这个是逻辑分析仪 saleae 的配套软件 logic2 保存的文件格式,因此使用 logic2 打开,打开之后看着乱七八糟的波形有点懵,当要缩放一下看看细节的时候发现好像是波形组成了文字... flag 为:
HTB{b391N_tH3_HArdWAr3_QU3St}
下载后得到一堆 GBR 文件,这是 Gerber 格式,用来给 PCB 厂商打板子的,有些在线网站可以解析 gerber 压缩包格式
https://viewer.digipcba.com/viewer/https://www.pcbway.com/project/OnlineGerberViewer.html
直接上传题目压缩包后点击 layers 可以看到不同的层,然后点击小眼睛关闭某些层的干扰,能够找到两部分字符串,拼接起来为 flag:
HTB{533_7h3_1nn32_w02k1n95_0f_313c720n1c5#$@}
下载附件得到一个 sal 文件,使用 logic2 打开,发现是通道 0 和 通道 1 标记着 TX 和 RX,那应该是 UART,鼠标放到波形图上,找个最窄的脉冲宽度,其时间为 8.68us 也就是 0.00000868s,1 / 0.00000868 ≈ 115200
因此选择波特率 115200 尝试解码,得到 flag:
HTB{547311173_n37w02k_c0mp20m153d}
下载后查看附件,发现是 verilog 代码
module encoder(input [3:0] data_in,output [6:0] ham_out );wire p0, p1, p2;assign p0 = data_in[3] ^ data_in[2] ^ data_in[0];assign p1 = data_in[3] ^ data_in[1] ^ data_in[0];assign p2 = data_in[2] ^ data_in[1] ^ data_in[0];assign ham_out = {p0, p1, data_in[3], p2, data_in[2], data_in[1], data_in[0]};endmodulemodule main;wire[3:0] data_in = 5;wire[6:0] ham_out; encoder en(data_in, ham_out);initialbegin #10;$display("%b", ham_out);endendmodule
嗯?我是不是缺了点信息?看了看原题是这样描述的
嗯,应该是还有个地址,nc 过去会获得一串数据,但是那串数据其实是有错误 bit 的,需要尝试对包含错误比特的数据进行解码,从 github 上搜到有人保存了 nc 获得的数据,那就以这段数据作为输入吧:
https://github.com/MicheleMosca/CTF/blob/main/Cyber%20Apocalypse%202023/Hardware/HM74/signals.txt
先来分析一下 verilog 代码,module main 主要就是实例化了 encoder 这个模块,然后打印比特,因此主要逻辑是在 encoder 模块的,通过这个模块的声明可以看到他接收 4bit 输入,产生 7bit 输出,我们应该能够通过其异或结果与原始数据对比,找出没有受到干扰的原始数据
把从 nc 获取到的数据保存下来,根据 verilog 逻辑,应该是每 7bit 为一组,每组中除了原始数据,还有原始数据异或的结果,一开始想的是按照 verilog 逻辑,p0、p1、p2 与 data[0]、data[1]、data[2]、data[3] 异或的结果完全匹配则记为有效,结果算完发现有效的没几个...
其实根据不同的异或结果,可以筛选出到底是哪一个 bit 错位了,然后再直接反转那个 bit 就好了
defbit_check(data): p0 = int(data[0]) p1 = int(data[1]) data_in3 = int(data[2]) p2 = int(data[3]) data_in2 = int(data[4]) data_in1 = int(data[5]) data_in0 = int(data[6]) p0_error = False p1_error = False p2_error = Falseif (p0 != data_in3 ^ data_in2 ^ data_in0): p0_error = Trueif (p1 != data_in3 ^ data_in1 ^ data_in0): p1_error = Trueif (p2 != data_in2 ^ data_in1 ^ data_in0): p2_error = Trueif (p0_error == True) and (p1_error == True) and (p2_error == True): # 如果三个都错了,说明data_in0错了 data_in0 = int(not data_in0)if (p0_error == True) and (p1_error == True) and (p2_error == False): # 如果p2是对的,说明data_in3错了 data_in3 = int(not data_in3)if (p0_error == True) and (p1_error == False) and (p2_error == True): # 如果p1是对的,说明data_in2错了 data_in2 = int(not data_in2)if (p0_error == False) and (p1_error == True) and (p2_error == True): # 如果p0是对的,说明data_in1错了 data_in1 = int(not data_in1)return str(data_in3)+str(data_in2)+str(data_in1)+str(data_in0)
但问题是错的可能不只有一位,这就导致即使通过异或检查过后还是会有错误的结果,可以看到代码中经过检查后打印出来即使是有些符合 flag 规则的,也全都是错误
这时候可以考虑对结果进行统计,取其中出现频率最高的字符拼接出来,得到真实的 flag:
HTB{hmm_w1th_s0m3_ana1ys15_y0u_c4n_3x7ract_7h3_h4mmin9_7_4_3nc_fl49}
file = open("signals.txt",'r').readlines()defbit_check(data): p0 = int(data[0]) p1 = int(data[1]) data_in3 = int(data[2]) p2 = int(data[3]) data_in2 = int(data[4]) data_in1 = int(data[5]) data_in0 = int(data[6]) p0_error = False p1_error = False p2_error = Falseif (p0 != data_in3 ^ data_in2 ^ data_in0): p0_error = Trueif (p1 != data_in3 ^ data_in1 ^ data_in0): p1_error = Trueif (p2 != data_in2 ^ data_in1 ^ data_in0): p2_error = Trueif (p0_error == True) and (p1_error == True) and (p2_error == True): # 如果三个都错了,说明data_in0错了 data_in0 = int(not data_in0)if (p0_error == True) and (p1_error == True) and (p2_error == False): # 如果p2是对的,说明data_in3错了 data_in3 = int(not data_in3)if (p0_error == True) and (p1_error == False) and (p2_error == True): # 如果p1是对的,说明data_in2错了 data_in2 = int(not data_in2)if (p0_error == False) and (p1_error == True) and (p2_error == True): # 如果p0是对的,说明data_in1错了 data_in1 = int(not data_in1) data = str(data_in3)+str(data_in2)+str(data_in1)+str(data_in0)return dataoutputs = [] # 包含所有输出的列表for line in file: line = line[10:].strip() s = ''for i in range(0,len(line),7): data = line[i:i+7] data_checked = bit_check(data) my_data = hex(int(data_checked,2))[2:] s += my_datatry: outputs.append(bytes.fromhex(s)) print(bytes.fromhex(s))except:passchar_counts = {} # 初始化一个字典,用于存储每个位置上的字符出现次数for output in outputs: # 遍历每个输出 s = output.decode('latin1') # 将字节串转换为字符串for i, char in enumerate(s): # 遍历字符串中的每个字符和它们的位置if i notin char_counts: char_counts[i] = {} # 如果位置不在字典中,添加进去if char in char_counts[i]: # 如果字符已经在字典中,增加它的计数,否则设置为1 char_counts[i][char] += 1else: char_counts[i][char] = 1most_common_chars = ''for i in range(len(char_counts)): # 找出每个位置上出现次数最多的字符 most_common_chars += max(char_counts[i], key=char_counts[i].get)print(most_common_chars) # 打印结果
下载下来之后打开,发现有一个逻辑分析仪的记录文件和一堆 Gerber 文件,打开逻辑分析仪记录文件,抓取了 8 个通道的波形,但不知道是什么含义
Gerber 文件在线查看是一个圆形的 PCB,中间有一个数码管,数码管的各个引脚连接到了周围的触点上,触点还标记了 channelx 应该要与逻辑分析仪抓取的通道对应起来,那应该是要解析这些通道的高低电平,对应到不同的触点连接到的数码管的位置,从而还原出数码管显示的不同的字符
首先梳理通道与数码管的连接关系,在立创商城随便找一个数码管的手册
根据手册,这个数码管的引脚定义可能是
再根据电路走线,对应到各个通道就是:
e <-> channel 6d <-> channel 0c <-> channel 4dp <-> channel 1b <-> channel 5a <-> channel 2f <-> channel 7g <-> channel 3
观察逻辑分析仪波形,dp(即 channel1)非常规律,像是分割每个显示字符的,因此尝试以 dp 作为分割,根据其他通道的电平画出数码管的显示图案
例如刚开始是:G、C、B、F 亮,在数码管上标记出来就是字符:4
如此整理完后得到:
4854427b70307733325F63306d33355F6632306d5F77313768316E4021237d
转为ascii 为:
HTB{p0w32_c0m35_f20m_w17h1n@!#}
原文始发于微信公众号(陈冠男的游戏人生):Cyber Apocalypse 2023 硬件 CTF 题解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论