Pwning WD NAS, NetGear Routers, and Overflowing Kernel Pages
An out of bounds access bug in the netatalk open source library for the Apple Filing Protocol (AFP) that could be exploited on WD PR4100 NAS for pre-auth RCE. The bug happens when parsing resource fork file headers, which can be reached through the
afp_openfork handler over AFP or SMB without authentication. Resource fork file headers contain entry descriptors, which have an offset and length for the backing data. When
parse_entries(), it will check if an offset is out of bounds and set a failure return value. Even though this return value was checked, it just throws a warning and continues on in the error handling. This leads to the offset being used later on when
ad_convert_osx() is called and uses it for a
memmove(). The offset is used in calculating both the source and destination, and the attacker has control over the size of the copy via the entry length.
This bug was exploited twice to full chain, as the binary was ASLR’d and position independent (PIE). First, it was used to force the
memmove() source stack pointer out of bounds to leak stack data via saving to a file which could be exfil’d over SMB. This allowed them to defeat ASLR and resolve the address of
system(). The control flow hijack was a bit more interesting. They found by providing a file that was less than 0x1000 in size, they were able to get the file mapped 0xC000 bytes before the ld (dynamic linker) library. They could corrupt the .data segment to overwrite the
_dl_rtld_lock_recursive function pointer. Since
memmove() was being used which uses SSE (and SSE requires 16-byte alignment for operands), it was possible to get this pointer called by intentionally triggering an SSE exception through misaligning the source pointer.
Two issues, the first simply being that the update check would make an HTTPS request but not validate the certificate, enabling some attack surface for a Man-in-the-Middle, second was in parsing the file downloaded a checksum would be copied from the file into a fixed size stack buffer. As an attacker can control the response file, the attack can overflow the stack buffer.
The first issue, is not terribly interesting, it uses
curl to periodically download an info file that contains version information and a db checksum and the call uses the
-k flag to disable certificate checking.
The second issue is in parsing that file, it reads line by line using fgets then uses
sscanf() to read two
%s strings into two 256-byte character arrays. As
%s does not provide any bounds, and the
fgets reads 1024 bytes, a value longer than 256-bytes will overflow the character array.
Exploiting this issue started with a “one-gadget”. The gadget loaded a value from stack memory, added it to another register and then called system. As the stack value was controlled this could be a pretty easy gadget to use. Additionally as only partial ASLR in was in play (heap wasn’t randomized, but shared libraries and stack was). The binary was also not a position independent executable so the gadget’s location would remain constant in memory. Some work was needed though to figure out where to point the argument for
They took a bruteforce approach as the heap location was partially known and they could retry (with a time delay), and crafted their
system payload in a way such that the argument pointer jsut needed to land somewhere within a 200+ byte region and their payload would be executed.
The vulnerability here is a fairly straightforward overflow in the esp6 crypto module. When receiving messages, an 8-page buffer is allocated for the incoming data, but it’s possible for messages to be sent that exceed 8 pages in size. When it comes to exploitation though, it gets more complex, as this overflow is at the page-level and not the cache-level.
Background Without going into too much detail, kmalloc caches request pages from the page allocator when needed. Depending on the object size, different “order” pages are requested. Order-0 pages consist of 1 page, order-1 of 2 pages, order-2 of 4 pages, and order-3 of 8 pages. Where the overflow is in an 8-page buffer, corruption will happen in order-3 pages. Only kmalloc-2k, kmalloc-4k, and kmalloc-8k allocate from order-3. After some heap shaping and mitigating the noise of the page allocator, the author was able to get corruption of carefully setup objects in the kmalloc-4k cache.
Another important bit of background for understanding the primitives is on the
msg_msg object. If a message exceeds 4048 bytes in size, the
next pointer will be allocated and used to store excess data. For example, if you pass a 4056-byte message, a
msg_msg object will be allocated for the first 4048 bytes, then the last 8 bytes will be allocated in kmalloc-32 and stored in
Two objects were primarily used for building primitives,
user_key_payload (from the
add_key() syscall) and
sendmsg/recvmsg. By setting up a
user_key_payload adjacent to the overflow pages, it was possible to corrupt it’s
datalen to get an out of bounds read to leak
msg_msg objects on an adjacent page. These
msg_msg objects would contain a
next pointer into kmalloc-32. By then exploiting the bug again to corrupt a
msg_msg object directly and smash it’s
m_ts (message text size) and
next pointer to one leaked in the first step, OOB read in kmalloc-32 can be achieved. This is useful because
seq_operations objects end up in this cache (which contain .text pointers), and leaking these objects can be used to defeat kASLR.
Finally, the bug can be exploited once more to obtain arbitrary write, again using
msg_msg. Recall that messages > 4048 bytes will use
next for extra storage, and
copy_from_user() data to it. By freezing the first copy using the FUSE trick, corrupting the
next pointer with a pointer to
modprobe_path, and resuming the copy,
modprobe_pathcan be smashed with arbitrary data to get root.