AUXV disable stack exploration (#2641)

* add auxv explore + procfs_auxv

* add auxv explore + procfs_auxv

* catch err
pull/2643/head
patryk4815 12 months ago committed by GitHub
parent 9f1753f4d0
commit afbc93ff79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,24 +2,39 @@ from __future__ import annotations
import os
import re
from typing import Dict
import struct
from typing import Optional
from typing import Union
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",
"Enable or disable 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
@ -43,84 +58,47 @@ example_info_auxv_linux = """
"""
AT_CONSTANTS = {
0: "AT_NULL", # /* End of vector */
1: "AT_IGNORE", # /* Entry should be ignored */
2: "AT_EXECFD", # /* File descriptor of program */
3: "AT_PHDR", # /* Program headers for program */
4: "AT_PHENT", # /* Size of program header entry */
5: "AT_PHNUM", # /* Number of program headers */
6: "AT_PAGESZ", # /* System page size */
7: "AT_BASE", # /* Base address of interpreter */
8: "AT_FLAGS", # /* Flags */
9: "AT_ENTRY", # /* Entry point of program */
10: "AT_NOTELF", # /* Program is not ELF */
11: "AT_UID", # /* Real uid */
12: "AT_EUID", # /* Effective uid */
13: "AT_GID", # /* Real gid */
14: "AT_EGID", # /* Effective gid */
15: "AT_PLATFORM", # /* String identifying platform */
16: "AT_HWCAP", # /* Machine dependent hints about processor capabilities */
17: "AT_CLKTCK", # /* Frequency of times() */
18: "AT_FPUCW",
19: "AT_DCACHEBSIZE",
20: "AT_ICACHEBSIZE",
21: "AT_UCACHEBSIZE",
22: "AT_IGNOREPPC",
23: "AT_SECURE",
24: "AT_BASE_PLATFORM", # String identifying real platforms
25: "AT_RANDOM", # Address of 16 random bytes
31: "AT_EXECFN", # Filename of executable
32: "AT_SYSINFO",
33: "AT_SYSINFO_EHDR",
34: "AT_L1I_CACHESHAPE",
35: "AT_L1D_CACHESHAPE",
36: "AT_L2_CACHESHAPE",
37: "AT_L3_CACHESHAPE",
}
AT_CONSTANT_NAMES = {v: k for k, v in AT_CONSTANTS.items()}
class AUXV(Dict[str, Union[int, str]]):
AT_PHDR: Optional[int]
AT_BASE: Optional[int]
AT_PLATFORM: Optional[str]
AT_ENTRY: Optional[int]
AT_RANDOM: Optional[int]
AT_EXECFN: Optional[str]
AT_SYSINFO: Optional[int]
AT_SYSINFO_EHDR: Optional[int]
def set(self, const: int, value: int) -> None:
name = AT_CONSTANTS.get(const, "AT_UNKNOWN%i" % const)
if name in ["AT_EXECFN", "AT_PLATFORM"]:
try:
value = (
pwndbg.dbg.selected_inferior()
.create_value(value)
.cast(pwndbg.aglib.typeinfo.pchar)
.string()
)
except Exception:
value = "couldnt read AUXV!"
self[name] = value
def __getattr__(self, attr: str) -> Optional[Union[int, str]]:
if attr in AT_CONSTANT_NAMES:
return self.get(attr)
raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, attr))
def __str__(self) -> str:
return str({k: v for k, v in self.items() if v is not None})
@pwndbg.lib.cache.cache_until("objfile", "start")
def get() -> AUXV:
return use_info_auxv() or walk_stack() or 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]:
@ -144,33 +122,22 @@ def use_info_auxv() -> Optional[AUXV]:
return auxv
def find_stack_boundary(addr: int) -> int:
# For real binaries, we can just use pwndbg.aglib.memory.find_upper_boundary
# to search forward until we walk off the end of the stack.
#
# Unfortunately, qemu-user emulation likes to paste the stack right
# before binaries in memory. This means that we walk right past the
# stack and to the end of some random ELF.
#
# In order to mitigate this, we search page-by-page until either:
#
# 1) We get a page fault, and stop
# 2) We find an ELF header, and stop
addr = pwndbg.lib.memory.page_align(addr)
try:
while True:
if b"\x7fELF" == pwndbg.aglib.memory.read(addr, 4):
break
addr += pwndbg.lib.memory.PAGE_SIZE
except pwndbg.dbg_mod.Error:
pass
return addr
_warn_explore_once = True
def walk_stack() -> AUXV | None:
if not pwndbg.dbg.selected_inferior().is_linux():
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
if pwndbg.aglib.qemu.is_qemu_kernel():
elif auto_explore.value == "no":
return None
auxv = walk_stack2(0)
@ -207,7 +174,7 @@ def walk_stack2(offset: int = 0) -> AUXV:
# set of known AT_ enums.
# 5) Vacuum up between the two.
#
end = find_stack_boundary(sp)
end = _find_stack_boundary(sp)
p = pwndbg.dbg.selected_inferior().create_value(end).cast(pwndbg.aglib.typeinfo.ulong.pointer())
p -= offset
@ -269,6 +236,29 @@ def walk_stack2(offset: int = 0) -> AUXV:
return AUXV()
def _find_stack_boundary(addr: int) -> int:
# For real binaries, we can just use pwndbg.aglib.memory.find_upper_boundary
# to search forward until we walk off the end of the stack.
#
# Unfortunately, qemu-user emulation likes to paste the stack right
# before binaries in memory. This means that we walk right past the
# stack and to the end of some random ELF.
#
# In order to mitigate this, we search page-by-page until either:
#
# 1) We get a page fault, and stop
# 2) We find an ELF header, and stop
addr = pwndbg.lib.memory.page_align(addr)
try:
while True:
if b"\x7fELF" == pwndbg.aglib.memory.read(addr, 4):
break
addr += pwndbg.lib.memory.PAGE_SIZE
except pwndbg.dbg_mod.Error:
pass
return addr
def _get_execfn() -> str | None:
# If the stack is not sane, this won't work
if not pwndbg.aglib.memory.peek(pwndbg.aglib.regs.sp):

@ -15,3 +15,20 @@ def auxv() -> None:
for k, v in pwndbg.auxv.get().items():
if v is not None:
print(k.ljust(24), v if not isinstance(v, int) else pwndbg.chain.format(v))
@pwndbg.commands.ArgparsedCommand(
"Explore and print information from the Auxiliary ELF Vector.", category=CommandCategory.LINUX
)
@pwndbg.commands.OnlyWhenRunning
@pwndbg.commands.OnlyWhenUserspace
def auxv_explore() -> None:
old_value = pwndbg.config.auto_explore_auxv.value
pwndbg.config.auto_explore_auxv.value = "yes"
try:
pwndbg.auxv.get.cache.clear() # type: ignore[attr-defined]
pwndbg.auxv.get()
finally:
pwndbg.config.auto_explore_auxv.value = old_value
auxv()

@ -31,6 +31,8 @@ from __future__ import annotations
import ctypes
from typing import Dict
from typing import Optional
from typing import Union
import pwndbg.aglib.ctypes
@ -50,42 +52,59 @@ Elf64_Xword = ctypes.c_uint64
Elf64_Sxword = ctypes.c_int64
# Copied from https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/elf/elf.h#L1193
AT_CONSTANTS: Dict[int, str] = {
0: "AT_NULL", # /* End of vector */
1: "AT_IGNORE", # /* Entry should be ignored */
2: "AT_EXECFD", # /* File descriptor of program */
3: "AT_PHDR", # /* Program headers for program */
4: "AT_PHENT", # /* Size of program header entry */
5: "AT_PHNUM", # /* Number of program headers */
6: "AT_PAGESZ", # /* System page size */
7: "AT_BASE", # /* Base address of interpreter */
8: "AT_FLAGS", # /* Flags */
9: "AT_ENTRY", # /* Entry point of program */
10: "AT_NOTELF", # /* Program is not ELF */
11: "AT_UID", # /* Real uid */
12: "AT_EUID", # /* Effective uid */
13: "AT_GID", # /* Real gid */
14: "AT_EGID", # /* Effective gid */
15: "AT_PLATFORM", # /* String identifying platform */
16: "AT_HWCAP", # /* Machine dependent hints about processor capabilities */
17: "AT_CLKTCK", # /* Frequency of times() */
18: "AT_FPUCW",
19: "AT_DCACHEBSIZE",
20: "AT_ICACHEBSIZE",
21: "AT_UCACHEBSIZE",
22: "AT_IGNOREPPC",
23: "AT_SECURE",
0: "AT_NULL", # End of vector
1: "AT_IGNORE", # Entry should be ignored
2: "AT_EXECFD", # File descriptor of program
3: "AT_PHDR", # Program headers for program
4: "AT_PHENT", # Size of program header entry
5: "AT_PHNUM", # Number of program headers
6: "AT_PAGESZ", # System page size
7: "AT_BASE", # Base address of interpreter
8: "AT_FLAGS", # Flags
9: "AT_ENTRY", # Entry point of program
10: "AT_NOTELF", # Program is not ELF
11: "AT_UID", # Real uid
12: "AT_EUID", # Effective uid
13: "AT_GID", # Real gid
14: "AT_EGID", # Effective gid
15: "AT_PLATFORM", # String identifying platform
16: "AT_HWCAP", # Machine-dependent hints about processor capabilities
17: "AT_CLKTCK", # Frequency of times()
18: "AT_FPUCW", # Used FPU control word
19: "AT_DCACHEBSIZE", # Data cache block size
20: "AT_ICACHEBSIZE", # Instruction cache block size
21: "AT_UCACHEBSIZE", # Unified cache block size
22: "AT_IGNOREPPC", # Entry should be ignored
23: "AT_SECURE", # Boolean, was exec setuid-like?
24: "AT_BASE_PLATFORM", # String identifying real platforms
25: "AT_RANDOM", # Address of 16 random bytes
26: "AT_HWCAP2", # More machine-dependent hints about processor capabilities
27: "AT_RSEQ_FEATURE_SIZE", # rseq supported feature size
28: "AT_RSEQ_ALIGN", # rseq allocation alignment
29: "AT_HWCAP3", # Extension of AT_HWCAP
30: "AT_HWCAP4", # Extension of AT_HWCAP
31: "AT_EXECFN", # Filename of executable
32: "AT_SYSINFO",
33: "AT_SYSINFO_EHDR",
34: "AT_L1I_CACHESHAPE",
35: "AT_L1D_CACHESHAPE",
36: "AT_L2_CACHESHAPE",
37: "AT_L3_CACHESHAPE",
32: "AT_SYSINFO", # Pointer to the global system page used for system calls
33: "AT_SYSINFO_EHDR", # Header for sysinfo
34: "AT_L1I_CACHESHAPE", # Shape of L1 instruction cache
35: "AT_L1D_CACHESHAPE", # Shape of L1 data cache
36: "AT_L2_CACHESHAPE", # Shape of L2 cache
37: "AT_L3_CACHESHAPE", # Shape of L3 cache
40: "AT_L1I_CACHESIZE", # Size of L1 instruction cache
41: "AT_L1I_CACHEGEOMETRY", # Geometry of L1 instruction cache
42: "AT_L1D_CACHESIZE", # Size of L1 data cache
43: "AT_L1D_CACHEGEOMETRY", # Geometry of L1 data cache
44: "AT_L2_CACHESIZE", # Size of L2 cache
45: "AT_L2_CACHEGEOMETRY", # Geometry of L2 cache
46: "AT_L3_CACHESIZE", # Size of L3 cache
47: "AT_L3_CACHEGEOMETRY", # Geometry of L3 cache
51: "AT_MINSIGSTKSZ", # Stack needed for signal delivery
}
AT_CONSTANT_NAMES = {v: k for k, v in AT_CONSTANTS.items()}
class constants:
EI_MAG0 = 0
@ -318,3 +337,40 @@ class Elf64_Phdr(pwndbg.aglib.ctypes.Structure):
("p_memsz", Elf64_Xword),
("p_align", Elf64_Xword),
]
class AUXV(Dict[str, Union[int, str]]):
AT_PHDR: Optional[int]
AT_BASE: Optional[int]
AT_PLATFORM: Optional[str]
AT_BASE_PLATFORM: Optional[str]
AT_ENTRY: Optional[int]
AT_RANDOM: Optional[int]
AT_EXECFN: Optional[str]
AT_SYSINFO: Optional[int]
AT_SYSINFO_EHDR: Optional[int]
def set(self, const: int, value: int) -> None:
name = AT_CONSTANTS.get(const, "AT_UNKNOWN%i" % const)
if name in ["AT_EXECFN", "AT_PLATFORM", "AT_BASE_PLATFORM"]:
try:
value = (
pwndbg.dbg.selected_inferior()
.create_value(value)
.cast(pwndbg.aglib.typeinfo.pchar)
.string()
)
except Exception:
value = "couldnt read AUXV!"
self[name] = value
def __getattr__(self, attr: str) -> Optional[Union[int, str]]:
if attr in AT_CONSTANT_NAMES:
return self.get(attr)
raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, attr))
def __str__(self) -> str:
return str({k: v for k, v in self.items() if v is not None})

Loading…
Cancel
Save