Google NUL Poison attack
Table of contents
- Introduction
- Required reading
- Constraints and and
NUL
termination errors - Heap manipulation
- References
Introduction
Memory corruption vulnerabilities can arise from very benign programming errors and can lead to arbitrary code execution if exploited by a skilled attacker.
One byte overflows are no different and can be leveraged to craft fully fledged exploits against binaries linked to the Gnu C Library’s memory allocator, which is derived from ptmalloc (pthreads malloc), which in turn is derived from dlmalloc (Doug Lea malloc).
This technique was originally discovered by Google Project Zero’s team, as an
updated version of The House of Einherjar,
a NUL
byte overflow exploitation technique dating several years prior to 2014.
As many modern heap exploitation techniques, it can be used to leak memory addresses by obtaining two malloc chunks overlapping the same memory region. One considered free and the other one allocated and attacker controlled.
This post covers backward consolidation specifically, but the principles can be applied to forward consolidation as well.
Required reading
The reader should be familiar with malloc internals before reading the following chapters:
Constraints and and NUL
termination errors
One of the core differences in this version of the attack
is the lack of control of the prev_size
field. NUL
termination errors are typical of string related tasks,
therefore an attacker would not be able to
provide a prev_size
containing NUL
bytes
as they would terminate the string supplied
prematurely, being restricted to impractically
large values.
For instance, 0x0000000000000090
would terminate the string
provided before overflowing into the size
field.
Even worse, they might not be able to control the prev_size
quadword at all
, which might have program-defined value (such as a string suffix).
Heap manipulation
In order to achieve its goal, which is creating two overlapping chunks, an
attacker needs to be able to somewhat control memory allocations.
Such degree of control over the memory layout may look unrealistic at first
sight, but its actually a very common instance of any program running a
scripting engine.
Initial memory state
Note: this assumes a GLIBC version < 2.26
Start by allocating 4 consecutive chunks A
, B
, C
and D
:will of sizes
0x20
, 0x110
, 0x90
, 0x20
respectively. Note that chunk D
is only used
to prevent consolidation with the top chunk and as such is not strictly necessary.
Free chunk B
, the runtime will set C.prev_size = 0x210
and C.prev_inuse =
0
. Chunk B
is added to the unsortedbin as its size does not fit any of the
fastbins (singly-linked lists holding sizes from 0x20
to 0xb0
)
Leveraging the overflow
Now it’s the time to use the overflow bug at the attacker’s disposal:
overflow into B
and decrease its size from 0x210
to 0x200
.
Keep in mind that C.prev_size
is still 0x210
Request a chunk B1
of size 0x100
. Malloc searches through its unsortedbin
for a block of size greater than or equal to 0x100
.
Finding chunk B
, it triggers remaindering, linking the remaining 0x100
bytes into the unsortedbin.
Request a chunk B2
of size 0x100
: malloc tries to access the next chunk at
address &B2+chunk_size(B2)
and tries to set its prev_size
to 0x100
and
its prev_inuse
bit to 1. Fortunately, this happens to be exactly 0x10
before C
, therefore leaving its fields untouched.
Any requested size is fine, requesting a 0x100
chunk requests the last
remainder leaving the heap in a cleaner state.
Free chunk B1
, setting up a backward consolidation that starts from chunk
C
and goes all the way back to C1
.
Free chunk C
this triggers the backward consolidation and create a free chunk spanning from
B1
to C
therefore overlapping B2
, which is still allocated.
Allocating a big enough chunk will trigger remaindering once again and links
the remainder into the unsortedbin, thus setting the first two quadwords of C
to the address of the unsortedbin.
Leakage of memory addresses
If this attack is used in the first stage of an exploit, usually an attacker
leaks libc and heap addresses via this memory setup:
Leaking a libc address is as simple as getting an chunk into the unsortedbin
(therefore setting its forward and backward pointers) and reading one of these
values by reading memory from a chunk overlapping the one just free’d.
References
« CVE-2018-1160: Netatalk RCE
Exploitation of CVE-2020-9802: a JavaScriptCore JIT Bug »