4 ms·
Both of the error messages we're given indicate that the "top" chunk of the heap was corrupted, which is a special internal allocation used by glibc malloc to r
by NobodyNada 2y ago
Both of the error messages we're given indicate that the "top" chunk of the heap was corrupted, which is a special internal allocation used by glibc malloc to represent any unused capacity from the last time malloc decided to grow the heap:
high memory addresses ("top" of heap)
| unmapped memory |
|-----------------|
| |
| top chunk |
| (mapped, but |
| not yet |
| allocated) |
| |
|-----------------|
| allocated chunk |
|-----------------|
| freed chunk |
|-----------------|
| allocated chunk |
| |
|-----------------|
| allocated chunk |
|-----------------|
| freed chunk |
|-----------------|
| tcache |
|-----------------|
low memory addresses ("bottom") of heap
That likely indicates a heap buffer overflow. If a call to malloc() doesn't find a freed chunk, it will split the top chunk in two and return a pointer to the bottom portion. If you then write past the end of the returned allocation, you clobber the metadata of the top chunk, and get errors like the ones in the article.
With the exploit mitigations built into modern Linux and glibc, it's a lot of work to go from here to arbitrary code execution; but it very well may be possible, depending on exactly how much control the attacker has over what atop does. The attacker can probably trigger the heap buffer overflow multiple times by spawning multiple processes, and if the length and contents of the heap buffer overwrite are attacker-controlled, they can probably play some games to overwrite any data stored in the heap. If that's true, the only thing preventing full arbitrary-code-execution is ASLR; there are many clever ways to get around that, but it's often quite difficult and may or may not be possible here.
This year's LACTF had a challenge with essentially this exact setup. My solution writeup is a good example of what it takes to defeat the exploit mitigations and turn a heap buffer overflow into an RCE: https://jonathankeller.net/ctf/lamp/ https://jonathankeller.net/ctf/lamp/
- progmetaldev 2y agoI thought I would ask, does this mean that memory restricted environments can be a better target for attack than one with a large amount of memory available? In my mind it seems like this would be the case, but I'm not sure if there is anything in place to protect these types of environments, like intentionally breaking up and segmenting memory so it's not possible to read much linearly. I admit that I haven't touched low-level code since the early 2000's, and earlier than that for anything other than a course requirement, so I apologize if you explained it in your linked article and I don't understand.
- NobodyNada 2y agoYes, with the caveat that virtual memory restrictions matter a lot more than physical memory restrictions. Heap exploitation is difficult because 1) glibc malloc is hardened to try to defeat many common exploit strategies, and 2) ASLR means that even if you have the ability to corrupt a pointer, you might not be able to control what the pointer points to. Regarding #1, memory constraints don't really make a difference until you're so constrained on memory that you can't run glibc anymore, so you use an allocator that is optimized for low overhead/code size rather than performance and security. For #2: ASLR works by placing every section of the process (heap, stack, program binary, and each library) at a randomized virtual address so that attackers can't forge pointers. ASLR is much more effective on a 64-bit system than on a 32-bit system, simply because there's so much virtual address space available to choose. If memory addresses are only 32 bits, it's feasible to just brute force guess the memory address of some data you're interested in; with 64 bits, it's not. And if your system doesn't have virtual memory at all (like a microcontroller), you probably don't have any kind of ASLR. > intentionally breaking up and segmenting memory so it's not possible to read much linearly This is typically done at the level of individual sections (stack, heap, program binary, library binaries) but not within sections; mainly for performance, memory overhead, and cache locality reasons. The entire heap is contiguous, so if you overwrite past the end of one heap allocation you can overwrite adjacent allocations, but you can't overwrite the stack (without more work). Breaking up the heap into smaller chunks wouldn't really help that much; it just means an attacker has to manipulate the heap layout so they can be sure the allocation they're targeting ends up in the chunk they're targeting. Exploit hardening in the heap allocator is something of a last-ditch stopgap measure: "we've already lost, but let's see if we can minimize the damage to the user/maximize the difficulty to the attacker". There's certainly much more you could do to harden the heap, but remember that any security measures implemented in glibc are applied to every program, with no way to opt out (except bringing your own libc). So glibc is designed to maximize performance and compatibility; exploit mitigations are only included when they doesn't compromise these goals. If you don't mind giving up some performance for the sake of security, you probably would already be using a garbage-collected language instead of C. (And now that Rust is sufficiently mature for most use cases, you probably should be using that if you're in a situation where you need both performance and security).
- LPisGood 2y agoIt’s a lot easier to use an exploit if you know your chunk is from fast bins if I recall correctly.