Rooting a VMC2040 security camera part 6: What did work

Posted by Marcus Folkesson on Thursday, May 7, 2026

Rooting a VMC2040 security camera part 6: What did work

Brief

In this part I will show how I finally got root access to the camera.

/media/tux-root.png

The other parts of the series are:

  • Part1: Basic examination
  • Part2: Extract the firmware
  • Part3: Analyse the boot sequence
  • Part4: Deeper analysis
  • Part5: What didn't work
  • Part6: What did work
  • Part7: Conclusion and summary

Replace config_tool

So, the next step is to replace the /config/config_tool binary with something that gives us a root shell. In the "Developer" mode, the startSPARROW starts a telnetd server, so that could be a good start.

But where is telnetd?

The telnetd symlink is removed from the production image, but it appearently use the same busybox binary as it still contains the telnetd applet:

1	$ strings busybox  | grep telnetd
2	telnetd

Our own config_tool

I wrote a simple C program that forks, the child becomes a detached telnetd daemon and the parent execs the real config_tool to keep the boot sequence happy. The child process redirects stdio to /dev/null so it doesn't interfere with anything.

 1	#include <unistd.h>
 2	#include <fcntl.h>
 3	#include <sys/types.h>
 4
 5	int main(int argc, char *argv[])
 6	{
 7	    pid_t pid = fork();
 8
 9	    if (pid == 0) {
10		/* child become a detached telnetd daemon */
11		setsid();   /* new session, detach from any controlling terminal */
12
13		/* redirect stdio to /dev/null so it doesn't interfere with anything */
14		int fd = open("/dev/null", O_RDWR);
15		if (fd >= 0) {
16		    dup2(fd, 0);
17		    dup2(fd, 1);
18		    dup2(fd, 2);
19		    if (fd > 2) close(fd);
20		}
21
22		/* busybox telnetd: -l /bin/sh gives a root shell without a password prompt */
23		char *telnetd_argv[] = { "busybox", "telnetd", "-l", "/bin/sh", NULL };
24		execv("/bin/busybox", telnetd_argv);
25		_exit(1);
26	    }
27
28	    /* ── parent: exec the real config_tool as fast as possible ──
29	     *
30	     * argv[0] is already "/config/config_tool" (set by call_usermodehelper),
31	     * so we pass argv unchanged.
32	     */
33	    execv("/sdk/config_tool", argv);
34
35	    /* should never be reached */
36	    return 1;
37	}

I took a toolchain from ARM [1] and compiled it with:

1	CC=.../arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-gcc
2	$CC -static -Os -o myhack myhack.c

It is important to compile it as a static binary because the dynamic linker and libraries are in the read-only rootfs, and we don't want to mess with those. A static binary will include everything it needs to run without relying on external libraries nor the dynamic linker.

I made the /config/config_tool symlink point to my own binary on the UBI volume:

1	ln -sf /config/myhack /config/config_tool

And rebooted the camera.

Root access

Finally! After the camera booted up, I could connect to it with telnet and got a root shell:

/media/arlo-root.png

Take it further

Telnet is not the most secure way to access a shell, a better way would be to use SSH, but we still don't know the root password. Instead of starting telnetd, we could do other things in the child process, e.g. bind mount a custom /etc with a modified /etc/passwd file:

1	mount -o bind /config/myrootfs/etc /etc

And start the dropbear SSH server:

1	dropbear
/media/arlo-ssh.png

And there we go.

Summary

Finally root access. In Part7 I will give some thoughts about the security of the camera and what to do to prevent this kind of attack.