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.
112 lines
4.6 KiB
Python
112 lines
4.6 KiB
Python
"""
|
|
Getting Thread Local Storage (TLS) information.
|
|
"""
|
|
import sys
|
|
from types import ModuleType
|
|
|
|
import gdb
|
|
|
|
import pwndbg.disasm
|
|
import pwndbg.gdblib.arch
|
|
import pwndbg.gdblib.memory
|
|
import pwndbg.gdblib.regs
|
|
import pwndbg.symbol
|
|
import pwndbg.vmmap
|
|
|
|
|
|
class module(ModuleType):
|
|
"""Getting Thread Local Storage (TLS) information."""
|
|
|
|
_errno_offset = None
|
|
|
|
def get_tls_base_via_errno_location(self) -> int:
|
|
"""Heuristically determine the base address of the TLS."""
|
|
if not pwndbg.symbol.address("__errno_location") or pwndbg.gdblib.arch.current not in (
|
|
"x86-64",
|
|
"i386",
|
|
"arm",
|
|
):
|
|
# Note: We doesn't implement this for aarch64 because its TPIDR_EL0 register seems always work
|
|
# If oneday we can't get TLS base via TPIDR_EL0, we should implement this for aarch64
|
|
return 0
|
|
already_lock = gdb.parameter("scheduler-locking") == "on"
|
|
old_config = gdb.parameter("scheduler-locking")
|
|
if not already_lock:
|
|
gdb.execute("set scheduler-locking on")
|
|
errno_addr = int(gdb.parse_and_eval("(int *)__errno_location()"))
|
|
if not already_lock:
|
|
gdb.execute("set scheduler-locking %s" % old_config)
|
|
|
|
if not self._errno_offset:
|
|
__errno_location_instr = pwndbg.disasm.near(
|
|
pwndbg.symbol.address("__errno_location"), 5, show_prev_insns=False
|
|
)
|
|
if pwndbg.gdblib.arch.current == "x86-64":
|
|
for instr in __errno_location_instr:
|
|
# Find something like: mov rax, qword ptr [rip + disp]
|
|
if instr.mnemonic == "mov":
|
|
self._errno_offset = pwndbg.gdblib.memory.s64(instr.next + instr.disp)
|
|
break
|
|
elif pwndbg.gdblib.arch.current == "i386":
|
|
for instr in __errno_location_instr:
|
|
# Find something like: mov eax, dword ptr [eax + disp]
|
|
# (disp is a negative value)
|
|
if instr.mnemonic == "mov":
|
|
# base offset is from the first `add eax` after `call __x86.get_pc_thunk.bx`
|
|
base_offset_instr = next(
|
|
instr for instr in __errno_location_instr if instr.mnemonic == "add"
|
|
)
|
|
base_offset = base_offset_instr.address + base_offset_instr.operands[1].int
|
|
self._errno_offset = pwndbg.gdblib.memory.s32(base_offset + instr.disp)
|
|
break
|
|
elif pwndbg.gdblib.arch.current == "arm":
|
|
ldr_instr = None
|
|
for instr in __errno_location_instr:
|
|
if not ldr_instr and instr.mnemonic == "ldr":
|
|
ldr_instr = instr
|
|
elif ldr_instr and instr.mnemonic == "add":
|
|
offset = ldr_instr.operands[1].mem.disp
|
|
offset = pwndbg.gdblib.memory.s32((ldr_instr.address + 4 & -4) + offset)
|
|
self._errno_offset = pwndbg.gdblib.memory.s32(instr.address + 4 + offset)
|
|
break
|
|
if not self._errno_offset:
|
|
raise OSError("Can not find tls base")
|
|
return errno_addr - self._errno_offset
|
|
|
|
@property
|
|
def address(self) -> int:
|
|
"""Get the base address of TLS."""
|
|
if pwndbg.gdblib.arch.current not in ("x86-64", "i386", "aarch64", "arm"):
|
|
# Not supported yet
|
|
return 0
|
|
|
|
tls_base = 0
|
|
|
|
if pwndbg.gdblib.arch.current == "x86-64":
|
|
tls_base = int(pwndbg.gdblib.regs.fsbase)
|
|
elif pwndbg.gdblib.arch.current == "i386":
|
|
tls_base = int(pwndbg.gdblib.regs.gsbase)
|
|
elif pwndbg.gdblib.arch.current == "aarch64":
|
|
tls_base = int(pwndbg.gdblib.regs.TPIDR_EL0)
|
|
|
|
# Sometimes, we need to get TLS base via errno location for the following reason:
|
|
# For x86-64, fsbase might be 0 if we are remotely debugging and the GDB version <= 8.X
|
|
# For i386, gsbase might be 0 if we are remotely debugging
|
|
# For arm (32-bit), we doesn't have other choice
|
|
# Note: aarch64 seems doesn't have this issue
|
|
is_valid_tls_base = (
|
|
pwndbg.vmmap.find(tls_base) is not None and tls_base % pwndbg.gdblib.arch.ptrsize == 0
|
|
)
|
|
return tls_base if is_valid_tls_base else self.get_tls_base_via_errno_location()
|
|
|
|
|
|
@pwndbg.gdblib.events.exit
|
|
def reset():
|
|
# We should reset the offset when we attach to a new process or something
|
|
pwndbg.tls._errno_offset = None
|
|
|
|
|
|
# To prevent garbage collection
|
|
tether = sys.modules[__name__]
|
|
sys.modules[__name__] = module(__name__, "")
|