简介
如承诺的那样,我们在活动结束一个月后发布了 Off-By-One 徽章的固件和这篇帖子,给感兴趣的参与者提供了探索的机会。如果你对徽章设计过程感兴趣,请告知我们。在首次举办的 Off-By-One 会议 2024 上,我们非常高兴地介绍了章鱼徽章。这款徽章是会议的一大亮点,因为它包含了以硬件为主的 CTF 挑战。在本文中,我们将探讨徽章的构思和设计过程,并讨论解决挑战所需的概念。
硬件设计
这幅艺术作品由 Sarah Tan 设计,特征是一个可爱的带有大眼睛的章鱼。经过各种设计头脑风暴后,我们决定为眼睛使用两个独立的圆形显示屏。以下是早期原型中的一幅草图:
将概念转换为电路设计,徽章的核心是一个ESP32-S3 主处理器,它驱动一对GC9A01 OLED 显示屏。用户可以通过按键和方向摇杆与徽章互动。另外,一个小的协处理器ATmega328P通过I2C 协议进行通信。
电子设计是在 KiCad 中完成的,以下是 3D 渲染图。徽章有三种颜色,以区分不同的人群,如参与者、工作人员和志愿者。
原计划是包含一个可以持续整个会议的可充电 LiPo 电池。然而,由于供应链困难,我们改用了 AAA 电池。希望明年的徽章可以加入 LiPo 电池。
最后,这是实际成品的样子!
硬件 CTF 挑战
像任何会议徽章一样,我们的徽章也包含了 CTF 挑战。在本节中,我将解释这些挑战背后的灵感和预期的解决方案。
特别是,嵌入式系统与全功能的计算机非常不同,它们最初设计用于资源受限的应用。例如,ESP32-S3 处理器没有内存管理单元 (MMU)。这意味着嵌入式工程师编写代码的方式与软件工程师非常不同。
我们的目标是让参与者接触到硬件黑客技术,而不仅仅是在便携式硬件设备中提供软件挑战。我们也在这个过程中学习如何改进我们的电子徽章。
1. USB 字符串描述符
第一步是确定设备类型。因此,欢迎标志被隐藏在 USB 字符串描述符 中。
USB 描述符将告诉我们设备的来源,例如供应商和产品标识符,并且还用于您的 PC 以确定加载什么驱动程序。
在 Linux 中,可以使用 dmesg
打印出内核调试消息。也可以查看 Windows 中的设备管理器。
$ dmesg -w
[3240249.488872] usb 3-3.2: New USB device found, idVendor=303a, idProduct=4001, bcdDevice= 1.00
[3240249.488883] usb 3-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[3240249.488887] usb 3-3.2: Product: #BadgeLife
[3240249.488889] usb 3-3.2: Manufacturer: STAR LABS SG
[3240249.488892] usb 3-3.2: SerialNumber: {Welcome_To_OffByOne_2024}
或者,你也可以使用 lsusb
打印出连接到 PC 的所有设备。
$ lsusb -vd 303a:
iManufacturer 1 STAR LABS SG
iProduct 2 #BadgeLife
iSerial 3 {Welcome_To_OffByOne_2024}
2. C 编译的内部库
下一个标志隐藏在名为 flaglib
的库中。这可以通过显示 MicroPython REPL 中的所有模块来看到
>>> help('modules')
[...] flaglib [...]
>>> import flaglib
>>> dir(flaglib)
['__class__', '__name__', '__dict__', 'getflag']
最简单的解决方案是编写一个脚本,通过暴力破解提取每个字符。
>>> flaglib.getflag("")
''
>>> flaglib.getflag("{____________________________}")
'{??_????????_??????_??????????'
>>> flaglib.getflag("{my_compiled_python_library}")
'{my_compiled_python_library}'
然而,知道这是一个C 编译的内部库,它与固件捆绑在一起,这意味着如果闪存被转储,其内容可以被检索到。特别是在低成本系统中,加密是资源密集型的,我们可以通过转储固件或闪存内存发现以明文保存的密码或密钥。
在尝试转储固件时,首先要确定设备的类型。从电路板上的标签可以看到,它是一个 ESP32-S3-WROOM-1-N4
。我们可以搜索数据手册,这告诉我们它有 4MiB
的闪存。
可以使用 esptool.py
包从 ESP32-S3 转储固件。按住 BOOT
按钮并按 RESET
按钮,将其置于引导加载程序模式。运行以下命令将全部内容保存到一个文件中:
$ esptool.py --baud 115200 --port /dev/serial/by-id/usb-** read_flash 0x0 0x400000 fw-backup-4M.bin
随后,通常会对 IoT 设备的转储固件进行简单的静态分析。
$ strings fw-backup-4M_black.bin | strings | grep {
{my_compiled_python_library}
3. 硬件随机数生成器
通过显示菜单,会显示一个破损的轮盘游戏。许多人通过反编译可以从设备中提取的 MicroPython 编译库 roulette.mpy
解决了这个问题。这些文件可以通过 MicroPython IDE 如 Thonny 或 Mu Editor 轻松提取。
>>> from starlabs import roulette
>>> roulette.roulette()
([1, 0, 1, 2, 1, 2, 2, 1, 2, 2], None)
然而,我们预期的解决方案是理解天真的 RNG 方法是使用模拟到数字转换器 (ADC) 生成的噪声。这在缺乏硬件 RNG 外围设备的旧微控制器中尤其相关。
模拟到数字转换器 (ADC) 通常用于将外部传感器的模拟信号转换为处理器可以在数字领域使用的数字格式。
以下是周围噪声的示例可视化,可以用作随机数采样的来源。
通过硬件模糊测试可以找到正确的引脚。将引脚短接或焊接到地,我们可以控制数的生成并打印出标志。这可以通过使用电阻器完成,但裸线也足够。
4. Arduino 协处理器
该板还包含一个使用 Arduino 平台 构建的协处理器。
协处理器在许多 IoT 应用中很常见,例如拥有一个外部安全元件或处理单元(如用于加密、证书存储或神经网络处理器)。如果通信协议未加密,则可以进行物理中间人攻击。向前看,可以进行重放或欺骗攻击以控制主处理器的行为。
根据我们在硬件介绍指南中提供的提示,用户可以访问 I2C 接口与 Arduino 通信。通过扫描 I2C 总线,用户可以发现总线上存在的地址。
>>> arduino.i2c.scan()
[48, 49]
互联集成电路 (I2C) 是一种常用于 PCB 上多个设备(微控制器、传感器或其他外部设备)之间的通信协议。多个设备连接到 I2C 总线,可以通过寻址方案交换数据。
这里我们看到两个 I2C 外设地址,分别是十六进制的 0x30
和 0x31
。通过执行从 I2C 外设读取请求,可以找到标志。
>>> arduino.i2c.readfrom(0x30, 100).rstrip(b'xff')
b'Welcome to STAR LABS CTF. Your first flag is starlabs{i2c_flag_1}'
5. 时间攻击
如果我们从第二个地址执行读取请求,会收到一条消息,指示我们应重新启动并尽早再次尝试。换句话说,该挑战指示参与者基于系统正常运行时间快速读取标志。
>>> arduino.i2c.readfrom(0x31, 200).rstrip(b'xff')
b'The early bird catches the worm. System uptime: 221. You are too late. Reboot the arduino and try again.'
这给我们提供了一个情况,即目标设备中的某些信息可能在引导序列期间的短时间内存在于内存中。我们可以通过重置 MCU,等待已知时间,然后从内存中读取,执行**“时间攻击”**。
注意:由于小的时间变化(我们讨论的是毫秒级别),可能需要重复该过程,直到获得相应的字符。
这是一个解决该挑战的示例实现。
def derp(x):
global arduino
arduino.off()
time.sleep(1) # shutdown the arduino
arduino.on()
time.sleep(x); # turn on and wait a known amount of time
return arduino.i2c.readfrom(0x31, 200).rstrip(b'xff') # immediately read
for i in range(200, 500): print(derp(0.01*i)) # repeat in sequence
每个标志的字符如下所示:
b'The early bird catches the worm. System uptime: 197. You too early, wait a little longer!'
b'The early bird catches the worm. System uptime: 198. You too early, wait a little longer!'
b'The early bird catches the worm. System uptime: 199. You too early, wait a little longer!'
b'The early bird catches the worm. System uptime: 200. You are an early bird, here is your gift: s'
b'The early bird catches the worm. System uptime: 201. You are an early bird, here is your gift: t'
b'The early bird catches the worm. System uptime: 201. You are an early bird, here is your gift: t'
b'The early bird catches the worm. System uptime: 202. You are an early bird, here is your gift: a'
b'The early bird catches the worm. System uptime: 203. You are an early bird, here is your gift: r'
b'The early bird catches the worm. System uptime: 203. You are an early bird, here is your gift: r'
b'The early bird catches the worm. System uptime: 204. You are an early bird, here is your gift: l'
b'The early bird catches the worm. System uptime: 204. You are an early bird, here is your gift: l'
b'The early bird catches the worm. System uptime: 205. You are an early bird, here is your gift: a'
b'The early bird catches the worm. System uptime: 206. You are an early bird, here is your gift: b'
b'The early bird catches the worm. System uptime: 206. You are an early bird, here is your gift: b'
b'The early bird catches the worm. System uptime: 207. You are an early bird, here is your gift: s'
b'The early bird catches the worm. System uptime: 208. You are an early bird, here is your gift: {'
b'The early bird catches the worm. System uptime: 208. You are an early bird, here is your gift: {'
b'The early bird catches the worm. System uptime: 209. You are an early bird, here is your gift: i'
b'The early bird catches the worm. System uptime: 209. You are an early bird, here is your gift: i'
b'The early bird catches the worm. System uptime: 210. You are an early bird, here is your gift: 2'
b'The early bird catches the worm. System uptime: 211. You are an early bird, here is your gift: c'
b'The early bird catches the worm. System uptime: 211. You are an early bird, here is your gift: c'
b'The early bird catches the worm. System uptime: 212. You are an early bird, here is your gift: _'
b'The early bird catches the worm. System uptime: 212. You are an early bird, here is your gift: _'
b'The early bird catches the worm. System uptime: 213. You are an early bird, here is your gift: f'
b'The early bird catches the worm. System uptime: 214. You are an early bird, here is your gift: l'
b'The early bird catches the worm. System uptime: 214. You are an early bird, here is your gift: l'
b'The early bird catches the worm. System uptime: 215. You are an early bird, here is your gift: a'
b'The early bird catches the worm. System uptime: 216. You are an early bird, here is your gift: g'
b'The early bird catches the worm. System uptime: 216. You are an early bird, here is your gift: g'
b'The early bird catches the worm. System uptime: 217. You are an early bird, here is your gift: _'
b'The early bird catches the worm. System uptime: 217. You are an early bird, here is your gift: _'
b'The early bird catches the worm. System uptime: 218. You are an early bird, here is your gift: 3'
b'The early bird catches the worm. System uptime: 218. You are an early bird, here is your gift: 3'
b'The early bird catches the worm. System uptime: 219. You are an early bird, here is your gift: }'
b'The early bird catches the worm. System uptime: 220. You are an early bird, here is your gift: '
b'The early bird catches the worm. System uptime: 220. You are an early bird, here is your gift: '
b'The early bird catches the worm. System uptime: 221. You are too late. Reboot the arduino and try again.'
b'The early bird catches the worm. System uptime: 222. You are too late. Reboot the arduino and try again.'
6. 电压干扰
要访问此挑战,我们必须从 Arduino 的 UART 串口读取。USB 串口适配器的例子包括 CH340 Serial Adapter
或 FT232 Serial Adapter
。
我们可以使用方便的引脚连接起来。连接示例如下:
你会在新连接的串口上看到如下信息:
Flag is Locked! Please help me to jailbreak it.
int i = 0, k = 0;
for (i = 0; i < 123456; i++) k++;
if (k != 123456) { unlock(); } else { lock(); }
在正常操作中,k == 123456
总是会被满足。因此,我们可以推断标志只有通过未定义的操作才能获得。这是通过干扰 if
语句来实现的。
电压干扰涉及在处理器处于未定义状态时,暂时切断电源。在此时,我们快速恢复电源,以使其继续正常操作,而不会触发重启。我们重复此过程,希望在未定义状态期间发生干扰。
从 MicroPython REPL 中,已经有一些代码为我们编写,以便通过简单的函数调用打开和关闭 Arduino。
>>> arduino
<MyArduino object at 3fcaac10>
>>> arduino.off();
>>> arduino.on();
为了执行干扰,我们快速切换电源。当电压迅速降低然后再次升高时,可能会发生干扰。
>>> arduino.on(); arduino.off(); arduino.on();
我们可以使用示波器来查看电压波形的实时变化(虽然这不是解决挑战所必需的,但有助于我们可视化发生了什么)。下图显示了 Arduino 从引脚的电压。
最终,如果我们幸运的话,标志将弹出。
Flag is Locked! Please help me to jailbreak it.
int i = 0, k = 0;
for (i = 0; i < 123456; i++) k++;
if (k != 123456) { unlock(); } else { lock(); }
This should not happen! k=123271
Unlocked. Here is your flag starlabs{voltage_glitching_is_cool}
完善干扰:
由于制造上的差异,可能不容易重复,因为我们看到切换可能会导致 Arduino 完全重启而不是干扰。这是因为电源可能过快地耗尽(即干扰“过强”)。
通过识别附近的组件(Qx1:MOSFET),我们了解到电源是通过 MOSFET 晶体管切换的。我们还看到一个“Glitcher”测试点,它连接在 MOSFET 晶体管上。通过在 MOSFET 晶体管上焊接电容器或电阻器,我们可以使干扰更具可重复性。
结论
感谢所有参与会议并参与 CTF 挑战的人!徽章设计成功促进了对嵌入式系统和解决硬件设备所需技能的深入理解。我们希望徽章激发了更多的兴趣,并提供了一个有价值的平台,供会议结束后进行探索和学习。
原文始发于微信公众号(3072):Off-By-One con 徽章设计与破解