자동차의 시계를 한 시간 당기는 일은 보통 버튼 몇 번이면 끝난다. 하지만 2014년형 르노 라티튜드를 모는 에스토니아의 한 개발자는 그 단순한 작업을 위해 차량 헤드유닛의 펌웨어를 추출하고, QEMU로 에뮬레이터를 만들고, 결국 리눅스 커널 익스플로잇 기법까지 동원해야 했다. 블로그에 공개된 이 기록은 한 편의 해킹 무용담이면서, 동시에 임베디드 기기의 폐쇄적 설계가 사용자에게 어떤 비용을 떠넘기는지 보여주는 사례다.
발단은 사소했다. 그는 차를 살 때 전 주인이 건넨 '내비게이션 지도 SD 카드'를 거절했다. 지도가 낡아 쓸모없었고, 스마트폰으로 길 안내를 하면 그만이었기 때문이다. 전 주인은 '시계를 바꾸려면 이 카드가 필요하다'고 했지만 그는 믿지 않았다. 온라인 영상 속 동일 기종 헤드유닛에는 시간 설정 메뉴가 버젓이 보였기 때문이다. 그러나 여름시간에서 겨울시간으로 바뀌어 대시보드 시계가 한 시간 어긋나자 문제가 현실이 됐다. 설정 메뉴를 아무리 뒤져도 시간 설정 항목이 없었고, 두 개의 설정 버튼 중 하나는 블루투스와 전화번호부만 다뤘으며 다른 하나는 누를 때마다 '지도를 찾을 수 없음' 팝업만 띄웠다.
SD 카드가 없으면 설정조차 열 수 없는 구조
한 시간 넘게 헤매던 그는 펌웨어를 뜯어보기로 했다. 다행히 이 기종(TomTom Carminat LIVE, 코드명 beaumont)을 앞서 리버스 엔지니어링한 사람이 있어 업데이트 파일 URL을 알아냈다. 추출된 ttsystem 파일은 삼성 S5P6440 SoC와 리눅스 2.6.28.10 기반 톰톰 커스텀 OS가 담긴 ext3 루트 파일시스템이었다. 분석 결과 전 주인의 말이 옳았다. 설정 애플릿으로 들어가는 메뉴 정의 자체가 SD 카드에서 읽어오도록 설계돼 있어, 정식 카드가 없으면 기기는 설정 화면을 여는 방법조차 알지 못했다. 설계 의도가 지도 불법 복제 방지였다 하더라도, 시계 설정처럼 지도와 무관한 기능까지 카드에 묶어둔 것은 납득하기 어려운 결정이었다.
게다가 그 카드는 아무 SD 카드가 아니어야 했다. 시스템은 카드에 각인된 고유 ID(CID)와 그 CID에 기반한 파일 서명을 검사하는 DRM을 걸어두어, 파일을 빈 카드에 복사하는 우회조차 막았다. 정품 카드를 구하려면 에스토니아까지 배송비 포함 약 60유로가 들었다. 지도가 아니라 시계만 필요했던 그는 결국 직접 기기를 루팅하기로 한다.
막다른 길의 연속
2011년식 리눅스 2.6 시스템이니 쉬울 거라는 예상은 빗나갔다. ext3 업데이트 이미지를 수정해 설치하려 했지만 변조된 이미지는 계속 거부됐다. 분석해 보니 이미지 끝에 고엔트로피 데이터와 40바이트의 서명 추정 값(SHA-1 기반 DSA로 보이는)이 붙어 있었고, 검증 바이너리에 접근할 수 없어 공략이 막혔다. 주목할 점은 대부분의 파티션이 읽기 전용이나 noexec(실행 금지)로 마운트돼 있었다는 것이다. SD 카드의 FAT32 영역은 물론, 그 위에 올린 unionfs 계층까지 모두 noexec였다. 2011년 제품치고는 놀랄 만큼 단단한 보안이었다.
그는 부팅 때마다 로드되는 master.script라는 커스텀 스크립트 언어 파일로 눈을 돌렸다. 이를 해석하는 ttn 바이너리를 리버스 엔지니어링하며 공격 지점을 찾았다. SHEX/SHELLEXECUTE 명령은 임의 명령을 execve로 실행했지만, 공백으로 구분된 토큰 하나만 받아 인자를 넘길 수 없었다. noexec는 /bin/sh를 거쳐 스크립트를 실행하는 방식으로 우회할 수 있었으나, 인자 전달이 막혀 루트FS에 이미 있는 바이너리밖에 돌릴 수 없었다. LOG 명령에는 경로 탈출(path traversal) 취약점이 있어 루트 권한으로 임의 위치에 파일을 쓸 수 있었지만, 출력 가능한 ASCII(0x20~0x7E)만 한 줄 쓸 수 있어 바이너리 기록은 불가능했고, 덮어쓸 만한 곳은 읽기 전용이거나 noexec였다. 이미지 디코더를 Unicorn으로 퍼징해 OOB 읽기를 여럿 찾았지만 쓸 만한 쓰기 취약점은 없었다.
커널에게 실행을 떠넘기다
조각들이 따로 놀던 상황에서 그는 커널 익스플로잇의 상투적 기법 하나를 떠올렸다. 유저랜드가 스크립트를 실행하게 만들지 못한다면, 커널이 대신 실행하게 하면 된다. 리눅스의 /proc/sys/kernel/core_pattern은 프로세스가 크래시할 때 코어 덤프를 저장할 방식을 지정하는데, 파이프 문법(|/bin/handler)을 쓰면 덤프를 특정 프로그램으로 넘기며 그 프로그램을 커널이 루트 권한으로 띄운다. 인자 전달 지원은 2.6.24부터 들어갔고 이 기기는 2.6.28이라 운 좋게 맞아떨어졌다. 경로 탈출이 가능한 LOG로 core_pattern에 '|/bin/sh /media/sdcard/payload.sh'를 써넣고, 인자 없이 실행 가능한 바이너리로 프로세스를 일부러 크래시시키면 페이로드가 루트로 돌아간다. 다만 스크립트 파서나 메인 UI를 죽이면 시스템 전체가 재부팅돼 스크립트가 함께 날아가므로, 전체 재시작을 유발하지 않는 크래시 대상을 골라야 했다.
이 사례가 실무자에게 주는 함의는 분명하다. 임베디드 기기의 '보안'은 종종 사용자 통제권을 빼앗는 DRM과 뒤섞여 있고, 그 비용은 시계 설정 같은 가장 평범한 기능에서 먼저 드러난다. 개발자가 동원한 QEMU 에뮬레이터는 실제 공격을 안전하게 반복 검증하고 시스템 거동을 파악하는 데 결정적이었는데, 이는 펌웨어 분석에서 재현 가능한 실행 환경 구축이 얼마나 중요한지 보여준다. 동시에 한계도 분명하다. 이 공격은 특정 기종의 오래된 커널과 우연한 버전 궁합에 기댄 일회성 묘기에 가깝고, 일반 사용자가 따라 할 수 있는 해법은 아니다. 글쓴이 스스로 '이런 일을 할 필요가 없어야 했다'고 적은 대로, 근본 문제는 제조사가 기본 기능을 유료 액세서리에 인질로 잡아둔 설계에 있다.