I played around a bit with the A9 mini WiFi camera using all of the great information here in this thread. So, I thought I would add what I found in case it helps anyone.
Setup:
I have the A9 (v2) mini WiFi camera. I created a WiFi hot spot on my Ubuntu machine and then connected my phone (with Little Stars app) and the camera to the WiFi hot spot. (Camera connection setup given here.) Then I used Wireshark to save off the communication between the app and the camera. Finally, I analyzed the data that was sent with a python script.
Findings:
The packets were encrypted with the weak encryption method mentioned above. The key for my cameras was: [0xB8, 0x48, 0x90, 0x00] (also given above).
Using the pppp decoder ring (from above) I was able to make some sense of the data. I only saw a couple of message formats: [‘MSG_ALIVE’, ‘MSG_PUNCH_PKT’, ‘MSG_P2P_RDY’, ‘MSG_ALIVE_ACK’, ‘MSG_DRW’, ‘MSG_DRW_ACK’, ‘MSG_CLOSE’]. Maybe there are more possible message, I wasn’t exhaustive in my testing.
Of the messages, there really only seemed to be one interesting message: MSG_DRW. This seems to be the message that is used for command and control of the camera, as well as sending back data like the video stream or the name of the video files stored on the SD card.
Looking at the MSG_DRW packet, it has a header on it that consists of a magic_number, a channel, and an index. The magic number was always 0xd1. I’m not sure what that magic number signifies. Maybe identifying that it is an A9 (v2) mini WiFi camera. For command and control type messages the channel seemed to be 0 and for the video stream the channel seemed to be 1.
I was unable to make any sense of the data in the MSG_DRW packet. The video streaming packets didn’t have the first couple of bytes being [0xff, 0xd8] like JFIF header for the unencrypted video stream. The data seemed to be random.
I didn’t know where to start on trying to decode the command and control type packets, so I didn’t do much with them.
Code:
In case this helps anyone here’s some code snippets.
I modified the encode and decode method to make a copy of the input so that it isn’t destroyed on encryption and decryption. (Also passes in the key by default.)
LITTLE_STARS_KEY = [0xB8, 0x48, 0x90, 0x00]
def encrypt(buf, key=None):
if key is None:
key = LITTLE_STARS_KEY
ret = buf.copy()
prev_byte = 0
for i in range(0, len(ret)):
key_idx = (key[prev_byte & 0x03] + prev_byte) & 0xFF
ret[i] = ret[i] ^ KEY_TABLE[key_idx]
prev_byte = ret[i]
return ret
def decrypt(buf, key=None):
if key is None:
key = LITTLE_STARS_KEY
ret = buf.copy()
prev_byte = 0
for i in range(0, len(ret)):
key_idx = (key[prev_byte & 0x03] + prev_byte) & 0xFF
prev_byte = ret[i]
ret[i] = ret[i] ^ KEY_TABLE[key_idx]
return ret
Here’s how I was reading in a Wireshark capture and filtering on the packets of interest:
import struct
from pyshark import FileCapture
def ip_info(packet):
return f"[src={packet.ip.src}:{packet.udp.srcport} dst={packet.ip.dst}:{packet.udp.dstport}]"
def to_str(val):
return val.rstrip(b'\x00').decode("utf-8")
def parse_uid(data):
prefix, serial, check_code = struct.unpack(">8sI8s", data)
return f"{to_str(prefix)}{serial}{to_str(check_code)}"
ips = ["x.x.x.x", "y.y.y.y"] # Fill in with your values (or remove ip address filter)
cap = FileCapture("data.pcapng")
for packet in cap:
if "ip" not in packet:
continue
if packet.ip.src not in ips or packet.ip.dst not in ips:
continue
if "icmp" in packet:
continue
raw_data = bytearray.fromhex(packet.DATA.get_field("data"))
data = decrypt(raw_data)
magic_num, msg_type, msg_size = struct.unpack(">BBH", data[:4])
if magic_num != 0xf1:
print(f"WARNING: Did not find correct magic number ({hex(magic_num)}) != 0xf1)")
continue
if msg_type == 0xd0:
msg_drw_magic_num, channel, index = struct.unpack(">BBH", data[4:8])
print(f"MSG_DRW ({msg_size}): {ip_info(packet)} -- {hex(msg_drw_magic_num)} {channel} {index} -> {hex(data[8])} {hex(data[9])} -- {hex(data[-2])} {hex(data[-1])}")
Summary:
Mostly just a restating of everything else on this thread, but hopefully there is some helpful information in there for someone. A big thanks to everyone for the great information in this thread!