Rooting a VMC2040 security camera part 1: Basic examination
Brief
My friend had a Arlo VMC2040 security camera [1] on his shelf. He bought it a while ago, but never really used it as it required a subscription to work properly. The only feature that he wanted was to get the video stream out from the camera without any cloud involvement, but that was unfortunately not possible.
Here in Sweden we have a long holiday due to Easter, so I decided to take a look at the camera and see what's possible. It would be interresting to see how secure a security camera actually is, and if there are any backdoors or debug features left enabled in the firmware.
The first part of the series is focused on examine the hardware and software to get as much information as possible out from the camera.
The series is split up into seven parts:
Examine The Hardware
The first thing to do is to examine the hardware to get an idea of what we're dealing with. The VMC2040 is pretty much what to expect from a cloud connected camera:
- SoC: MStar/SigmaStar Infinity6 (SSC009B-S01A) - ARM-based
- NAND Flash: GigaDevice GD5F1GQ4UBYIG - 128MB SPI NAND
- RAM: 128 MB DDR3
- WiFi: MediaTek MT7682
- Sensors: PIR motion detector, light sensor, speaker
In search for UART
Allmost every embedded system has a UART console for debugging, so I searched for pads and test points that might be connected to the UART pins on the SOC. These pads were hot condidates:
Yep. I've verified that the pins are connected to the UART pins of the SoC:
There were also UART pins to the WiFi module, but I didn't care about those.
Examine The Software
Now when we have a UART available, lets see if we get any output on the console when we power up the camera.
The initial bootloader
The output from the initial bootloader gives us some useful information:
IPL b4d3638 D-0b miupll_200MHz 256MB BIST0_0001-OK Load IPL_CUST from SPINAND CLK->36 SPI+54M SNI: 0010,0000,0005 BlSize 00003ed0 SNI: 0010,0000,0005 CUST_KEY Checksum OK IPL_CUSTb4d3638 CLK->36 SPI+54M SNI: 0016,001c,0005 SNI: 0016,001c,0005 Load BL from SPINAND CUST Key KEYN_SIZE(0x0100) KEYN_ADDRESS(0x23c03cd0) decomp_size=0x000784dc
We see that the next bootloader is loaded from the SPI-NAND:
Load BL from SPINAND
And that the bootloader is probably signed:
CUST Key KEYN_SIZE(0x0100) KEYN_ADDRESS(0x23c03cd0) decomp_size=0x000784dc
U-Boot
Next comes the U-boot:
U-Boot 2015.01 (May 12 2021 - 03:08:24), Build: jenkins-CI-VMC2040-master-619 Version: I6gf088bce I2C: ready DRAM: WARNING: Caches not enabled SPINAND_I: SPINAND: _MDrv_SPINAND_GET_INFO: Found spinand INFO (0xC8), (0xD9), GD Quad mode enabled SPINAND: board_nand_init: CIS contains part info 128 MiB MMC: MStar SD/MMC: 0 INFO: ENV loaded In: serial Out: serial Err: serial No ethernet for Sparrow... PEGA-UBOOT VERSION:1.010-PRODUCTION gpio debug MHal_GPIO_Pad_Set:603 gpio debug MHal_GPIO_Pad_Set:603 gpio debug MHal_GPIO_Pad_Set:603 gpio debug MHal_GPIO_Pad_Set:603 reset wifi done secure_value 0x9, secure_flg 1:Enabled NAND read: device 0 offset 0x200000, size 0x20000 Time:13059 us, speed:10036 kBps 131072 bytes read: OK # Booting No.1 rootfs/kernel # - Checking rootfs [ROOTFS]... NAND read: device 0 offset 0x1300000, size 0x1000 Time:608 us, speed:6736 kBps 4096 bytes read: OK Magic number 0x73717368. Magic number 0x73717368 is a squashfs archive. squashfs image_size 0xf76000 NAND read: device 0 offset 0x1300000, size 0xf77000 Time:1612774 us, speed:2065 kBps 16216064 bytes read: OK secure checking ... [U-Boot] Key in IPL_CUST Magic number 0x73717368. Magic number 0x73717368 is a squashfs archive. squashfs image_size 0xf76000 runAuthenticate: compare RSA address --> MDrv_RSA_Run done [U-Boot] Authenticate pass! Pass - Checking kernel [KERNEL]... NAND read: device 0 offset 0x780000, size 0x1000 Time:607 us, speed:6747 kBps 4096 bytes read: OK Magic number 0x56190527. Magic number 0x56190527 is a kernel. data_size 0x1a7f1c, 0x1c7f1a00 kernel image_size 0x1a7f5c NAND read: device 0 offset 0x780000, size 0x1a9000 Time:173275 us, speed:10046 kBps 1740800 bytes read: OK secure checking ... [U-Boot] Key in IPL_CUST runAuthenticate: compare RSA address --> MDrv_RSA_Run done [U-Boot] Authenticate pass! Pass bootargs rootfs is root=/dev/mtdblock9 already. - bootm run kernel... ## Booting kernel from Legacy Image at 22000000 ... Image Name: MVX2##I6gf5c0b43KL_LX409####[BR: Image Type: ARM Linux Kernel Image (lzma compressed) Data Size: 1736476 Bytes = 1.7 MiB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK -usb_stop(USB_PORT0) -usb_stop(USB_PORT2) Uncompressing Kernel Image ... [XZ] !!!reserved 0x21000000 length=0x 1000000 for xz!! XZ: uncompressed size=0x377000, ret=7 OK ERR: Can't find KIMG header and initrd address, 0x00000000 atags:0x20000000 Starting kernel ...
Even this dump has some valuable information. We see that the U-boot is quite old (from 2015):
U-Boot 2015.01 (May 12 2021 - 03:08:24), Build: jenkins-CI-VMC2040-master-619
The U-boot environment is loaded in runtime, probably from flash:
INFO: ENV loaded
Both the kernel and the SquashFS root filesystem is RSA-signed and verified against keys in the IPL_CUST partition:
secure checking ... [U-Boot] Key in IPL_CUST runAuthenticate: compare RSA address --> MDrv_RSA_Run done [U-Boot] Authenticate pass! Pass
It is not possible to stop the boot process and get a U-boot prompt by sending key strokes to the UART.
The kernel
The kernel log is quite long, so long that I only paste the interesting lines here.
We can see that we are running a pretty old kernel as well:
Linux version 4.9.84 (root@firmware-device-build-1754937378489-33-mcz0d-g35sm) (gcc version 8.3.0 (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) ) #20 PREEMPT Mon Aug 11 19:03:09 UTC 2025
We already knew the SoC is an Infinity6, but it's nice to get it confirmed:
OF: fdt:Machine model: INFINITY6 SSC009B-S01A QFN128
The kernel command line is also interesting. It gives us the partition layout and the UBI parameters:
Kernel command line: ubi.mtd=11,2048 root=/dev/mtdblock9 rootfstype=squashfs ro init=/linuxrc LX_MEM=0xffc6000 mma_heap=mma_heap_name0,miu=0,sz=0x2400000 mma_memblock_remove=1 mtdparts=nand0:0xC0000@0x140000(IPL0),0xC0000(IPL_CUST0),0xC0000(UBOOT0),0xC0000(UBOOT1),0x140000(ENV),0x140000(ENV_P1),0xC0000(MANUFACTURE),0x5C0000(KERNEL),0x5C0000(KERNEL_P1),0x1E00000(ROOTFS),0x1E00000(ROOTFS_P1),-(UBI)
The SoC has a hardware AES engine, so we should not expect any keys to be stored in plaintext in the rootfs:
[SAR] infinity_sar_probe MSYS: DMEM request: [AESDMA_ENG]:0x00001000 MSYS: DMEM request: [AESDMA_ENG]:0x00001000 success, CPU phy:@0x2D647000, virt:@0xCD647000 MSYS: DMEM request: [AESDMA_ENG1]:0x00001000 MSYS: DMEM request: [AESDMA_ENG1]:0x00001000 success, CPU phy:@0x2D648000, virt:@0xCD648000 infinity_aes soc:aesdma: SSTAR AES engine enabled. cryptodev: driver aesdmadev loaded.
The MTD partitions and their address range:
0x000000380000-0x000000440000 : "UBOOT1" 0x000000440000-0x000000580000 : "ENV" 0x000000580000-0x0000006c0000 : "ENV_P1" 0x0000006c0000-0x000000780000 : "MANUFACTURE" 0x000000780000-0x000000d40000 : "KERNEL" 0x000000d40000-0x000001300000 : "KERNEL_P1" 0x000001300000-0x000003100000 : "ROOTFS" 0x000003100000-0x000004f00000 : "ROOTFS_P1" 0x000004f00000-0x000008000000 : "UBI"
The UBI partition has a volume called "nvrservice"
ubi0: attaching mtd11 ubi0: VID header offset: 2048 (aligned 2048), data offset: 4096 ubi0: LEB size: 126976 bytes (124.0 KiB) ubi0: PEB size: 131072 bytes (128.0 KiB), min. I/O size: 2048 bytes ubi0: attached mtd11 (name "UBI", size 82 MiB) ubi0: Volume name "nvrservice", ID 0
Near the end of boot I have this log:
Press Enter to activate console
Unfortunately it doesn't work to press Enter. We will find out why later on.
Portscan
No hack-friendly services were running on the target:
1$ nmap 192.168.1.12
2Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-04 15:45 +0200
3Nmap scan report for 192.168.1.12
4Host is up (0.012s latency).
5Not shown: 997 closed tcp ports (conn-refused)
6PORT STATE SERVICE
7554/tcp open rtsp
85061/tcp open sip-tls
9
10Nmap done: 1 IP address (1 host up) scanned in 4.07 secondsWhat we know so far
So far we know that:
- The SoC is a ARM-based SigmaStar Infinity6 (SSC009B-S01A)
- All software is on a SPINAND flash (GigaDevice GD5F1GQ4UBYIG)
- We have physical connection to a debug UART
- The ROM bootloader load a custom signed bootloader from flash
- U-Boot version is 2015.01
- The kernel is Linux 4.9.84
- The root filesystem is a SquashFS
- The VID header offset for UBI is 2048 bytes
- The UBI partition has a volume called nvservice
- Both kernel and rootfs are RSA-signed and verified against keys in the IPL_CUST partition
- The U-Boot environment is not signed and loaded from flash at runtime
- No obvious services to hack via the network
We also have figured out how the SPINAND flash is partitioned and (guessed) what each partition is used for:
| Partition | Signed? | Notes |
|---|---|---|
| IPL0 | Yes | Initial Program Loader |
| IPL_CUST0 | No? | Contains RSA public keys |
| UBOOT0/1 | Yes | Bootloader |
| ENV/ENV_P1 | No | U-boot environment |
| KERNEL | Yes | |
| ROOTFS | Yes | |
| UBI | No | Mounted RW |
As The IPL0, U-Boot, Kernel and the rootfs are all signed, we can't modify them without the private key. The only writable partitions are ENV and UBI.
Summary
In this part we have examined the hardware and software to get as much information as possible about the camera. We have also identified the flash partitions and their purpose, and we know that the only writable partitions are ENV and UBI.
In Part2, we will extract the firmware from the flash and take a closer look at what it contains.