엔비디아 앰페어(Ampere) GPU 잠금 해제 사례들은 부팅 과정의 같은 결함을 이용한다. 그중 널리 알려진 cmpunlocker는 8GB·10GB CMP 170hx 카드를 각각 64GB·40GB로 잠금 해제하며, LTT와 여러 매체에서 소개됐다.
앰페어 GPU의 부팅 과정
Nouveau와 엔비디아 오픈 GPU 드라이버의 부팅 과정은 대체로 다음과 같다.
- PCI 초기화: 커널 드라이버가 GPU를 탐지하고, 메모리 매핑 입출력(MMIO)에 사용할 기본 주소 레지스터(BAR)를 매핑하며, 직접 메모리 접근(DMA)이 가능한 메모리를 설정한다.
- 펌웨어 로드: 드라이버는 리눅스 펌웨어 기반 기능을 통해 펌웨어 파일을 가져온다. Nouveau는
request_firmware()를 사용한다. 이 파일에는 GPU 시스템 프로세서 리소스 관리자(GSP-RM), 부트로더, 보조 마이크로코드 등이 포함된다. - 하드웨어 초기 설정: VBIOS가 제공하는 펌웨어 보안(FWSEC) 및 devinit 펌웨어가 VRAM, 클럭, 전원 순서 등 핵심 하드웨어를 초기화한다. 이 작업 중 상당 부분은 드라이버가 로드되기 전에 이뤄진다.
- 보안 부팅 준비: 드라이버는 GPU 시스템 프로세서(GSP)의 Falcon에서 FWSEC 펌웨어 런타임 서비스(FRTS)를 높은 보안 모드로 실행하고, 보호된 VRAM 영역인 WPR2를 설정한다.
- GSP 부트스트랩: 드라이버는 보안 프로세서 SEC2에서 서명된 Booter 펌웨어를 실행한다. SEC2는 GSP-RM을 인증한 뒤 GSP에 로드한다.
- 런타임 초기화: GSP-RM이 시작돼 GPU 하위 시스템을 초기화하고, 호스트 드라이버와 공유 메모리 RPC 대기열을 설정한다.
취약한 Booter와 익스플로잇
취약점은 SEC2에서 실행되는 Booter 펌웨어에 있다. 드라이버는 하드웨어 DMA를 통해 이 펌웨어를 로드하며, Booter는 DMA 채널을 통해 드라이버에서 암호화된 GSP 펌웨어를 받은 뒤 서명을 확인하고 복호화해 GSP에서 실행한다.
이 Booter에는 스택에 직접 데이터를 쓰는 버퍼 오버플로가 있다. 이를 이용하면 반환 지향 프로그래밍(ROP)으로 코드를 실행할 수 있다. ROP는 여러 CPU 아키텍처에 있는 함수 호출·복귀 명령의 동작을 이용한다. call 명령은 지정된 주소의 코드로 이동하기 전에 바로 다음 명령어의 주소를 스택에 넣고, ret 명령은 스택에서 그 주소를 꺼내 실행을 이어간다. 스택에 임의의 값을 쓸 수 있으면 반환 주소를 제어해 펌웨어에 이미 있는 코드 조각을 이어 붙일 수 있다. 이런 조각은 ‘가젯’이라 하며, 이를 찾는 작업은 ‘마이닝’이라고 부른다.
펌웨어에 접근할 수 없는데도 버퍼 오버플로를 어떻게 알아냈을까? 엔비디아는 한동안 디버그용과 생산용 Booter 펌웨어 파일을 모두 공개했다. 파일은 암호화돼 있었지만, 연구자들은 엔비디아가 디버그 이미지를 공개 테스트 키 방식(키 인덱스 37)으로 암호화했다는 사실을 알아냈다. 복호화한 펌웨어를 역어셈블해 가젯을 찾을 수 있었다. 디버그 이미지는 생산용 이미지와 완전히 같지는 않지만 상당히 비슷하다.
이 도구를 바탕으로 연구자들은 ‘높은 보안’ 모드에서 코드 실행 권한을 얻은 뒤 이를 어떻게 활용할지 알아냈다. 그 결과 이 드라이버가 사용하는 v67 페이로드가 나왔다. 페이로드는 가젯을 연속으로 실행해 페이로드 안의 주소에 있는 한 워드(4바이트)에 페이로드 안의 다른 워드 값을 쓴다. 결과적으로 MEMORY[payload_address_word] = payload_value_word 형태의 단일 쓰기를 수행한다.
레지스터 쓰기 권한 변경
명령어별 발행 속도를 제어하는 레지스터는 다음과 같다.
FEAT_OVR_SM_SPD(SS0), 주소0x0082381c: GA102 기본값은0x00000001이다. DP, FFMA, FMLA16, FMLA32, IMLA0–3에 해당하는 4비트 필드 8개를 제어한다.FEAT_OVR_SM_SPD_1(SS1), 주소0x00823820: GA102 기본값은0x00000001이다.[3:0]의 4비트 필드 하나로 IMLA4를 제어한다.
각 4비트 필드는 다음처럼 인코딩된다.
bit 3: 재정의 활성화 여부bits 2:0: 속도 선택자.00은 최대 속도,01은 절반 속도,10은 4분의 1 속도(미확인),11은 8분의 1 속도(미확인)를 뜻한다.
따라서 목표 값은 SS0에 0x88888888, SS1에 0x8을 쓰는 것이다. 하지만 호스트 측, 즉 드라이버에서는 이 레지스터에 쓸 수 없다. 직접 쓰기를 시도해도 아무 일도 일어나지 않는다. ROP 가젯은 이미 크기 한계에 가까워 두 번 쓰기 어렵고, Booter를 여러 차례 실행하는 데도 문제가 있다.
해법은 이 레지스터들의 접근 권한을 제어하는 권한 수준 마스크(PLM)를 변경하는 것이다. 관찰된 동작으로부터 추론한 PLM 비트 필드 모델에 따르면 읽기·쓰기 권한 마스크는 다음과 같다.
- 비트
[3:0]은READ_PROTECTION으로, 각 권한 수준(L0/L1/L2/L3)이 읽을 수 있는지를 나타낸다. 비트가 설정되면 해당 수준에서 읽을 수 있다. - 비트
[7:4]는WRITE_PROTECTION으로, 각 권한 수준의 쓰기 권한을 나타낸다. 비트가 설정되면 해당 수준에서 쓸 수 있다. - 비트
[23:8]은SOURCE_ENABLE으로, 다이 내부의 어떤 주체가 접근을 요청할 수 있는지를 제어한다.
두 레지스터는 모두 주소 0x00823804의 FEAT_OVR_PLM에 의해 제어된다. 모든 GA100 및 GA102 카드에서 기본값은 0xffffff8f다. 하위 바이트 0x8f를 2진수 1000 1111로 보면 [3:0] = 1111이므로 네 권한 수준 모두 읽을 수 있다. [7:4] = 1000이므로 L3만 쓸 수 있고 L0/L1/L2의 쓰기는 차단된다. 이 모델에서 L3은 높은 보안 컨텍스트에 해당하며, 비트 7이 해당 컨텍스트의 쓰기를 허용한다.
따라서 ROP 가젯 안에서 FEAT_OVR_PLM에 0xFFFFFFFF를 쓰면 SS0과 SS1을 호스트 드라이버에서 쓸 수 있게 된다.
“대성공”이라는 표현은 보랏(Borat)의 말을 빌린 것이다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요