Mercury MIPC251C-4 Web Camera with ONVIF and PTZ Control

Jul 23, 2019
Use Charles JADX WireShark reverse engineering Mercury MIPC251C-4 Web Camera App and admin web page to write a control script.


Charles cannot capture packets. APK decompile but no luck, control CMD implemented in JNI. But WireShark can capture control packets for iOS app. Login logic can be found in admin web page.
notion image


Recently, I found a Mercury IPC on SMZDM. Apart from supporting PTZ and night vision features, it also claims to support the ONVIF protocol. So I decided to buy one and add it to HomeAssistant for monitoring. However, after getting my hands on it, I realized that things weren't as simple as it seemed. Here are my findings.

Integrating with HomeAssistant

According to the HomeAssistant documentation on ONVIF Camera, integrating should be as simple as adding a couple of lines in the configuration file. But whether I used the default configuration or tried various ports on the backend page, I couldn't add the camera successfully.
Finally, using DSM Surveillance Station from Synology, I found the correct port. Later, I discovered that I could also find the correct port using iSpy. The port is 2020.
Here is the HomeAssistant configuration:

ONVIF and RTSP Parameters

ONVIF URL: http://192.168.2.x:2020/onvif/device_service RTSP 1920x1080 URL: rtsp://admin:pass@192.168.2.x:554/stream1 RTSP 640x480 URL: rtsp://admin:pass@192.168.2.x:554/stream2

PTZ / Pan-Tilt-Zoom Control

After integrating with HomeAssistant and DSM Surveillance Station, I realized that PTZ control was not working. It seems that the claim of ONVIF support only applies to recording.
The rest of this article focuses on how to control the PTZ function.

Capturing Packets

I tried capturing packets using Charles, but the application didn't go through the configured proxy.

Decompiling the Android App

After unsuccessful attempts to capture packets using Charles, I tried decompiling the Mercury Security App. To my surprise, I found a lot of code related to com.tplink.ipc, indicating that Mercury is indeed a sibling of TP-Link. The app is obfuscated, which makes it challenging to analyze.
  • Adding Local LAN Devices: com.tplink.ipc.ui.device.add.DeviceAddByDeviceDetailInputFragment
  • Login: com.tplink.ipc.ui.device.add.DeviceAddByDeviceDetailInputFragment#xcom.tplink.ipc.core.IPCAppContext#devReqAddDevice(java.lang.String, int, java.lang.String, java.lang.String, int, int). The devReqAddDevice method calls a native function, and the actual implementation is found in It seems that the network requests are made within the JNI code, making it difficult to capture them using Charles. Decompiling the .so library is not worth the effort, so let's try a different approach.

WireShark iOS Packet Capture

Using rvictl, I can also capture packets over 3G/4G connections, which is useful for capturing packets related to issues that only occur in a 4G environment. Additionally, for devices that do not support setting up a proxy, I can enable the iPhone's personal hotspot and capture packets that way.
To capture packets, run the following command:
rvictl -s 0000xxxx-00xxxxxxxxxxxxxx
I discovered the network requests for the login and PTZ control steps:


Retrieve stok:


Use the obtained stok to send POST requests:


Currently, it seems that obtaining the stok is the key to controlling the PTZ function. It can be obtained by capturing packets, but it seems to become invalid after the device restarts or after some time has passed (not confirmed).

Obtaining stok

Although this camera has a web interface, it does not support PTZ control through the web. Therefore, in the initial packet capture, I did not consider the web interface. However, after analyzing the web interface, I found that the login verification process uses the same logic.
Here are the steps:
  1. Retrieve the RSA public key and nonce (both change every time)
  1. Encrypt the password password as tpPassword using TP-Link's universal encryption method
  1. Append :nonce to tpPassword as tpPassword:nonce
  1. Encrypt tpPassword:nonce using the public key as rsaPassword
  1. Send rsaPassword as the password for camera verification
Regarding steps 2 and 3, here is the record:

Encrypting Password as tpPassword Using TP-Link Encryption

notion image
A search for "RDpbLfCPsJZ7fiv" reveals various implementations of this encryption method.

Encrypting tpPassword as rsaPassword Using RSA

notion image
In the image, a = a.concat(":", $.authRltObj.nonce) appends the nonce. c.setPublicKey($.authRltObj.key) sets the public key. The subsequent sendAjaxReq is thecontinuation of the process.

The Script

Based on the analysis above, I have written a control script.

© likaci 2013 - 2023