tl;dr; Calls to memmove(); that use a source buffer that is smaller than the destination buffer can be at times exploitable if the size value is bit aligned, is mapped in memory and that the original source buffer is also mapped in memory.
So, the other day I was debugging a vulnerability I had found and trying to understand the issue by performing a root cause analysis (RCA). I was, all up in windbg doing my thing, setting break points and getting all crazy. I ended up with the following breakpoint when messing in the windbg.
So this breakpoint will only break into the debugger if a call to memmove has the second argument set to null. Checking my windbg log, I see the following:
calling memmove(0x1613fe8, 0x0, 0x0161aa10);
Everyone knows that the memmove prototype across architectures is:
void *memmove( void *dest, const void *src, size_t count );
So naturally, I attempt to investigate the situation.
As it turns out, the size value is actually a mapped heap chunk! To make it worse (or better), the least significant bytes are always mapped to offset 0xaa10, which I can control based on allocation size in the target. It looks like the developer of my target got his/her parameters mixed up! My guess is that the second and third arguments should have switched:
calling memmove(0x1613fe8, 0x0161aa10, 0x0);
Anyway, when I continued execution, I was surprised to see the following output:
An Out-of-Bounds write on unmapped memory? How could this be? Well as it turns out, the copy operation is doing a backwards copy. To find that out I dove into the Microsoft’s implementation of memmove within the 32bit architecture. I used the following DLL: C:\Windows\System32\msvcrt.dll v7.0.7601.1744 (latest at the time of writing).
As you can see, its very similar to OS X’s implementation or GNU’s implementation. So the same situation would happen on Linux or Mac OS X under 32 bit implementations. What you will notice here is that there is no sanity check on the source buffer whatsoever inside memmove. The source buffer is NULL? No worries, continue execution. However, do note that if NULL is not mapped, then an access violation will occur since the backwards copy performs a – instead of a ++. Thanks to @badd1e for pointing this out.
Depending on how you are targeting your exploitation, an access violation might be ok as you can potentially use the initial out-of-bounds write to target an
unhandled exception function pointer.
Regarding exploitation, the following is needed:
(void *)0x0 as the source buffer.Actually, as long as the src buffer is smaller than the dst buffer, this is still possible.
- You will likely need whatever the src value is, to be mapped in memory. If it is null, then the null page will need to be mapped to survive the copy operation.
- A bit aligned size value, in my case it was 32bits or 4 bytes
- The destination + size to point to a mapped and writable location in memory.
- The size value to be a valid pointer to controlled data, rare indeed.
Now I know what a lot of you neckbeards are going to say, that developers should be careful about the parameters parsed to mem* functions. But the simple matter is, is that a simple check for a source buffer that is not mapped would have made this particular vulnerability un-exploitable.
Whilst this is a very bizarre corner case, it goes to show that the lack of sanity checking for the sake of speed can cause all sorts of undesired effects, potentially leading to an exploitable condition.
Since I can relatively control the size value (based on the allocation bucket) and the source buffer passed into memmove() is always NULL, I can trigger a relative wild write at a semi-controlled location. Sure, not the most amazing primitive, but when the application is installed and running as SYSTEM on 99% of enterprise applications, hackers become motivated.