Files
cam-reverse/README.md
T
2024-01-30 20:14:36 +01:00

148 lines
5.0 KiB
Markdown

Re-implementation of the "iLnk"/"iLnkP2P"/"PPPP" protocol used on some cheap (\<$5) IP cameras (sometimes branded as 'X5' or 'A9').
* Bought [here](https://www.aliexpress.com/item/1005006287788979.html).
* Waiting for [this A9 camera](https://www.aliexpress.com/item/1005006117593880.html) to validate support.
* App is [YsxLite](https://play.google.com/store/apps/details?id=com.ysxlite.cam&hl=en&gl=US)
Per [pictures](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb.jpg?raw=true) the main chip is TXW817 ([chinese](https://www.taixin-semi.com/Product/ProductDetail?productId=306), [eng, google translate](https://www-taixin--semi-com.translate.goog/Product/ProductDetail?productId=306&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp))
The interesting implementation is in `libvdp.so`, part of the apk bundle.
Protocol reversing was done with a combination of static analysis of the shared object with [Ghidra](https://ghidra-sre.org/) and dynamic analysis with [Frida](https://frida.re/docs/javascript-api/).
The headers reversed with Ghidra are at `types/all.h`. They are almost not used by this minimal implementation though.
The hooks used with frida are at `frida-hooks.js`, but it's mostly a playground - some useful functions got deleted once I understood the protocol.
There's also a partial Wireshark dissector at `dissector.lua`. You can install it with `make install-wireshark-dissector`.
## Running
To execute the HTTP server, run `make run`; you can access the JPEG stream at http://localhost:1234/ and a file `audio.pcm` will be created.
The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg).
## Protocol
The protocol is weirdly complex, though very little communication is necessary to use the device
The base structure of a packet is:
![](diagrams/packet.svg)
The payload is command-dependent; most commands have only a literal payload, but the `Drw` (`0xf1d0`) command has a framing scheme:
By using the second byte in the payload as a discriminant, we can split the payload into two types of subcommands:
**Control packets**:
![](diagrams/control_packet.svg)
The payload on control packets is "encrypted" when the length is > 5.
**Data packets**:
![](diagrams/data_packet.svg)
Data packets further discriminate based on the first 4 bytes into: Audio Data (0x55aa15a8), Video data.
### Session
To establish a session, a few _control packets_ are sent.
```mermaid
---
title: Establish session
---
sequenceDiagram
autonumber
App->>+Cam: [C] LanSearch
Cam->>-App: [C] PunchPkt (SerialNo)
App->>+Cam: [C] P2PRdy
Cam->>-App: [C] P2PRdy
App->>+Cam: [C] ConnectUser
Cam->>-App: [C] ConnectUserAck (Ticket)
loop Every 400-500ms
Cam-->>+App: [C] P2PAlive
App-->>-Cam: [C] P2PAliveAck
end
```
To start a stream, a single _control packet_ is sent.
The received stream is broken up into 1028 byte payloads, along with a sequence number.
Stitching the payloads together yields JPEG frames for video, and 8KHz A-law PCM for audio.
```mermaid
---
title: Stream audio/video
---
sequenceDiagram
App->>Cam: [C] StreamStart (with Ticket)
loop
Cam-->>+App: [D] Audio/Video Payload
App-->>-Cam: [C] DrwAck
end
```
### Serial
The A9 cameras have a TX/RX test points - connecting with UART at 921600 81N gives _read only_ access to some debug logs.
### Discrepancies between cameras
1. Wifi Strength
- A9 reports '100%' strength
- X5 reports different strength values
Seems like the A9 cameras brick themselves if you:
1- Give them a bad wifi password
2- Give them a good wifi password, with no internet
Have not yet tried to give it internet access.
After bricking itself, it reports very broken configuration via serial:
```
network interface: ƀ (Default)
MTU: 51050
MAC: 06 18 40 06 3e 51 b4 e2 c6 80 06 3f 77 30 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 74 00 00 00 01 00 00 00 9c ea 01 20 00 00 00 00 00 00 00 00 00 28 60 00 00 00 00 00 00 00 00 00 06 4e 00 20 2a 00 2a 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 00 01 08 00 06 04 00 01 3e 51 b4 e2 c6 80 01 01 01 01 00 00 00 00 00 00 01 01 01 01 00 28 74 00 00 00 00 00 00 00 00 00 58 4e 00 20 48 00 48 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 45 00 00 48 00 51 00 00 ff 11 c8 be 01 01 01 01 23 9c cc f7 7d 6c
FLAGS: DOWN LINK_DOWN IGMP
ip address: 1.1.1.1
gw address: 1.1.1.1
net mask : 1.1.1.1
network i
nterface: ^@^@
MTU: 0
MAC:
FLAGS: DOWN LINK_DOWN
ip address: 127.0.0.1
gw address: 127.0.0.1
net mask : 255.0.0.0
```
### Take APK from emulator/sacrificial device
```
adb shell pm list packages | grep ysx
adb shell pm path com.ysxlite.cam
adb shell pm path com.ysxlite.cam | while read -r line ; do adb pull $(echo $line | cut -d: -f2-) ; done
```
### Push to sacrificial device
```
adb install-multiple *apk
```
### Frida install Android
[docs](https://frida.re/docs/android/)
### Start frida server
```
adb shell 'su -c nohup /data/local/tmp/frida-server-16.1.11-android-arm64 &'
```