Tapo C200
The TP-Link Tapo C200 is a 1080p IP camera that can tilt and pan. The camera supports RTSP out of the box without needing to hack or modify the stock firmware.
Serial console
Based on the information from https://drmnsamoliu.github.io/shell.html
Serial console can be accessed by wiring a USB UART to the test pads on the main board. Note that the TX/RX appears to be at 3.3v levels, so take care to use an appropriate UART device or a logic level shifter.
Pin | Function |
---|---|
TP9 | to UART RX (via 3.3v level shifter) |
TP10 | to UART TX (via 3.3v level shifter) |
TP11 | GND |
You should see some boot messages when the device is powered on. Enter slp
when you see the second Autobooting in 1 seconds
message to enter the U-Boot menu.
U-Boot 2014.01-v1.2 (Aug 25 2021 - 12:36:20)
Board: IPCAM RTS3903 CPU: 500M :rx5281 prid=0xdc02
force spi nor mode
DRAM: 64 MiB @ 1066 MHz
Skipping flash_init
Flash: 0 Bytes
SF: Unsupported flash IDs: manuf 20, jedec 4017, ext_jedec 0000
flash status is 0, 2, 0
SF: Detected unknown with page size 256 Bytes, erase size 64 KiB, total 8 MiB
Using default environment
Autobooting in 1 seconds
set watchdog, resetting...
U-Boot 2014.01-v1.2 (Jun 27 2022 - 09:04:38)
Board: IPCAM RTS3903 CPU: 500M :rx5281 prid=0xdc02
force spi nor mode
DRAM: 64 MiB @ 1066 MHz
manuf 20, jedec 4017, ext_jedec 0000
flash status is 0, 2, 0
SF: Detected unknown with page size 256 Bytes, erase size 64 KiB, total 8 MiB
Flash: 0 Bytes
manuf 20, jedec 4017, ext_jedec 8204
flash status is 0, 2, 0
SF: Detected unknown with page size 256 Bytes, erase size 64 KiB, total 8 MiB
Using default environment
In: serial
Out: serial
Err: serial
Net: Realtek PCIe GBE Family Controller mcfg = 0024
new_ethaddr = 00:00:00:00:00:00
r8168#0
VF: validateLocalFirmware: copying flash to 0x82000000
manuf 20, jedec 4017, ext_jedec 0000
flash status is 0, 2, 0
SF: Detected unknown with page size 256 Bytes, erase size 64 KiB, total 8 MiB
SF: 8388608 bytes @ 0x0 Read: OK
VF: validateLocalFirmware: ret=0(82fb7de0)
VF: validateLocalFirmware: validate local firmware...
TP Header at 82070000
Autobooting in 1 seconds <-- At this point, type 'slp'
rlxboot#
On the latest firmware as of November 2022, these are the settings that are set on my unit. Note the address to the kernel is at 0x70000
rather than 0x60000
as documented by the Tapo C200 research docs.
rlxboot# printenv
addmisc=setenv bootargs ${bootargs}console=ttyS0,${baudrate}panic=1
baudrate=57600
bootaddr=(0xBC000000 + 0x120000)
bootargs=console=ttyS1,57600 root=/dev/mtdblock6 spdev=/dev/mtdblock7 rts-quadspi.channels=dual
bootcmd=sf probe;sf read 0x82000000 0x70000 0x300000;bootm 0x82000000
bootdelay=1
bootfile=/vmlinux.img
ethact=r8168#0
ethaddr=00:00:00:00:00:00
load=tftp 80500000 ${u-boot}
loadaddr=0x80100000
stderr=serial
stdin=serial
stdout=serial
Environment size: 477/131068 bytes
If you let the camera boot normally, you will eventually be allowed to log in after hitting the enter key. Unfortunately, the root password is currently unknown (we talk about this in a section below). Instead, we'll have to boot into single user mode to bypass this password.
To boot into single user mode, tack on init=/bin/sh
to the bootargs
variable and run the bootcmd
commands:
# setenv bootargs ${bootargs} init=/bin/sh
# sf probe
# sf read 0x82000000 0x70000 0x300000
# bootm 0x82000000
Continuing the boot process
Because we're running the shell as init, nothing is mounted at this stage. After some exploring, it turns out that the system's based on OpenWRT 12. The init scripts that /sbin/init runs to mount our filesystems are actually in /etc/preinit
. Calling /etc/preinit
is sufficient in getting our system in a more usable state.
# /etc/preinit
## Set the root password since it's set to something unknown
# passwd
## Continue with the boot process
# exec /sbin/init
After letting the system boot, you should now be able to log in as root using the password you assigned.
You can start telnetd by running: telnetd -l /bin/sh -F
Exploring the system
The system is running OpenWRT 12.09-rc1 with kernel Linux SLP 3.10.27 #2 PREEMPT Mon Jun 27 09:12:59 CST 2022 rlx GNU/Linux
root@SLP:~# cat /proc/cpuinfo
system type : RLX Linux for IPCam Platform
machine : Unknown
processor : 0
cpu model : Taroko V0.2 FPU V0.1
BogoMIPS : 497.66
tlb_entries : 64
mips16 implemented : yes
root@SLP:~# free -m
total used free shared buffers
Mem: 40524 37556 2968 0 2236
-/+ buffers: 35320 5204
Swap: 0 0 0
Process list
After getting telnet to run, this is what the process list looks like.
root@SLP:~# ps -w
PID USER VSZ STAT COMMAND
1 root 2324 S init
2 root 0 SW [kthreadd]
3 root 0 SW [ksoftirqd/0]
4 root 0 SW [kworker/0:0]
5 root 0 SW< [kworker/0:0H]
6 root 0 SW [kworker/u2:0]
7 root 0 SW [rcu_preempt]
8 root 0 SW [rcu_bh]
9 root 0 SW [rcu_sched]
10 root 0 SW< [khelper]
11 root 0 SW< [writeback]
12 root 0 SW< [bioset]
13 root 0 SW< [kblockd]
14 root 0 SW [khubd]
15 root 0 SW [kworker/0:1]
16 root 0 SW [kswapd0]
17 root 0 SW [fsnotify_mark]
18 root 0 SW< [crypto]
46 root 0 SW [kworker/u2:2]
47 root 0 SW< [deferwq]
50 root 0 SW< [kworker/0:1H]
277 root 2324 S -ash
292 root 0 DW [reset_thread]
303 root 0 SW< [cryptodev_queue]
316 root 864 S /sbin/hotplug2 --override --persistent --set-rules-file /etc/hotplug2.rules --set-coldplug-cmd /sbin/udev
332 root 888 S /sbin/ubusd
359 root 8036 S tp_manage
394 root 3420 S /usr/bin/ledd
396 root 3220 S /usr/sbin/netlinkd
399 root 5464 S < /usr/bin/system_state_audio
473 root 1636 S /sbin/netifd
474 root 1516 S /usr/sbin/connModed
479 root 1528 S /usr/sbin/connModed
485 root 10124 S /usr/sbin/wlan-manager
609 root 0 SW [RTW_CMD_THREAD]
651 root 1200 S wpa_supplicant -B -Dwext -iwlan0 -P/tmp/supplicant_pid -C/var/run/wpa_supplicant -bbr-wan
677 root 13516 S /usr/bin/dsd
705 root 4412 S /bin/cloud-brd -c /var/etc/cloud_brd_conf
800 root 14972 S /bin/cloud-client
804 root 13764 S /bin/cloud-service
1072 root 3876 S /usr/sbin/uhttpd -f -h /www -T 180 -A 0 -n 8 -R -r C200 -C /tmp/uhttpd.crt -K /tmp/uhttpd.key -s 443
1082 root 5880 S /usr/bin/rtspd
1084 root 5988 S /usr/bin/relayd
1092 root 7196 S /usr/bin/p2pd
1096 root 11144 S /bin/dn_switch
1098 root 4200 S /bin/storage_manager
1138 root 39736 R /bin/cet
1192 root 30360 S /bin/vda
1198 root 3804 S /bin/wtd
1206 root 11344 S /bin/nvid
1258 root 2324 S udhcpc -p /var/run/static-dhcpc.pid -s /lib/netifd/static-dhcp.script -f -t 0 -i br-wan -r 10.1.3.247 -H
1344 root 2328 S /usr/sbin/ntpd -n -p time.nist.gov -p 128.138.140.44 -p 192.36.144.22 -p time-a.nist.gov -p time-b.nist.g
1359 root 3840 S /usr/bin/motord
3887 root 2320 S telnetd -l /bin/sh -F
3900 root 2324 S /bin/sh
3918 root 2320 R ps -w
Network daemons
root@SLP:~# netstat -lnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:8800 0.0.0.0:* LISTEN 1138/cet
tcp 0 0 127.0.0.1:929 0.0.0.0:* LISTEN 1092/p2pd
tcp 0 0 0.0.0.0:20002 0.0.0.0:* LISTEN 359/tp_manage
tcp 0 0 0.0.0.0:2020 0.0.0.0:* LISTEN 1206/nvid
tcp 0 0 0.0.0.0:554 0.0.0.0:* LISTEN 1138/cet
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 3887/telnetd
tcp 0 0 127.0.0.1:921 0.0.0.0:* LISTEN 1084/relayd
tcp 0 0 127.0.0.1:922 0.0.0.0:* LISTEN 1082/rtspd
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 1072/uhttpd
udp 0 0 0.0.0.0:20002 0.0.0.0:* 359/tp_manage
udp 0 0 0.0.0.0:3702 0.0.0.0:* 1206/nvid
Filesystems
root@SLP:~# df -h
Filesystem Size Used Available Use% Mounted on
/dev/root 2.5M 2.5M 0 100% /rom
tmpfs 19.8M 1012.0K 18.8M 5% /tmp
tmpfs 512.0K 0 512.0K 0% /dev
rwroot 19.8M 24.0K 19.8M 0% /overlay
/dev/mtdblock7 3.5M 3.5M 0 100% /rom/sp_rom
overlayfs:/overlay 3.5M 3.5M 0 100% /rom/mnt
overlayfs:/overlay 19.8M 24.0K 19.8M 0% /
Root password
The root password on this firmware version has changed and is no longer 'slprealtek' as discovered by kubik369 and documented on the Tapo C200 research site. The root hash is now set to:
root:$1$IjsWXCH9$z2Woc0uNFN4a7bZM7SGCF.:0:0:root:/root:/bin/ash
RTSP
RTSP works out of the box after you enable it with the Tapo app. You must set a username and password in the Tapo app (Advanced settings -> Camera Account).
There are two streams that are enabled with this option.
- Full resolution -
rtsp://username:password@$IP/stream1
- 360p resolution -
rtsp://username:password@$IP/stream2
Hack
There are a few approaches that could be taken to hack the device so that there is persistent telnet access across reboots. The one that DrmnSamoliu used was an exploit in the art partition (I think he went with this approach because modifying the system partitions would cause UBoot to stop booting as it does checks at startup).
Getting persistent telnet access
The approach I went with to avoid needing to flash anything was to tack on a Arduino to the serial port which takes over the startup sequence from UBoot. The Arduino will make the camera enter single user mode, reset the root password, enable telnet from any IP address, and also attempt to run a script from the SD card on startup. This should hopefully also survive firmware upgrades unless they decide to change how UBoot behaves as the changes are applied each time the camera boots.
Here's the Arduino sketch I eventually came up with.
int boot_stage = 0;
bool boot_done = false;
unsigned long last_act = 0;
void setup() {
// initialize serial:
Serial.begin(57600);
Serial.setTimeout(250);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
}
void loop() {
if (Serial.available() <= 0) {
// no serial activity before boot is complete? Hmmm..
if ( ! boot_done && millis() - last_act > 10000ul) {
Serial.println("");
last_act = millis();
}
return;
}
// work on the current line
String input = Serial.readString();
input.trim();
// empty? Do nothing
if (input.length() == 0) {
return;
}
// got something? Try to work with it.
digitalWrite(LED_BUILTIN, HIGH);
last_act = millis();
if (input.indexOf("Autobooting in 1 seconds") >= 0) {
boot_stage++;
//if (boot_stage >= 2) {
delay(100);
Serial.println("slp");
delay(200);
//}
}
else if (input.indexOf("rlxboot#") >= 0) {
delay(250);
Serial.println("setenv bootargs ${bootargs} init=/bin/sh");
delay(250);
// Clear buffer and get prompt
while (Serial.available() > 0) Serial.read();
Serial.println("printenv bootcmd");
while (true) {
input = Serial.readString();
if (input.indexOf("bootcmd=") >= 0) {
delay(100);
serial_write_string(input.substring(input.indexOf("=") + 1));
break;
}
}
delay(1000);
// Clear buffer
while (Serial.available() > 0) Serial.read();
}
else if (input.indexOf("/ #") >= 0) {
delay(250);
Serial.println("/etc/preinit");
delay(2500);
Serial.println("echo -en password\\\\npassword | passwd");
delay(250);
Serial.println("sed -i 's/-b 127.0.0.1//g' /etc/init.d/telnet");
delay(250);
Serial.println(F("echo IyEvYmluL3NoIC9ldGMvcmMuY29tbW9uCgpTVEFSVD00MQoKc3RhcnQoKSB7CnsKCXdoaWxlIHRydWUgOyBkbwoJCXNkX3Jlc3VsdD0kKG1vdW50IHwgZ3JlcCAibW1jYmxrMCIpCgkJaWYgW1sgIiRzZF9yZXN1bHQiWCA9PSAiIlggXV07IHRoZW4KCQkJIyBubyBTRCBjYXJkIHlldD8KCQkJZWNobyAiV2FpdGluZyBmb3IgU0QgY2FyZC4iCgkJCXNsZWVwIDUKCQllbHNlCgkJCWJyZWFrCgkJZmkKCWRvbmUKCglbIC1mIC90bXAvbW50L2hhcmRkaXNrXzEvc3RhcnQuc2ggXSAmJiAuIC90bXAvbW50L2hhcmRkaXNrXzEvc3RhcnQuc2gKfSYKfQoK | base64 -d > /etc/init.d/hack"));
delay(250);
Serial.println("chmod 755 /etc/init.d/hack");
delay(250);
Serial.println("ln -s /etc/init.d/hack /etc/rc.d/S41hack");
delay(250);
Serial.println("exec /sbin/init");
boot_done = true;
boot_stage = 1;
}
digitalWrite(LED_BUILTIN, LOW);
}
void serial_write_string(String data) {
for (int i = 0; i < data.length(); i++) {
Serial.print(data[i]);
}
Serial.println("");
}
I've created a startup.sh script on the SD card which will also stop the Cloud services as I plan to run this without internet access. That script is given below.
#!/bin/bash
# Reset the root password, in case you don't want 'password' as the default.
echo -e "changeme\nchangeme" | passwd root
# Kill cloud services
killall wtd cloud-client cloud-brd cloud-service
Interesting info
GPIO LEDs
The red and green LEDs are controllable via the GPIO devices at:
/sys/devices/platform/leds-gpio/leds/led-red/brightness
/sys/devices/platform/leds-gpio/leds/led-green/brightness
Settings
The Tapo firmware uses the underlying OpenWRT configuration system and can be interacted with using the uci
utility. Run uci show
to show all settings. The following settings are custom to the Tapo firmware.
Description | Key |
---|---|
Enable SD Card recording | record_plan.chn1_channel.enabled=on / off |
Video recording resolution | video.main.resolution=1920*1080
video.main.resolution=1280*720 video.main.resolution=640*360 |
Motion detection / alert | motion_detection.motion_det.enabled=on |
Camera alarm | msg_alarm.chn1_msg_alarm_info.enabled=on / off
msg_alarm.chn1_msg_alarm_info.light_type=0 / 1 (enable light) msg_alarm.chn1_msg_alarm_info.alarm_type=0 / 1 (0 siren, 1 tone) msg_alarm.chn1_msg_alarm_info.alarm_mode=sound / light / light sound |
Flip (invert) image | image.switch.flip_type=off / center |
Loop recording | harddisk_manage.harddisk.loop=on / off (on will result in SD card being filled with empty files) |
Privacy mode | lens_mask.lens_mask_info.enabled=off / on |
Distortion correction | image.switch.ldc=off / on |
OSD text | OSD.label_info_1.enabled=off / on
OSD.label_info_1.text=custom text |
OSD date | OSD.date.enabled=on / off
OSD.week.x_coor=6000 (?) OSD.week.y_coor=500 (?) |
Applying changes could possibly be made by restarting cet.
# /etc/init.d/cet terminate
# /etc/init.d/cet resume
Third account
The account to access the RTSP stream is controlled by the third account that's defined in uci
.
user_management.third_account=third_account
user_management.third_account.username=camadmin
user_management.third_account.passwd=5F4DCC3B5AA765D61D8327DEB882CF99
Here, we have a 'camadmin
' account with a password 'password
' in MD5.
Change the default NTP server
Because the firmware is based on OpenWRT, you can see the NTP settings using uci
:
root@SLP:/etc# uci show|grep -i ntp
system.basic.timing_mode=ntp
system.ntp=timing
system.ntp.server=0.0.0.0
system.ntp.ntp_port=999
system.ntp.timing_interval=1440
system.ntp.def_server=time.nist.gov 128.138.140.44 192.36.144.22 time-a.nist.gov time-b.nist.gov time.windows.com time-nw.nist.gov au.pool.ntp.org nz.pool.ntp.org
You can change the list of NTP servers to something else with uci set
. However, because the settings are saved on a read only partition, this change won't survive a reboot (calling uci commit
will just revert the changes when it tries to re-read the settings from ROM again). Your best bet would just be to set the servers and restart the ntpd service in a startup script.
root@SLP:~# uci set system.ntp.def_server=10.1.2.12
root@SLP:~# /etc/init.d/sysntpd restart
Tapo API
There has been some work at reverse engineering the Tapo app API. Here are two main projects I've found:
- https://github.com/JurajNyiri/pytapo/blob/main/pytapo/__init__.py. pytapo has a lot of supported API calls
- https://github.com/KusoKaihatsuSha/appgotapo/. This library is quite slow because it makes a lot of unnecessary API calls for each call.
The API itself is quite simple and involves POSTing JSON payloads. Most options you see with uci can be set with it.
Get a token
You need to obtain a stok (security token?) before being able to issue commands.
POST to the camera with the following json payload containing the MD5 hash of your password.
{"method":"login","params":{"hashed":true,"username":"camadmin","password":"5F4DCC3B5AA765D61D8327DEB882CF99"}}
This should return your token if successful.
{"error_code": 0, "result" : { "stok": "85c34addb51fb200f9a9108417b9678c", "user_group": "third_account"}}
Sending commands
Commands are POSTed to the camera with the token as part of the URI: https://camera-ip/stok=$TOKEN$/ds
Things you can do are listed below.
What | Payload |
---|---|
Camera movement. Set your X/Y coord values as positive or negative steps. | {"method":"do","motor":{"move":{"y_coord":"0","x_coord":"-1"}}}
|
Set the OSD data | {"method":"set","OSD":{"date":{"enabled":"on","x_coor":6800,"y_coor":9400},"week":{"enabled":"off"},"font":{"color":"white","color_type":"auto","display":"ntnb","size":"auto"},"label_info_1":{"enabled":"off","text":"Howdy","x_coor":0,"y_coor":450}}}
|
Infrared / Night mode | {"method":"set","image":{"common":{"inf_type":"on"}}}
|
Using CURL
Perhaps something like this
#!/bin/bash
function stok() {
curl -s -k -X POST https://10.1.3.247 --data '{"method":"login","params":{"hashed":true,"username":"camadmin","password":"5F4DCC3B5AA765D61D8327DEB882CF99"}}' | grep -ohE '[0-9a-z]{32}'
}
Stok=$(stok)
curl -k -X POST https://10.1.3.247/stok=$Stok/ds --data '{"method":"do","motor":{"move":{"y_coord":"0","x_coord":"-1"}}}'
curl -k -X POST https://10.1.3.247/stok=$Stok/ds --data '{"method":"set","OSD":{"date":{"enabled":"on","x_coor":6800,"y_coor":9400},"week":{"enabled":"off"},"font":{"color":"white","color_type":"auto","display":"ntnb","size":"auto"},"label_info_1":{"enabled":"off","text":"Howdy","x_coor":0,"y_coor":450}}}'
Other
Setting WiFi connection details
You have to set the Wifi connection details from the app. You can try to use the /bin/onboarding.sh
script to change the connection details, but I didn't have luck with that.
See also
- https://drmnsamoliu.github.io/ - Lots of good information on the device hardware
- https://www.tp-link.com/us/support/faq/2680/ - RTSP information
- https://github.com/JurajNyiri/HomeAssistant-Tapo-Control - Home Assistant integration using pytapo. I opted to not use this but rather have Home Assistant call a custom bash script to simplify my setup.
- https://www.hacefresko.com/posts/tp-link-tapo-c200-unauthenticated-rce / https://github.com/hacefresko/CVE-2021-4045-PoC - A remote code execution bug for firmwares 1.1.16 or older. Exploits inappropriate handling of language payload in the API.