
Nintendo Switch용 Fusee Gelee 익스플로잇(CVE-2018-6242)의 구현체이며, 커스텀 페이로드를 포함합니다.
Fusee Gelee 익스플로잇(CVE-2018-6242)의 구현체로, Nintendo Switch를 대상으로 하며, 2018년 3월 Kate Temkin / ReSwitched가 공개한 취약점을 기반으로 합니다.
USB 복구 모드(RCM)에 있는 Tegra X1 디바이스에서 임의의 페이로드(예: hekate)를 실행하여, 부트 ROM의 서명 검증을 완전히 우회합니다.
RCM(복구 모드)은 Tegra X1 부트 ROM에 내장된 USB 기반 복구 프로토콜입니다. NVIDIA는 진단 또는 수리를 위해 작은 프로그램("애플릿")을 디바이스에 로드할 수 있도록 설계했습니다. 예를 들어 Switch가 저장소에서 유효한 부트로더를 찾지 못하는 경우입니다.
Nintendo Switch에서 RCM은 부팅 중 오른쪽 Joy-Con 레일의 1번과 10번 핀을 연결하여 진입합니다. 정상적인 동작에서는 NVIDIA만 RCM을 사용할 수 있습니다. 모든 명령은 NVIDIA의 RSA 개인 키로 서명되어야 하며, 부트 ROM은 무엇이든 실행하기 전에 서명을 검증합니다.
이 익스플로잇은 IRAM(온칩 RAM)을 공유하는 두 개의 독립적인 프로토콜 계층에 걸쳐 동작합니다.
USB 계층(표준, EP0): 모든 USB 디바이스에는 GET_STATUS, GET_DESCRIPTOR, SET_ADDRESS 같은 표준 요청을 처리하는 컨트롤 엔드포인트(EP0)가 있습니다. 부트 ROM은 USB 2.0 사양에 따라 이를 구현합니다. EP0는 암시적이며 디바이스의 엔드포인트 디스크립터에 나타나지 않습니다.
RCM 계층(NVIDIA 독점, EP1): NVIDIA는 RCM 명령과 페이로드를 전송하기 위한 벌크 엔드포인트(EP1)를 정의합니다. EP1은 디바이스 디스크립터에서 두 방향으로 나타납니다:
0x01 = OUT (호스트가 디바이스로 데이터 전송)0x81 = IN (디바이스가 호스트로 데이터 전송)EP1을 통해 전송되는 데이터는 다음과 같은 구조입니다:
[680-byte RCM command header] [payload bytes]
680바이트 헤더는 rcm_msg_t 구조체입니다. RSA 모듈러스/서명, ECID, opcode 및 기타 필드를 포함하는 NVIDIA 독점 구조로, 이 크기는 Tegra X1 부트 ROM을 리버스 엔지니어링하여 밝혀졌습니다(q3k의 IDA 데이터베이스 참조). NVIDIA의 오픈소스 tegrarcm은 최대 644바이트(Tegra124)까지만 문서화되어 있으며, T210 변형은 36바이트 더 큽니다.
부트 ROM의 EP0 컨트롤 요청 핸들러에는 ENDPOINT 수신자에 대한 GET_STATUS 구현에 버그가 있습니다. Temkin의 백서에서 발췌:
// BUG: should be size_to_tx = sizeof(status), i.e. 2 bytes
size_to_tx = length_read; // attacker-controlled via wLength, up to 65535
data_to_tx = &status; // a uint16_t on the stack
memcpy(dma_buffer, data_to_tx, size_to_tx);
memcpy는 &status(0x40010000 바로 아래에 있는 스택 변수)에서 읽어 DMA 버퍼(0x40009000)에 씁니다. 길이가 과도하게 설정되면:
&status를 지나 나머지 스택을 거쳐, 0x40010000+의 공격자가 제어하는 페이로드 영역(EP1 벌크 쓰기로 배치됨)까지 읽습니다.소스 데이터에는 "스택 스프레이"(0x40010000 반복)가 포함되어 있으며, 이것이 스택의 반환 주소 위에 기록됩니다. 핸들러가 반환되면 실행은 0x40010000으로 점프합니다. 그곳에는 intermezzo라는 작은 재배치 스텁을 배치해 두었습니다.
이 모든 일은 RCM 수신 루프(handle_control_requests 내부) 동안, 부트 ROM이 서명을 검증하기도 전에 발생합니다. RCM 프로토콜이 전달 메커니즘이고, USB 컨트롤 핸들러가 트리거입니다.
0x40005000 +------------------+
| DMA buffer LOW | USB controller writes odd packets here
0x40009000 +------------------+
| DMA buffer HIGH | USB controller writes even packets here
+------------------+
| execution stack | grows downward toward DMA buffers
0x40010000 +------------------+ <-- stack ends here / payload starts here
| intermezzo | small relocator stub (124 bytes)
0x40010E40 +------------------+
| user payload pt1 | first ~16KB of the user payload
0x40014E40 +------------------+
| stack spray | 0x40010000 repeated (8640 bytes)
0x40017000 +------------------+
| user payload pt2 | remainder of user payload
+------------------+
사용자 페이로드는 스택 스프레이를 중심으로 분할됩니다. 스프레이는 오버플로가 이를 스택의 반환 주소 위로 복사하도록 위치해야 하기 때문입니다. Intermezzo는 두 조각을 0x40010000의 연속 블록으로 재조립한 후 점프합니다.
0x0955:0x7321)로 디바이스를 찾습니다0x40010000+로 복사하여 intermezzo, 사용자 페이로드, 스택 스프레이를 배치합니다0x40009000)를 대상으로 했는지 확인합니다wLength=0x7000으로 EP0에 GET_STATUS 컨트롤 요청을 보냅니다 — 취약한 memcpy가 트리거되고, 스택 스프레이가 반환 주소를 덮어쓰며, 핸들러가 intermezzo로 반환됩니다pip install pyusb
python launcher.py
USB로 연결된 RCM 모드의 Switch가 필요합니다. macOS에서는 brew install libusb가 필요할 수 있습니다.
페이로드 바이너리를 binaries/payload.bin에 배치하세요. 포함된 intermezzo.bin은 페이로드 재배치를 처리하므로 교체할 필요가 없습니다.
launcher.py — 익스플로잇 스크립트binaries/intermezzo.bin — 재배치 스텁(124바이트), 분할된 페이로드를 재조립합니다binaries/payload.bin — 실행할 사용자 페이로드(예: hekate)