Files
atlas0fd00m 4979ea079f Vtrace conversion and bugfixes (#406)
* lotsa work making it all work, with segments and maps and collapsing adjacent maps and bugfixes and...

* pagemap and memorymap deletion

* update unittests

* memory-related updates (and unittests for delMemoryMap)

* unittests and MapNotFoundException

* update unittest to test appropriately

* change in symstore fix

* more bugfixes for vdb/win32.  symbols are a bit of a mess and may need to be refactored in the future.

* make syms work (do_syms and do_bp) on Win32

* unittest mods per @rakuy0

* aaaaand this will make unittests break.
unittest discovered a bug in symstore about storing subresolvers... still need to fix the bug.

* lockstep emulator class and some improvements for Win32 PEB/TEB capture

* enhancements for envitools.LockstepEmulator class

* cleanup and import bugfix

* collapseMemoryMaps::strict

* bugfix:  deleting symbols and tests

* bugfix: Win32 detaching

* updates per @rakuy0

* touchups per @rakuy0

* tweaking the test to account for different versions of Linux and python

* updates per @rakuy0 and improvements from i386_emu_... PR.  we're housing the refugee code here while the cull takes place in PR#405

* update to identify FreeBSD ELF files (OS matters on some of these vtrace and emu changes)

* a few bugfixes for Elf and Vtrace handling of Elfs.

* Break On Library Init (and stub for Library Load)
and bugfix for clicking EFLAGS gui buttons.

* modified flaky unittest

* removed prints

* bugfix for event/threading for notify/breakpoints

* Finally!  LockStepper Class moving into vtrace.envitools.  Raw move here... updates to follow (for easy diffing between commits)

* bugfix

* revamped the LockStepper class (still have to remove LockStepEmulator class and revamp "lockStepEmulator" when we're done)

* oops

* bugfix

* change a few INS_SYSTEM x86/x64 instruction opcodes to be unique

* unify @rakuy0's and my lockstep emu classes and clean up

* de-x86ify

* bsd commented code removal
.gitignore to ignore docs build files

* bugfix: vwFromTrace() call to addFile was handing in the md5 object, not a serializable string (hexdigest)
bugfix: vdb snapshot wasn't capturing TEB's from a trace object, vw/emuFromTrace wasn't allowing for PEB/TEBs tracking from snapshot

* bugfix: str versus bytes

* cleanup 'Comparing' print/log message

* updates per @rakuy0

* finish the docstr (per rakuy0)

* finish the docstr (per rakuy0) (for realz this time, last commit was actually the @idlethread change)

* document user interface for InteractiveLSMon

* MM_* removed from envi.memory and left only in envi.const

* allow --LL and --LI settings to persist between different traces in the same session (instead of only for the first run and only if provided "-c /path/to/proggy" at the command line.  in the future, make this more cohesive by setting vdbbin to create the `db` and use `db.newTrace()` instead of punching low-level into vtrace.getTrace.  this will have to include handling of `platargs` which is beyond the scope of this PR currently and would unnecessarily delay review/merging

* cleanup per rakuy0

* cleanups per rakuy0

* bugfix (unittests have been screaming about a typo) and minor cleanup.

* kwargs to get new trace able to handle platform magic.
logging of --LI and --LL effects.

* yes, @rakuy0, that should do something ;)

* windows debugging privs (tested on Win7 and Win10)
debugging print statements that need to be cut/converted to logs

* Break on Library Load/Init bugfixes

* make POSIX (Linux) catch Library Loads and raise the correct notifiers.
this requires hooking a function in `ld` which causes libraries to be rediscovered and unresolved breakpoints to be resolved if possible.

* outdated code causing Python warnings.

* decouping Vtrace from VDB, where only the latter has a trace.db.config (or trace.db, for that matter)

* no prints!

* equaling out the unresolved breakpoint warnings.

* update vtrace unittest

* remove need for pywin32 (in README)

* remove dependency on pywin32 and winadmin

* vprint on both VdbTrace and Trace objects.
and string/bytes bugfixes.

* add to the BP execution context

* don't need to hand in **kwargs to `self.getTrace()` in fact, that's bad.

* REMOVE collapseMaps functionality.  no longer important since we can emulate/read across memory maps now.
there are many other bugfixes in the branch that should be merged, and this was always a little wonky.

* lost an import somewhere.

* test_privs was getting caught by unittests :)

* remove tests for the collapseMaps that i just yanked out of this PR.

* update checkprivs to use only ctypes instead of pywin32.
TODO: wrap this ability into indicating that Windows users don't have sufficient rights for debug.

* updates per @rakuy0

---------

Co-authored-by: James Gross <45212823+rakuy0@users.noreply.github.com>
2023-05-01 12:06:32 -04:00

986 lines
33 KiB
Python

"""
Linux Platform Module
"""
# Copyright (C) 2007 Invisigoth - See LICENSE file for details
import os
import sys
import signal
import struct
import logging
import binascii
import platform
import traceback
import envi.cli as e_cli
import envi.bits as e_bits
import envi.const as e_const
import vtrace
import vtrace.exc as v_exc
import vtrace.breakpoints as v_bp
import vtrace.archs.arm as v_arm
import vtrace.archs.i386 as v_i386
import vtrace.archs.amd64 as v_amd64
import vtrace.platforms.base as v_base
import vtrace.platforms.posix as v_posix
from ctypes import *
import ctypes.util as cutil
logger = logging.getLogger(__name__)
if os.getenv('ANDROID_ROOT'):
libc = CDLL('/system/lib/libc.so')
else:
libc = CDLL(cutil.find_library("c"), use_errno=True)
libc.lseek64.restype = c_ulonglong
libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint]
libc.read.restype = c_long
libc.read.argtypes = [c_uint, c_void_p, c_long]
libc.write.restype = c_long
libc.write.argtypes = [c_uint, c_void_p, c_long]
O_RDWR = 2
O_LARGEFILE = 0x8000
MAP_ANONYMOUS = 0x20
MAP_PRIVATE = 0x02
# Linux specific ptrace extensions
PT_GETREGS = 12
PT_SETREGS = 13
PT_GETFPREGS = 14
PT_SETFPREGS = 15
PT_ATTACH = 16
PT_DETACH = 17
PT_GETFPXREGS = 18
PT_SETFPXREGS = 19
PT_SYSCALL = 24
PT_SETOPTIONS = 0x4200
PT_GETEVENTMSG = 0x4201
PT_GETSIGINFO = 0x4202
PT_SETSIGINFO = 0x4203
PT_GETREGSET = 0x4204
PT_SETREGSET = 0x4205
PT_SEIZE = 0x4206
PT_INTERRUPT = 0x4207
# PT set options stuff. ONLY TRACESYSGOOD may be used in 2.4...
PT_O_TRACESYSGOOD = 0x00000001 # add 0x80 to TRAP when generated by syscall
# For each of the options below, the stop signal is (TRAP | PT_EVENT_FOO << 8)
PT_O_TRACEFORK = 0x00000002 # Cause a trap at fork
PT_O_TRACEVFORK = 0x00000004 # Cause a trap at vfork
PT_O_TRACECLONE = 0x00000008 # Cause a trap at clone
PT_O_TRACEEXEC = 0x00000010 # Cause a trap at exec
PT_O_TRACEVFORKDONE = 0x00000020 # Cause a trap when vfork done
PT_O_TRACEEXIT = 0x00000040 # Cause a trap on exit
PT_O_MASK = 0x0000007f
# Ptrace event types (TRAP | PT_EVENT_FOO << 8) means that type
# when using GETEVENTMSG for most of these, the new pid is the data
PT_EVENT_FORK = 1
PT_EVENT_VFORK = 2
PT_EVENT_CLONE = 3
PT_EVENT_EXEC = 4
PT_EVENT_VFORK_DONE = 5
PT_EVENT_EXIT = 6
# Reg sets for use in ptrace PT_GETREGSET
# typlically defined in /usr/include/elf.h
# for now, we only need the one, but we can add more on demand
NT_X86_XSTATE = 0x202 # x86 extended state using xsave
# Used to tell some of the additional events apart
SIG_LINUX_SYSCALL = signal.SIGTRAP | 0x80
SIG_LINUX_CLONE = signal.SIGTRAP | (PT_EVENT_CLONE << 8)
SIG_LINUX_EXIT = signal.SIGTRAP | (PT_EVENT_EXIT << 8)
#following from Pandaboard ES (OMAP4460) Armv7a (cortex-a9)
class user_regs_arm(Structure):
_fields_ = (
("r0", c_ulong),
("r1", c_ulong),
("r2", c_ulong),
("r3", c_ulong),
("r4", c_ulong),
("r5", c_ulong),
("r6", c_ulong),
("r7", c_ulong),
("r8", c_ulong),
("r9", c_ulong),
("r10", c_ulong), #aka 'sl' ?
("r11", c_ulong),
("r12", c_ulong),
("sp", c_ulong),
("lr", c_ulong),
("pc", c_ulong),
("cpsr", c_ulong),
("orig_r0", c_ulong),
)
class fp_reg_arm(Structure):
_fields_ = (
("sign1", c_long, 1),
("unused", c_long, 15),
("sign2", c_long, 1),
("exponent", c_long, 14),
("j", c_long, 1),
("mantissa1", c_long, 31),
("mantissa0", c_long, 32),
)
class user_fpregs_arm(Structure):
_fields_ = (
("fpregs", fp_reg_arm*8),
("fpsr", c_ulong, 32),
("fpcr", c_ulong, 32),
("ftype", c_ubyte*8),
("init_flag", c_ulong),
)
class USER_arm(Structure):
_fields_ = (
("regs", user_regs_arm),
("u_fpvalid", c_long),
("u_tsize", c_ulong),
("u_dsize", c_ulong),
("u_ssize", c_ulong),
("start_code", c_ulong),
("start_stack",c_ulong),
("signal", c_long),
("reserved", c_long),
("u_ar0", c_void_p),
("magic", c_ulong),
("u_comm", c_char*32),
("u_debugreg", c_long*8),
("fpregs", user_fpregs_arm),
("u_fp0", c_void_p)
)
class user_regs_i386(Structure):
_fields_ = (
("ebx", c_ulong),
("ecx", c_ulong),
("edx", c_ulong),
("esi", c_ulong),
("edi", c_ulong),
("ebp", c_ulong),
("eax", c_ulong),
("ds", c_ushort),
("__ds", c_ushort),
("es", c_ushort),
("__es", c_ushort),
("fs", c_ushort),
("__fs", c_ushort),
("gs", c_ushort),
("__gs", c_ushort),
("orig_eax", c_ulong),
("eip", c_ulong),
("cs", c_ushort),
("__cs", c_ushort),
("eflags", c_ulong),
("esp", c_ulong),
("ss", c_ushort),
("__ss", c_ushort),
)
class iovec(Structure):
_fields_ = [
('iov_base', c_void_p),
('iov_len', c_size_t),
]
class user_fpregs_i386(Structure):
_fields_ = [
('cwd', c_long),
('swd', c_long),
('twd', c_long),
('fip', c_long),
('fcs', c_long),
('foo', c_long),
('fos', c_long),
('st_space', c_long * 20),
]
class user_fpxregs_i386(Structure):
_fields_ = [
('cwd', c_ushort),
('swd', c_ushort),
('twd', c_ushort),
('fop', c_ushort),
('fip', c_long),
('fcs', c_long),
('foo', c_long),
('fos', c_long),
('mxcsr', c_long),
('reserved', c_long),
('st_space', c_long * 32),
('xmm_space', c_long * 32),
('padding', c_long * 56),
]
class USER_i386(Structure):
_fields_ = (
# NOTE: Expand out the user regs struct so
# we can make one call to _rctx_Import
("regs", user_regs_i386),
("u_fpvalid", c_ulong),
("u_tsize", c_ulong),
("u_dsize", c_ulong),
("u_ssize", c_ulong),
("start_code", c_ulong),
("start_stack",c_ulong),
("signal", c_ulong),
("reserved", c_ulong),
("u_ar0", c_void_p),
("u_fpstate", c_void_p),
("magic", c_ulong),
("u_comm", c_char*32),
("debug0", c_ulong),
("debug1", c_ulong),
("debug2", c_ulong),
("debug3", c_ulong),
("debug4", c_ulong),
("debug5", c_ulong),
("debug6", c_ulong),
("debug7", c_ulong),
)
class user_regs_amd64(Structure):
_fields_ = [
('r15', c_uint64),
('r14', c_uint64),
('r13', c_uint64),
('r12', c_uint64),
('rbp', c_uint64),
('rbx', c_uint64),
('r11', c_uint64),
('r10', c_uint64),
('r9', c_uint64),
('r8', c_uint64),
('rax', c_uint64),
('rcx', c_uint64),
('rdx', c_uint64),
('rsi', c_uint64),
('rdi', c_uint64),
('orig_rax', c_uint64),
('rip', c_uint64),
('cs', c_uint64),
('eflags', c_uint64),
('rsp', c_uint64),
('ss', c_uint64),
('fs_base', c_uint64),
('gs_base', c_uint64),
('ds', c_uint64),
('es', c_uint64),
('fs', c_uint64),
('gs', c_uint64),
]
intel_dbgregs = (0,1,2,3,6,7)
class LinuxMixin(v_posix.PtraceMixin, v_posix.PosixMixin):
"""
The mixin to take care of linux specific platform traits.
(mostly proc)
"""
def __init__(self):
# Wrap reads from proc in our worker thread
v_posix.PtraceMixin.__init__(self)
v_posix.PosixMixin.__init__(self)
self.memfd = None
self._stopped_cache = {}
self._stopped_hack = False
self.fireTracerThread()
self.setMeta('BadMaps', ['[vvar]', '[vsyscall]'])
self.initMode("Syscall", False, "Break On Syscalls")
def setupMemFile(self, offset):
"""
A utility to open (if necessary) and seek the memfile
"""
if self.memfd is None:
self.memfd = libc.open(b"/proc/%d/mem" % self.pid, O_RDWR | O_LARGEFILE, 0o755)
if self.memfd < 0:
logger.warning('Failed to get proper file descriptor (errno: %d)', get_errno())
retn = libc.lseek64(self.memfd, offset, 0)
if retn < 0:
logger.warning('lseek64 hit issue with error: %d' % get_errno())
@v_base.threadwrap
def platformReadMemory(self, address, size):
"""
A *much* faster way of reading memory that the 4 bytes
per syscall allowed by ptrace
"""
self.setupMemFile(address)
# Use ctypes cause python implementation is teh ghey
buf = create_string_buffer(size)
x = libc.read(self.memfd, addressof(buf), size)
if x != size:
# libc.perror('libc.read %d (size: %d)' % (x,size))
raise Exception("reading from invalid memory %s (%d returned) (errno: %d) (fd: %d)" % (hex(address), x, get_errno(), self.memfd))
# We have to slice cause ctypes "helps" us by adding a null byte...
return buf.raw
@v_base.threadwrap
def whynot_platformWriteMemory(self, address, data):
"""
A *much* faster way of writting memory that the 4 bytes
per syscall allowed by ptrace
"""
self.setupMemFile(address)
buf = create_string_buffer(data)
size = len(data)
x = libc.write(self.memfd, addressof(buf), size)
if x != size:
libc.perror('write mem failed: 0x%.8x (%d)' % (address, size))
raise Exception("write memory failed: %d" % x)
return x
def _findExe(self, pid):
exe = os.readlink("/proc/%d/exe" % pid)
if "(deleted)" in exe:
if "#prelink#" in exe:
exe = exe.split(".#prelink#")[0]
elif ";" in exe:
exe = exe.split(";")[0]
else:
exe = exe.split("(deleted)")[0].strip()
return exe
@v_base.threadwrap
def platformExec(self, cmdline):
# Very similar to posix, but not
# quite close enough...
self.execing = True
cmdlist = e_cli.splitargs(cmdline)
os.stat(cmdlist[0])
pid = os.fork()
if pid == 0:
try:
# Don't use PT_TRACEME -- on some linux (tested on ubuntu)
# it will cause immediate asignment of ptrace slot to parent
# without parent having PT_ATTACH'D.... MAKES SYNCHRONIZATION HARD
# SIGSTOP ourself until parent continues us
os.kill(os.getpid(), signal.SIGSTOP)
os.execv(cmdlist[0], cmdlist)
except Exception as e:
logger.error(e)
sys.exit(-1)
# Attach to child. should cause SIGSTOP
if 0 != v_posix.ptrace(PT_ATTACH, pid, 0, 0):
raise Exception("PT_ATTACH failed! linux platformExec")
# Eat all SIGSTOP (or other signal) and break from loop on SIGTRAP.
# SIGTRAP triggered by execv while PTRACE_ATTACH'd
while True:
wpid, status = os.waitpid(pid, os.WUNTRACED)
if wpid != pid: # should never happen
continue
if os.WIFSTOPPED(status):
cause = os.WSTOPSIG(status)
if cause == signal.SIGTRAP:
break
if v_posix.ptrace(v_posix.PT_CONTINUE, pid, 0, 0) != 0:
raise Exception("PT_CONTINUE failed! linux platformExec")
# Do a single step, which will allow a new stop event for the
# rest of vtrace to eat up.
if v_posix.ptrace(v_posix.PT_STEP, pid, 0, 0) != 0:
raise Exception("PT_CONTINUE failed! linux platformExec")
self.pthreads = [pid]
self.setMeta("ExeName", self._findExe(pid))
return pid
@v_base.threadwrap
def platformAttach(self, pid):
self.pthreads = [pid]
self.setMeta("ThreadId", pid)
if v_posix.ptrace(PT_ATTACH, pid, 0, 0) != 0:
raise Exception("PT_ATTACH failed!")
self.setMeta("ExeName", self._findExe(pid))
def _LibraryLoadHook(self):
# drop special breakpoint at ld._dl_catch_exception
bp = v_bp.PosixLibLoadHookBreakpoint('ld._dl_catch_exception')
self.addBreakpoint(bp)
def platformPs(self):
pslist = []
for dname in self.platformListDir('/proc'):
try:
if not dname.isdigit():
continue
cmdline = self.platformReadFile('/proc/%s/cmdline' % dname)
cmdline = cmdline.replace(b"\x00", b" ")
if len(cmdline) > 0:
pslist.append((int(dname), cmdline.decode('utf-8')))
except Exception as e:
pass # Permissions... quick process... whatev.
return pslist
def _simpleCreateThreads(self):
for tid in self.threadsForPid( self.pid ):
if tid == self.pid:
continue
self.attachThread( tid )
def attachThread(self, tid, attached=False):
self.doAttachThread(tid, attached=attached)
self.setMeta("ThreadId", tid)
self.fireNotifiers(vtrace.NOTIFY_CREATE_THREAD)
@v_base.threadwrap
def detachThread(self, tid, ecode):
self.setMeta('ThreadId', tid)
self._fireExitThread(tid, ecode)
if v_posix.ptrace(PT_DETACH, tid, 0, 0) != 0:
raise Exception("ERROR ptrace detach failed for thread %d" % tid)
self.pthreads.remove(tid)
@v_base.threadwrap
def platformWait(self):
# Blocking wait once...
pid, status = os.waitpid(-1, 0x40000002)
self.setMeta("ThreadId", pid)
# Stop the rest of the threads...
# why is linux debugging so Ghetto?!?!
if not self.stepping: # If we're stepping, only do the one
for tid in self.pthreads:
if tid == pid:
continue
try:
# We use SIGSTOP here because they can't mask it.
os.kill(tid, signal.SIGSTOP)
os.waitpid(tid, 0x40000002)
except Exception as e:
logger.warning("WARNING TID is invalid %d %s", tid, e)
return pid, status
@v_base.threadwrap
def platformContinue(self):
cmd = v_posix.PT_CONTINUE
if self.getMode("Syscall", False):
cmd = PT_SYSCALL
pid = self.getPid()
sig = self.getCurrentSignal()
if sig is None:
sig = 0
# Only deliver signals to the main thread
if v_posix.ptrace(cmd, pid, 0, sig) != 0:
libc.perror('ptrace PT_CONTINUE failed for pid %d' % pid)
raise Exception("ERROR ptrace failed for pid %d" % pid)
for tid in self.pthreads:
if tid == pid:
continue
if v_posix.ptrace(cmd, tid, 0, 0) != 0:
pass
@v_base.threadwrap
def platformStepi(self):
self.stepping = True
tid = self.getMeta("ThreadId", 0)
if v_posix.ptrace(v_posix.PT_STEP, tid, 0, 0) != 0:
raise Exception("ERROR ptrace failed!")
@v_base.threadwrap
def platformDetach(self):
libc.close(self.memfd)
for tid in self.pthreads:
v_posix.ptrace(PT_DETACH, tid, 0, 0)
@v_base.threadwrap
def doAttachThread(self, tid, attached=False):
"""
Do the work for attaching a thread. This must be *under*
attachThread() so callers in notifiers may call it (because
it's also gotta be thread wrapped).
"""
if not attached:
if v_posix.ptrace(PT_ATTACH, tid, 0, 0) != 0:
raise Exception("ERROR ptrace attach failed for thread %d" % tid)
# We may have already revcieved the stop signal
if not self._stopped_cache.pop(tid, None):
os.waitpid(tid, 0x40000002)
self.setupPtraceOptions(tid)
self.pthreads.append(tid)
@v_base.threadwrap
def setupPtraceOptions(self, tid):
"""
Called per pid/tid to setup proper options
for ptrace.
"""
opts = PT_O_TRACESYSGOOD
ver = tuple(platform.release()[:3].split('.'))
if (int(ver[0]), int(ver[1])) >= (2, 6):
opts |= PT_O_TRACECLONE | PT_O_TRACEEXIT
x = v_posix.ptrace(PT_SETOPTIONS, tid, 0, opts)
if x != 0:
libc.perror('ptrace PT_SETOPTION failed for thread %d' % tid)
def threadsForPid(self, pid):
ret = []
tpath = "/proc/%s/task" % pid
if os.path.exists(tpath):
for pidstr in os.listdir(tpath):
ret.append(int(pidstr))
return ret
def platformProcessEvent(self, event):
# Skim some linux specific events before passing to posix
tid, status = event
if os.WIFSTOPPED(status):
sig = status >> 8 # Cant use os.WSTOPSIG() here...
# print('STOPPED: %d %d %.8x %d' % (self.pid, tid, status, sig))
# Ok... this is a crazy little state engine that tries
# to account for the discrepancies in how linux posts
# signals to the debugger...
# Thread Creation:
# In each case below, the kernel may deliver
# any of the 3 signals in any order... ALSO
# (and more importantly) *if* the kernel sends
# SIGSTOP to the thread first, the debugger
# will get a SIGSTOP *instead* of SIG_LINUX_CLONE
# ( this will go back and forth on *ONE BOX* with
# the same kernel version... Finally squished it
# because it presents more frequently ( 1 in 10 )
# on my new ARM linux dev board. WTF?!1?!one?!? )
#
# Case 1 (SIG_LINUX_CLONE):
# debugger gets SIG_LINUX CLONE as expected
# and can then use ptrace(PT_GETEVENTMSG)
# to get new TID and attach as normal
# Case 2 (SIGSTOP delivered to thread)
# Thread is already stoped and attached but
# parent debugger doesn't know yet. We add
# the tid to the stopped_cache so when the
# kernel gets around to telling the debugger
# we don't wait on him again.
# Case 3 (SIGSTOP delivered to debugger)
# In both case 2 and case 3, this will cause
# the SIG_LINUX_CLONE to be skipped. Either
# way, we should head down into thread attach.
# ( The thread may be already stopped )
if sig == SIG_LINUX_SYSCALL:
self.fireNotifiers(vtrace.NOTIFY_SYSCALL)
elif sig == SIG_LINUX_EXIT:
ecode = self.getPtraceEvent() >> 8
if tid == self.getPid():
self._fireExit( ecode )
self.platformDetach()
else:
self.detachThread(tid, ecode)
elif sig == SIG_LINUX_CLONE:
# Handle a new thread here!
newtid = self.getPtraceEvent()
# print('CLONE (new tid: %d)' % newtid)
self.attachThread(newtid, attached=True)
elif sig == signal.SIGSTOP and tid != self.pid:
#print('OMG IM THE NEW THREAD! %d' % tid)
# We're not even a real event right now...
self.runAgain()
self._stopped_cache[tid] = True
elif sig == signal.SIGSTOP:
# If we are still 'exec()'ing, we havent hit the SIGTRAP
# yet ( so our process info is still python, lets skip it )
if self.execing:
self._stopped_hack = True
self.setupPtraceOptions(tid)
self.runAgain()
elif self._stopped_hack:
newtid = self.getPtraceEvent(tid)
#print("WHY DID WE GET *ANOTHER* STOP?: %d" % tid)
#print('PTRACE EVENT: %d' % newtid)
self.attachThread(newtid, attached=True)
else: # on first attach...
self._stopped_hack = True
self.setupPtraceOptions(tid)
self.handlePosixSignal(sig)
#FIXME eventually implement child catching!
else:
self.handlePosixSignal(sig)
return
v_posix.PosixMixin.platformProcessEvent(self, event)
@v_base.threadwrap
def getPtraceEvent(self, tid=None):
"""
This *thread wrapped* function will get any pending GETEVENTMSG
msgs.
"""
p = c_ulong(0)
if tid is None:
tid = self.getMeta("ThreadId", -1)
if v_posix.ptrace(PT_GETEVENTMSG, tid, 0, addressof(p)) != 0:
raise Exception('ptrace PT_GETEVENTMSG failed!')
return p.value
def platformGetThreads(self):
ret = {}
for tid in self.pthreads:
ret[tid] = tid # FIXME make this pthread struct or stackbase soon
return ret
def platformGetMaps(self):
maps = []
with open("/proc/%d/maps" % self.pid, 'r') as mapfile:
for line in mapfile:
perms = 0
sline = line.split(" ")
addrs = sline[0]
permstr = sline[1]
fname = sline[-1].strip()
addrs = addrs.split("-")
base = int(addrs[0],16)
max = int(addrs[1],16)
mlen = max-base
if "r" in permstr:
perms |= e_const.MM_READ
if "w" in permstr:
perms |= e_const.MM_WRITE
if "x" in permstr:
perms |= e_const.MM_EXEC
#if "p" in permstr:
#pass
maps.append((base,mlen,perms,fname))
return maps
def platformGetFds(self):
fds = []
for name in os.listdir("/proc/%d/fd/" % self.pid):
try:
fdnum = int(name)
fdtype = vtrace.FD_UNKNOWN
link = os.readlink("/proc/%d/fd/%s" % (self.pid, name))
if "socket:" in link:
fdtype = vtrace.FD_SOCKET
elif "pipe:" in link:
fdtype = vtrace.FD_PIPE
elif "/" in link:
fdtype = vtrace.FD_FILE
fds.append((fdnum, fdtype, link))
except Exception:
logger.error(traceback.format_exc())
return fds
############################################################################
#
# NOTE: Both of these use class locals set by the i386/amd64 variants
#
@v_base.threadwrap
def platformGetRegCtx(self, tid):
ctx = self.archGetRegCtx()
u = self.user_reg_struct()
if v_posix.ptrace(PT_GETREGS, tid, 0, addressof(u)) == -1:
raise v_exc.PtraceException("PT_GETREGS")
ctx._rctx_Import(u)
return ctx
@v_base.threadwrap
def platformSetRegCtx(self, tid, ctx):
u = self.user_reg_struct()
# Populate the reg struct with the current values (to allow for
# any regs in that struct that we don't track... *fs_base*ahem*
if v_posix.ptrace(PT_GETREGS, tid, 0, addressof(u)) == -1:
raise v_exc.PtraceException("PT_GETREGS")
ctx._rctx_Export(u)
if v_posix.ptrace(PT_SETREGS, tid, 0, addressof(u)) == -1:
raise v_exc.PtraceException("PT_SETREGS")
class Linuxi386Trace(
vtrace.Trace,
LinuxMixin,
v_i386.i386Mixin,
v_posix.ElfMixin,
v_base.TracerBase):
user_reg_struct = user_regs_i386
user_dbg_offset = 252
reg_val_mask = 0xffffffff
def __init__(self):
vtrace.Trace.__init__(self)
v_base.TracerBase.__init__(self)
v_posix.ElfMixin.__init__(self)
v_i386.i386Mixin.__init__(self)
LinuxMixin.__init__(self)
# Pre-calc the index of the debug regs
self.dbgidx = self.archGetRegCtx().getRegisterIndex("debug0")
@v_base.threadwrap
def platformGetRegCtx(self, tid):
ctx = LinuxMixin.platformGetRegCtx( self, tid )
for i in intel_dbgregs:
offset = self.user_dbg_offset + (self.psize * i)
r = v_posix.ptrace(v_posix.PT_READ_U, tid, offset, 0)
ctx.setRegister(self.dbgidx+i, r & self.reg_val_mask)
return ctx
@v_base.threadwrap
def platformSetRegCtx(self, tid, ctx):
LinuxMixin.platformSetRegCtx( self, tid, ctx )
for i in intel_dbgregs:
val = ctx.getRegister(self.dbgidx + i)
offset = self.user_dbg_offset + (self.psize * i)
if v_posix.ptrace(v_posix.PT_WRITE_U, tid, offset, val) != 0:
libc.perror('PT_WRITE_U failed for debug%d' % i)
@v_base.threadwrap
def platformAllocateMemory(self, size, perms=e_const.MM_RWX, suggestaddr=0):
sp = self.getStackCounter()
pc = self.getProgramCounter()
# Xlate perms (mmap is backward)
realperm = 0
if perms & e_const.MM_READ:
realperm |= 1
if perms & e_const.MM_WRITE:
realperm |= 2
if perms & e_const.MM_EXEC:
realperm |= 4
#mma is struct of mmap args for linux syscall
mma = struct.pack("<6L", suggestaddr, size, realperm, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0)
regsave = self.getRegisters()
stacksave = self.readMemory(sp, len(mma))
ipsave = self.readMemory(pc, 2)
SYS_mmap = 90
self.writeMemory(sp, mma)
self.writeMemory(pc, b"\xcd\x80")
self.setRegisterByName("eax", SYS_mmap)
self.setRegisterByName("ebx", sp)
self._syncRegs()
try:
# Step over our syscall instruction
tid = self.getMeta("ThreadId", 0)
self.platformStepi()
os.waitpid(tid, 0)
eax = self.getRegisterByName("eax")
if eax & 0x80000000:
raise Exception("Linux mmap syscall error: %d" % eax)
return eax
finally:
# Clean up all our fux0ring
self.writeMemory(sp, stacksave)
self.writeMemory(pc, ipsave)
self.setRegisters(regsave)
class LinuxAmd64Trace(
vtrace.Trace,
LinuxMixin,
v_amd64.Amd64Mixin,
v_posix.ElfMixin,
v_base.TracerBase):
user_reg_struct = user_regs_amd64
user_dbg_offset = 848
reg_val_mask = 0xffffffffffffffff
def __init__(self):
vtrace.Trace.__init__(self)
v_base.TracerBase.__init__(self)
v_posix.ElfMixin.__init__(self)
v_amd64.Amd64Mixin.__init__(self)
LinuxMixin.__init__(self)
self.dbgidx = self.archGetRegCtx().getRegisterIndex("debug0")
@v_base.threadwrap
def platformGetRegCtx(self, tid):
ctx = LinuxMixin.platformGetRegCtx( self, tid )
for i in intel_dbgregs:
offset = self.user_dbg_offset + (self.psize * i)
r = v_posix.ptrace(v_posix.PT_READ_U, tid, offset, 0)
ctx.setRegister(self.dbgidx+i, r & self.reg_val_mask)
self.platformGetExtendedRegs(tid, ctx)
return ctx
@v_base.threadwrap
def platformSetRegCtx(self, tid, ctx):
LinuxMixin.platformSetRegCtx( self, tid, ctx )
for i in intel_dbgregs:
val = ctx.getRegister(self.dbgidx + i)
offset = self.user_dbg_offset + (self.psize * i)
if v_posix.ptrace(v_posix.PT_WRITE_U, tid, offset, val) != 0:
libc.perror('PT_WRITE_U failed for debug%d' % i)
def parseXSave(self, ctx, iovec):
'''
PT_GETREGSET basically just dumps the xsave memory region, so we need to parse that out
'''
# There are other registers that are saved in here, but for now, fuck 'em
simd_regs = [0 for i in range(16)]
fpu_off = 32
fpu_len = 8
# yes you could get this by various other PTRACE calls, but since we already have to call into NT_X86_XSTATE,
# might as well grab these while we're here
regidx = self.archGetRegCtx().getRegisterIndex("st0")
mask = 0xFFFFFFFFFFFFFFFFFFFF # 80 bit mask for floating points regs
for i in range(fpu_len):
offset = fpu_off + i * 16
# the upper 48 bits of the st/mm registers are marked as reserved
valu = e_bits.parsebytes(bytes(iovec[offset:offset+10]), 0, 10)
ctx.setRegister(regidx+i, valu)
xmm_off = 160
# these are just the lower bits of the simd registers (just the xmm portion)
for i in range(len(simd_regs)):
offset = xmm_off + i * 16
simd_regs[i] = e_bits.parsebytes(bytes(iovec[offset:offset+16]), 0, 16)
xstate_bv = e_bits.parsebytes(bytes(iovec[512:520]), 0, 8)
has_avx = xstate_bv & 0x4
# XXX: Sooooo....we're gonna cheat a bit here. Technically what we're supposed to do
# is check CPUID.(EAX=0x0D, ECX=i) for every feature and se how many bytes it takes up, but
# right now the goal is just to get the upper YMM registers, and we know exactly how
# many bytes those take up, and they're literally the first state component in the extended
# xsave region (standard or compacted), so let's parse us some bytes if the has_avx bit is set.
# (we're also doing it this way because I don't feel like figuring out how to directly call
# the cpuid asm instruction from python)
if has_avx:
ymm_offset = 576
for i in range(len(simd_regs)):
offset = ymm_offset + i*16
valu = e_bits.parsebytes(bytes(iovec[offset:offset+16]), 0, 16)
simd_regs[i] |= valu << 128
regidx = self.archGetRegCtx().getRegisterIndex("ymm0")
for i, valu in enumerate(simd_regs):
ctx.setRegister(regidx+i, valu)
def platformGetExtendedRegs(self, tid, ctx):
'''
for now, the only real way to get access to things like YMM and ZMM registers
'''
buflen = 2048 # Guess. Actual length will be set by kernel
buffer = (c_uint8 * buflen)()
vec = iovec(cast(buffer, c_void_p), buflen)
if v_posix.ptrace(PT_GETREGSET, tid, NT_X86_XSTATE, addressof(vec)) != 0:
raise v_exc.PtraceException("PT_GETREGSET(NT_X86_XSTATE)")
self.parseXSave(ctx, buffer)
arm_break_be = binascii.unhexlify('e7f001f0')
arm_break_le = binascii.unhexlify('f001f0e7')
class LinuxArmTrace(
vtrace.Trace,
LinuxMixin,
v_arm.ArmMixin,
v_posix.ElfMixin,
v_base.TracerBase):
user_reg_struct = user_regs_arm
reg_val_mask = 0xffffffff
def __init__(self):
vtrace.Trace.__init__(self)
v_base.TracerBase.__init__(self)
v_posix.ElfMixin.__init__(self)
v_arm.ArmMixin.__init__(self)
LinuxMixin.__init__(self)
self._break_after_bp = False
self._step_cleanup = []
def _fireStep(self):
# See notes below about insanity...
if self._step_cleanup is not None:
[ self.writeMemory( bva, bytes ) for (bva,bytes) in self._step_cleanup ]
self._step_cleanup = None
return v_base.TracerBase._fireStep( self )
def archGetBreakInstr(self):
return arm_break_le
@v_base.threadwrap
def platformStepi(self):
# This is a total rediculous hack to account
# for the fact that the arm platform couldn't
# be bothered to implement single stepping in
# the stupid hardware...
self.stepping = True
pc = self.getProgramCounter()
op = self.parseOpcode( pc )
branches = op.getBranches( self )
if not branches:
raise Exception('''
The branches for the instruction %r were not decoded correctly. This means that
we cant properly predict the possible next instruction executions in a way that allows us
to account for the STUPID INSANE FACT THAT THERE IS NO HARDWARE SINGLE STEP CAPABILITY ON
ARM (non-realtime or JTAG anyway). We *would* have written invalid instructions to each
of those locations and cleaned them up before you ever knew anything was amiss... which is
how we pretend arm can single step... even though IT CANT. (please tell visi...)
''' % op)
# Save the memory at the branches for later
# restoration in the _fireStep callback.
self._step_cleanup = []
for bva,bflags in op.getBranches( self ):
self._step_cleanup.append( (bva, self.readMemory( bva, 4 )) )
self.writeMemory( bva, arm_break_le )
tid = self.getMeta('ThreadId')
if v_posix.ptrace(v_posix.PT_CONTINUE, tid, 0, 0) != 0:
raise Exception("ERROR ptrace failed for tid %d" % tid)