- ThinkPad X1 Carbon 6th Gen의 숨겨진 xDCI 컨트롤러를 켜자 노트북 자체가 키보드나 저장장치 같은 임의의 USB 장치를 에뮬레이션할 수 있게 됨
- 단서는
/sys/class/usb_role/intel_xhci_usb_sw-role-switch였지만, xHCI 역할 전환만으로는 부족했고 별도의 DWC3 기반 xDCI UDC가 PCIe 장치로 노출돼야 했음 - BIOS 이미지에는
xDCI Support설정이 있었지만 기본 Setup UI에서는 숨겨져 있었고, 활성화 후lspci에 xDCI8086:9d30과dwc3-pci드라이버가 나타남 - 성공한 방법은 Advanced BIOS 페이지 잠금 해제와 NVRAM 패치였으며, PCH 레지스터 재구성은 PMC 일부 변경에 그치고 PSF 잠금 때문에 실패함
- xDCI 활성화 뒤
g_mass_storage, Raw Gadget, syzkaller 재현기, Facedancer 백엔드가 외부 하드웨어 없이 동작했지만,VBUS가 끊긴 USB 케이블과device역할 전환이 필요함
ThinkPad에서 발견한 USB 장치 모드 단서
- Raw Gadget은 Linux 커널 USB Gadget 서브시스템에 사용자 공간 API를 제공하는 커널 모듈이며, 잘못된 USB 디스크립터를 제공해 USB 호스트 퍼징과 익스플로잇에 활용할 수 있음
- 일반 PC에는 USB Device Controller, 즉 UDC가 보통 없고 Raspberry Pi 같은 싱글보드 컴퓨터에 주로 내장돼 있음
- 기존 작업에는 Raspberry Pi나 USB 3380 기반 EC3380-AB 같은 외부 하드웨어가 필요했음
- ThinkPad X1 Carbon 6th Gen에서
/sys/class/usb_role아래intel_xhci_usb_sw-role-switch가 발견됨/sys/class/usb_role은 하드웨어 USB 구성요소를 host 모드와 device 모드 사이에서 전환하는 인터페이스임device를 role 파일에 써도 처음에는dmesg메시지나/sys/class/udc/항목이 생기지 않았음
- xHCI는 x86 시스템에서 USB host로 동작하게 하는 HCD이고, xDCI는 Intel이 USB device 쪽 인터페이스에 쓰는 이름임
커널 코드와 메일링 리스트로 확인한 구조
- Linux 커널에서
intel_xhci_usb_sw는 두 위치에서 확인됨drivers/usb/roles/intel-xhci-usb-role-switch.c는device나host가 쓰일 때 xHCI 레지스터를 조작하는 USB Role Switch 드라이버를 구현함drivers/usb/host/xhci-ext-caps.c는 xHCI 드라이버가XHCI_INTEL_USB_ROLE_SWquirk를 설정한 경우intel_xhci_usb_sw가상 플랫폼 장치를 만듦
- ThinkPad의 xHCI PCIe device ID는
0x9d2f였고, 커널의PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI와 일치함 - Linux 커널 메일링 리스트 논의에서 xHCI와 DWC3 UDC는 별도 장치이며, 둘 사이에 mux가 있다는 점을 확인함
- role switch는 이 mux를 바꾸는 역할을 함
- DWC3 UDC 자체가 PCI 장치로 보이지 않으면 device mode로 쓸 UDC가 없는 상태임
- DWC3는 Synopsys의 DesignWare Core SuperSpeed USB 3.0 Controller IP이며, Intel을 포함한 여러 벤더가 시스템에 사용함
- ThinkPad에서
lspci를 확인했을 때 처음에는 xHCI8086:9d2f만 보였고,dwc3-pci.c기준 Sunrise Point-LP의 xDCI device ID인0x9d30은 보이지 않았음
ACPI와 BIOS에 남아 있던 xDCI 흔적
- ACPI DSDT에는
Device (XDCI)와_STA메서드가 존재함/sys/bus/acpi/devices/device:33/path는\_SB_.PCI0.XDCI였음/sys/bus/acpi/devices/device:33/status는15였고, 이는 장치가 enabled and functioning 상태임을 뜻함- 하지만
lspci에는 xDCI가 없었기 때문에 ACPI 관점에서만 정상으로 보이는 상태였음
- 기본 BIOS Setup 화면에는 xDCI나 OTG 관련 설정이 보이지 않았음
- Lenovo BIOS Bootable CD 업데이트 이미지를 내려받아 BIOS 바이너리
/mnt/bios/FLASH/N23ET86W/$0AN2300.FL1를 추출하고 UEFITool로 확인함 Setup모듈의PE32 image section에서 문자열을 검색하자 다음 항목이 나타남xDCI SupportEnable/Disable xDCI (USB OTG Device).
- BIOS 안에는 xDCI Support 설정이 있지만 기본 UI에서는 숨겨져 있었음
Advanced BIOS 페이지로 xDCI 켜기
- x1c6-hackintosh 프로젝트 문서를 따라 숨겨진 Advanced BIOS 페이지를 여는 방식을 시도함
- 이 방식은 BIOS가 저장된 SPI 칩을 다시 플래시해야 했음
- SPI 클립을 쓰는 대신 SPI 칩을 소켓으로 교체해 여러 번 칩을 빼고 꽂아 실험함
- FTDI FT2232H Mini Module과
flashrom을 사용해 SPI 칩 내용을 덤프하고 다시 기록함
- BIOS 패치는 두 단계로 구성됨
UEFIPatch로Date/TimeBIOS 페이지 GUID 참조를Advanced페이지 쪽으로 바꿈- 별도 바이트 패치로 TPM을
MFG Mode로 강제해 Boot Guard가 BIOS 수정을 감지하지 않도록 함
- BIOS 1.37에서 첫 번째 패치만 적용하면 노트북이 비프음을 내며 부팅하지 않았고, 두 번째 패치까지 적용한 뒤 Advanced 페이지가 나타남
Intel Advanced Menu→PCI-IO Configuration→USB Configuration아래의xDCI Support를Enabled로 바꾸자 부팅 후 xDCI가 노출됨lspci에00:14.1 USB controller [8086:9d30]이 나타남- 커널은
dwc3-pci드라이버를 자동 로드함 /sys/class/udc/에dwc3.1.auto가 생김
xDCI 포트와 케이블 조건
- xDCI가 연결된 외부 USB 포트를 찾기 위해 role을
device로 둔 상태에서 USB 플래시 드라이브를 각 포트에 꽂아 확인함- 특정 포트에서는 OS가 플래시 드라이브를 감지하지 않았고, role을
host로 되돌리자 다시 동작함 - 해당 포트가 xDCI 연결 포트로 판단됨
- 특정 포트에서는 OS가 플래시 드라이브를 감지하지 않았고, role을
- ThinkPad의 xDCI 포트가 USB Type-A였기 때문에 host 컴퓨터에 연결하려면 Type-A to Type-A male-to-male 케이블이 필요했음
- role switching이 포트의 전원
VBUS를 끄지 않았기 때문에, 두 장치의VBUS가 직접 연결되지 않도록 VBUS가 끊긴 케이블을 사용함- 처음에는 USB 2.0 케이블 두 개를 잘라
VBUS를 끊은 male-to-male 케이블을 직접 제작함 - 이후에는 PortaPow USB Power Blocker와 male-to-male USB 2.0 케이블을 사용함
- 처음에는 USB 2.0 케이블 두 개를 잘라
g_zerogadget driver를 로드하자 다른 노트북에서 테스트 USB 장치가 정상적으로 에뮬레이션됨- 2024년 6월 11일 업데이트 기준으로, DataPro USB 3.0 Super-Speed A/A Debugging Cable은 USB 2.0
D-와D+도 끊겨 있어 SuperSpeed 연결 수립에 필요한 High-Speed 연결이 실패함VBUS를 수동으로 끊은 LogiLink CU0038 케이블은 SuperSpeed 장치 에뮬레이션에 동작함
PCH 레지스터로 소프트웨어 활성화 시도
- BIOS가 xDCI를 어떻게 켜는지 파악하기 위해 Kaby Lake BIOS 소스 코드의
Pch/Library/Private/PeiPchInitLib/PchXdci.c를 확인함 - BIOS의 xDCI 비활성화 경로는 xDCI를 PMC와 PSF에서 비활성화하는 구조였음
-
PMC 변경
- PMC의
NST_PG_FDIS_1레지스터 offset0x628의 bit #24XDCI Function Disable을 지우면 xDCI 전원 차단을 해제할 수 있을 것으로 봄 - Linux 커널의
pmc_core_device와pmc->regbase매핑을 재사용하는 커널 모듈을 작성함 - 부팅 직후에는
ST_PG_FDIS1의ST_FDIS_LKbit #31이 설정돼NST_PG_FDIS_1값을 바꿀 수 없었음 - 노트북을 suspend했다가 깨운 뒤에는 lock bit가 설정되지 않았고,
XDCI Function Disablebit를 지우는 데 성공함
- PMC의
-
PSF 변경
- BIOS는
PID_PSF2Port ID0xBB의 PSF2 PCR에서USB Dual Role (OTG) Function Disablebit를 설정해 xDCI를 비활성화함 - PCR 주소는
PCH_PCR_BASE_ADDRESS 0xFD000000와 Port ID, offset 조합으로 계산됨 - 커널 모듈에서 PSF2 PCR을
ioremap으로 매핑했지만 읽기 값은0xffffffff로 나왔고 변경되지 않았음 - P2SB를 unhide해도 결과는 같았음
- BIOS의
RemoveSidebandAccess가EPMASK5로PID_PSF2를 IOSF-SB에서 분리하고MASKLOCK으로 잠가, OS에서 PSF를 다시 설정할 수 없었음 - PCH 접근은 PMC 일부 설정에는 성공했지만 PSF 재구성 실패로 xDCI 활성화 방법이 되지 못함
- BIOS는
NVRAM 패치로 Boot Guard 우회 없이 활성화
- BIOS 설정 값은 SPI 칩의 NVRAM 영역에 저장되므로, 숨겨진 Advanced 페이지를 열지 않고
xDCI Support값만 바꾸는 방법을 시도함 - IFRExtractor-RS로
Setup모듈의 UEFI IFR 정보를 추출함 xDCI Support설정은PchSetupUEFI 변수 안에 저장돼 있었음- VarStore GUID:
4570B7F1-ADE8-4943-8DC3-406472842384 - VarStoreId:
0x5 - VarOffset:
0x40 - 값 범위:
0x0또는0x1
- VarStore GUID:
- Linux의
efivarfs에서/sys/firmware/efi/efivars/PchSetup-4570b7f1-ade8-4943-8dc3-406472842384를 확인함efivarfs파일 첫 4바이트는 헤더이므로 실제xDCI Support값은 파일 offset0x44에 있음- xDCI 비활성 상태에서는 값이
00, BIOS Advanced 페이지에서 활성화한 뒤에는01로 바뀜
- 사용자 공간이나 EFI Shell에서 해당 변수를 직접 쓰려 하자
EFI_WRITE_PROTECTED오류가 발생함efivarfs의 immutable 속성을 제거해도Read-only file system오류가 발생함- chipsec로 EFI Shell에서 변경해도 같은 오류가 발생함
- SPI 프로그래머로 읽은 원본 BIOS 이미지에서
PchSetup주변 바이트 시퀀스를 찾아xDCI Supportbit만01로 패치하고 다시 플래시하자 xDCI가 활성화됨 - 이 방식은 여전히 SPI 칩 reflashing이 필요하지만, Advanced 페이지 패치와 Boot Guard 우회가 필요 없고 TPM도 깨지지 않았음
- BIOS 1.37에서 NVRAM을 패치한 뒤 1.61로 업데이트했을 때 BIOS 설정 값이 유지돼 패치를 다시 적용할 필요가 없었음
xDCI로 USB 장치 에뮬레이션하기
- 모든 예제는 xDCI가 BIOS에서 활성화돼 있고,
intel_xhci_usb_sw-role-switch가device로 전환돼 있으며,VBUS가 끊긴 케이블로 xDCI 포트와 USB host가 연결돼 있다는 전제를 가짐 -
Legacy gadget drivers
- Linux USB Gadget 서브시스템의 legacy gadget drivers는 모듈을 로드하면 특정 USB class 장치를 에뮬레이션함
g_mass_storage는 파일 시스템 이미지를 USB mass storage 장치로 노출함- FAT 이미지
disk.img를 만들고file.txt에Hi from xDCI를 넣은 뒤sudo modprobe g_mass_storage file=./disk.img stall=0로 로드함 - USB host에서 드라이브가 자동 마운트되고 파일 내용이 확인됨
-
Raw Gadget
- Raw Gadget을 xDCI와 함께 실행해 USB 키보드 예제를 시도함
- 처음에는
SET_CONFIGURATION처리 중 멈췄음 - 원인은
dwc3와 몇몇 UDC 드라이버가 0-length control request를 gadget driver 확인 없이 즉시 acknowledge할 수 있다고 가정한 점이었음 - Raw Gadget에 workaround patch를 적용하고 GadgetFS도 수정한 뒤 키보드 에뮬레이션이 성공함
- 관련 동작은 문서 패치 commit로 정리됨
syzkaller와 Facedancer 활용
- syzkaller는 Raw Gadget을 사용해 Linux 커널 USB 스택을 외부 퍼징할 수 있음
- 기본 syzkaller USB 퍼징은 커널 내부에서 virtual USB Device와 Host controller를 연결하는 Dummy HCD/UDC를 사용함
- xDCI를 쓰기 위해 syzkaller의 UDC 지정 코드를
dummy_udc대신dwc3-gadget,dwc3.1.auto로 바꿈 - syzkaller가 발견한
WARNING in smsusb_start_streaming/usb_submit_urb버그의 reproducer를syz-execprog -enable=usb로 실행함- Ubuntu
5.15.0-91-generic커널을 host로 둔 노트북에서usb_submit_urb경고가 재현됨 - 해당 버그는
smsusb드라이버가 USB endpoint type 검증을 빠뜨려 발생한WARNING이며, 해로운 동작은 아니었음
- Ubuntu
- Facedancer는 Python 기반 USB 장치 에뮬레이션 프레임워크임
- Raw Gadget 기반 Facedancer backend는 아직 prototype이고 out-of-tree Raw Gadget 패치에 의존함
BACKEND=rawgadget,RG_UDC_DRIVER=dwc3-gadget,RG_UDC_DEVICE=dwc3.1.auto를 설정하고rubber-ducky.py예제를 실행하자 정상 동작함
- Raw Gadget 기반 USB Proxy도 xDCI와 함께 실행됐고, xDCI를 이용하면 노트북을 USB sniffer나 USB Man-in-the-Middle 도구로 사용할 수 있음
다른 기기와 소프트웨어 방식의 가능성
- ThinkPad X1 Carbon 6th Gen 외 다른 PC에서도 xDCI 활성화가 가능할 수 있음
- 가장 단순한 경우 BIOS 설정에서 xDCI를 켜는 것만으로 동작할 수 있음
- ACPI와 role switching 지원이 있고 xDCI 포트가 외부 포트에 배선돼 있어야 함
- 다른 ThinkPad도 xDCI BIOS 설정이 숨겨지고 보호돼 있을 가능성이 있어 SPI 칩 reflashing이 필요할 수 있음
- NVRAM 방식은 Boot Guard 우회 없이 적용 가능함
- 일부 최신 시스템의 SPI 칩은 RPMC로 reflashing이 보호될 수 있으며, 이런 칩은
W25R128FW처럼 이름에R이 포함될 수 있음
- 업데이트에 따르면 Shiny Quagsire는 XPS 13과 Surface Go 1에서
grub-mod-setup_var로PchSetup변수를 수정해 xDCI를 활성화한 적이 있음- 해당 기기에서는
PchSetup변수가 보호되지 않아 순수 소프트웨어 방식이 가능했음
- 해당 기기에서는
- 다른 소프트웨어 접근으로는 BIOS 취약점을 이용해 xDCI 관련 NVRAM 변수를 바꾸거나, 부팅 중 DMA로 BIOS를 공격해 임의 코드를 실행하는 방법이 가능할 수 있음
- 제목의 “secret”은 의도적 은닉이라기보다 문서화되지 않은 기능을 가리키는 표현에 가까움