Skip to main content
Be Bitwise

Why stack canaries are often 0x00

Glibc stack canaries on x86_64 always contain a literal NUL byte at their lowest address. The reason for that placement comes straight out of the bug class they're built to catch.

#exploitation#canaries#memory#x86_64

A stack canary is a value placed between a function's local variables and the saved return address. On function exit, the epilogue verifies the canary still matches its expected value. If the values disagree, the program aborts before returning into anything an attacker controls.

The interesting detail is the value itself. On glibc for x86_64, the canary always has a literal 0x00 (NUL) byte as its low byte. The placement of that zero is deliberate, and the reasoning becomes clear once you look at how the canary actually gets attacked.

What buffer overflows do to a stack canary

To overwrite a saved return address by smashing a buffer, an attacker has to write past the canary. So the canary's job is to fail loudly when an overflow steps over it.

The most common primitive that overwrites the stack is strcpy() and its relatives, which are functions that copy bytes until they hit a '\0'. With a NUL byte sitting inside the canary, any strcpy style overflow that tries to write past the canary runs into one of two outcomes. The copy can stop early when the source buffer's bytes line up against the existing NUL inside the canary, in which case the overflow ends before reaching the return address. Alternatively, the copy can replace the canary with whatever sits in the source buffer, at which point the new canary's bytes will fail to match the saved one, and the check fires.

The NUL byte is a tripwire tuned to the most common class of stack overflow bug.

What the rest of the canary looks like

The full glibc canary on x86_64 is 8 bytes: 7 random bytes and one NUL terminator. On x86_64 the NUL goes into the low byte of the value, which sits at the lowest address thanks to little endian encoding. Some platforms put the NUL at the top of the canary instead, which matters for endianness when an attacker can do a partial overwrite that touches only the high bytes.

That is why every glibc canary on Linux x86_64 ends in 00 when you print it as a uint64_t. The byte pattern follows directly from the design constraint, sized to defeat null terminating string copies.

What this trick does not defend against

memcpy style overflows take an explicit length and ignore NULs, so they happily overwrite the canary verbatim. They need different mitigations entirely. Off by one bugs that overwrite a single byte adjacent to the canary will skip the canary as well. Heap overflows belong to a different class of bug with their own primitives.

The takeaway is that canaries cover one very specific bug class. The NUL byte is the part of the design that makes them effective for that case in particular.

Where this fits in

If you want to build the mental model from the ground up, the Memory Management track walks through stack frames, calling conventions, and exploitation primitives in sequence. The Exploitation Fundamentals track covers what attackers actually do to get past canaries, including memcpy overflows, partial overwrites, and stack pivoting.