- 저가 Bluetooth LE LED 조명을 Home Assistant에 붙이려는 역공학 과정에서, 10M 주소 지정 가능 LED 스트링의 숨겨진 효과 값을 시험하다 컨트롤러가 동작하지 않게 됨
- Android의 Bluetooth HCI snoop과 Wireshark/tshark로 앱이 조명에 쓰는 BLE 패킷을 캡처하고,
btatt.opcode.method==0x12쓰기 요청에서 제어 바이트를 추적함 - iDeal LED 앱의 패킷은 단순한 on/off 값처럼 보이지 않았고, APK 디컴파일과 기존 분석 글을 거쳐
libAES.so안의 고정 AES 키로 복호화함 - 복호화된 on/off 패킷은 고정 헤더와 5번째 바이트의
1/0차이로 정리됐고, 색상·밝기·효과 기능을 반복 실행해 바이트 패턴을 기록함 - RGB에 앱이 쓰는 5비트 범위
0x1F를 넘어 8비트 값을 보내자 더 밝은 색상이 가능했지만, 효과 번호 12를 보내는 순간 조명이 꺼지고 Bluetooth 광고도 사라짐
저가 BLE 조명을 홈 자동화에 붙이는 출발점
- Bluetooth LE로 통신하고 전용 앱이 있는 기기는 홈 자동화 시스템에 통합할 수 있다는 전제에서 여러 저가 LED 스트립을 역공학함
- 이전에는 £2.38짜리 Bluetooth LE 제어 5M 비주소 지정 LED 스트립을 몇 시간 만에 Home Assistant에 연결했고, 관련 코드는 bj_led에 공개됨
- LEDnetWF 컨트롤러의 BLE 역공학 작업도 lednetwf_ble에 있음
- 이번 대상은 책상 위에 있던 10M 주소 지정 가능 LED 스트링으로, “iDeal LED” 앱으로 제어됨
- 앱은 기능이 많고 비교적 잘 동작함
- LED는 WS2812 또는 유사 제품일 가능성이 있음
- 제품은 AliExpress에서 구매한 조명임
앱이 보내는 BLE 바이트 캡처
- 자체 소프트웨어로 기기를 제어하려면 앱이 Bluetooth로 기기에 보내는 바이트열을 먼저 확인해야 함
- 일반적인 조명 프로토콜은 헤더, on/off나 색상 변경 같은 명령 바이트, 체크섬일 수 있는 푸터로 구성될 수 있음
- Android에서는 다음 순서로 캡처함
- 개발자 모드를 켬
- 조명 앱을 설치함
- 개발자 설정에서
Bluetooth HCI snoop을 활성화함 - 앱에서 조명을 켜고 끄는 등 동작을 수행함
adb pull sdcard/btsnoop_hci.log .로 로그를 컴퓨터에 복사함
- Wireshark에서 로그를 열면 조명으로 전송된 바이트를 볼 수 있음
- 필터 예시는
bluetooth.dst == ff:ff:ff:ff:ff:ff && btatt.opcode.method==0x12 - MAC 주소는 실제 조명의 MAC으로 바꿔야 함
btatt.opcode.method==0x12는 Android 기기에서 조명으로 쓰기 작업이 발생했음을 뜻함
- 필터 예시는
- tshark를 쓰면 패킷 값을 터미널에서 바로 뽑을 수 있음
tshark -r <filename> -T fields -e btatt.value는 LED 컨트롤러에 쓰인 페이로드를 출력함
단순 리플레이로는 부족했던 iDeal LED 프로토콜
- 일부 조명은 on/off 동작에서 거의 그대로 읽히는 패턴을 보임
- 예시는
69 96 02 01 01과69 96 02 01 00이 반복되는 형태임 - 마지막 바이트가
1과0으로 바뀌며 켜기/끄기를 나타냄
- 예시는
- 이번 iDeal LED 조명은 훨씬 긴 바이트열이 반복됐고, on/off에 해당하는 두 종류의 패킷은 구분되지만 값이 노이즈처럼 보였음
- 단순히 켜고 끄는 목적이면 캡처한 바이트열을 그대로 재전송하는 리플레이로 충분할 수 있음
gatttool로 BLE 기기에 연결해 바이트를 보낼 수 있음- 전송할 핸들은 Wireshark에서 확인해야 함
- 더 많은 제어를 하려면 패킷 구조를 이해해야 했고, Android 앱 자체를 분석하는 단계로 넘어감
APK 디컴파일과 AES 키 찾기
- APK를 내려받아 jadx로 열고 앱 코드를 확인함
- 소스 안에서 AES 참조가 보여 프로토콜이 암호화됐을 가능성이 생김
- 암호화된 데이터에 대해 다음 조건을 가정함
- 같은 동작의 암호문이 매번 바뀌지 않아 일관된 키가 있을 수 있음
- 저전력 MCU에서 빠르게 복호화해야 하므로 짧은 키가 유리함
- 키가 기기마다 고유하지 않고 고정 키일 수 있음
- 앱에는
libAES.so라는 컴파일된 AES 라이브러리가 들어 있었고,jadx만으로는 분석할 수 없었음 - 다른 사람이
ida free로 AES 라이브러리를 디컴파일해 내장 키를 찾은 분석 글을 발견했고, 그 키를 시험함 Crypto.Cipher의 AES ECB 모드로 복호화하자 on/off 패킷이 의미 있는 형태로 바뀜- 복호화된 값은
05 54 55 52 4E 01 ...와05 54 55 52 4E 00 ...처럼 나타남 - 고정 헤더 뒤 5번째 바이트가
1또는0으로 바뀌며 on/off를 나타냄 - 나머지는 0으로 채워짐
- 복호화된 값은
- 이 단계부터 앱이 보내는 패킷을 복호화하고, 자체 코드에서 같은 제어를 재현할 수 있게 됨
기능별 바이트 패턴 기록
- 앱의 모든 기능을 하나씩 실행하며 전송 바이트를 기록하는 방식으로 프로토콜 범위를 넓힘
- 각 동작은 여러 번 반복하고, 섹션 구분을 위해 조명을 껐다 켜는 패턴을 끼워 넣음
- 색상을 red, green, blue 순서로 여러 번 바꿈
- 밝기를 100%, 50%, 10%, 50%, 100%로 바꿈
- 각 묶음 사이에 off/on을 넣어 캡처 로그에서 경계를 찾기 쉽게 함
- 이 방식으로 동작에 따라 어떤 바이트가 바뀌는지 확인하고, 기록한 동작과 캡처된 패킷을 맞출 수 있음
컨트롤러를 벽돌로 만든 효과 번호 12
- 색상 변경을 조사하던 중 앱이 red, green, blue 값에
0x1F보다 큰 값을 보내지 않는 것을 확인함0x1F는 5비트 범위임- 8비트 값을 직접 보내자 더 밝은 색상이 동작함
- 앱이 쓰는 10개 효과 외에 추가 효과가 있는지 확인하려고
range(20)루프를 돌려 효과 번호를 순서대로 보냄- 1부터 10까지는 정상적으로 진행됨
- 11에서 숨겨진 모드처럼 보이는 동작을 발견함
- 12로 넘어가자 조명이 꺼짐
- 이후 조명은 다시 켜지지 않음
- Bluetooth 광고를 더 이상 하지 않음
- 연결도 되지 않음
- 전원을 켤 때 버튼을 누르고 있어도 복구되지 않음
- 밤새 전원을 뽑아 둬도 돌아오지 않음
- 버퍼 오버플로로 펌웨어가 손상됐을 가능성을 추정했지만, 원인은 확정되지 않음
- LED 자체는 표준 주소 지정 가능 LED라서 다른 마이크로컨트롤러에 연결해 스트링을 재사용할 수 있음
남은 결과물과 주의점
- 실패에도 불구하고 프로토콜 대부분을 문서화했고, Home Assistant 커스텀 컴포넌트가 포함된 GitHub 프로젝트를 만들었음
- 컴포넌트는 동작하지만, 같은 방식의 실험은 조명 컨트롤러를 고장 낼 수 있으므로 각자 책임하에 진행해야 함