18년 된 레고 NXT 펌웨어를 덤프하다: 임베디드 ARM 익스플로잇과 NOP Sled의 추억


최근 해커뉴스(Hacker News)를 뜨겁게 달군 흥미로운 글이 하나 있었습니다. 바로 2006년에 출시된 레고 마인드스톰 NXT(Lego Mindstorms NXT)의 초기 1.01 펌웨어를 덤프하기 위해 임베디드 취약점을 파헤친 Arcane Nibble의 포스트입니다.

우리 중 상당수는 어린 시절 레고 마인드스톰을 만지며 로봇 공학과 프로그래밍의 꿈을 키웠을 겁니다. 저 역시 예외는 아니었죠. 하지만 15년 차 엔지니어의 시각으로 이 기기를 다시 바라보면, 당시 임베디드 시스템이 얼마나 보안에 무방비했는지, 그리고 역설적으로 그것이 얼마나 훌륭한 해킹 교육용 타겟이 될 수 있는지 깨닫게 됩니다.

오늘은 JTAG 납땜 없이, 오직 소프트웨어 취약점만을 이용해 레고 NXT의 펌웨어를 추출해 낸 우아한 과정을 기술적으로 깊이 파헤쳐 보겠습니다.

문제의 발단: 왜 펌웨어를 덤프하기 어려운가?

오래된 하드웨어를 다룰 때 가장 큰 적은 비트롯(Bitrot)과 자료의 유실입니다. 저자는 Pybricks 프로젝트에 기여하던 중, 2006년 출시 당시의 1.01 원본 펌웨어가 탑재된 NXT를 입수하게 됩니다. 당연히 인터넷 어딘가에 덤프본이 있을 줄 알았지만, 커뮤니티의 수많은 커스텀 펌웨어에 밀려 순정 1.01 버전은 유실된 상태였습니다.

일반적으로 펌웨어를 추출하는 방법은 두 가지입니다.

  • Bootloader: 칩(AT91SAM7S256)에 내장된 SAM-BA 부트로더의 PEEK/POKE 명령어를 사용하는 방법. 하지만 이 모드에 진입하는 순간 펌웨어의 일부가 덮어씌워지는 치명적인 문제가 있습니다.
  • JTAG: 하드웨어 디버깅 인터페이스를 사용하는 방법. 확실하지만 기기를 분해하고 납땜해야 하며, 구형 칩이라 최신 SWD 도구와 호환되지 않습니다.

결국 저자는 기기를 뜯지 않고 USB 통신만으로 펌웨어를 빼내는 소프트웨어 익스플로잇(Software Exploit)을 찾기로 결심합니다.

샌드박스를 탈출하라: IO-Map과 함수 포인터

NXT는 사용자가 작성한 프로그램을 바이트코드(Bytecode) 형태로 가상 머신(VM) 위에서 실행합니다. 즉, 샌드박스 환경이므로 임의의 메모리에 접근할 수 없습니다.

하지만 공식 통신 프로토콜 문서와 오픈소스로 공개된 (일부) 펌웨어 코드를 분석하던 중 거대한 보안 구멍이 발견됩니다. 바로 IO-Map이라는 구조체입니다. IO-Map은 드라이버 스택과 VM 사이의 상태를 저장하는 메모리 영역인데, USB 통신을 통해 이 영역을 자유롭게 읽고 쓸 수 있었습니다.

더 놀라운 것은 VM 모듈의 IO-Map(c_cmd.iom) 내부에 네이티브 C 함수 포인터인 pRCHandler가 고스란히 노출되어 있었다는 점입니다.

솔직히 말해서, 2006년 장난감 펌웨어에 최신 OS 수준의 보안을 기대하는 것은 무리입니다. 하지만 인증되지 않은 외부 USB 인터페이스에서 디바이스 내부의 실행 흐름을 제어하는 함수 포인터를 직접 덮어쓸 수 있다는 사실은, 예전 2000년대 초반 라우터 취약점들을 떠올리게 할 만큼 노골적이고 치명적입니다.

NOP Sled를 활용한 임의 코드 실행 (ACE)

함수 포인터를 조작할 수 있다면 다음 단계는 임의 코드 실행(Arbitrary Code Execution)입니다. 현대의 시스템이라면 DEP, NX bit, ASLR 같은 완화 기법(Mitigation)들 때문에 ROP(Return-Oriented Programming) 체인을 구성하는 등 복잡한 과정을 거쳐야 합니다. 하지만 이 칩은 폰 노이만 구조의 특성을 그대로 가지며, 데이터 메모리에서 코드를 실행하는 데 아무런 제약이 없습니다.

저자는 다음과 같은 기가 막힌 페이로드 전달 기법을 사용합니다.

  1. IO-Map 내부에는 사용자 프로그램의 데이터를 저장하기 위한 32 KiB 크기의 MemoryPool이 존재합니다. (NXT의 전체 RAM은 64 KiB이므로 절반에 해당하는 거대한 공간입니다.)
  2. 이 공간을 ARM의 NOP(No Operation) 명령어인 0xe1a00000으로 가득 채웁니다.
  3. 버퍼의 맨 끝에 우리가 실행할 쉘코드(메모리 읽기 프리미티브)를 배치합니다.
  4. RAM의 대략적인 중간 주소인 0x00208000으로 함수 포인터를 덮어씁니다.

정확한 버퍼의 시작 주소를 몰라도, 실행 흐름이 NOP Sled(썰매)를 타고 미끄러져 내려와 결국 우리의 페이로드에 도달하게 되는 고전적이고 확실한 기법입니다.

ARM ABI와 네이티브 쉘코드 작성

이제 펌웨어 전체(256 KiB 플래시 메모리)를 읽어 USB로 응답을 보내는 어셈블리 코드를 작성해야 합니다. 기존 pRCHandler 함수는 C 언어로 작성되어 있으며, 입력 버퍼, 출력 버퍼, 출력 길이를 인자로 받습니다.

우리가 삽입할 쉘코드가 기존 시스템을 크래시 내지 않고 정상적으로 동작하려면 ARM ABI(Application Binary Interface)의 호출 규약을 엄격히 준수해야 합니다.

# save r4 (ABI 규약에 따라 호출된 함수가 보존해야 하는 레지스터)
push {r4}
# skip 2 bytes of packet
add r0, #2
# load 4 bytes of the address (USB로 전달받은 타겟 메모리 주소)
ldrb r3, [r0]
add r0, #1
ldrb r4, [r0]
add r0, #1
orr r3, r4, lsl #8
ldrb r4, [r0]
add r0, #1
orr r3, r4, lsl #16
ldrb r4, [r0]
add r0, #1
orr r3, r4, lsl #24
# read data from that address (실제 메모리 읽기)
ldr r3, [r3]
# ... (결과를 출력 버퍼 r1에 저장하는 코드 생략) ...
# return success
mov r0, #0
pop {r4}
bx lr

이 코드는 정말 교과서적입니다. 4개 이하의 인자는 r0~r3 레지스터를 통해 전달된다는 ARM RISC 아키텍처의 특징을 정확히 이해하고 작성되었습니다. 특히 r4 레지스터를 스택에 푸시하고 팝하는 과정은, 로우레벨 엔지니어링에서 레지스터 보존(Register Preservation)이 얼마나 중요한지 보여주는 좋은 예시입니다.

이 익스플로잇을 통해 저자는 주소 0번지부터 루프를 돌며 전체 플래시 메모리를 덤프하는 데 성공합니다. 주소 0번지가 C 언어에서는 Null 포인터지만, 하드웨어 관점에서는 인터럽트 벡터 테이블(Exception Vectors)이 위치하는 엄연한 유효 메모리라는 점을 짚고 넘어간 부분도 훌륭한 통찰이었습니다.

Hacker News 커뮤니티의 반응

해커뉴스 스레드에서는 이 글의 훌륭한 스토리텔링과 교육적 가치에 대한 찬사가 쏟아졌습니다.

많은 유저들이 “What is an ABI?”, “What is a function pointer?”와 같이 스스로 질문을 던지고 답하는 구성 덕분에, 임베디드나 로우레벨 지식이 부족한 웹/백엔드 개발자들도 전체 흐름을 완벽히 이해할 수 있었다고 호평했습니다. 저 역시 동의합니다. 단순히 “내가 이렇게 해킹했다”를 과시하는 글이 아니라, 독자를 배려한 진정한 시니어 엔지니어의 글쓰기 방식입니다. 또한, 어린 시절 고양이와 계단을 피하는 로봇을 만들던 추억을 공유하는 훈훈한 모습도 인상적이었습니다.

총평 (Verdict)

요즘 IoT 기기들이 TrustZone, Secure Boot, 포인터 인증(PAC) 같은 겹겹의 방어막을 두르고 나오는 시대에, 이런 베어메탈(Bare-metal) 익스플로잇은 마치 고전 문학을 읽는 듯한 향수와 쾌감을 줍니다.

이 글은 단순히 18년 된 장난감을 해킹한 에피소드가 아닙니다. 컴퓨터 아키텍처의 근본적인 원리(데이터와 코드의 등가성, 메모리 맵, 호출 규약)를 이해하고, 시스템의 경계면(IO-Map)에 존재하는 설계상 결함을 찾아내 목적을 달성하는 엔지니어링 문제 해결의 정수를 보여줍니다.

가끔은 우리가 매일 다루는 거대한 클라우드 추상화 계층에서 벗어나, 이렇게 레지스터와 메모리 주소를 직접 다루는 로우레벨의 세계를 들여다보는 것이 엔지니어로서의 감각을 벼리는 데 큰 도움이 됩니다. 서랍 속에 잠들어 있는 오래된 기기들을 꺼내어 여러분만의 익스플로잇을 시도해 보는 것은 어떨까요?


References: