Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/rva3/cve-2026-40003
Embedded Systems SecurityExploitationReverse EngineeringHardware HackingPayload DevelopmentFirmware AnalysisBinary Exploitation
GitHubrva3/cve-2026-40003

CVE-2026-40003

CVE-2026-40003에 대한 익스플로잇으로, ZXIC/Sanechips ZX297520V3 SoC BootROM의 임의 메모리 쓰기 취약점을 이용하여 USB 다운로드 모드를 통한 코드 실행을 가능하게 합니다.

저장소 보기
1413개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
웹사이트

CVE-2026-40003, 일명 Joselito는 ZXIC/Sanechips ZX297520V3 SoC BootROM에서 발견된 임의 메모리 쓰기 취약점입니다.

[!CAUTION] 이 PoC는 연구 목적으로만 공개되었습니다. 이 익스플로잇을 악의적인 목적으로 사용해서는 안 됩니다. 소유하지 않은 기기나 소유자의 명시적 동의 없이 익스플로잇을 사용하지 마십시오.

상태

이 취약점은 2026년 3월 이후 최신 SoC 개정판에서 수정되었습니다. 패치된 칩을 사용하는 알려진 제품은 아직 없습니다. 기존의 모든 기기는 취약합니다.

어떻게 동작하나요?

요약: 로더 코드를 확인하세요.

BootROM은 스택 포인터가 0x81FF0인 0x4 주소에서 시작합니다. 코드는 특수 테스트 포인트 또는 플래시에서 이미지를 로드하는 기능을 기반으로 모드 선택을 수행합니다. BootROM이 플래시에서 이미지를 로드하거나 검증하지 못하면 USB 다운로드 모드로 폴백합니다.

이 동작은 여기에서 확인할 수 있습니다:

root@kitploit:~
v0 = MEMORY[0x13B004] & 7; // hardware pin
switch ( (char)v0 )
{
  case 0:
    if ( nand_init() )
      goto LABEL_12;
    parse_header((Header *)0x8A000);
    v4 = "\nMODE:NAND\n";
    goto LABEL_11;
  case 1:
    if ( !nand_init() )
    {
      parse_header((Header *)0x8A000);
      uart_puts("\nMODE:USB-N\n");
    }
    usbdl_init(0x1500000);
    v5 = "TURN TO NAND\n";
    goto LABEL_16;
/* ... */
LABEL_16:
  uart_puts(v5);
LABEL_7:
  verify_and_jump((Header *)0x8A000, 0, 1);
  break;

usbdl_init 함수는 USB IP를 초기화하고 다운로드 루프에 진입합니다:

root@kitploit:~
void __fastcall usbdl_init(int usb_base)
{
  char *v1; // r0

  if ( usb_base == 0x1500000 )
  {
    alt_usb_base = 0;
    usb_init();
  }
  else
  {
    alt_usb_base = 1;
    noidea();
  }
  if ( usb_setup() )
  {
    try_sync_with_host();
    v1 = "FAILED\n";
  }
  else
  {
    v1 = "\nNOLINK\n";
  }
  uart_puts(v1);
}

디스어셈블러 출력:

root@kitploit:~
ROM:00001F68 ; void __fastcall usbdl_init(int usb_base)
ROM:00001F68 usbdl_init                              ; CODE XREF: entry+5E↑p
ROM:00001F68                                         ; entry+7C↑p ...
ROM:00001F68                 PUSH            {R4,LR}
ROM:00001F6A                 MOVS            R2, #0x1500000
ROM:00001F6E                 LDR             R1, =dword_8014C
ROM:00001F70                 MOV             R4, R0
ROM:00001F72                 CMP             R0, R2
ROM:00001F74                 BNE             loc_1F80
ROM:00001F76                 MOVS            R0, #0
ROM:00001F78                 STRB            R0, [R1,#(alt_usb_base - 0x8014C)]
ROM:00001F7A                 BL              usb_init
ROM:00001F7E                 B               loc_1F88
ROM:00001F80 ; ---------------------------------------------------------------------------
ROM:00001F80
ROM:00001F80 loc_1F80                                ; CODE XREF: usbdl_init+C↑j
ROM:00001F80                 MOVS            R0, #1
ROM:00001F82                 STRB            R0, [R1,#(alt_usb_base - 0x8014C)]
ROM:00001F84                 BL              noidea
ROM:00001F88
ROM:00001F88 loc_1F88                                ; CODE XREF: usbdl_init+16↑j
ROM:00001F88                 MOV             R0, R4
ROM:00001F8A                 BL              usb_setup
ROM:00001F8E                 CMP             R0, #0
ROM:00001F90                 BEQ             loc_1F9E
ROM:00001F92                 BL              try_sync_with_host
ROM:00001F96                 ADR             R0, aFailed ; "FAILED\n"
ROM:00001F98
ROM:00001F98 loc_1F98                                ; CODE XREF: usbdl_init+38↓j
ROM:00001F98                 BL              uart_puts
ROM:00001F9C                 POP             {R4,PC}
ROM:00001F9E ; ---------------------------------------------------------------------------
ROM:00001F9E
ROM:00001F9E loc_1F9E                                ; CODE XREF: usbdl_init+28↑j
ROM:00001F9E                 ADR             R0, aNolink ; "\nNOLINK\n"
ROM:00001FA0                 B               loc_1F98
ROM:00001FA0 ; End of function usbdl_init

try_sync_with_host는 동기화 바이트(0x5A)를 기다렸다가 메인 루프에 진입합니다.

root@kitploit:~
void try_sync_with_host()
{
  int v0; // r4
  unsigned int i; // r0
  int v2; // r5
  int v3; // r2
  int v4; // r3
  int v5; // r2
  int v6; // r3
  int v7; // r0

  v0 = 0x805A0;
  time_to_boot = 0;
  for ( i = 0; i < 0x40; ++i )
    *(_BYTE *)(i + 0x805A0) = 0;
  g_state = 0;
  v2 = 0x200;
  if ( usb_recv(byte_805A0, 0x200, 0x5A) == 1 ) // 0x5a = ack sent by the host
  {
    uart_puts("TIMEOUT\n");
  }
  else
  {
    resp[0] = 0xA5;
    usb_send((int)resp, 1, v3, v4);
    g_state = 1;
    while ( !time_to_boot )
    {
      v7 = sub_11CA(v0, v2, v5, v6);
      handler(v0, v7);
      if ( g_state == 4 )
      {
        v2 = size;
        v0 = start_addr;
      }
      else
      {
        v0 = 0x805A0;
        v2 = 0x200;
      }
    }
    verify_and_jump(unk_80160, 0, 1);
  }
}

핸드셰이크가 완료된 후 BootROM은 메인 루프에 진입하여 일반적으로 벽돌된 기기를 복구하는 데 사용되는 스테이지 1 바이너리를 기다립니다. 그러나 BootROM은 대상 주소를 검증하지 않으므로 0x7A 명령을 통한 임의 쓰기가 가능합니다. 0xA1 상태는 이미지 주소와 크기가 수락되었음을 의미합니다.

root@kitploit:~
usb_send:
        resp[0] = status;
        v5 = 1;
        v6 = resp;
        return usb_send((int)v6, v5, state, v3);
      case 3u: // 0x7A
        if ( *(int *)&resp[4] >= 4 )
        {
          if ( *(int *)&resp[4] < 8 )
            *(_DWORD *)&resp[12] = (*(_DWORD *)&resp[12] << 8) | result;
        }
        else
        {
          *(_DWORD *)&resp[8] = (*(_DWORD *)&resp[8] << 8) | result;
        }
        v3 = *(_DWORD *)&resp[4] + 1;
        *(_DWORD *)&resp[4] = v3;
        if ( v3 != 8 )
          continue;
        *(_DWORD *)&resp[4] = 0;
        start_addr = *(_DWORD *)&resp[8];
        size = *(_DWORD *)&resp[12];
        g_state = 4;
        status = 0xA1;
        goto usb_send;

verify_and_jump 함수는 기기가 퓨즈된 경우 이미지 검증을 수행합니다.

root@kitploit:~
int __fastcall verify_and_jump(Header *a1, int zero, int must_verify)
{
  int *p_sp; // r6
  int result; // r0

  p_sp = (int *)&a1->sp;
  result = memcmp((int)a1->magic, 0x80008, 2u);
  if ( !result )
  {
    if ( !zero || (result = a1->usbdl_en, result == 0x5A) )
    {
      if ( !must_verify )
        return jump(p_sp);
      result = verify(a1);
      if ( !result )
        return jump(p_sp);
    }
  }
  return result;
}

verify 함수는 하드웨어 MD5 및 RSA 엔진을 사용하여 이미지의 무결성을 검증하며, jump는 이미지 헤더의 주소로 스택 포인터를 설정하고 엔트리포인트로 점프합니다.

ARM/Thumb 호출 규약에서 함수 호출은 일반적으로 PUSH {regs, LR}로 시작하고 꼬리 호출, BX LR 또는 POP {regs, PC}로 끝납니다. usbdl_init은 컴파일러에 의해 꼬리 호출로 최적화되지 않았으므로 PUSH 및 POP 명령을 포함합니다.

이미 아이디어를 얻으셨나요?


BootROM에는 일회성 다운로드 플래그가 없으므로 원하는 만큼 많은 이미지를 보낼 수 있습니다. 익스플로잇은 이 기능(자체적으로 취약점은 아니지만)을 임의 메모리 쓰기와 함께 악용합니다.

  1. 지정된 주소로 페이로드를 보냅니다
  2. 스택 오프셋 계산: 6 (entry 함수: PUSH {R3-R7,LR}) + 2 (usbdl_init 함수: PUSH {R4,LR}). 레지스터 크기를 곱하면 8 * 4 = 32
  3. 스택에 저장된 LR 레지스터로 점프 주소를 보냅니다 (단순화를 위해 PoC는 모든 저장된 레지스터에 동일한 주소를 반복합니다)
  4. 점프 명령을 보냅니다
  5. 이미지 검증이 실패하도록 합니다 (헤더 매직 누락)
  6. verify_and_jump 함수는 usbdl_init으로 반환됩니다
  7. usbdl_init은 FAILED를 출력하고 POP {R4, PC} 명령을 실행하여 페이로드를 실행합니다

소스에서 빌드하려면

  • curl, llvm-objcopy (일반적으로 llvm 패키지의 일부) 및 gcc를 설치합니다. Ubuntu 22.04 예시: sudo apt update && sudo apt install curl llvm gcc
  • curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 또는 배포판별 패키지 관리자를 사용하여 rustup을 설치합니다
  • rust 명령을 사용할 수 있는지 확인합니다: . "$HOME/.cargo/env" (bash) 또는 source "$HOME/.cargo/env.fish" (fish)
  • 페이로드 빌드: cd payload && ./build.sh && cd ..
  • 호스트 도구 실행: sudo -E cargo r -p loader -- target/thumbv6m-none-eabi/release/payload

이제 USB 다운로드 모드(특수 테스트 포인트 또는 손상된 플래시)가 트리거된 기기를 연결하고 UART 출력을 확인합니다.

타임라인

  • 2월 5일 - 최초 보고
  • 3월 18일 - ZTE PSIRT에 의해 확인됨
  • 5월 7일 - 공개 릴리스

외부 크레딧

  • 많은 SoC 리버스 엔지니어링: Stefan Dösinger
  • 퓨즈된 기기 테스트: Mio Naganohara
  • 다른 퓨즈된 기기 사전 릴리스 테스트: exp-3

라이선스

AGPLv3

도구 다운로드