
에뮬레이션 기반 MSP430 펌웨어 퍼저로, 메모리 손상 버그를 찾아 분석하고 상세한 크래시 리포트와 재현 가능한 입력을 제공합니다.
| # | Target | 설명 |
|---|
| I03 | Goodwatch | dmesg_buffer에 쓸 때의 잘못된 비교 |
| I04 | Goodwatch | 길이가 0인 메시지 |
| I05 | Goodwatch | RNG 오버플로 |
| I06 | Goodwatch | ook keypress에서 범위를 벗어난 액세스 |
| I07 | Goodwatch | 스톱워치에서 범위를 벗어난 액세스 |
| I08 | Goodwatch | 요일(DOW) 표시 시 범위를 벗어난 액세스 |
| I09 | Goodwatch | Hex viewer 애플리케이션 |
| I10 | Goodwatch | PEEK/POKE 모니터 명령 |
| I11 | H4_PacketProtocol | Get Descriptor의 검사되지 않은 인터페이스 인덱스 |
| I12 | H4_PacketProtocol | Set Report의 버퍼 오버플로 |
dmesg buffer에 쓸 때의 잘못된 비교putchar의 off-by-one 오류로 인해 0x2c00에서 1바이트 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>
길이 필드가 0으로 설정된 메시지가 UART 인터페이스로 전송되면 잘못 처리됩니다. MSG 상태에서는 방금 수신한 바이트를 uart_buffer의 현재 index에 저장한 후 index를 증가시키고 그 값이 length와 같은지 확인합니다. 그러나 index의 저장과 증가가 length와의 비교보다 먼저 수행되므로, 길이가 0인 메시지의 경우 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>
펌웨어는 룩업 테이블을 사용하여 정수를 2진화 10진수(BCD)로 변환합니다. 그러나 룩업 테이블(bcdtable)에는 0에서 59까지의 정수 변환 값만 들어 있습니다. 초와 분은 카운트할 때 60에서 순환하므로 문제가 없지만, hour 변수는 60을 초과할 수 있어 인덱스가 범위를 벗어나게 됩니다.
이 버그는 길이가 0인 메시지(Zero length message) 버그로 인한 메모리 손상의 부수 효과로 퍼저에 의해서만 발견되었습니다. 퍼저가 발견한 크래시에서는 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 주변 장치에 큰 값을 생성하여 발생하는 오탐(false positive) 버그입니다.
시계 애플리케이션에서 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