132 - 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 ad_header_read_osx()
calls 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.
Exploitation
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.
Exploitation
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 system
to.
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 next
.
Exploitation
Two objects were primarily used for building primitives, user_key_payload
(from the add_key()
syscall) and msg_msg
from 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 load_msg()
will 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_path
can be smashed with arbitrary data to get root.