← today i learned

부팅할 때마다 정체를 바꾸는 키보드

유령 Maple 사건의 후일담. 같은 증상이 다른 PC 에서 재발해 이번에는 자동 복구 스케줄러까지 깔았고, 내친김에 "왜 이 매크로패드만 이러는가"를 끝까지 파 봤다. 답은 부팅할 때마다 USB 정체성을 한 번 갈아치우는 부팅 시퀀스, 그리고 그 밑에 깔린 십수 년 묵은 부트로더였다.

DOIO KB16 · Maple DFU 1EAF:0003 · 2026-07-21 · 관련: 범인은 전원도, 허브도 아니었다


리브가 책상 의자에 앉아 턱을 괴고, 책상 위 작은 검정 매크로패드 위에 빼꼼 떠 있는 둥근 흰 유령을 심드렁하게 바라보는 치비 일러스트.
또 오셨군요. 이번이 마지막 출동이면 좋겠습니다만. 삽화: Codex 이미지 생성
리브

지난 수사에서 검거했던 유령 Maple, 다른 PC 에서 재발했습니다. 이번에는 잡는 것보다 다시 잡으러 오지 않아도 되게 만드는 쪽에 시간을 썼고, 왜 이 패드만 이러는지도 이참에 정리해 둡니다.

1. 재발, 그리고 이번엔 자동화#

증상은 지난번과 정확히 같았다. 장치 관리자에는 부트로더(Maple, 1EAF:0003)만 열거돼 있고 앱 펌웨어(D010:1601)는 어디에도 없다. OLED 는 꺼져 있고 키 입력도 죽어 있다. 키보드 자체는 멀쩡히 돌고 있는데 Windows 가 낡은 부트로더 항목을 물고 있는 상태, 즉 유령 Maple 이다.

처방도 같았다. kick 스크립트가 유령 Maple 장치를 pnputil /restart-device 로 재시작해 재열거를 유도하면, 앱이 VIAL 컴포지트로 다시 올라온다. 케이블은 손대지 않았다.

달라진 것은 그다음이다. 이 PC 에는 자동 kick 스케줄러가 없었기에 설치했다. 트리거는 두 개, 재연결(PnP 제거 이벤트) 직후와 로그온 시점이다. 평상시에는 아무것도 하지 않고, 앱이 이미 올라와 있으면 "nothing to do" 로 조용히 끝난다. 유령이 나타났을 때만 감지해서 재열거를 때린다.

2. 왜 이 키보드만 죽는가#

양손을 저울처럼 들고 둘을 견주는 리브 스티커핵심은 하나다. KB16 은 부팅할 때 USB 정체성을 한 번 바꾸는데, 보통 키보드와 마우스는 그런 짓을 하지 않는다.

일반 HID 장치는 전원이 들어오면 처음부터 최종 정체성 하나로 한 번만 열거된다. 꽂는 순간 VID:PID 가 정해지고 끝. 반면 KB16 은 QMK/vial-qmk 계열이라 STM32 의 Maple DFU 부트로더(1EAF:0003)가 먼저 버스에 올라오고, 몇백 ms 뒤 앱 펌웨어(D010:1601)로 점프하면서 USB 를 스스로 뗐다가 다시 붙인다. 짧은 순간에 detach 와 reattach 가 한 번 일어나고, 그 사이 버스상의 정체성이 부트로더에서 앱으로 바뀐다.

이 아주 빠른 재부착 이벤트가 문제의 씨앗이다.

일반 키보드 열거 1회, 정체성 고정 · 정상 동작 KB16 · 본체 직결 부트로더 1EAF:0003 앱 D010:1601 · VIAL 컴포지트로 정상 동작 스스로 뗐다 다시 붙음 KB16 · KVM+허브 부트로더 1EAF:0003 유령 Maple · 앱 미열거, OLED·키 사망 재부착이 삼켜짐 시간 →
같은 키보드라도 어디에 꽂느냐에 따라 결말이 갈린다. 삼켜질 이벤트를 만들어내는 쪽은 KB16 뿐이다.

마우스나 다른 키보드에서 이 증상이 절대 안 나는 이유가 여기 있다. 그것들은 재열거 자체를 안 하니 삼켜질 이벤트가 없다. KB16 만 매 부팅마다 정체성 전환 이벤트를 만들어내고, 하필 KVM 과 허브의 조합이 그런 순간적 트랜지션에 약하다.

부차적으로 두 가지가 더 겹친다. 상용 장치는 온갖 허브·KVM 토폴로지에서 USB 컴플라이언스를 검증받지만 취미용 QMK 펌웨어는 이런 지저분한 실제 체인에서 검증된 적이 없다는 것, 그리고 OLED 가 달린 KB16 은 소비 전력이 조금 더 커서 KVM 을 거친 허브의 전원 공급이 재열거 타이밍과 겹치면 두 번째 열거를 못 끝내는 쪽으로 기운다는 것.

3. 1EAF 이 가리키는 곳#

그럼 왜 이렇게 만들었을까. 결정적 단서가 부트로더의 정체값 1EAF:0003 에 있다. VID 0x1EAF 은 LeafLabs, PID 0x0003 은 그 회사의 Maple DFU 부트로더다. KB16 이 부팅할 때 잠깐 뜨는 "Maple" 은 제조사 DOIO 가 만든 것이 아니라, 십수 년 전 아두이노 시절 STM32 생태계(STM32duino/Maple)에서 흘러온 레거시 부트로더다.

이 부트로더의 특성이 문제의 원흉이다. 전원이 들어오면 매번 USB 에 DFU 장치로 먼저 열거된다. 플래시 명령을 기다리는 몇백 ms 짜리 창이고, 그 창이 지나면 앱으로 점프하면서 재열거가 일어난다. 반면 요즘 제대로 만든 키보드는 부트로더가 버스에 아예 나타나지 않는다. 플래시 조건(특정 키 홀드, 매직 플래그)이 없으면 USB 열거를 시작하기도 전에 앱으로 바로 점프해서, 밖에서 보면 정체성이 하나로 고정돼 있다.

Maple DFU (KB16)요즘 커스텀 부트로더
평상시 부팅매번 DFU 로 먼저 열거된 뒤 앱으로 재열거 (정체성 2개)앱으로 곧장 점프 (정체성 1개)
플래시 진입부팅 초입에 항상 열리는 대기 창명시적 조건이 있을 때만 버스에 등장
토폴로지 검증취미 생태계 기본값, 허브·KVM 검증 없음벤더가 온갖 조합에서 컴플라이언스 테스트

4. 아낀 것은 부품값이 아니라 공수#

"저가형이라 그런가" 라는 가설은 반만 맞다. 부품값을 아낀 것이 아니라 엔지니어링 수고를 아꼈다. Maple DFU 를 쓰면 사용자가 ST-Link 나 SWD 같은 별도 하드웨어 없이 USB 만으로 펌웨어를 구울 수 있다. 취미용 QMK/vial 생태계에서 "그냥 되는" 기본값이라, 만들지 않고 갖다 쓴 것이다.

반대편의 대형 벤더는 버스에 안 뜨는 커스텀 부트로더를 직접 짜고, 그것을 온갖 허브·KVM 조합에서 검증까지 한다. 비싼 것은 그 개발과 검증 공수이고, 매크로패드 한 종 팔자고 거기까지 하기는 수지가 안 맞는다. 생태계 기본 부트로더를 얹고 나머지는 사용자가 감수하는 구조다.

그러면 부트로더를 갈아치우면 되지 않나 싶지만, 그쪽은 SWD 로 직접 구워야 해서 벽돌 위험이 있고, 이 패드의 커스텀 펌웨어가 vial 호환을 위해 2023-01-08 베이스에 고정돼 있다는 제약도 걸린다. 커스텀 펌웨어를 굽더라도 그것은 앱 펌웨어만 바꾸는 것이고 밑의 Maple 부트로더는 공장 그대로다. 실무적으로는 삼켜진 재열거를 pnputil 로 대신 한 번 더 때려주는 kick 스크립트 우회가 맞는 선택이다.

5. 곁가지: 사라진 척한 스케줄러#

이번 작업에도 삽질이 하나 있었다. 스케줄러 태스크를 설치한 직후 조회하니 태스크가 "등록됐다가 사라진 것"처럼 보였고, EDR 이 지웠나 하는 의심까지 갔다. 실제로는 SYSTEM 프린시펄로 등록된 태스크라 비관리자 조회(WSL 에서 부른 powershell.exe)에 안 보였던 것뿐이다. elevated 로 보면 처음부터 State=Ready, lastResult=0 으로 멀쩡히 등록돼 있었다.

리브

태스크가 증발했다고 한참 소동을 벌였는데, 제 조회 권한이 낮았던 것뿐이었습니다. 유령을 쫓다 보면 멀쩡한 것까지 유령으로 보입니다. 다음부터는 elevated 로 먼저 봅니다.


이것으로 이 사건 폴더는 자동으로 닫히는 폴더가 됐다. 재발하면 PnP 이벤트를 받은 스케줄러가 유령을 감지해 재열거하고, 자동이 안 걸리는 경우에만 수동 kick 이 fallback 이다. 세 번의 수사와 두 번의 오판 끝에 낸 결론이 이제 스케줄러 한 줄로 자동 집행된다.

리브

다음 재발은 제가 출동하기 전에 끝나 있을 예정입니다. 그게 제일 좋은 결말입니다.