由 Icicle 在 MSP430 二进制文件中发现的崩溃。
| # | 目标 | 描述 |
|---|---|---|
| I03 | Goodwatch | 写入 dmesg_buffer 时的错误比较 |
| I04 | Goodwatch | 零消息长度 |
| I05 | Goodwatch | RNG 溢出 |
| I06 | Goodwatch | OOK 按键中的越界访问 |
| I07 | Goodwatch | 秒表运行 60 小时后的越界访问 |
| I08 | Goodwatch | 显示星期时的越界访问 |
| I09 | Goodwatch | 十六进制查看器应用 |
| I10 | Goodwatch | PEEK/POKE 监视器命令 |
| I11 | H4_PacketProtocol | Get Descriptor 中未检查的接口索引 |
| I12 | H4_PacketProtocol | Set Report 中的缓冲区溢出 |
dmesg buffer 时的错误比较putchar 中的差一错误导致在 0x2c00 处发生单字节 OOB 写入。崩溃期间的示例调用栈:
UnhandledException(code=WriteUnmapped, value=0x2c00):
0x000000c81a: putchar+0x1c
0x000000c832: dmesg_putc+0x6
0x0000009706: tfp_format+0x22
0x000000ad9c: tfp_printf+0x12
0x000000b924: key_scan.part.0+0x68
0x000000cd3c: PORT2_ISR+0x1a
0x0000008e04: <unknown>
发送到 UART 接口且长度字段为零的消息会被错误处理。在 MSG 状态下,将刚收到的字节存储到 uart_buffer 中当前 index 位置后,它会递增 index 并检查其是否等于 length。然而,由于对零长度消息而言,index 的存储和递增发生在与 length 比较之前,因此 index == length 条件永远不会成立。
最终 index 会超出 uart_buffer 数组,从而允许修改存储在 uart_buffer 之后的全局变量。这通常会导致程序在 stdout_putf 或 stdout_putp 函数指针被覆盖并打印调试消息时崩溃。
崩溃输入:
./run_goodwatch.sh I04a
崩溃期间的调用栈:
UnhandledException(code=InvalidInstruction)
0x000000d3d3: <unknown>
0x0000009648: putchw+0x40
0x0000009826: tfp_format+0x142
0x000000ad9c: tfp_printf+0x12
0x000000b924: key_scan.part.0+0x68
0x000000cd3c: PORT2_ISR+0x1a
0x0000008e04: <unknown>
appindex(或 subindex)被覆盖,导致下一次使用 appindex 加载 applet 时崩溃(例如在 app_draw 中)。崩溃期间的调用栈:
UnhandledException(code=InvalidInstruction)
0x000000531c: <unknown>
0x0000009a96: app_init+0xe
0x000000c0a8: settime_draw+0x28
0x0000009b0a: app_draw+0x3e
0x000000ce78: watchdog_timer+0x6a
0x0000008e04: <unknown>
RANDINT 监视器命令会使固件生成并发送一列随机数。然而,要生成的随机值个数由接收到的命令控制。如果要生成的值个数过大,rints 将超出为堆栈保留的空间,从而触发 OOB 写入。崩溃输入:
./run_goodwatch.sh I05
崩溃期间的调用栈:
UnhandledException(code=WriteUnmapped, value=0x6e6a)
0x000000cbd4: USCI_A0_ISR+0x24c (inlined `send_randint`)
0x0000008e04: <unknown>
ook keypress 中的越界访问:OOK 应用在按下数字键(0-9)之一时,会从 button_array 发送一个预配置的 OOK 数据包。然而 ook_keypress 并未验证所按下的键是否在 button_array 数组范围内。共有 10 个数字按钮(0-9),button_array 却只包含 9 个条目。
button_array 包含一个指针数组;当在 OOK 应用中按下 9 按钮时,程序的 setrate 函数会越界读取 button_array 中的一个指针并解引用它,从而导致程序崩溃。
它读取的指针是 ook_settings(0x3012)中的第一个值,而该地址在内存中不存在。
崩溃输入:
./run_goodwatch.sh I06
崩溃期间的调用栈:
UnhandledException(code=ReadUnmapped, value=0x3012)
0x000000b87a: ook_keypress+0x2c
0x000000cd7e: PORT2_ISR+0x5c
0x0000008e04: <unknown>
固件使用查找表实现整数到二进制编码十进制(BCD)的转换,但查找表(bcdtable)仅包含用于将 0 到 59 的整数进行转换的值。对于秒和分钟的转换而言这没有问题,因为计数时这些值在 60 处回绕;然而 hour 变量可能超过 60,从而导致索引越界。
该缺陷只是被模糊测试器作为零消息长度缺陷所导致破坏的副作用而发现的。在模糊测试器发现的崩溃中,当 uart_buffer 溢出时,min 和 hour 全局变量均被破坏。
崩溃输入:
./run_goodwatch.sh I07
崩溃期间的调用栈:
UnhandledException(code=ReadUnmapped, value=0x4b57)
0x000000a3e4: stopwatch_draw+0x9e
0x0000009b0a: app_draw+0x3e
0x000000ce78: watchdog_timer+0x6a
0x0000008e04: <unknown>
这是由模糊测试器为 RTCDOW 外设生成一个较大值而导致的误报缺陷。
在时钟应用中按下 9 按钮会尝试在 LCD 上绘制当前星期。星期值直接从 RTC 外设的 RTCDOW 寄存器读取;该寄存器数据手册规定返回值为 0-6,但在模糊测试期间该寄存器的值不受限制。由于该值被用作 dayofweek 数组的索引,如果模糊测试器为 RTCDOW 寄存器生成大于 6 的值,程序将读取 daysofweek 数组之外的指针,从而导致(误报)崩溃。
该崩溃可能同时出现在 clock_keypress 和 hebrew_keypress 中。
崩溃输入:
./run_goodwatch.sh I08a
崩溃期间的调用栈:
UnhandledException(code=ReadUnmapped, value=0x5150)
0x0000009e6e: lcd_string+0x8
0x000000c1dc: clock_keypress+0x6c
0x000000cd7e: PORT2_ISR+0x5c
0x0000008e04: <unknown>
崩溃输入:
./run_goodwatch.sh I08b
崩溃期间的调用栈:
UnhandledException(code=ReadPerm, value=0x1006)
0x0000009e6e: lcd_string+0x8
0x000000b2aa: hebrew_keypress+0x116
0x000000c48c: clock_keypress+0x31c
0x000000cd7e: PORT2_ISR+0x5c
0x0000008e04: <unknown>
崩溃输入:
./run_goodwatch.sh I09
崩溃期间的调用栈:
UnhandledException(code=ReadUnmapped, value=0x7000)
0x0000009c74: hex_draw.part.0+0x3c
0x0000009c88: hex_draw+0x8
0x0000009b0a: app_draw+0x3e
0x000000cd8c: PORT2_ISR+0x6a
0x0000008e04: <unknown>
PEEK 的崩溃输入:
./run_goodwatch.sh I10a
崩溃期间的调用栈:
UnhandledException(code=ReadUnmapped, value=0x5100)
0x000000cb00: USCI_A0_ISR+0x178
0x0000008e04: <unknown>
POKE 的崩溃输入:
./run_goodwatch.sh I10b
崩溃期间的调用栈:
UnhandledException(code=WritePerm, value=0x4b00)
0x000000cb32: USCI_A0_ISR+0x1aa
0x0000008e04: <unknown>
Get_Descriptor 处理器中,接口索引未经过检查。这可能导致越界访问。例如:GetHidDescriptor:
./run_H4_PacketProtocol.sh I11a
崩溃期间的调用栈:
UnhandledException(code=ReadUninitialized, value=0xe90e)
0x0000011176: usbSendNextPacketOnIEP0 at ./USB_API/USB_Common/usb.c:1082.13
0x0000011c12: usbGetHidDescriptor at ./USB_API/USB_HID_API/UsbHidReq.c:78.5
0x0000010fb0: usbDecodeAndProcessUsbRequest at ./USB_API/USB_Common/usb.c:1655.5
0x0000011724: SetupPacketInterruptHandler at ./USB_config/UsbIsr.c:250.5
0x0000004410: iUsbInterruptHandler at ./USB_config/UsbIsr.c:88.9
0x0000004572: _c_int00_noargs
GetReportDescriptor:
./run_H4_PacketProtocol.sh I11b
崩溃期间的调用栈:
UnhandledException(code=ReadUninitialized, value=0xa44c)
0x0000011c28: usbGetReportDescriptor at ./USB_API/USB_HID_API/UsbHidReq.c:85.5
0x0000010fb0: usbDecodeAndProcessUsbRequest at ./USB_API/USB_Common/usb.c:1655.5
0x0000011724: SetupPacketInterruptHandler at ./USB_config/UsbIsr.c:250.5
0x00000044fe: iUsbInterruptHandler at ./USB_config/UsbIsr.c:166.9
0x0000004572: _c_int00_noargs
在接收到 Set_Report 数据包后,固件会将用于存储报告的缓冲区分配给 pbOEP0Buffer,并将报告长度分配给 wBytesRemainingOnOEP0。然而,固件并未检查报告是否适合分配的缓冲区,导致稍后在 usbReceiveNextPacketOnOEP0 中写入该缓冲区时发生缓冲区溢出。
在下面的崩溃输入中,8 字节数组(0x24a4: abUsbRequestIncomingData)的地址被分配给 pbOEP0Buffer;数组溢出后,函数指针(0x24b4: USB_RX_memcpy)被破坏,导致该函数指针稍后被调用时发生崩溃。
./run_H4_PacketProtocol.sh I12
崩溃期间的调用栈:
UnhandledException(code=InvalidInstruction, value=0xf95b7)
0x00000f95b7: <unknown>
0x0000011066: HidCopyUsbToBuff at ./USB_API/USB_HID_API/UsbHid.c:417.5
0x0000010172: USBHID_receiveData at ./USB_API/USB_HID_API/UsbHid.c:577.13
0x000001026a: main at ./main.c:115.25
0x0000004572: _c_int00_noargs