add TLS canary address display (#2944)

* fix: use @pwndbg.commands.Command instead of @pwndbg.commands.ArgparsedCommand

* fix: linting

* Minor cleanup: f-strings and replaced 'latest' links with version-specific ones

* fix type annotations for return values

* add tests for canary command on x86-64 and i386

* fix linting
pull/2988/head^2
Er3X 7 months ago committed by GitHub
parent c07d843d68
commit d0c9f690ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,10 +1,13 @@
from __future__ import annotations
import argparse
from typing import Optional
from typing import Tuple
import pwndbg.aglib.memory
import pwndbg.aglib.regs
import pwndbg.aglib.stack
import pwndbg.aglib.tls
import pwndbg.auxv
import pwndbg.commands
import pwndbg.commands.telescope
@ -14,8 +17,25 @@ from pwndbg.commands import CommandCategory
DEFAULT_NUM_CANARIES_TO_DISPLAY = 1
def canary_value():
# Architecture-specific TLS canary offsets
# These offsets are from the TLS base to the canary
# References:
# - x86_64: fs:0x28 (https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/x86_64/nptl/tls.h)
# - i386: gs:0x14 (https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/i386/nptl/tls.h)
# - aarch64: tpidr_el0 + 0x28 (https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/aarch64/nptl/tls.h)
TLS_CANARY_OFFSETS = {
"x86-64": 0x28,
"i386": 0x14,
"aarch64": 0x28,
}
def canary_value() -> Tuple[Optional[int], Optional[int]]:
"""Get the global canary value from AT_RANDOM with its last byte masked (as glibc does)
Returns:
tuple: (canary_value, at_random_addr) or (None, None) if not found
"""
at_random = pwndbg.auxv.get().AT_RANDOM
if at_random is None:
return None, None
@ -28,6 +48,36 @@ def canary_value():
return global_canary, at_random
def find_tls_canary_addr() -> Optional[int]:
"""Find the address of the canary in the Thread Local Storage (TLS).
The canary is stored at a fixed offset from the TLS base, which varies by architecture.
The TLS base can be accessed through architecture-specific registers:
- x86_64: fs register
- i386: gs register
- aarch64: tpidr_el0 register
Returns:
int: The virtual address of the canary in TLS, or None if not found/supported
"""
arch = pwndbg.aglib.arch.name
# Get TLS base address
tls_base = (
pwndbg.aglib.tls.find_address_with_register()
or pwndbg.aglib.tls.find_address_with_pthread_self()
)
if not tls_base:
return None
# Get architecture-specific offset
offset = TLS_CANARY_OFFSETS.get(arch)
if offset is None:
return None
return tls_base + offset
parser = argparse.ArgumentParser(description="Print out the current stack canary.")
parser.add_argument(
"-a",
@ -40,16 +90,31 @@ parser.add_argument(
@pwndbg.commands.Command(parser, command_name="canary", category=CommandCategory.STACK)
@pwndbg.commands.OnlyWhenRunning
def canary(all) -> None:
"""Display information about the stack canary, including its location in TLS and any copies found on the stack."""
global_canary, at_random = canary_value()
if global_canary is None or at_random is None:
print(message.error("Couldn't find AT_RANDOM - can't display canary."))
return
print(
message.notice("AT_RANDOM = %#x # points to (not masked) global canary value" % at_random)
)
print(message.notice("Canary = 0x%x (may be incorrect on != glibc)" % global_canary))
print(message.notice(f"AT_RANDOM = {at_random:#x} # points to global canary seed value"))
# Get and display the TLS canary address
tls_addr = find_tls_canary_addr()
if tls_addr is not None:
print(message.notice(f"TLS Canary = {tls_addr:#x} # address where canary is stored"))
# Verify the value at the TLS address matches our computed canary
try:
tls_canary = pwndbg.aglib.memory.pvoid(tls_addr) & (pwndbg.aglib.arch.ptrmask ^ 0xFF)
if tls_canary != global_canary:
print(message.warn("Warning: TLS canary value doesn't match global canary!"))
except Exception:
print(message.warn("Warning: Could not read TLS canary value"))
else:
print(message.warn("Note: Could not determine TLS canary address for current architecture"))
print(message.notice(f"Canary = {global_canary:#x} (may be incorrect on != glibc)"))
found_canaries = False
global_canary_packed = pwndbg.aglib.arch.pack(global_canary)

@ -0,0 +1,5 @@
int main() {
char buffer[16] = {0};
(void)buffer;
return 0;
}

@ -0,0 +1,5 @@
int main() {
char buffer[16] = {0};
(void)buffer;
return 0;
}

@ -179,3 +179,17 @@ reference_bin_nopie.i386.out: reference-binary.c
symbol_1600_and_752.out: symbol_1600_and_752.cpp
${CXX} -O0 -ggdb -Wno-pmf-conversions symbol_1600_and_752.cpp -o symbol_1600_and_752.out
canary.x86-64.out: canary.x86-64.c
@echo "[+] Building canary.x86-64.out"
${ZIGCC} \
${CFLAGS} \
-target x86_64-linux-gnu \
-o canary.x86-64.out canary.x86-64.c
canary.i386.out: canary.i386.c
@echo "[+] Building canary.i386.out"
${ZIGCC} \
${CFLAGS} \
-target x86-linux-gnu \
-o canary.i386.out canary.i386.c

@ -0,0 +1,48 @@
from __future__ import annotations
import gdb
import pytest
import pwndbg.aglib.memory
import pwndbg.aglib.regs
import tests
CANARY_X86_64_BINARY = tests.binaries.get("canary.x86-64.out")
CANARY_I386_BINARY = tests.binaries.get("canary.i386.out")
@pytest.mark.integration
@pytest.mark.parametrize(
"binary, reg_name",
[
(CANARY_X86_64_BINARY, "rax"),
(CANARY_I386_BINARY, "eax"),
],
ids=["x86-64", "i386"],
)
def test_command_canary(start_binary, binary, reg_name):
"""
Tests the canary command for x86-64 and i386 architectures
"""
start_binary(binary)
gdb.execute("break main")
gdb.execute("run")
gdb.execute("stepi")
register = getattr(pwndbg.aglib.regs, reg_name)
canary_value, at_random = pwndbg.commands.canary.canary_value()
raw = pwndbg.aglib.memory.pvoid(at_random)
mask = pwndbg.aglib.arch.ptrmask ^ 0xFF
masked_raw = raw & mask
tls_addr = pwndbg.commands.canary.find_tls_canary_addr()
raw_tls = pwndbg.aglib.memory.pvoid(tls_addr) & mask
# Check AT_RANDOM
assert masked_raw == canary_value
# Check TLS Canary
assert raw_tls == canary_value
# Check Canary
assert register == canary_value
Loading…
Cancel
Save