Executive Summary
This research documents the investigation of CVE-2024-45694, a critical unauthenticated stack-based buffer overflow in the D-Link DIR-X5460 Wi-Fi 6 router. The vulnerability exists in the httpd web server binary's HTTP request parsing function, where an unbounded sscanf call writes attacker-controlled data into a fixed-size 1028-byte stack buffer, allowing full control of the ARM32 Program Counter.
Working entirely without physical hardware - using firmware extraction tools, QEMU emulation, and an LD_PRELOAD stub library to compensate for missing Broadcom CMS dependencies - I built a complete ret2libc ROP chain that bypasses NX and achieves arbitrary code execution under emulation. The chain proves full Program Counter control, end to end. It does not, however, translate into a reliable exploit against a physical device - and that gap, not the crash, is the most interesting part of this write-up.
Under emulation (ASLR disabled), a single 1092-byte HTTP GET to port 8080 achieves unauthenticated RCE as root, demonstrating full PC control. On a physical unit the same primitive is gated by ASLR with no usable bypass - see §08, The Wall.
| Property | Value |
|---|---|
| CVE | CVE-2024-45694 |
| CVSS Score | 9.8 (Critical) |
| Target Device | D-Link DIR-X5460 Rev A |
| Firmware | v1.01B02 (patched in v1.11B01) |
| Binary | /bin/httpd |
| Architecture | ARM 32-bit (EABI5), Little-Endian |
| Vulnerability Type | Stack-based Buffer Overflow (CWE-121) |
| Root Cause | Unbounded sscanf with %[^ ] format specifier |
| Authentication | Not required (pre-auth) |
| Exploit Technique | ret2libc via ROP (libc gadgets) |
| Impact | PC control proven; RCE as root demonstrated under emulation |
| Hardware Status | Weaponization gated by ASLR - characterized, unsolved |
Target Selection
Network appliances - routers, VPN gateways, firewalls - are the primary targets for nation-state actors in the 2024–2026 threat landscape. Fortinet, Ivanti, and D-Link have each had critical CVEs in this period. These devices often run embedded Linux on ARM processors with weaker exploit mitigations than desktop operating systems, making them ideal targets for demonstrating modern ROP chain construction.
The D-Link DIR-X5460 was selected after evaluating several candidates. CVE-2024-45694 is a stack-based buffer overflow with a CVSS score of 9.8 - the exact vulnerability class needed for a ROP demonstration. The firmware is freely downloadable from D-Link's support site, requiring no hardware purchase or license. The DIR-X5460 is a current-generation Wi-Fi 6 router used in homes and small offices, making it directly relevant to both ISP and enterprise audiences.
The vulnerability was patched in v1.11B01, whose release notes explicitly state "Vulnerability patch for firmware version 1.10" - confirming the attack surface. The v1.01B02 firmware was identified as the earliest available version, also vulnerable, and potentially easier to extract.
Three constraints defined this project from the start: no public PoC (the exploit had to be built from scratch), zero budget and zero hardware (the device was never purchased), and - as a direct consequence - everything runs under emulation. That last constraint is the reason every claim about real-hardware behaviour in this write-up is reasoned from the firmware, not measured on a device.
Firmware Extraction
Encryption Discovery
The first attempt to extract the firmware using binwalk produced only false positive signatures - PARity archive data, TROC filesystem, MySQL MISAM index. A hex dump revealed the truth:
00000000 65 6e 63 72 70 74 65 64 5f 69 6d 67 |encrpted_img|
The encrpted_img header (note: D-Link misspelled "encrypted") is a proprietary AES encryption wrapper. Standard tools - binwalk, dlink-decrypt, manual OpenSSL with known DIR-X series keys - all failed. The breakthrough came from unblob, a modern firmware extraction tool by ONEKEY that natively handles the D-Link Alpha encryption format.
The Matryoshka
The extracted firmware revealed a nested structure spanning four layers:
Encrypted Wrapper
The .bin file wrapped in the encrpted_img header. Decrypted by unblob.
UBI Volume
A UBI (Unsorted Block Image) container - standard for modern NAND flash in Wi-Fi 6 routers.
SquashFS Filesystem
A compressed read-only filesystem (squashfs_v4_le, 28.27 MB) containing the router's complete Linux root filesystem.
The Target
1,779 files across 121 directories. The vulnerable binary: ./final_rootfs/bin/httpd.
Binary Analysis
$ file ./final_rootfs/bin/httpd
ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux.so.3,
for GNU/Linux 4.1.0, stripped
The checksec results were a security researcher's dream - with one exception that doesn't show up in the output:
| Mitigation | Status | Impact |
|---|---|---|
| Stack Canary | Disabled | No stack cookie to bypass - direct overflow |
| PIE | Disabled | The binary's own code sits at fixed addresses |
| NX | Enabled | Cannot execute stack shellcode - must use ROP |
| RELRO | Partial | GOT partially writable |
| FORTIFY | Disabled | No compile-time overflow checks |
No canary means there is no stack cookie to leak or bypass. No PIE means the binary's own code addresses are deterministic. NX is the only mitigation checksec reports - and ROP is built to defeat it.
But there is a fourth control that checksec never prints, because it is not a property of the binary at all: ASLR. ASLR is a runtime setting of the kernel, and on this device it is on. The httpd image is No-PIE, but the C library it links against is loaded at a randomized base on every process start. As the exploitation and the section that follows it make clear, that single fact is the entire difference between a working emulation demo and a dead end on real hardware.
ARM processors support two instruction sets that switch on the fly. ARM mode uses 4-byte instructions; Thumb mode uses 2-byte instructions. The CPU decides which to use based on the LSB of the jump target address - odd means Thumb, even means ARM. The same bytes at the same memory location decode completely differently depending on which mode is active.
Vulnerability Discovery
D-Link routers have a long history of vulnerabilities in their HNAP (Home Network Administration Protocol) implementation. HNAP is a SOAP-based protocol for managing network devices, accessed via the SOAPAction HTTP header. It is processed before authentication, making it an unauthenticated attack surface.
A string search for "HTTP_SOAPACTION" in Ghidra led to FUN_00058ba0, internally called "ParseSetEnv" - the HTTP header parsing function. The decompiled code revealed the vulnerability:
__isoc99_sscanf(param_1,
"%[^ ] %[^ ] %[^ ]",
auStack_458, acStack_418, auStack_438);
The format string %[^ ] means "read everything until a space character" - with no length limit. The destination buffer acStack_418 is declared as char acStack_418[1028]. A URI longer than 1028 bytes overflows into the saved registers and the Link Register (return address).
The Function Epilogue
At address 0x00058f78, the function exits with:
00058f70 add sp, sp, #0x440 ; Restore stack
00058f74 add sp, sp, #0xc ; Restore stack
00058f78 ldmia sp!, {r4,r5,r6,r7,pc} ; Pop and RETURN
The ldmia instruction pops 5 values from the stack into registers r4, r5, r6, r7, and pc (the Program Counter). Whatever value is at the saved LR position goes directly into the PC. If we control that value, we control execution.
Emulation Challenges
The DIR-X5460 runs on a Broadcom BCM675x platform with a deeply intertwined proprietary software stack. Getting the vulnerable binary to process network requests without physical hardware required solving several problems.
The Broadcom CMS Problem
httpd depends on Broadcom's CMS (Configuration Management System), which requires smd (System Message Daemon), which requires nvram, which requires a physical Broadcom flash chip. The dependency chain: nvram → smd → cms → httpd. Without any of these, httpd refuses to start.
FirmAE Attempt
Full-system emulation with FirmAE was attempted. After patching six init scripts to skip Broadcom-specific hardware setup, the kernel booted and networking came up at 192.168.0.1. However, httpd's child processes crashed silently when handling requests due to missing CMS subsystem interactions.
The LD_PRELOAD Solution
The breakthrough was an LD_PRELOAD shared library (fake_cms.so) containing 123+ stub functions that intercept every CMS API call httpd makes. Critical discoveries during stub development:
cmsUtl_getRunTimePath returned a pointer
httpd checks orrs r6, r6, r0 - non-zero means error. Returning a pointer was interpreted as failure → exit(-10). Fix: return 0 for success.
cmsMsg_getEventHandle set fd to 0
After daemonize, fd 0 = /dev/null. httpd's select() loop monitoring fd 0 broke request handling. Fix: return -1 - httpd's code explicitly checks for -1 and skips the CMS fd.
GLIBC version mismatch
Host libc (2.38) vs firmware libc (2.26). At runtime: GLIBC_2.34 not found. Fix: cross-compile with --sysroot=./final_rootfs to link against the firmware's own libc.
One-byte binary patch
Changed beq to b (unconditional branch) at 0x1d4e4 in httpd's main event loop to skip the CMS event fd in select(). This single byte - 0x0a → 0xea - made child processes survive long enough to parse HTTP requests.
The Architecture
On the real router, two separate web services run in parallel. Only one is in scope here:
| Service | Ports | Notes |
|---|---|---|
lighttpd | 80, 443 | Front-end web UI. Requests pass through a FastCGI handler (prog.cgi) - a different parsing path, outside the scope of CVE-2024-45694. |
httpd | 8080, 4430 | Parses raw HTTP directly via the vulnerable sscanf. This is the CVE-2024-45694 target. |
Exploitation
Everything in this section was developed and demonstrated under emulation with ASLR disabled (the norandmaps kernel parameter). That condition is exactly what makes the addresses below deterministic - and §08 examines what happens the moment it is removed.
The Null Byte Problem
Every address inside httpd starts with 0x00 (e.g., 0x000a0c61 → little-endian \x61\x0c\x0a\x00). Since sscanf with %[^ ] reads C strings, null bytes terminate the payload before the ROP chain is delivered. The binary's own gadgets - the deterministic ones - are therefore unusable. The chain has to come from libc instead, which loads at 0xb66xxxxx - no null bytes in any packed byte.
Address Resolution
With ASLR disabled, libc loads at a deterministic base address. All runtime addresses below are null-byte-free and space-free (critical for the sscanf format) - but every one of them is a value that ASLR randomizes on a live device:
| Component | Address | Source |
|---|---|---|
pop {r0, pc} gadget | 0xb676c730 | libc + 0x10b730 |
system() | 0xb66986c8 | libc + 0x0376c8 |
"/bin/csh" | 0xb6783bd8 | libc + 0x122bd8 |
The ROP Chain
The chain calls system("/bin/csh"). For the proof of concept, /bin/csh was a script that executes telnetd -p 4444 -l /bin/sh, spawning a telnet shell on port 4444. Under emulation, connecting with nc target 4444 yields an interactive root shell.
The HTTP Request
GET /[1028 bytes padding][gadget]["/bin/csh" addr][system() addr] HTTP/1.1\r\n
Host: x\r\n\r\n
Total: 1092 bytes. Single packet. Unauthenticated.
The Wall: Why Weaponization Stops Here
Under emulation, the exploit is complete. Against a physical DIR-X5460, it is not - and after characterizing exactly why, I stopped trying to weaponize this CVE. Three constraints stack on top of each other, and together they close the door.
No-PIE addresses carry null bytes
Every address inside httpd is of the form 0x000xxxxx. Delivered through sscanf's %[^ ] - a C-string read - the leading null truncates the payload before the chain lands. The binary's own gadgets, the deterministic ones, are unusable. That forces the entire chain into libc.
libc means ret2libc, and ret2libc means defeating ASLR
libc addresses (0xb6xxxxxx) are null-free and usable - but libc's base is randomized on every process start. To call system() you must first know where libc is. The deterministic addresses from §07 only exist because emulation turned ASLR off.
There is no way to learn where libc is
The two classic routes to a libc base are both closed here. Brute force needs the service to come back after each wrong guess - but httpd is single-process, a wrong guess crashes it, and its CMS entity flags have EIF_AUTO_RELAUNCH (0x200) clear, so smd does not relaunch it. One wrong attempt takes the server down and it stays down: there is no second guess. An information leak would sidestep the brute entirely - but an extensive search of the pre-auth attack surface found no primitive that discloses a runtime address.
The emulation "shortcut" doesn't transfer either: under full ASLR the heap is randomized too, so the fixed command-string address baked into the emulation chain - roughly 21 bits of combined entropy, not 8 - was an artifact of the harness, not a property of the device.
Full Program Counter control is proven. The complete chain is demonstrated under emulation. Weaponization against a physical unit's full ASLR is characterized but unsolved. A single blind attempt sits on the order of one in two million and, on failure, simply takes the service down until reboot. That is not a reliable exploit, and I won't present it as one. This is where the CVE-2024-45694 exploitation effort ends.
One caveat on method, because it matters: every statement in this section is derived from static analysis of the firmware - the entity-flag value, the single-process model, the absence of a leak primitive. Consistent with the project's core constraint, no physical device was ever in the loop. The real-hardware verdict is a conclusion read out of the binary, not a result measured on hardware.
Key Takeaways
A CVSS 9.8 is a ceiling, not a guarantee. This bug is pre-auth, needs neither a canary nor PIE to reach PC control, and hands over the Program Counter cleanly - and it still does not yield a reliable exploit on the shipping device, because ASLR with no respawn and no leak closes every path to a libc base. The score measures impact and a theoretical notion of exploitability; it says nothing about whether a working exploit can actually be built. On this target, those two things have quietly decoupled.
Modern routers still ship without stack canaries and PIE in 2024 - the same fundamental gaps from two decades ago. A single unbounded sscanf is enough to seize the Program Counter on a current Wi-Fi 6 router; what stops the attack is a runtime mitigation the vendor did not have to write, not a hardened binary.
Firmware encryption is an obstacle, not a defense. Once one device in a product family is extracted, all firmware in the series can be analyzed. D-Link's encrpted_img wrapper added hours to the research but ultimately changed nothing.
Broadcom BCM675x platforms are extremely hard to emulate. The deep nvram → smd → cms dependency chain requires 123+ function stubs and careful attention to return-value semantics. A pointer return vs an integer return was the difference between httpd running and calling exit(-10).
The null-byte constraint is the hinge of the whole exploit. All httpd addresses start with 0x00, making them unusable through sscanf. That one detail pushes the chain into libc - and pushing the chain into libc is what hands the exploit straight into the ASLR wall above.
The vulnerability is real - patch it. D-Link DIR-X5460 firmware v1.11B01 fixes this issue. Reliable exploitation on current firmware is gated by ASLR - there is no public path from the crash to a stable shell - but the underlying memory-corruption primitive is pre-authentication and yields full PC control. A future information leak changes that calculus overnight. Treat any pre-2024 firmware as exposed and update.
AI-Assisted Vulnerability Research
This research would not have reached its current form without Claude Code Opus 4.8 (with max effort) I want to be transparent about its role, because I think it reflects where this work is heading.
What Claude Code did
The heavy lifting. It cross-compiled the fake_cms.so stub library (iterating through 123+ function signatures), debugged QEMU crashes in real time, rewrote stubs when return values caused subtle failures, calculated ROP gadget offsets, and built the final exploit script. When cmsUtl_getRunTimePath was returning a pointer instead of zero and httpd was calling exit(-10), it identified the ARM calling-convention issue from the disassembly and fixed it. The entire path from abandoned FirmAE to a working, network-delivered demo under emulation was a single collaborative session.
What this means for the field
AI doesn't replace the researcher. It didn't choose the target, decide to pivot from FirmAE to QEMU user-mode when full-system emulation failed, or judge that the Broadcom CMS architecture was too intertwined for simple stubbing. Those calls required understanding the problem space.
What AI does is compress the iteration cycle. The fake_cms.so library went through dozens of revisions - adding stubs, fixing return values, untangling GLIBC mismatches, throttling log spam. Each cycle that would have cost me 30–60 minutes of manual cross-compilation, deployment, and crash analysis collapsed to a few minutes.
And - just as importantly - AI did not beat the wall in §08. It did not conjure an information leak that wasn't there, and it could not make a non-respawning process respawn. The hard limit stayed hard for both of us. What AI changed was the cost of reaching a rigorous answer - including the rigorous answer that this particular door is closed. For a researcher who had never touched ARM assembly or Ghidra, that was the difference between abandoning the target at the first Broadcom dependency and producing a complete, honest characterization of it - wall included.
Earlier phases also involved conversations with other assistants for target selection, firmware-decryption guidance, GDB debugging, offset calculations, and ROP chain construction. The multi-tool approach reflected the reality of modern research - different tools have different strengths.
One Binary Is Never Alone
A vulnerability is rarely a property of a single binary. It is a property of a coding pattern - and patterns repeat. The unbounded, attacker-controlled parse that put Program Counter control within reach inside httpd is exactly the kind of mistake that tends to recur: in sibling binaries on the same device, in the same vendor's other models, in the next firmware that reuses the code.
httpd is where this write-up started. It is not where the search ended.
To be continued.