doc:appunti:hardware:insta360_one_rs_wifi_reverse_engineering

Insta360: WiFi protocol reverse engineering

Get the open source software from the RigacciOrg/insta360-wifi-api GitHub repository.

I purchased an Insta360 ONE RS action camera in June 2023, I'm rather satisfied by its performances, but I'm really disappointed by the accompanying Android app. The app (version 1.40.1) is a monster download of 676 Mb, once installed it requires more than 1 Gb of storage. It is rather invasive about permissions request because it wants access to the camera (the phone's camera!), contacts, microphone and phone. Once started it is totally social oriented, presenting me blatantly useless info about what other people does with their cameras.

What I need is a simple remote control for my action camera, not another invasive and useless social network. Beside that, the app doesn't work on my phone; maybe because the hardware specs are not at the cutting edge of techonolgy (but neverthless my phone has 4 Gb of RAM and a 4 cores MediaTek Helio A22 SoC), whenever I tap the icon to start the live view the app crashes. So the basic function of remote controlo does not work.

The app is required not only to “activate” the camera for the first use, it is also necessary to do very basic and daily operations, like setting the date and time into the camera. For all that reasonos I definitely need some custom software to control my camera and get rid of that crappy “official” app.

Connecting to the WiFi

Fortunately enough it is possibile to connect a GNU/Linux PC to the Insta360 through the WiFi, the default passowrd of the camera internal access point is 88888888.

Said incidentally, this is an huge security hole of the camera: as far I know it is not possibile to disable the WiFi interface or change the password (at least from the camera touch screen interface), so any host in the nearby can connect to your camera as soon it is turned on; once estabilished the connection you can also do a telnet into the Insta360's GNU/Linux operating system as root (the IP address of the camera is 192.168.42.1) and do whaterver you want, even to damage permanently (brick) the camera.

Capturing the WiFi traffic

I was hoping the control channel was the 7878/TCP port, like into the SJCAM SJ8 Pro camera which is equipped with the same Ambarella H22 chip, but I was not able to estabilish a connection (cannot succeed at the AMBA_START_SESSION command).

So I installed the Insta360 app into a rooted Android smartphone. On the same phone I'm running the Termux app where I installed the tcpdump package. So I discovered that the app talks to the Insta360 camera through the port 6666/TCP, where a server is expecting and returning messages. The messages are not in clear text: it turned out that they are serialized using the Protocol Buffers mechanism (thanks to this Hackaday.io post which gave me the enlightening hint).

Running tcpdump into the Insta360

If your Android device is not rooted, you can still capture the WiFi traffic between the Android app and the Insta360 camera by just executing tpcudmp directly on the GNU/Linux operating system of the camera. You have to download and install the required binaries, which are fortunately provided by the Entware Projec.

Download from entware.net the following packages (choose aarch64 architecture, kernel 3.10):

  • libc_2.27-11_aarch64-3.10.ipk
  • libgcc_8.4.0-11_aarch64-3.10.ipk
  • libpcap_1.10.4-1_aarch64-3.10.ipk
  • librt_2.27-11_aarch64-3.10.ipk
  • libssp_8.4.0-11_aarch64-3.10.ipk
  • libthread-db_2.27-11_aarch64-3.10.ipk
  • tcpdump_4.99.4-1_aarch64-3.10.ipk

Create a directory into the SD card /tmp/SD0/opt/ and unpack the above archives, keeping the directory structure and dereferencing links into plain files (the SD filesystem does not support symbolic links).

Suppose that the Android device running the Insta360 app has IP address 192.168.42.2, run the following script at the command line of the camera:

#!/bin/sh
test -f /opt/bin/tcpdump || mount -o bind /tmp/SD0/opt /opt
/opt/bin/tcpdump -w /tmp/SD0/tcpdump-$(date +%m%d%H%M%S).log -s0 -n 'host 192.168.42.2'

Packets anatomy

Sent Packets

Sync Packet

Offset Content Bytes Note
0 Packet Length 4 Overall length of the packet, including this 4 bytes.
4 0x06 0x00 0x00 3 Message Type: Sync Packet.
7 syNceNdinS 10 Magic String.

Keep Alive Packet

Offset Content Bytes Note
0 Packet Length 4 Overall length of the packet, including this 4 bytes.
4 0x05 0x00 0x00 3 Message Type: Keep Alive.

Phone Commands

Offset Content Bytes Note
0 Packet Length 4 Overall length of the packet, including this 4 bytes.
4 0x04 0x00 0x00 3 Message Type: Phone Command.
7 Message Code 2 Examples: PHONE_COMMAND_GET_OPTIONS, PHONE_COMMAND_SET_OPTIONS, PHONE_COMMAND_TAKE_PICTURE, PHONE_COMMAND_START_CAPTURE, etc.
9 0x02 1
10 Sequence Number 3 Each command sent to the camera have its increasing sequence number, the relative response contains the same sequence number.
13 0x80 0x00 0x00 3
16 Protobuf Message Variable The message serialized using Protocol Buffers.

Received Packets

Notifications or Response to Phone Commands

Offset Content Bytes Note
0 Packet Length 4 Overall length of the packet, including this 4 bytes.
4 0x04 0x00 0x00 3 Response Type: Phone Command.
7 Response Code 2 Examples: 200: OK, 500: ERROR, CAMERA_NOTIFICATION_CURRENT_CAPTURE_STATUS, etc.
9 0x02 1
10 Sequence Number 3 Matches to the requesting message.
13 0x80 1
14 Unknown 2
16 Protobuf Message Variable The response message serialized using Protocol Buffers.

Inspecting the raw Protobuf messages

Unfortunately the Protocol Buffers is not self-describing; that is, there is no way to tell the names, meaning, or full datatypes of exchanged messages without an external specification.

You can however use some freely available programs to inspect the binary data of a protobuf message, e.g. the BlackBox Protobuf Library for the Python language can decode and re-encode protobuf messages without access to the source protobuf descriptor file.

Firstly install the bbpb Python library (using the pip system will take care of dependencies, e.g. installing also the Google protobuf Python library):

pip3 install bbpb

Then extract some protobuf binary messages from the tcpdump output; in the following example we try to decode a binary message received from the camera by the Android app. Each message is prefixed by a 12 bytes header (see packets anatomy, above), so in the Python code strip that header away and keep only the message body before calling the protobuf_to_json() function:

#!/usr/bin/env python3
import blackboxprotobuf
packet_hex = '040000c80002000000800000080f0824083012467a0e495242454e323230364d5441424ea202220a114f4e4520525320364d5441424e2e4f534312083838383838383838189501200082030e496e737461333630204f6e655253'
packet = bytearray.fromhex(packet_hex)
message, typedef = blackboxprotobuf.protobuf_to_json(packet[12:])
print(message)

The output will reveal the structure of the message:

{
  "1": [
    15,
    36,
    48
  ],
  "2": {
    "15": "IRBEN2206MTABN",
    "36": {
      "1": "ONE RS 6MTABN.OSC",
      "2": {
        "7": [
          56,
          56,
          56,
          56
        ]
      },
      "3": 149,
      "4": 0
    },
    "48": "Insta360 OneRS"
  }
}

As you can see dictionary indices and the elements themselves are integers whose meaning is unknown. In some cases the strings are correctly decoded, (for example element 48 is the model of the camera), in other cases they are not (element 7 would be a string with the WiFi password, but it has been interpreted by mistake as a list of integers).

If you have access to the .proto files which define the messages syntax, you can compile some ad-hoc Python modules and write a code that will produce a much more understandable result like this:

option_types: SERIAL_NUMBER
option_types: WIFI_INFO
option_types: CAMERA_TYPE
value {
  serial_number: "IRBEN2206MTABN"
  wifi_info {
    ssid: "ONE RS 6MTABN.OSC"
    password: "88888888"
    channel: 149
  }
  camera_type: "Insta360 OneRS"
}

Let's see in the following paragraphs how to obtain the .proto files and compile them for the Python language.

Getting the .proto definitions

:!: WARNING: In this paragraph you can read about my first attempt in getting the *.proto files from a demo executable. It turned out that the files were not complete and not fully up-to-date. It is advised to use the current Android app to get a more recent version of the *.proto files. See the next paragraph for the full recipe.

To understand the messages structure of the messages exchanged betwwen the software and the camera it is necessary to have the .proto files that define the syntax, but how can you do it without having access to the non-free source codes of Insta360?

Browsing the Insta360Develop GitHub repository you can find some documentation about the SDK for the Android, iOS and C++ environment, but the SDK itself is not available. It seems that you can ask to access the Insta360 SDK, but the End User License agreement forbid to create software that mix the SKD with Open Source software, so I cannot apply.

On the GitHub repository there is an example demo of the CameraSDK-Cpp, where you can download a compiled executable CameraSDKTest.exe; that executable almost certainly includes the protobuf definitions that we need. Thankfully there are several programs that can extract .proto definitions from an executable, the one that worked for me is pbtk, a compilation of reverse engineering Protobuf apps.

Once you have installed the Google protobuf Python library (due the dependencies seen above or e.g. executing pip3 install protobuf), you can simply copy into your working directory the utils subdirectory of the pbtk repository and the script from_binary.py (found into the extractor subdirectory of the same repository) and then execute:

./from_binary.py CameraSDKTest.exe

The program analyzes the executable and extracts 21 files with the .proto extension: 10 are into the current directory and the others 11 into a subdirectory called commands. It is necessary to use the protoc program (the protobuf compiler, installable e.g. with the protobuf-compiler Debian package) to create the Python source code files which will handle that specific protobuf messages.

Place the *.proto files into a folder e.g. called proto, create the output directory e.g. called pb2 and then compile them all:

protoc --proto_path='proto/' --python_out='pb2/' proto/*.proto
protoc --proto_path='proto/' --python_out='pb2/' proto/commands/*.proto

Here are the files created into the destination directory:

pb2/
├── commands
│   ├── delete_files_pb2.py
│   ├── get_current_capture_status_pb2.py
│   ├── get_file_list_pb2.py
│   ├── get_options_pb2.py
│   ├── get_photography_options_pb2.py
│   ├── set_photography_options_pb2.py
│   ├── start_capture_pb2.py
│   ├── start_live_stream_pb2.py
│   ├── stop_capture_pb2.py
│   ├── stop_live_stream_pb2.py
│   └── take_picture_pb2.py
├── battery_pb2.py
├── button_press_pb2.py
├── capture_state_pb2.py
├── extra_info_pb2.py
├── media_pb2.py
├── options_pb2.py
├── photography_options_pb2.py
├── photo_pb2.py
├── storage_pb2.py
└── video_pb2.py

When you move the pb2 directory into the folder of your Python program, you can finally use the generated Python modules to parse the protobuf messages:

#!/usr/bin/env python3
import sys
sys.path.append('pb2')
sys.path.append('pb2/commands')
import get_options_pb2
packet_hex = '040000c80002000000800000080f0824083012467a0e495242454e323230364d5441424ea202220a114f4e4520525320364d5441424e2e4f534312083838383838383838189501200082030e496e737461333630204f6e655253'
packet = bytearray.fromhex(packet_hex)
message = get_options_pb2.GetOptionsResp()
message.ParseFromString(packet[12:])
print(message)

Getting the .proto files from the Android APK

Download the .proto file extractor tool from this repository. Download the Android app and extract the libOne.so file from it. Then run the following recipe:

#!/bin/sh -e
 
# Extract the Insta360 Proto Buffer files (*.proto) from the library
# libOne.so contained into the Android app.
#
# The protobuf definition files are required to talk to Insta360
# cameras over the WiFi API.
#
# Requirements: google.protobuf Python library and protoc compiler.
#
# The from_binary.py script and the utils/* modules were downloaded
# from https://github.com/marin-m/pbtk
 
if [ ! -f "libOne.so" ]; then
    echo "ERROR: File libOne.so not found."
    echo "Download the Android app from https://www.insta360.com/it/download"
    echo "The file is inside the apk (zip) file: lib/arm64-v8a/libOne.so"
    exit 1
fi
 
# Extract the .proto source files from the compiled executables.
# The from_binary.py Python script requires the google.protobuf Python library.
test -d proto || mkdir proto
cd proto
../from_binary.py ../libOne.so
cd ..
echo "Proto files were extracted into the proto directory."
 
# Compile the *.proto source files into Python classes.
# Requires the protoc compiler from the protobuf-compiler Debian package.
test -d pb2 || mkdir pb2
protoc --proto_path='proto/' --python_out='pb2/' proto/*.proto
echo "Python files were compiled into the pb2 directory."

The Insta360 Python remote program

On the insta360-wifi-api GitHub repository you can find a Python library which implements basic communication with the Insta360 camera over WiFi connection. There is also an example Python program with the basic functionality of remote control. You can easily run it from a PC with GNU/Linux, but you can also install the required Python language and libraries in MS-Windows and even on Android. I run that program on my smartphone, it just required to install the Termux app.

Unsolved Problems

It seems impossibile to change some settings via the WiFi API; e.g. I was not able to change:

  • Sharpness
  • Prompt Sound
  • Indicator Light (LEDs)

When some settings are changed via the WiFi API, the preview on the camera screen does not reflect that change; nor in the live stream, nor into the on-screen-display labels. E.g. white balance, capture resolution, fielf of view. Fortunately if you start the video capture, the settings are effective.

White Balance Settings

It is possible to change the white balance setting by changing the value of white_balance choosing from some enumerated presets or directly by changing the temperature value of white_balance_value. There seems to be some inconsistency between the labels assigned to the presets in the .proto files and the actual temperature values. I think that the best choice is to assign the white_balance_value, ignoring the enumerated presets.

white_balance_value white_balance Protobuf Enum Label
AUTO 0 WB_AUTO
2000 6
2200 7
2400 8
2600 9
2800 1 WB_2700K
3000 10
3200 11
3400 12
3600 13
3800 14
4000 2 WB_4000K
4500 15
5000 5 WB_7500K
5500 16
6000 17
6500 3 WB_5000K
7000 18
7500 4 WB_6500K
8000 19
8500 20
9000 21
9500 22
10000 23

Web References

doc/appunti/hardware/insta360_one_rs_wifi_reverse_engineering.txt · Last modified: 2023/09/08 10:46 by niccolo