Trong kỷ nguyên của nhà thông minh, việc tích hợp các thiết bị vào một hệ sinh thái đồng bộ như Home Assistant đang trở thành xu hướng tất yếu. Tuy nhiên, không phải lúc nào mọi thứ cũng diễn ra suôn sẻ. Nhiều thiết bị cũ hơn, hoặc thậm chí một số thiết bị mới, thường đi kèm với các ứng dụng độc quyền và API hạn chế, gây khó khăn cho người dùng muốn tận dụng tối đa khả năng điều khiển. Điển hình là trường hợp của đèn LED dải Govee H615B, một sản phẩm phổ biến nhưng lại đặt ra thách thức lớn khi người dùng cố gắng điều khiển chúng theo cách riêng của mình, vượt ra ngoài ứng dụng chính thức.
Govee cung cấp cả API dựa trên web và API cục bộ. Mặc dù API web có thể sử dụng được trong một số trường hợp, nó lại bị giới hạn tần suất truy cập một cách nghiêm ngặt, khiến việc điều chỉnh các thông số như độ sáng trở nên khó khăn. Chỉ sau vài lần thay đổi trạng thái, bạn có thể bị khóa truy cập trong một phút. Đối với API cục bộ, dù về mặt kỹ thuật là có tồn tại, nhưng với dòng đèn Govee H615B, tùy chọn này lại bị vô hiệu hóa hoàn toàn trong ứng dụng Govee, khiến việc kích hoạt gần như bất khả thi. Chính những rào cản này đã thúc đẩy một hành trình khám phá sâu hơn: reverse engineering đèn Govee H615B để có thể điều khiển chúng một cách tự do qua Bluetooth LE, tích hợp mượt mà vào Home Assistant mà không phụ thuộc vào bất kỳ dịch vụ đám mây hay ứng dụng độc quyền nào.
Bài viết này sẽ đưa bạn qua từng bước của quá trình reverse engineering, từ việc thu thập dữ liệu gói tin Bluetooth đến phân tích chúng bằng Wireshark và cuối cùng là xây dựng các lệnh điều khiển tùy chỉnh bằng Python. Mục tiêu là giúp cộng đồng người dùng Việt Nam hiểu rõ hơn về cách các thiết bị thông minh hoạt động dưới lớp vỏ bọc, đồng thời trao quyền cho bạn để làm chủ hoàn toàn các thiết bị của mình.
Hiểu Rõ Vấn Đề: Phân Tích Công Cụ Và Mục Tiêu
Bước đầu tiên trong bất kỳ dự án reverse engineering nào là đánh giá kỹ lưỡng vấn đề, các công cụ sẵn có và mục tiêu cuối cùng bạn muốn đạt được. Với thử thách điều khiển đèn Govee H615B, chúng ta cần xác định rõ những gì có thể sử dụng và kết quả mong muốn. Các công cụ trong dự án này bao gồm:
- MacBook Pro M4 Pro: Nền tảng phát triển chính để chạy Wireshark, Python và các công cụ khác.
- Google Pixel 8 Pro: Một chiếc điện thoại Android cao cấp có khả năng ghi lại log Bluetooth HCI cấp thấp, rất quan trọng cho việc thu thập dữ liệu.
- Ứng dụng Govee chính thức: Cần thiết để thực hiện các thao tác điều khiển ban đầu và ghi lại các gói tin Bluetooth tương ứng.
- Wireshark: Công cụ phân tích gói tin mạng mạnh mẽ, cho phép chúng ta kiểm tra và giải mã các gói tin Bluetooth LE đã ghi lại.
- Bleak: Một framework Bluetooth LE cho Python, hữu ích để quét thiết bị và tương tác với chúng trong quá trình thử nghiệm.
- Milk-V Duo S: Một bộ vi điều khiển nhỏ gọn, mạnh mẽ với cả nhân Arm và RISC-V, hỗ trợ Wi-Fi/Bluetooth và có thể chạy Python, lý tưởng cho việc triển khai giải pháp cuối cùng.
MacBook Pro 16 inch M4 Pro, công cụ chính trong dự án reverse engineering đèn Govee.
Mục tiêu chính là tránh việc root chiếc Google Pixel 8 Pro nếu có thể, bởi quá trình này có thể tiềm ẩn rủi ro và phức tạp. Nếu không có cơ chế xác thực phức tạp nào được yêu cầu để điều khiển đèn, việc phát sóng các lệnh điều khiển tùy chỉnh từ bất kỳ thiết bị Bluetooth nào sẽ trở nên khả thi. Cuối cùng, dự án sẽ tạo ra một script Python có thể chạy trên Milk-V Duo S, hoạt động như một máy chủ nhận lệnh từ Home Assistant và phát sóng chúng tới đèn Govee, tạo thành một giải pháp điều khiển tích hợp hoàn chỉnh và linh hoạt.
Thu Thập Dữ Liệu Bluetooth
Để tìm hiểu cách điều khiển đèn Govee H615B từ xa, cách hiệu quả nhất là ghi lại các gói tin Bluetooth được gửi đi và nhận về giữa ứng dụng Govee và đèn. Đây là lý do tại sao Google Pixel 8 Pro lại trở thành một công cụ quan trọng. Mặc dù có các phần cứng chuyên dụng để “ngửi” (sniff) gói tin Bluetooth đang truyền tải, nhưng chúng ta không có những thiết bị đó. May mắn thay, Android có một trình ghi log Bluetooth HCI (Host Controller Interface) tích hợp sẵn, hoạt động rất hiệu quả. Log HCI là một bản ghi cấp độ rất thấp của mọi thứ điện thoại của bạn gửi đi và nhận được qua Bluetooth. Bạn có thể kích hoạt tính năng này trong tùy chọn nhà phát triển trên điện thoại Android.
Điện thoại Google Pixel 8 Pro, thiết bị dùng để thu thập Bluetooth HCI log cho đèn Govee.
Một câu hỏi đặt ra là tại sao lại chọn Google Pixel 8 Pro thay vì các thiết bị Android khác. Vấn đề nằm ở vị trí lưu trữ và khả năng truy cập log. Thông thường, bạn có thể truy cập log này qua ADB bằng cách tạo một bản báo cáo lỗi (bug report) được lưu vào máy tính. Log HCI nên được lưu trong thư mục log Bluetooth. Tuy nhiên, trên một số thiết bị của các hãng khác (ví dụ như Oppo Find N5), dù file log có tồn tại, nó lại trống rỗng. Do các công ty thường tùy biến hệ thống Android của họ, việc lựa chọn Google Pixel 8 Pro giúp tránh các “trò đùa” của nhà sản xuất (OEM shenanigans), đảm bảo rằng bản ghi Bluetooth HCI thực sự chứa dữ liệu hữu ích.
Trước khi trích xuất log, cần đảm bảo nó chứa đầy đủ dữ liệu cần thiết. Hãy cài đặt ứng dụng Govee và đăng nhập vào tài khoản của bạn. Sau đó, tắt kết nối Wi-Fi trên điện thoại. Điều này buộc ứng dụng Govee phải sử dụng Bluetooth để điều khiển đèn, đảm bảo rằng dữ liệu tương tác sẽ được ghi vào log. Thực hiện các thao tác điều khiển khác nhau: thay đổi độ sáng, màu sắc, bật tắt đèn nhiều lần. Mỗi hành động này sẽ tạo ra các gói tin Bluetooth cần thiết cho quá trình phân tích. Sau khi đã tạo đủ dữ liệu, kết nối Pixel 8 Pro với máy tính và sử dụng ADB để kéo bản báo cáo lỗi về. File log Bluetooth HCI sẽ nằm trong đường dẫn FS/data/misc/bluetooth/logs
bên trong bản báo cáo lỗi đã giải nén, với phần mở rộng là .cfa
.
Phân Tích Cơ Bản Với Wireshark
Sau khi thu thập được file log .cfa
(BTSnoop), bước tiếp theo là sử dụng Wireshark để phân tích các gói tin Bluetooth Low Energy (BLE) bên trong. Không cần phải đi sâu vào chi tiết ngay lập tức; hãy tập trung vào việc xác định những thông tin cơ bản nhất.
File .cfa
chứa các gói L2CAP, chi tiết tất cả các giao tiếp mà thiết bị của bạn thực hiện qua Bluetooth, đặc biệt thường được sử dụng bởi các thiết bị BLE. Trong trường hợp này, việc xác định thiết bị cần xem xét khá dễ dàng. Đồng thời, chúng ta cũng có thể sử dụng thư viện Python Bleak để quét các thiết bị và xác định ID của chúng.
Kết quả quét thiết bị Bluetooth LE của đèn Govee H615B sử dụng thư viện Bleak trong Python.
Một trình quét đơn giản bằng Bleak sẽ giúp nhận diện đèn Govee H615B qua UUID của nó. Sau khi xác định được UUID, chúng ta có thể truy vấn thiết bị để lấy các Characteristics của nó. Characteristics về cơ bản là các profile: một client có thể khởi tạo lệnh nhắm mục tiêu vào một characteristic và nhận phản hồi, trong khi một máy chủ có thể chấp nhận các lệnh đó và thực hiện chúng. (Một lưu ý nhỏ: macOS và framework CoreBluetooth của nó sẽ cung cấp cho bạn một UUID cho các thiết bị BLE thay vì địa chỉ MAC để giao tiếp. Điều này không ảnh hưởng nhiều nhưng đáng để lưu ý nếu bạn viết code để chuyển sang thiết bị khác sau này. Địa chỉ MAC bạn cần sử dụng sẽ có trong log).
Trong quá trình quét, chúng ta dễ dàng nhận ra Govee H615B. Các characteristics quan trọng được xác định bao gồm:
- Handle:
0x0009
, UUID:00010203-0405-0607-0809-0a0b0c0d2b10
- Handle:
0x000d
, UUID:00010203-0405-0607-0809-0a0b0c0d2b11
- Handle:
0x0012
, UUID:f000ffc1-0451-4000-b000-000000000000
- Handle:
0x0016
, UUID:f000ffc2-0451-4000-b000-000000000000
Đáng chú ý là hai characteristics kết thúc bằng “b10” và “b11” có khả năng liên quan đến nhau, tương tự với “c1” và “c2”. Chúng ta sẽ tập trung vào hai characteristic kết thúc bằng “b10” và “b11”, vì theo nghiên cứu về các thiết bị Govee khác, dường như hai characteristic này liên quan đến việc thiết lập trạng thái cho đèn, và các thiết bị khác cũng có các chuỗi khớp chính xác.
Một điểm cần lưu ý khi làm việc với các thiết bị Govee là: mặc dù chúng có cách tương tác tương tự, nhưng cách chúng chấp nhận lệnh lại có sự khác biệt nhỏ. Một số thiết bị có các phân đoạn riêng cho các phần khác nhau của dải đèn (để có thể điều khiển riêng lẻ), và một số yêu cầu xác thực trong quá trình ghép nối. Quá trình xác thực này là một mối lo ngại lớn, nhưng may mắn thay, khi chiếc Pixel 8 Pro kết nối lần đầu tiên, không có bất kỳ kiểm tra nào về thiết bị gửi lệnh. Đây có thể là một lỗ hổng bảo mật nhỏ (dù không gây hại lớn), nhưng nó lại là lợi thế cho chúng ta.
Phân tích gói tin thông báo Bluetooth LE từ đèn Govee H615B trong Wireshark.
Một phát hiện quan trọng trong quá trình kết nối ban đầu giữa điện thoại và đèn là thông báo được gửi từ characteristic kết thúc bằng “b10”. Thiết bị client (Pixel 8 Pro) nhận được thông báo từ bộ điều khiển Bluetooth của đèn. Điều này cho biết chúng ta cần lắng nghe dịch vụ này khi viết code để kết nối sau này.
Tiếp theo, trong log Wireshark, chúng ta sẽ thấy rất nhiều gói tin được gửi từ Pixel đến đèn chứa giá trị sau:
aa010000000000000000000000000000000000ab
Những gói tin này dường như là gói “keep-alive”, thông báo cho đèn Govee rằng chúng ta vẫn muốn gửi và nhận thông tin. Chúng được gửi khoảng hai giây một lần và chiếm phần lớn log. Khi kết nối với đèn bình thường bằng Bleak, chúng thường bị ngắt kết nối khỏi máy tính chỉ sau vài giây. Vì điện thoại không có bất kỳ liên lạc nào khác trong thời gian dài ngoài việc gửi các giá trị tương tự, nên có thể suy ra đây là gói tin keep-alive. Điều này cũng khớp với cách các thiết bị Govee khác hoạt động. Các gói tin Bluetooth LE có độ dài 20 byte (không có MTU mở rộng), và có vẻ như các gói tin này sử dụng byte đệm (zero padding) để đạt độ dài đó. Chúng ta có thể kết luận rằng các gói tin bắt đầu bằng 0xaa
biểu thị một gói tin keep-alive. Về hai chữ số cuối cùng (ab
), chúng ta sẽ tìm hiểu sau.
Gói tin Bluetooth LE điều khiển bật đèn Govee H615B được ghi lại bởi Wireshark.
Bây giờ, chúng ta sẽ xem xét các lệnh điều khiển thực tế. Khi lần đầu mở ứng dụng và kết nối với đèn, chúng ta đã bật và tắt chúng. Trong log, gói tin đầu tiên được truyền tải mà không phải là gói keep-alive hoặc liên quan đến kết nối ban đầu là gói trên. Khi tắt đèn lại, chúng ta tìm thấy một giá trị rất giống với gói tin bật đèn nhưng có sự khác biệt nhỏ. Điều này khớp với thời gian chúng ta ghi lại trong ghi chú. Từ đó, chúng ta có thể suy luận rằng hai giá trị để bật và tắt đèn như sau:
- Bật đèn:
3301010000000000000000000000000000000033
- Tắt đèn:
3301000000000000000000000000000000000032
Hãy chú ý cấu trúc của gói tin: chúng ta lại có rất nhiều byte 00
và hai byte khác nhau ở cuối. Để giải thích sơ bộ, “0x” dùng để chỉ giá trị thập lục phân (hệ cơ số 16). 0x33
là “51” trong hệ thập phân. 0x33
dường như biểu thị một lệnh, với dữ liệu theo sau là hướng dẫn để thực thi. Trong trường hợp này, chúng ta có 0x33
, 0x01
và 0x01
để bật đèn, và 0x33
, 0x01
và 0x00
để tắt đèn. Điều này cho thấy một giá trị boolean (0 hoặc 1) ở byte thứ ba kiểm soát trạng thái bật/tắt. Đến đây, chúng ta đã có đủ thông tin để thử nghiệm bằng cách thiết lập bộ thu thông báo và lặp lại các lệnh này cho đèn. Chúng ta chưa cần lo lắng về hai chữ số cuối cùng vì chúng đã được tính toán sẵn cho chúng ta, nhưng chúng ta sẽ tìm hiểu cách tự tính toán chúng sau.
Thiết Lập Màu Sắc Và Độ Sáng Đèn
Chúng ta đã có thể bật và tắt đèn Govee H615B qua Bluetooth, đây là một bước tiến lớn. Tuy nhiên, những chiếc đèn thông minh đầy màu sắc như thế này còn có nhiều tính năng hơn chỉ là bật và tắt. Chúng ta cần tìm hiểu cách điều khiển màu sắc và độ sáng.
Gói tin Bluetooth LE thiết lập màu sắc cho đèn Govee được phân tích trong Wireshark.
Sau khi phân tích các gói tin đã ghi được khi thay đổi màu sắc trong ứng dụng, chúng ta tìm thấy gói tin sau:
33050dfe0e1f00000000000000000000000d4
Lại một lần nữa, gói tin này bắt đầu bằng byte 0x33
, cho thấy đây là một lệnh điều khiển. Chúng ta chưa chắc chắn về 050d
, nhưng chuỗi “fe0e1f” trông giống như một mã màu hex. Khi chuyển đổi fe0e1f
từ hệ thập lục phân sang màu RGB, nó hiển thị rõ ràng là màu đỏ, và chúng ta đã thay đổi đèn sang màu đỏ trong quá trình thử nghiệm. Chúng ta muốn kiểm tra xem có thể thay thế “fe0e1f” bằng một mã màu khác không, nhưng có một vấn đề. Trước đây, chúng ta có thể chỉ cần phát lại các gói tin cho đèn, và chúng sẽ thực hiện lại các lệnh đã thấy trong log. Làm thế nào để tạo ra các lệnh mới? Chúng ta không thể chỉ đơn giản hoán đổi các giá trị hex màu cho mã màu của mình. Lý do là sự hiện diện của byte cuối cùng.
Byte cuối cùng đó là một checksum, về cơ bản xác nhận rằng dữ liệu đã đến trong tình trạng hoàn chỉnh và không bị lỗi. Nó được tính toán bằng cách thực hiện một phép toán XOR tích lũy (cumulative XOR) trên từng byte. Một phép toán XOR là một loại cổng logic tạo ra ‘1’ khi hai giá trị đầu vào khác nhau. Mỗi byte sau đó được XOR với byte trước đó cho đến khi đạt đến byte thứ 19. Kết quả tính toán cuối cùng được nối vào cuối gói tin, vào byte thứ 20, và đây là gói tin được gửi đến thiết bị. Cuối cùng, thiết bị thực hiện phép toán XOR riêng trên 19 byte đầu tiên, kiểm tra xem byte cuối cùng có khớp với giá trị nó tính toán được hay không. Nếu khớp, thiết bị biết rằng dữ liệu đã đến đúng như dự định và an toàn để thực thi.
Hãy thử thay đổi đèn sang màu magenta, mã hex #FF00FF
. Gói tin sẽ có dạng như sau:
33050d[ff00ff]00000000000000000000000d4[checksum]
ff00ff
(đặt trong ngoặc vuông để dễ hiểu) là mã màu của chúng ta, và [checksum]
là giá trị chúng ta muốn tính toán. Chúng ta bắt đầu với giá trị của một byte rỗng, hoặc 00000000
.
- Bắt đầu với
0x00
(nhị phân:00000000
). - XOR với
0x33
(00110011
), kết quả:0x33
(00110011
). - XOR với
0x05
(00000101
), kết quả:0x36
(00110110
). - Tiếp tục XOR từng byte trong thông điệp với kết quả trước đó.
- Kết quả cuối cùng cho
ff00ff
sẽ là0xff
(nhị phân:11111111
).
Chúng ta thực hiện phép XOR lần lượt trên từng byte của chuỗi cho đến khi hết. Kết quả cuối cùng là giá trị ff
, với giá trị nhị phân là 11111111
. Gói tin cuối cùng chúng ta sẽ gửi đến đèn là:
33050dff00ff00000000000000000000000d4ff
Tuy nhiên, việc tính toán checksum thủ công mỗi khi chúng ta muốn thay đổi màu sắc của đèn là không thuận tiện. Thay vào đó, chúng ta có thể tự động hóa quá trình này bằng Python. Chúng ta có thể triển khai một phương thức nhận các giá trị hex RGB, chèn chúng vào chuỗi gói tin và sau đó tính toán checksum để nối vào cuối chuỗi. Phương thức này sẽ tự động hóa phép tính XOR đã trình bày ở trên để có được checksum mới mỗi khi chúng ta gửi lệnh thay đổi màu sắc.
Cuối cùng, hãy xem xét việc điều khiển độ sáng. Sử dụng cùng một quy trình phân tích, chúng ta phát hiện ra rằng lệnh để thiết lập độ sáng có dạng như sau:
3304[brightness]00000000000000000000000000000000[checksum]
Giá trị độ sáng là một byte đơn, dao động từ 00
(0%) đến FF
(255, tương ứng với 100%). Checksum một lần nữa phải được tính toán, nhưng điều này dễ dàng thực hiện sau khi chúng ta đã hiểu cách nó được tính. Ví dụ, để đặt độ sáng 100% (tức ff
trong hex), gói tin sẽ là:
3304ff00000000000000000000000000000000c8
Đến đây, chúng ta đã hoàn toàn tìm ra cách điều khiển đèn Govee H615B! Chúng ta có thể:
- Bật và tắt đèn H615B
- Đặt màu sắc
- Đặt độ sáng
Tất cả những điều này đều có thể thực hiện mà không cần sử dụng ứng dụng chính thức của Govee. Giải pháp này giúp bỏ qua API dựa trên đám mây, khắc phục vấn đề không thể sử dụng API cục bộ và cho phép chúng ta tự động hóa việc điều khiển đèn từ một thiết bị khác bằng cách tích hợp chúng vào hệ thống nhà thông minh của mình.
Reverse Engineering: Một Quá Trình Dài, Thú Vị Và Đầy Thử Thách
Giao diện web điều khiển đèn Govee H615B, minh họa thành quả reverse engineering.
Reverse engineering có thể là một công việc khó khăn và bạn có thể gặp phải nhiều trở ngại trong quá trình thực hiện. Có vô số tài nguyên sẵn có để hỗ trợ bạn, nhưng rất có thể, nếu bạn đang reverse engineering một thứ gì đó, đó là bởi vì chưa có ai khác làm điều đó trước đây. Trong dự án này, chúng ta đã may mắn khi quá trình xác định những gì cần thay đổi và cách thực hiện tương đối đơn giản.
Mục tiêu của bài viết này là hướng dẫn bạn các bước cần thiết khi reverse engineering một thiết bị tương tự như đèn Govee H615B. Có rất nhiều thiết bị thông minh giá rẻ trên thị trường tương tự Govee H615B yêu cầu một ứng dụng độc quyền để điều khiển. Tuy nhiên, việc tìm hiểu cách chúng hoạt động không phải là điều bất khả thi. Với hàng giờ hoặc thậm chí hàng ngày mày mò bên máy tính, bạn đôi khi có thể tìm ra cách để tự mình điều khiển chúng. Đó chính xác là những gì chúng ta đã làm. Những gì bắt đầu như một dự án cuối tuần vui vẻ đã biến thành một nỗ lực kéo dài nhiều ngày đầy thử thách, nhưng với quyết tâm phải tìm ra câu trả lời cuối cùng.
Từ kết quả này, việc triển khai một cơ chế điều khiển từ Home Assistant trở nên rất đơn giản, cho phép bạn sử dụng những chiếc đèn Govee này giống như bất kỳ thiết bị thông minh nào khác. Ví dụ, bạn có thể triển khai một REST API bằng Flask và sau đó sử dụng tích hợp rest_command
trong Home Assistant để gửi lệnh. Cuối cùng, tạo một script sẽ bật hoặc tắt đèn, và bạn có thể xây dựng một template switch, một thanh trượt đầu vào, hoặc một điều khiển Lovelace tùy chỉnh cho nguồn, độ sáng và màu sắc. Đây chính là những gì đã được thực hiện khi triển khai giải pháp này trên Milk-V Duo S, và nó hoạt động hoàn hảo. Mặc dù không được khám phá trong bài viết này, bạn cũng có thể đánh giá trạng thái hiện tại của đèn Govee (đang bật hay tắt) bằng cách in dữ liệu được phát sóng bởi nó khi quét.
Nếu bạn sở hữu dòng đèn Govee H615B và muốn tự mình kiểm soát chúng từ bất kỳ thiết bị hỗ trợ Bluetooth nào, bạn có thể tham khảo kho mã nguồn GitHub. Tất cả những gì bạn cần là địa chỉ MAC của đèn (có thể lấy bằng ứng dụng như nRF Connect trên điện thoại), và phần còn lại sẽ hoạt động. Đây là một quá trình học hỏi vô cùng bổ ích, và chúng tôi hy vọng rằng điều này sẽ truyền cảm hứng cho bạn để tìm hiểu sâu hơn về các thiết bị xung quanh mình, khám phá cách chúng hoạt động và cách bạn có thể tự mình làm chủ chúng!
Tài liệu tham khảo: