welcome to           __                           __ __  
.--.--.---.-.-----.--|  |-----.----.--------.-----|__|__| 
|  |  |  _  |     |  _  |  -__|   _|        |  -__|  |  | 
 \___/|___._|__|__|_____|_____|__| |__|__|__|_____|__|  | 
                                                    |___|.tech

Hacking a cloud pet feeder for full local use

📅 Published on

Some time ago I went on a trip, and against better judgement bought a smart pet feeder to take care of two adorable cats in my absence. All I want out of a device like this is to pull its lever through some kind of local API and an endpoint to integrate its camera into my network. But that product doesn't exist.

Instead, so-called smart appliances almost without exception require an Apple or Google phone for initial configuration and subsequent control of the device. Requiring customers to forward ports is problematic on many different levels, so instead a cloud platform sits in between to relay traffic between your smartphone and the target appliance. And since our cloud is already processing your data - how would you like a monthly subscription so we can store it longer or analyse it further..?

Pettadore Nutri View front view

This state of affairs is unfortunate, but I wasn't planning to abide by this normal operating mode anyway. So I simply picked a feeder that looked sturdy and whose manufacturer had been around for some time: the Pettadore Nutri View and got to work.

Previous work

Our particular device is powered by Tuya; a leading Chinese IoT cloud company in the smart home market. Tuya's platform lets makers buy pre-made hardware and software bundles for specified smart home goals. Just add a plastic shell and some graphics to individualize the included app and you're on your way to a product. To their credit, Tuya is pretty decent with opening up specifications (which is what developers crave) which has helped third party initiatives:

Libraries such as TinyTuya (python) or Tuyapi (node.js) provide a way to communicate with your Tuya device outside of the official apps. Using these libraries allow you to disconnect your feeder from the internet and control it over LAN instead. However, you are required to create a Tuya developer account to register your device and obtain a decryption key to communicate with it.

Another approach is taken by projects like ESPHome and Tasmota which flash the Tuya MCU (Micro Controller Unit) with alternative open-source firmware. Unfortunately only a subset of the chips used are supported.

Initial probing

In its unconfigured state, the feeder will boot with its wireless module set to AP mode, advertising an open network with SSID SmartLife_{random-id}. We connect to it and use nmap to discover any open ports:

vandermeij-tech ~ # nmap --open -sS -sU 192.168.10.1

Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-04 11:02 CEST
Nmap scan report for feeder (192.168.10.1)
Host is up (0.0039s latency).
Not shown: 1 closed udp port (port-unreach)
PORT      STATE         SERVICE
23/tcp    open          telnet
554/tcp   open          rtsp
6668/tcp  open          irc
51238/tcp open          unknown
67/udp    open|filtered dhcps
6669/udp  open|filtered ircu
8600/udp  open|filtered asterix
MAC Address: [REDACTED] (Shenzhen Bilian Electronic,LTD)

This full scan reveal some ports that we immediately recognize as being associated with Tuya, like tcp/6668, tcp/51238 and udp/6669. Our attention is drawn however to a much more interesting set of ports:

RTSP at tcp/554 is a protocol used for streaming media data over a network. This is most likely an endpoint for the frontside camera. A quick test reveals we'll need to trace its credentials but having this freely accessible already certainly makes things easier:

alexander@vandermeij-tech ~ $ mpv rtsp://192.168.10.1:554/stream1

[ffmpeg/demuxer] rtsp: method DESCRIBE failed: 401 Unauthorized
[lavf] avformat_open_input() failed
Failed to recognize file format.
Exiting... (Errors when loading file)

Telnet at tcp/23 however holds the big prize in the form of a management shell:

Trying 192.168.10.1...
Connected to 192.168.10.1.
Escape character is '^]'.

goke login:

Network login crackers like hydra support telnet, but at 350 tries per minute there wasn't much hope for a successful brute force entry and before long we decide to get our screwdrivers out instead.

Hardware analysis

Opening up the device reveals three separate circuit boards:

Nutri View PCB overview

On the left is a board that supplies power via usb-a and has headers for the feeding motor and sensors related to the hopper like IR to detect low-feed status and an open/closed detector for its lid.

In the middle is a controller board featuring some LEDs, a physical button to drive the motor and the Tuya MCU by way of a STM8 chip. The backside of this PCB also includes (SWIM) pads for programming, but this is beyond the scope of this article.

The board on the right is a SoC (System on a Chip) using a Goke GK7102 by Goke Microelectronics. Soldered on to the board is a Realtek RTL8188FTV wireless module.

GK7102 SoC UART connection

The backside of the SoC reveals a set of 1+3 pin holes that we identify as a UART debugging port. Attaching probes as shown above using RX/TX/GND (top to bottom) provides access to the system at baud 115200.

Entering the bootloader

A full system-boot.txt scrolls by, settling at presumably the same login prompt that we saw earlier through telnet. Only now we have a new weapon in our arsenal. By interrupting the bootloader from autobooting, we can enter its console and manipulate the kernel boot parameters.

Yet this board throws up another barrier - all commands seem to be locked until a password is given:

U-Boot 2012.10 (Jan 11 2019 - 21:23:59) for GK7102 rb-aijia-v2.0 (GOKE)

HAL:   20160913 
DRAM:  64 MiB
Flash: [W25Q64FV] USE 4X mode read and 4X mode write
8 MiB
SF:    8 MiB [page:256 Bytes] [sector:64 KiB] [count:128] (W25Q64FV)
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   Int PHY 
Hit Enter key to stop autoboot:  0
GK7102 # setenv
Password invalid, Please try again! :(
GK7102 # help
Password invalid, Please try again! :(
GK7102 # 

Password encoding was not introduced in U-boot until their 2015 releases, so it's a fair assumption that the password is written into the firmware as plain text. We don't even make an attempt at brute forcing here and instead turn to the hardware again.

Dumping the firmware

Right next to the CPU sits a Winbond W25Q64 memory chip that we can attach our SPI programmer on - after which we dump.txt the firmware using flashrom.

GK7102 firmware dump

Our output is a 8 Megabyte ROM image that at first didn't seem to offer any workable content. Did the readout get corrupted? Is the data encrypted? Even binwalk couldn't make much sense of it but claimed to find a big endian CRC table. At this point the concept of Endianness resurfaced, and after reversing the byte order..

objcopy -I binary -O binary --reverse-bytes=4 gk7102.rom

.. the analysis became what we expected:

alexander@vandermeij-tech ~ $ binwalk -t gk7102.rom

DECIMAL       HEXADECIMAL     DESCRIPTION
------------------------------------------------------------------------------------------------
115504        0x1C330         CRC32 polynomial table, little endian
262144        0x40000         Linux kernel ARM boot executable zImage (little-endian)
277531        0x43C1B         xz compressed data
277752        0x43CF8         xz compressed data
2359296       0x240000        Squashfs filesystem, little endian, version 4.0, compression:xz,
                              size: 902384 bytes, 361 inodes, blocksize: 65536 bytes, created:
                              2019-12-02 12:59:59
3276800       0x320000        JFFS2 filesystem, little endian
7405744       0x7100B0        JFFS2 filesystem, little endian
7471104       0x720000        JFFS2 filesystem, little endian
7767104       0x768440        JFFS2 filesystem, little endian
7798972       0x7700BC        JFFS2 filesystem, little endian
7861064       0x77F348        ESP Image (ESP32): segment count: 6, flash mode: QUIO, flash
                              speed: 40MHz, flash size: 1MB, entry address: 0x1, hash: none
7861192       0x77F3C8        ESP Image (ESP32): segment count: 6, flash mode: QUIO, flash
                              speed: 40MHz, flash size: 1MB, entry address: 0x2, hash: none
7864056       0x77FEF8        Zlib compressed data, compressed
7864320       0x780000        JFFS2 filesystem, little endian
7930040       0x7900B8        JFFS2 filesystem, little endian
7996832       0x7A05A0        JFFS2 filesystem, little endian
8026836       0x7A7AD4        JFFS2 filesystem, little endian
8061112       0x7B00B8        JFFS2 filesystem, little endian
8126464       0x7C0000        JFFS2 filesystem, little endian
8257720       0x7E00B8        JFFS2 filesystem, little endian
8322928       0x7EFF70        Zlib compressed data, compressed
8323072       0x7F0000        JFFS2 filesystem, little endian

Before the kernel starting at region 0x40000 exists a space of 262144 bytes which should be the bootloader. Using classic unix tools we extract it and take a closer look at its content:

dd if=gk7102-reversed.bin of=gk7102-bootloader.bin bs=1 skip=0 count=262144
strings gk7102-bootloader-bin

As suspected, the password is right there next to the Password invalid, Please try again! :( string. :-)

Accessing the system

Armed with the bootloader password, we re-enter its console. By changing the init parameter within the bootargs value, we can have the kernel boot with a different PID 1 process, effectively skipping the login prompt that stopped us before:

GK7102 # sepcam1688
Welcome to sepcam uboot :)
GK7102 #
GK7102 # setenv bootargs console=ttySGK0,115200 mem=39M root=/dev/mtdblock2 rootfstype=squashfs init=/bin/sh phytype=0 mtdparts=gk_flash:256k(uboot),2048k(kernel),896k(rootfs),4992k(data)
GK7102 # boot

The kernel boots and lands us in a bare shell. Not much to do or see here right now. It appears most files including /etc/passwd are symlinked from another mount point, so we examine the /sbin/init_s program we replaced in the previous step and manually run some of its commands so the root account info appears:

~ # ls -lh /etc/passwd
lrwxrwxrwx    1 1010     1010          15 Dec  2  2019 /etc/passwd -> /rom/etc/passwd
~ #
~ # mount -t tmpfs none /dev
~ # mknod /dev/mtdblock3 b 31 3
~ # mount -t jffs2 /dev/mtdblock3 /rom
~ #
~ # cat /etc/passwd
root:$1$EmcmB/9a$UrsXTlmYL/6eZ9A2ST2Yl/:0:0:Administrator:/:/bin/sh
~ #

Gaining root

Each line in /etc/passwd represents user account data, with fields separated by colons. The string in the second field has three parts, delimited by $, and begins with $1, indicating it's a salted MD5 hash. We're lucky in that MD5 is reasonably feasible to crack up to a certain complexity - unlike stronger hashing methods which can exceed the lifespan of the solar system.

It appears we are double lucky here though, because the password turns out to be a variation of the bootloader password we found earlier. Even on my 2013 Thinkpad, hashcat made quick work of it:

alexander@vandermeij-tech ~ $ hashcat -O -m 500 -a 3 '$1$EmcmB/9a$UrsXTlmYL/6eZ9A2ST2Yl/' sepcam?d?d?d?d?d?d --increment

$1$EmcmB/9a$UrsXTlmYL/6eZ9A2ST2Yl/:sepcam0128

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 500 (md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5))
Hash.Target......: $1$EmcmB/9a$UrsXTlmYL/6eZ9A2ST2Yl/
Time.Started.....: Tue Aug  4 12:54:25 2025 (0 secs)
Time.Estimated...: Tue Aug  4 12:54:25 2025 (0 secs)
Kernel.Feature...: Optimized Kernel
Guess.Mask.......: sepcam?d?d?d?d [10]
Guess.Queue......: 10/12 (83.33%)
Speed.#1.........:    23026 H/s (10.89ms) @ Accel:128 Loops:500 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 6144/10000 (61.44%)
Rejected.........: 0/6144 (0.00%)
Restore.Point....: 5632/10000 (56.32%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:500-1000
Candidate.Engine.: Device Generator
Candidates.#1....: sepcam0654 -> sepcam3328
Hardware.Mon.#1..: Temp: 80c Util: 91%

Started: Tue Aug  4 12:54:17 2025
Stopped: Tue Aug  4 12:54:26 2025

And indeed, after rebooting, we can now login without tools through telnet:

alexander@vandermeij-tech ~ $ telnet 192.168.10.1

Trying 192.168.10.1...
Connected to 192.168.10.1.
Escape character is '^]'.

goke login: root
Password: 
# uname -a
Linux goke 3.4.43 #36 PREEMPT Thu Aug 29 20:27:30 CST 2019 armv6l GNU/Linux

Process analysis

The system is equipped with more safeguards that hinder friendly tampering; a filesystem that is mostly read-only, and a kernel watchdog that protects two processes so that the system reboots if either crash or are otherwise interrupted:

/usr/bin/sepcamera is the main (C++) application which is attached to various peripherals:

170     /usr/bin/sepcamera      /dev/watchdog
170     /usr/bin/sepcamera      /dev/gk_fw
170     /usr/bin/sepcamera      /dev/gk_video
170     /usr/bin/sepcamera      /proc/goke/video_sync
170     /usr/bin/sepcamera      /dev/ai_dev
170     /usr/bin/sepcamera      /dev/ao_dev
170     /usr/bin/sepcamera      /dev/ttySGK1
170     /usr/bin/sepcamera      /dev/key_gpio
170     /usr/bin/sepcamera      /dev/adc
231     /usr/bin/sepcamera      /dev/watchdog

/system/bin/daemon.sh is a script that continuously monitors the health of other application processes. It notably includes logic to terminate tuyaapp and start p2papp whenever /tmp/factory_mode.txt exists. Besides making a bunch of connections to Chinese webservers, p2papp also opens a set of ports so it might be worthwhile to disassemble it and look for a debugging interface.

Also worth mentioning are processes like guard, guide and networksapp which are awefully chatty between themselves and sepcamera via IPC sockets. Going over the function names within the binaries it seems to us there is much overlapping functionality so it's hard to ascertain who does what without a closer look.

Process tracing

The system includes only BusyBox (2016) utilities, but using hexdump -C < /dev/ttySGK1 we can see sepcamera doing the typical Tuya dance (55 aa ♬ ..) with the TuyaMCU.

It would be convenient to have more advanced debugging tools to trace interactions between sepcamera and the rest of the system. Using crosstool-ng, we build.sh a compatible strace binary for the arm-unknown-linux-uclibcgnueabi target and make it available on a TFTP server for our system to fetch:

tftp -l /tmp/strace -r strace -g {tftpd-ipv4} {tftpd-port}

We then attach strace to the running sepcamera processes using:

#!/bin/sh

chmod +x /tmp/strace

for pid in $(pidof sepcamera); do
    /tmp/strace -f -t -y -p "$pid" -e trace=write,writev -s4096 -x -o /tmp/$pid.log &
done

Our primary goal is to enable the feeding motor. Both the official app and the physical button on the device manually trigger the motor. Unfortunately it appears the button's signal goes directly to the Tuya MCU because we only receive what seems like an acknowledgement for bookkeeping purposes on ttySGK1.

We bite our tongue and install the app on a Waydroid instance so we make a full trace of all its behaviour. Now that we hopefully have everything we need we hold the physical button for 5 seconds and let the appliance reset to factory defaults.

Motor control

Our strace log shows that Tuya commands are written to the ttySGK1 device when the motor is manually triggered, in line with the specifications defined in Tuya's Serial Communication Protocol . By matching the log timestamps with our button presses, the command to trigger the motor is identified. By sending the following command to ttySGK1 we can now trigger it on our terms:

printf "\x55\xaa\x00\x06\x00\x08\xc9\x02\x00\x04\x00\x00\x00\x01\xdd" > /dev/ttySGK1

Wireless configuration

The official procedure to pair the appliance with your home network involves entering your wifi credentials into the app, which encodes them into a QR code. When shown to the camera, the sepcamera process, upon recognition, initiates a sequence of actions to reconfigure your appliance and connect it to your network.

Snooping on sepcamera reveals we can trigger network configuration by creating a file instead:

printf '{"p":"my-wifi-password","s":"my-wifi-ssid","t":"tuya-device-token"}' > /tmp/QrMsg

We now have choice in front of us, neither of which is admittedly ideal. Connect the feeder to:

  1. network with internet access: the device will exchange data with the Tuya cloud for some kind of registration and create tuya_user.db. This encrypted database holds a master copy of your wifi credentials. Whenever the device is rebooted it will restore the credentials that were written into the database. I have not looked into manipulating the database as of yet, but if any reader knows of existing work here please let me know.

  2. network without internet access: we can be at ease knowing our data never leaves our network, but if the device ever loses power we'll have to perform wireless configuration again. I made a little script to re-apply the configuration and neuter the processes that will otherwise stop us from doing this.

If you’ve read this far, you’ve probably guessed we stuck a powerbank in the back and chose option 2.

Camera access

Returning to our RTSP endpoint, we look through the sepcamera binary using a disassembler and find that functions related to rtsp include references to a memory address that holds the string admin. Could it really be this easy? It appears so - we use nmap's rtsp-url-brute script look for URLs and find.txt them:

alexander@vandermeij-tech ~ $ ffprobe -hide_banner -rtsp_transport tcp "rtsp://admin:admin@feeder/video"

Input #0, rtsp, from 'rtsp://admin:admin@feeder/video':
  Metadata:
    title           : Session 
  Duration: N/A, start: -0.005478, bitrate: N/A
  Stream #0:0: Video: h264 (Main), yuv420p(progressive), 1280x720, 15 fps, 13.75 tbr, 90k tbn
  Stream #0:1: Audio: adpcm_g726le (g726le), 8000 Hz, mono, s16, 16 kb/s

Conclusion and future work

All we wanted was a simple device that respects our privacy - and now we have it.

As is often the case, it came at the cost of time and convenience. For now, we are content triggering the motor at preset times using an external cronjob, but various improvements could be made like adding a small service to monitor ttySGK1 for signals like "low food availability" or integrating the feeder as a custom component in Home Assistant.

Wiping the entire SoC to flash something like OpenIPC would be ideal, but there is no support for the GK7102 chip as of yet, and given the chip's age it's unlikely anyone will put in the work at this point.