First, a small clarification: Classic heap sprays never needed to fill the entire virtual address anyway. They only needed to fill enough of memory to cover the range of uncertainty you have for the address of whether the shellcode will be. Thus, 100MB or so were enough.
The picture changed a lot with the advent of DEP, because one can no longer spray shellcode; now one must spray a ROP stack (for instance). You might think that ASLR also made heap spraying harder, but VirtualAlloc
allocates at deterministic addresses even if ASLR is enabled (maybe it still does), which made predicting the address a lot easier. Finally, the 64-bit address space increased the amount of variability available to ASLR, which has the potential to make some heap spray attacks harder.
The biggest change is found in Windows 8, which made two major changes that will likely make heap sprays more challenging. First, HiASLR enables greater entropy for ASLR. On 64-bit platforms, HiASLR introduces a 1TB range of possibilities for the base of the heap. This makes it harder to predict the address of something on the heap. Second, Windows 8 makes allocations non-deterministic: when you allocate a object using the default allocator, the position that is used is randomized (it is no longer deterministic), introducing fine-grained randomization at the individual object level. Object-by-object randomization is not used for everything (only for the LFH allocator, which I think is only used for heap objects that are at most 16KB in size), but it will add an additional challenge.
That said, heap spraying may still be possible in some cases. The situation where heap spraying becomes useful is where we have partial knowledge of a pointer value (or where some object will be in memory), but not exact knowledge. In that situation, a heap spray can still be a useful technique to make the exploit reliable despite the lack of perfect predictability in that address.
For instance, one example is Ivan Fratric's exploit of 64-bit IE11, where he used a heap spray to make the exploit reliable. In that vulnerability, the attacker could trigger a write to address A
+256MB, where A
is the address of some heap object. Due to ASLR, we can't predict the value of A
, and due to the 64-bit heap, we can't spray enough to fill all of the heap -- but Ivan noticed that in this case it is enough to spray around 256MB of data into the heap. This makes it likely that the address of a random heap object, plus 256MB, will land into the sprayed region.
So, as Ivan Frartic explains, heap spraying can still be useful for the attacker if "we can make a vulnerable application dereference memory at a valid heap address + a large offset" (for instance, imagine the buggy code do_something_with(a[i])
, where i
might be a offset that points past the end of the array). He gives another example of an earlier vulnerability that also had this form, to illustrate that this case is common enough.
Finally, 32-bit processes might be used in cases you wouldn't expect. For instance, even 64-bit IE on 64-bit platforms will use 32-bit child processes for each new tab, on Windows 8.1. Who knew?
Bottom line: No, we can't say that there's no need to worry about heap sprays against 64-bit processes. That was indeed too naive.