mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
7.5 KiB
Python
232 lines
7.5 KiB
Python
"""
|
|
Performs handling of the linux auxiliary vector.
|
|
|
|
https://www.man7.org/linux/man-pages/man3/getauxval.3.html
|
|
https://lwn.net/Articles/519085/
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import struct
|
|
from typing import Optional
|
|
|
|
import pwndbg.aglib.arch
|
|
import pwndbg.aglib.file
|
|
import pwndbg.aglib.memory
|
|
import pwndbg.aglib.proc
|
|
import pwndbg.aglib.qemu
|
|
import pwndbg.aglib.regs
|
|
import pwndbg.aglib.stack
|
|
import pwndbg.aglib.strings
|
|
import pwndbg.aglib.typeinfo
|
|
import pwndbg.color.message as M
|
|
import pwndbg.lib.cache
|
|
import pwndbg.lib.config
|
|
import pwndbg.lib.memory
|
|
from pwndbg.lib.elftypes import AT_CONSTANT_NAMES
|
|
from pwndbg.lib.elftypes import AUXV
|
|
|
|
# We use `info.auxv()` when available.
|
|
if pwndbg.dbg.is_gdblib_available():
|
|
import pwndbg.gdblib.info
|
|
|
|
|
|
auto_explore = pwndbg.config.add_param(
|
|
"auto-explore-auxv",
|
|
"warn",
|
|
"stack exploration for AUXV information; it may be really slow",
|
|
param_class=pwndbg.lib.config.PARAM_ENUM,
|
|
enum_sequence=["warn", "yes", "no"],
|
|
)
|
|
|
|
|
|
example_info_auxv_linux = """
|
|
33 AT_SYSINFO_EHDR System-supplied DSO's ELF header 0x7ffff7ffa000
|
|
16 AT_HWCAP Machine-dependent CPU capability hints 0xfabfbff
|
|
6 AT_PAGESZ System page size 4096
|
|
17 AT_CLKTCK Frequency of times() 100
|
|
3 AT_PHDR Program headers for program 0x400040
|
|
4 AT_PHENT Size of program header entry 56
|
|
5 AT_PHNUM Number of program headers 9
|
|
7 AT_BASE Base address of interpreter 0x7ffff7dda000
|
|
8 AT_FLAGS Flags 0x0
|
|
9 AT_ENTRY Entry point of program 0x42020b
|
|
11 AT_UID Real user ID 1000
|
|
12 AT_EUID Effective user ID 1000
|
|
13 AT_GID Real group ID 1000
|
|
14 AT_EGID Effective group ID 1000
|
|
23 AT_SECURE Boolean, was exec setuid-like? 0
|
|
25 AT_RANDOM Address of 16 random bytes 0x7fffffffdb39
|
|
31 AT_EXECFN File name of executable 0x7fffffffefee "/bin/bash"
|
|
15 AT_PLATFORM String identifying platform 0x7fffffffdb49 "x86_64"
|
|
0 AT_NULL End of vector 0x0
|
|
"""
|
|
|
|
|
|
@pwndbg.lib.cache.cache_until("objfile", "start")
|
|
def get() -> AUXV:
|
|
if not pwndbg.dbg.selected_inferior().is_linux() or pwndbg.aglib.qemu.is_qemu_kernel():
|
|
return AUXV()
|
|
|
|
return use_info_auxv() or procfs_auxv() or explore_stack_auxv() or AUXV()
|
|
|
|
|
|
def procfs_auxv() -> AUXV | None:
|
|
if pwndbg.aglib.arch.ptrsize == 8:
|
|
field_format = "QQ" # for 64bit system
|
|
elif pwndbg.aglib.arch.ptrsize == 4:
|
|
field_format = "II" # for 32bit system
|
|
else:
|
|
assert False
|
|
field_size = struct.calcsize(field_format)
|
|
|
|
try:
|
|
data = pwndbg.aglib.file.get(f"/proc/{pwndbg.aglib.proc.tid}/auxv")
|
|
except OSError:
|
|
return None
|
|
|
|
if not data:
|
|
return None
|
|
|
|
auxv = AUXV()
|
|
end_type = AT_CONSTANT_NAMES["AT_NULL"]
|
|
for i in range(0, len(data), field_size):
|
|
entry = data[i : i + field_size]
|
|
|
|
if len(entry) < field_size:
|
|
break # Ignore incomplete entry at the end
|
|
|
|
a_type, a_val = struct.unpack(field_format, entry)
|
|
|
|
# AT_NULL indicates the end of the vector
|
|
if a_type == end_type:
|
|
break
|
|
auxv.set(a_type, a_val)
|
|
|
|
return auxv
|
|
|
|
|
|
def use_info_auxv() -> Optional[AUXV]:
|
|
lines = None
|
|
if pwndbg.dbg.is_gdblib_available():
|
|
lines = pwndbg.gdblib.info.auxv().splitlines()
|
|
|
|
if not lines:
|
|
return None
|
|
|
|
auxv = AUXV()
|
|
for line in lines:
|
|
match = re.match("([0-9]+) .*? (0x[0-9a-f]+|[0-9]+$)", line)
|
|
if not match:
|
|
print(f"Warning: Skipping auxv entry '{line}'")
|
|
continue
|
|
|
|
const, value = int(match.group(1)), int(match.group(2), 0)
|
|
auxv.set(const, value)
|
|
|
|
return auxv
|
|
|
|
|
|
_warn_explore_once = True
|
|
|
|
|
|
def explore_stack_auxv() -> AUXV | None:
|
|
if auto_explore.value == "warn":
|
|
print(
|
|
M.warn(
|
|
"Warning: All methods to detect AUXV have failed.\n"
|
|
"You can explore AUXV using stack exploration, but it may be very slow.\n"
|
|
"To explicitly explore, use the command: `auxv-explore`\n"
|
|
"Alternatively, enable it by default with: `set auto-explore-auxv yes`\n\n"
|
|
"Note: AUXV is probably not necessary for debugging firmware or embedded systems."
|
|
)
|
|
)
|
|
return None
|
|
elif auto_explore.value == "no":
|
|
return None
|
|
|
|
return walk_stack2()
|
|
|
|
|
|
def walk_stack2(offset: int = 0) -> AUXV:
|
|
# FIXME: This function doesn't work with Go (Golang) binaries, as Golang has two stacks.
|
|
# NOTE: This function is intended to work only with real binaries, not those emulated under qemu-user.
|
|
sp = pwndbg.aglib.regs.sp
|
|
|
|
if not sp:
|
|
return AUXV()
|
|
|
|
#
|
|
# Strategy looks like this:
|
|
#
|
|
# 1) Find the end of the stack.
|
|
# 2) Scan backward from the end of the stack until we find what
|
|
# could be an AT_NULL entry (two consecutive ULONGs)
|
|
# 3) Scan back a little further until we find what could be an
|
|
# AT_ENTRY entry.
|
|
# 4) Keep scanning back until we find something that isn't in the
|
|
# set of known AT_ enums.
|
|
# 5) Vacuum up between the two.
|
|
#
|
|
end = pwndbg.aglib.stack.find_upper_stack_boundary(sp)
|
|
p = pwndbg.dbg.selected_inferior().create_value(end).cast(pwndbg.aglib.typeinfo.ulong.pointer())
|
|
|
|
p -= offset
|
|
|
|
# So we don't walk off the end of the stack
|
|
p -= 2
|
|
|
|
try:
|
|
# Find a ~guess at where AT_NULL is.
|
|
#
|
|
# Coming up from the end of the stack, there will be a
|
|
# marker at the end which is a single ULONG of zeroes, and then
|
|
# the ARGV and ENVP data.
|
|
#
|
|
# Assuming that the ARGV and ENVP data is formed normally,
|
|
# (i.e. doesn't include 8-16 consecutive zero-length args)
|
|
# this should land us at the *END* of AUXV, which is the
|
|
# AT_NULL vector.
|
|
while int(p.dereference()) != 0 or int((p + 1).dereference()) != 0:
|
|
p -= 2
|
|
|
|
# Now we want to continue until we fine, at a minimum, AT_BASE.
|
|
# While there's no guarantee that this exists, I've not ever found
|
|
# an instance when it doesn't.
|
|
#
|
|
# This check is needed because the above loop isn't
|
|
# guaranteed to actually get us to AT_NULL, just to some
|
|
# consecutive NULLs. QEMU is pretty generous with NULLs.
|
|
for i in range(1024):
|
|
if int(p.dereference()) == AT_CONSTANT_NAMES["AT_BASE"]:
|
|
break
|
|
p -= 2
|
|
else:
|
|
return AUXV()
|
|
|
|
# If we continue to p back, we should bump into the
|
|
# very end of ENVP (and perhaps ARGV if ENVP is empty).
|
|
#
|
|
# The highest value for the vector is AT_SYSINFO_EHDR, 33.
|
|
while int((p - 2).dereference()) < 37:
|
|
p -= 2
|
|
|
|
# Scan them into our structure
|
|
auxv = AUXV()
|
|
while True:
|
|
const = int((p + 0).dereference()) & pwndbg.aglib.arch.ptrmask
|
|
value = int((p + 1).dereference()) & pwndbg.aglib.arch.ptrmask
|
|
|
|
if const == AT_CONSTANT_NAMES["AT_NULL"]:
|
|
break
|
|
|
|
auxv.set(const, value)
|
|
p += 2
|
|
|
|
return auxv
|
|
except pwndbg.dbg_mod.Error:
|
|
# If SP is inaccessible or we went past through stack and haven't found AUXV
|
|
# then return an empty AUXV...
|
|
return AUXV()
|