硬件CTF题目汇总
硬件CTF题目汇总
题目附件备份地址:https://github.com/yichen115/HardWare-CTF
CrewCTF 2024
参考:
https://mwlik.github.io/2024-08-05-crewctf-2024-sniff-challenge/
https://xz.aliyun.com/t/15357
这两道题共用一个压缩包附件,需要找到在键盘上输入的密码和输完密码在屏幕上显示的内容
通过阅读 README 得到的信息如下:
他用逻辑分析仪嗅探了设备的通信过程,来看一张全局的图,里面除了树莓派和逻辑分析仪其他两个不知道是啥,看看别的图找线索
通过这张图搜索发现是个电子墨水屏:https://learn.pimoroni.com/article/getting-started-with-inky-phat
通过谷歌搜索键盘上的型号得知通信接口是 IIC:https://docs.m5stack.com/en/unit/cardkb
Sniff One
首先来看键盘输入的信息
用逻辑分析仪对应的软件打开给的嗅探文件,嚯,这么多,来了个大活...
这得确认一下哪根线是哪个啊,在 README 中已经给出了,根据这个描述去找接线图,键盘的黑是 GND,红是供电,那么白色和黄色是 IIC,对应了其他地方的灰色和紫色
找到逻辑分析仪上的灰色和紫色,通过这张图可以看出来是通道 0 和通道 1
那么在逻辑分析仪软件中把通道 0 和通道 1 设置成 IIC 解析一下,这软件搜索功能有点不太行啊,导出来搜索一下
证据确凿,就是你了
过滤一下没用的信息
得到 flag{717f7532}
Sniff Two
这个屏和树莓派之间的关系应该是树莓派上装个上位机软件,就能控制这个屏显示图像,然后搜了搜这个屏的引脚定义如下图:https://pinout.vvzero.com/pinout/inky_phat,估计要用剩下的 SPI 的数据了(后面看源码也能确定是 SPI)
但是正常使用应该是要盖在树莓派上的,因此引脚定义和实际接线图是对着的,要自己理解一下这个关系
因此按照接线图来看一下
绿色是 BUSY 对应通道 2
黄色是 Reset 对应通道 3
橙色是 Data/Command 对应通道 4
红色是 MOSI 对应通道 5
棕色是 SCLK 对应通道 6
蓝色是 Chip Select 对应通道 7
然而打开逻辑分析仪我傻眼了,这名字不一样啊,感觉 SPI 的引脚定义各家叫有些不统一?MOSI 和 SCLK 肯定一一对应,Chip Select 应该是片选,可以对应到 Enable 上
一个合理的解释:对于 LCD 控制器来说,MOSI 引脚用于接收数据和命令,并且 D/C 由主机设置低电平时表示写入命令;高电平时表示写入数据/参数(https://forums.adafruit.com/viewtopic.php?t=51949)
因此可以直接将 D/C 引脚设置为 MISO,这样解析为 0x00 就表示是低电平写入命令,解析为 0xFF 就表示高电平写入数据
这样就能从逻辑分析仪里面解析出来 SPI 发了啥,但是怎么从发出去的数据中恢复图像,还得分析源码...
这是它的源码:https://github.com/pimoroni/inky/blob/main/inky/inky.py
set_image 函数用来拷贝要显示的图像到 buffer 中
show 函数根据 buf 进行拆分,拆分成黑色的 buf_a 和红色的 buf_b
最后调用 _update 函数在屏幕上显示像素,里面有个 for 循环,0x24 时表示发送的是 buf_a,0x26 时发送的是 buf_b
在逻辑分析仪里面搜索一下,发现有两个 0x24 和 0x26,可能是传输了两张图片,正好 Data/Command 也有两段不同的波动
放大细节看一下,还是可以很好的区分 cmd 和 data 的
接下来就是对这些数据进行处理了,可以用逻辑分析仪将这段导出为 csv
然后根据 0x24 和 0x26 分割处理一下数据,分别保存到 buf_a 和 buf_b 中,再根据他源码中的 set_image 和 show 函数来往回倒腾一下数据,先看看源码里都做了啥
show 函数把黑色像素点设置为0其余为1,红色像素点设置为1其余为0,然后将 bit 打包成NumPy 数组,转成 Python 列表
buf_a = numpy.packbits(numpy.where(region == BLACK, 0, 1)).tolist()
buf_b = numpy.packbits(numpy.where(region == RED, 1, 0)).tolist()
对应的,我们已经有 buf_a 和 buf_b 的列表了,只需要用 unpackbits 把它先还原回 NumPy 数组
buf_a_unpacked = np.unpackbits(np.array(buf_a, dtype=np.uint8))
buf_b_unpacked = np.unpackbits(np.array(buf_b, dtype=np.uint8))
set_image 函数是根据图像的大小存到了 numpy 的数组中,因此接下来需要考虑一个对于图像来说比较重要的参数:长和宽,根据源码中的定义,设置这些长宽时的 command 分别为 0x44 和 0x45
因此在逻辑分析仪中找到这些值,宽就是 (0x10+1) * 8 = 136,计算高的时候要注意小端序计数,因此高为 0x00F9 = 249
使用 reshape 函数对长宽进行调整
width = 136
height = 249
buf_a = buf_a_unpacked[:height * width].reshape((height, width))
buf_b = buf_b_unpacked[:height * width].reshape((height, width))
最后再根据颜色往一个新的 buf 里面填充数值,最后创建一个图像数组根据 buf 将对应的坐标点改为不同的颜色
def display(buf_a, buf_b):
width = 136
height = 249
color_mapping = {
0:(255,255,255), # 白色
1:(0,0,0), # 黑色
2:(255,0,0) # 红色
}
buf_a_unpacked = np.unpackbits(np.array(buf_a, dtype=np.uint8))
buf_b_unpacked = np.unpackbits(np.array(buf_b, dtype=np.uint8))
buf_a = buf_a_unpacked[:height*width].reshape((height,width))
buf_b = buf_b_unpacked[:height*width].reshape((height,width))
buf = np.zeros((height,width), dtype=np.uint8)
for i in range(height):
for j in range(width):
if buf_a[i,j] == 0: # 黑色
buf[i,j] = 1
elif buf_b[i,j] == 1: # 红色
buf[i,j] = 2
else:
buf[i,j] = 0 # 白色
image_array = np.zeros((height, width, 3), dtype=np.uint8)
for y in range(height):
for x in range(width):
image_array[y,x] = color_mapping[buf[y,x]] # 修改对应位置的颜色
image = Image.fromarray(image_array,'RGB')
image.show()
最终可以打印出来两张图片,得到 flag{ec9cf2b7}