들어가며

알리익스프레스에서 LTE 동글을 구매했는데, 중국산 네트워크 장치는 아무래도 보안에 취약한 부분이 있을 수 있어서 OpenWrt 펌웨어 교체가 가능한 제품을 찾아서 펌웨어를 교체했다.
구매한 제품은 Hosaya LDW931. Qualcomm MSM8916(스냅드래곤 410) 칩셋을 쓰는 흔한 안드로이드 기반 4G 스틱형 라우터입니다. 다행히 커뮤니티에서 이미 이 기종에 대한 OpenWrt 포팅 작업(target msm89xx/msm8916-generic/uf02)이 되어 있어서, 출처를 알 수 없는 안드로이드 펌웨어 대신 직접 검증 가능한 오픈소스 라우터 OS로 완전히 갈아엎기로 했습니다.
결론부터 말하면 성공했습니다. WiFi도, 4G 모뎀도 정상 동작합니다. 하지만 그 과정이 순탄치는 않았습니다. Qualcomm EDL(Emergency Download) 모드 플래싱을 Windows 환경에서 진행하면서 겪은 삽질을 기록으로 남깁니다.
참고로 이번 작업은 처음부터 끝까지 Claude(Claude Code) 를 활용해서 진행했습니다. 명령어 하나하나를 실행하고, 로그를 해석하고, 문제가 생겼을 때 원인을 추적하는 과정을 AI와 같이 진행한 결과물이라는 점도 밝혀둡니다.

준비물
| 항목 | 비고 |
| Hosaya LDW931 동글 | Qualcomm MSM8916, target msm89xx/msm8916-generic/uf02 |
| Windows PC | WSL2(Ubuntu 24.04) 사용 가능한 상태 |
| bkerler/edl | Qualcomm EDL 플래싱 툴 |
| OpenWrt uf02 이미지 | hkfuertes/msm8916-openwrt 릴리즈에서 다운로드 |
⚠️ 이 글에서 다루는 작업은 부트로더/파티션 테이블을 직접 덮어쓰는 작업입니다. 원본 펌웨어 백업 없이 진행하면 벽돌(브릭) 위험이 있습니다. 아래 과정을 그대로 따라하다 문제가 생겨도 책임지지 않습니다 😅
1. 원본 펌웨어 백업
가장 먼저 한 일은 원본 안드로이드 펌웨어 전체 백업이었습니다. 나중에 WiFi/모뎀이 안 뜨면 이 백업에서 펌웨어 블롭을 추출해서 써야 하기 때문에, 플래싱 전에 반드시 해둬야 합니다.
리셋 버튼을 누른 채로 USB를 꽂으면 장치가 EDL(9008) 모드로 진입합니다:
git clone --recurse-submodules https://github.com/bkerler/edl
cd edl
pip install -e .
# EDL 모드 진입 확인 후
edl rl ./backup_original # 전체 파티션을 디렉터리로 백업
rl 명령으로 GPT의 모든 파티션(modem, aboot, boot, system, userdata 등)을 통째로 백업했습니다. 특히 modem.bin 안에는 WiFi(wcnss)와 모뎀(mba/modem) 펌웨어 블롭이 FAT 파일시스템으로 들어있는데, 이게 없으면 OpenWrt에서 WiFi/4G가 영구히 안 될 수도 있습니다.
$ mount -o loop modem.bin /mnt
$ ls /mnt/image/
cmnlib.mdt modem.mdt modem.b00...b28 wcnss.mdt wcnss.b00...b11 mba.mbn ...
다행히 전부 정상 크기로 확인되어 안심하고 다음 단계로 넘어갔습니다.
2. WSL2 이슈 — EDL 접근 후 (오류) 자꾸 끊김
처음엔 WSL2(Ubuntu) + usbipd-win으로 USB를 패스스루해서 진행했습니다. 읽기 작업(GPT 조회, 파티션 백업)은 문제없이 잘 됐는데, WSL 인스턴스가 몇 분마다 저절로 종료되면서 EDL 세션이 자꾸 끊기는 문제가 있었습니다.
원인은 WSL2의 vmIdleTimeout 기본 설정 때문이었습니다. %UserProfile%\.wslconfig에 다음을 추가해서 해결:
[wsl2]
vmIdleTimeout=-1
그리고 확실하게 하려고 백그라운드에 유지용 세션도 하나 열어뒀습니다:
wsl -d Ubuntu -- sleep 999999 &
💡추천 : 이 경험을 하고 나서 드는 생각은, EDL 플래싱처럼 USB 저수준 통신이 중요한 작업은 WSL2보다는 네이티브 환경을 쓰는 걸 권장 한다는 겁니다. WSL2는 usbipd-win 을 거쳐 USB 트래픽을 한 번 더 터널링하다 보니 위와 같은 예기치 못한 변수(자동 종료, 세션 불안정)가 끼어들 여지가 있습니다. 가능하다면 Windows 네이티브(WinUSB/Zadig) 로 진행하거나, 아예 리눅스 PC/듀얼부팅/라이브 USB 에서 직접 진행하는 걸 추천합니다. 실제로 이후 문제(4번 항목)는 WSL이든 네이티브 Windows든 동일하게 재현됐는데, 만약 처음부터 리눅스에서 진행했다면 최소한 이 WSL발 변수 하나는 처음부터 배제하고 문제를 훨씬 빨리 좁힐 수 있었을 겁니다.
3. 응답 불능 — GPT를 쓰자마자 장치가 응답 불능
플래시 스크립트를 실행해서 새 GPT(파티션 테이블)를 쓰는 순간, 진행률은 100%까지 잘 올라가는데 끝에 이상한 에러가 뜨고 그 이후로 장치가 완전히 응답을 멈췄습니다.
firehose - [LIB]: Host's payload to target size is too large
firehose - [LIB]: Sector size in XML 4096 does not match disk sector size 512
Progress: 100.0%
firehose - [LIB]: Error:{}
처음엔 "WSL/usbip 네트워크 터널링 때문에 벌크 전송이 깨지나?"라고 의심했습니다. 그래서 Windows 네이티브 환경(Zadig으로 WinUSB 드라이버 설치 + 순정 Python venv)으로 완전히 갈아탔는데도 똑같은 증상이 재현됐습니다. 즉, WSL 문제가 아니라 이 장치의 Firehose 로더 자체의 특성이었던 겁니다.
원인은 이랬습니다:
이 로더는 쓰기 요청의 크기가 자기가 기대하는 정확한 섹터 수와 다르면, 조용히 무시하거나 응답을 깨뜨리고 이후 세션을 응답 불능 상태로 만든다.
GPT 앞부분은 정확히 34섹터(MBR 1 + 헤더 1 + 엔트리 32)가 되어야 하는데, 처음에 복구용으로 썼던 파일이 10섹터짜리였던 게 문제였습니다. 34섹터가 되도록 0으로 패딩해서 다시 쓰니 그제서야 깔끔하게 성공했습니다.
# 10섹터짜리 원본 GPT 백업을 34섹터로 패딩
with open('gpt_main0.bin', 'rb') as f:
data = f.read()
padded = data + b'\x00' * (34 * 512 - len(data))
Wrote gpt_main0_padded34.bin to sector 0. # 이제야 성공 메시지가 뜬다
세션이 응답 불능이 될 때마다 물리적으로 USB를 뽑았다 꽂는 것 말고는 복구 방법이 없었습니다. 다행히 Qualcomm SoC는 부트롬(PBL)/Sahara 단계가 항상 살아있어서, 아무리 GPT가 깨져도 EDL 모드로 재진입은 항상 가능했습니다 — 이게 아니었으면 진짜 벽돌날 뻔했습니다.
사이드 이슈: 한글 Windows 콘솔 크래시
Windows 네이티브로 전환한 뒤엔 진행률 표시줄의 유니코드 블록 문자(█) 때문에 UnicodeEncodeError: 'cp949' codec can't encode character 크래시가 났습니다. 한국어 Windows의 기본 콘솔 코드페이지가 cp949라서 생기는 문제로, 아래 환경변수로 해결했습니다.
export PYTHONIOENCODING=utf-8
export PYTHONUTF8=1
4. 정석대로 다시 플래싱
이슈를 다 파악하고 나니 나머지는 순조로웠습니다. 순서는:
- GPT 쓰기 (34섹터 정확히 맞춰서) — primary + backup entries(32섹터) + backup header(1섹터)
- 부트로더 파티션 쓰기: aboot, hyp, rpm, sbl1, tz
- boot / rootfs(system) 이미지 쓰기
- rootfs_data 파티션 erase (OpenWrt가 첫 부팅 시 자동 포맷)
- 모뎀/WiFi 관련 파티션(fsc, fsg, modemst1, modemst2, modem, persist, sec)을 1번 백업에서 복원 — 이게 없으면 WiFi/모뎀 펌웨어가 없는 상태로 부팅됨
- edl reset으로 재부팅
edl w aboot aboot.mbn
edl w hyp hyp.mbn
edl w rpm rpm.mbn
edl w sbl1 sbl1.mbn
edl w tz tz.mbn
edl w boot openwrt-...-squashfs-boot.img
edl w rootfs openwrt-...-squashfs-system.img
edl e rootfs_data
edl w modem backup_original/modem.bin
edl w persist backup_original/persist.bin
# ...
edl reset
5. 부팅 성공!
재부팅 후 USB를 연결하니 UsbNcm Host Device라는 새 USB 네트워크 어댑터가 잡히고, DHCP로 192.168.1.x IP를 받아왔습니다. 192.168.1.1의 22번(SSH), 80번(LuCI) 포트가 모두 응답 — 완전히 부팅 성공이었습니다.
http://192.168.1.1 로 접속합니다.

부팅 로그(dmesg)를 까보니 더 확실했습니다:
[ 10.373677] remoteproc remoteproc1: Booting fw image wcnss.mdt, size 7260
[ 10.944436] remoteproc remoteproc1: remote processor a204000.remoteproc is now up
[ 11.780693] remoteproc remoteproc0: Booting fw image mba.mbn, size 234368
[ 12.837209] remoteproc remoteproc0: remote processor 4080000.remoteproc is now up
[ 12.862234] wcn36xx: mac address: 02:00:0b:a2:9f:2a
[ 13.374031] wwan wwan0: port wwan0at0 attached
[ 13.543214] wwan wwan0: port wwan0qmi0 attached
[ 346.630105] wcn36xx: firmware WLAN version 'WCN v2.0 RadioPhy vRhea_GF_1.12 ...'
[ 350.869053] br-lan: port 2(phy0-ap0) entered forwarding state
복원했던 펌웨어 파일 크기(wcnss.mdt 7260바이트, mba.mbn 234368바이트)와 부팅 로그의 로딩 크기가 정확히 일치하는 걸 보고 확신했습니다. WiFi 코프로세서, 모뎀 코프로세서 둘 다 정상 기동, wwan0 QMI/AT 포트까지 다 잡혔습니다.
로그에 ERROR HAL_8023_MULTICAST_LIST rsp failed 같은 에러성 메시지가 보이긴 하는데, 이건 wcn36xx 드라이버의 잘 알려진 무해한 버그(멀티캐스트 필터 실패 시 all-multicast로 자동 폴백)라 무시해도 됩니다.
WIFI SSID 설정
초기 상태에서는 WIFI가 켜져있지 않습니다.
Networks > Wireless 로 이동해서 SSID와 Security 부분을 업데이트 합니다.
정리
- 원본 백업은 선택이 아니라 필수. GPT 하나 잘못 써도 EDL로 복구 가능했던 건 순전히 백업 덕분이었습니다.
- 에러 메시지를 곧이곧대로 믿지 말 것. Error:{}가 떠도 실제로는 성공한 쓰기가 있었고, 반대로 겉보기엔 멀쩡해 보여도 실제로는 반영 안 된 쓰기도 있었습니다. 매번 다시 읽어서 검증하는 게 답이었습니다.
- EDL 쓰기는 로더가 기대하는 정확한 크기로. 특히 GPT처럼 고정 크기가 있는 영역은 부족한 만큼 0으로 패딩해야 합니다.
- 부트롬 단계는 최후의 안전장치. Qualcomm 칩의 EDL/Sahara는 소프트웨어로 아무리 깨져도 살아있어서, 물리 전원 재순환만 하면 항상 복구 접근이 가능했습니다.
- WSL이 범인이 아닐 수도 있다. 처음엔 WSL/usbip를 의심했지만, 실제로는 장치 로더 자체의 특성이었습니다. 환경을 바꾸기 전에 먼저 재현 조건을 좁혀보는 게 중요했습니다.
'지극히 개인적인 > 기기 관련' 카테고리의 다른 글
| [OTT/TV BOX] 묵혀둔 H96 MAX x2 펌웨어 업데이트를 해보았다. (0) | 2022.04.03 |
|---|