BadgeLife @ Off-By-One Conference 2024 | STAR Labs
目录
引言
如约,我们在活动结束约一个月后发布了Off-By-One徽章的固件和本文,以便感兴趣的参与者有机会探索它。如果您想了解更多关于徽章设计过程的信息,请告诉我们。我们很高兴在首届Off-By-One Conference 2024上推出Octopus徽章。该徽章是会议的一大亮点,因为它包含了以硬件为重点的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中查看设备管理器。
1
2
3
4
5
6
|
$ 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的所有设备。
1
2
3
4
|
$ lsusb -vd 303a:
iManufacturer 1 STAR LABS SG
iProduct 2 #BadgeLife
iSerial 3 {Welcome_To_OffByOne_2024}
|
2. C编译内部库
下一个标志隐藏在一个名为flaglib的库中。通过MicroPython REPL显示所有模块可以看到这一点。
1
2
3
4
5
|
>>> help('modules')
[...] flaglib [...]
>>> import flaglib
>>> dir(flaglib)
['__class__', '__name__', '__dict__', 'getflag']
|
天真的解决方案是编写一个脚本,通过暴力破解逐个字符提取。
1
2
3
4
5
6
|
>>> 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按钮进入引导加载程序模式。运行以下命令将完整内容保存到文件中:
1
|
$ esptool.py --baud 115200 --port /dev/serial/by-id/usb-** read_flash 0x0 0x400000 fw-backup-4M.bin
|
随后,通常会对IoT设备的转储固件进行简单的静态分析。
1
2
|
$ strings fw-backup-4M_black.bin | strings | grep {
{my_compiled_python_library}
|
3. 硬件随机数生成器
通过显示菜单,显示了一个损坏的轮盘游戏。许多人通过反转MicroPython编译的库解决了它,该库可以从设备中提取为roulette.mpy。文件可以轻松地通过MicroPython IDE(如Thonny或Mu Editor)提取。
1
2
3
|
>>> 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总线,用户可以发现总线上的地址。
1
2
|
>>> arduino.i2c.scan()
[48, 49]
|
Inter-Integrated Circuit(I2C)是一种通信协议,常用于PCB上多个设备(微控制器、传感器或其他外部设备)之间。多个设备连接到I2C总线,数据可以通过寻址方案交换。
这里我们看到两个I2C外设地址,分别是十六进制的0x30和0x31。通过从I2C外设执行读取请求,可以找到标志。
1
2
|
>>> arduino.i2c.readfrom(0x30, 100).rstrip(b'\xff')
b'Welcome to STAR LABS CTF. Your first flag is starlabs{i2c_flag_1}'
|
5. 定时攻击
如果我们从第二个地址执行读取请求,我们会收到一条消息,指示我们应该重新启动并更早地重试。换句话说,挑战指示参与者根据系统运行时间快速读取标志。
1
2
|
>>> 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,等待已知时间后再从内存读取来执行“定时攻击”。
注意:由于小的定时变化(我们以毫秒计),可能需要重复该过程直到获得相应的字符。
这是一个解决挑战的示例实现。
1
2
3
4
5
6
7
8
9
|
def derp(x):
global arduino
arduino.off()
time.sleep(1) # 关闭arduino
arduino.on()
time.sleep(x); # 打开并等待已知时间
return arduino.i2c.readfrom(0x31, 200).rstrip(b'\xff') # 立即读取
for i in range(200, 500): print(derp(0.01*i)) # 按顺序重复
|
标志的每个字符如下检索:
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
|
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串行适配器或FT232串行适配器。
有方便的引脚供我们使用。这是如何连接它的方法。
您将在新连接的串行端口上看到此消息。
1
2
3
4
|
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。
1
2
3
4
|
>>> arduino
<MyArduino object at 3fcaac10>
>>> arduino.off();
>>> arduino.on();
|
为了执行毛刺,我们快速切换电源。当电压降低并快速连续升高时,可能发生毛刺。
1
|
>>> arduino.on(); arduino.off(); arduino.on();
|
我们可以使用示波器,这是一种允许我们实时查看电压波形的工具(这不是解决挑战所必需的,但它帮助我们可视化正在发生的情况)。下图描绘了从引出引脚测量的Arduino电压。
最后,如果我们幸运,会看到标志弹出。
1
2
3
4
5
6
7
|
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挑战的人!徽章设计成功培养了对嵌入式系统的更深入理解以及处理硬件设备所需的技能。我们希望徽章能激发进一步兴趣,并在会议结束后长期提供有价值的探索和学习平台。