mirror of
https://github.com/reyammer/shellnoob
synced 2026-06-08 17:08:51 +00:00
52663a7b21
In newer versions of Python (e.g. 3.13.3) the lack of "r'" prefix before strings carrying regular expressions with character groups such as \s+ causes warnings about Python's attempts to interpret them as escape sequences, leading to warnings like this:
/home/kali/shellnoob.py:605: SyntaxWarning: invalid escape sequence '\s'
m = re.search('.text\s+\w+\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)', line)
/home/kali/shellnoob.py:614: SyntaxWarning: invalid escape sequence '\s'
m = re.search('.text\s+\w+\s+([0-9a-f]+)\s+([0-9a-f]+)', line)
/home/kali/shellnoob.py:620: SyntaxWarning: invalid escape sequence '\s'
m = re.search('\s+([0-9a-f]+)\s+[0-9a-f]+', line)
/home/kali/shellnoob.py:641: SyntaxWarning: invalid escape sequence '\s'
m = re.search('([0-9a-f]+):\s+[0-9a-f ]+\s+call.*fork', line)
/home/kali/shellnoob.py:1029: SyntaxWarning: invalid escape sequence '\s'
m = re.search('[0-9a-f]+:\s+([0-9a-f ]+)\t(.*)$', line)
Providing a fixed version with all affected re.search() parameters properly prefixed.
1565 lines
57 KiB
Python
Executable File
1565 lines
57 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2013 Yanick Fratantonio
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
from __future__ import print_function
|
|
__author__ = 'Yanick Fratantonio <yanick@cs.ucsb.edu>'
|
|
__description__ = 'Toolkit to write shellcodes'
|
|
__version__ = "2.0"
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
import stat
|
|
import traceback
|
|
import shutil
|
|
import socket
|
|
from tempfile import mktemp, NamedTemporaryFile
|
|
from subprocess import call, Popen, PIPE
|
|
import binascii
|
|
PY2 = sys.version_info.major == 2
|
|
|
|
try:
|
|
import IPython
|
|
except ImportError:
|
|
pass
|
|
|
|
if PY2:
|
|
import urllib2
|
|
input = raw_input
|
|
cbytes = lambda source, encoding='utf-8': bytes(source)
|
|
cstr = lambda source, encoding='utf-8': str(source)
|
|
urlread = lambda url: urllib2.urlopen(url).read()
|
|
HTTPError = urllib2.HTTPError
|
|
else:
|
|
import urllib
|
|
import urllib.request
|
|
import urllib.error
|
|
cbytes = lambda source, encoding='utf-8': bytes(source, encoding)
|
|
cstr = lambda source, encoding='utf-8': str(source, encoding)
|
|
urlread = lambda url: urllib.request.urlopen(url).read()
|
|
HTTPError = urllib.error.HTTPError
|
|
|
|
######################
|
|
### main functions ###
|
|
######################
|
|
|
|
def print_usage():
|
|
script_fn = os.path.basename(sys.argv[0])
|
|
print('%s [--from-INPUT] (input_file_path | - ) [--to-OUTPUT] [output_file_path | - ]' % script_fn, file=sys.stderr)
|
|
print('%s -c (prepend a breakpoint (Warning: only few platforms/OS are supported!)' % script_fn, file=sys.stderr)
|
|
print('%s --64 (64 bits mode, default: 32 bits)' % script_fn, file=sys.stderr)
|
|
print('%s --intel (intel syntax mode, default: att)' % script_fn, file=sys.stderr)
|
|
print('%s -q (quite mode)' % script_fn, file=sys.stderr)
|
|
print('%s -v (or -vv, -vvv)' % script_fn, file=sys.stderr)
|
|
print('%s --to-strace (compiles it & run strace)' % script_fn, file=sys.stderr)
|
|
print('%s --to-gdb (compiles it & run gdb & set breakpoint on entrypoint)' % script_fn, file=sys.stderr)
|
|
print('', file=sys.stderr)
|
|
print('Standalone "plugins"', file=sys.stderr)
|
|
print('%s -i [--to-asm | --to-opcode ] (for interactive mode)' % script_fn, file=sys.stderr)
|
|
print('%s --get-const <const>' % script_fn, file=sys.stderr)
|
|
print('%s --get-sysnum <sysnum>' % script_fn, file=sys.stderr)
|
|
print('%s --get-strerror <errno>' % script_fn, file=sys.stderr)
|
|
print('%s --file-patch <exe_fp> <file_offset> <data> (in hex). (Warning: tested only on x86/x86_64)' % script_fn, file=sys.stderr)
|
|
print('%s --vm-patch <exe_fp> <vm_address> <data> (in hex). (Warning: tested only on x86/x86_64)' % script_fn, file=sys.stderr)
|
|
print('%s --fork-nopper <exe_fp> (this nops out the calls to fork(). Warning: tested only on x86/x86_64)' % script_fn, file=sys.stderr)
|
|
print('', file=sys.stderr)
|
|
print('"Installation"', file=sys.stderr)
|
|
print('%s --install [--force] (this just copies the script in a convinient position)' % script_fn, file=sys.stderr)
|
|
print('%s --uninstall [--force]' % script_fn, file=sys.stderr)
|
|
print('', file=sys.stderr)
|
|
print('Supported INPUT format: %s' % ', '.join(ShellNoob.INPUT_FMT), file=sys.stderr)
|
|
print('Supported OUTPUT format: %s' % ', '.join(ShellNoob.OUTPUT_FMT), file=sys.stderr)
|
|
print('All combinations from INPUT to OUTPUT are supported!', file=sys.stderr)
|
|
print('', file=sys.stderr)
|
|
print('Check out the README file for more info.', file=sys.stderr)
|
|
|
|
|
|
class ShellNoob():
|
|
|
|
INPUT_FMT = ['asm', 'obj', 'bin', 'hex', 'c', 'shellstorm']
|
|
OUTPUT_FMT = ['asm', 'obj', 'exe', 'bin', 'hex', 'c', 'completec',
|
|
'python', 'bash', 'ruby', 'pretty', 'safeasm']
|
|
|
|
# {kernel#hardware#flag_64_bit#flag_intel}
|
|
objdump_options_map = {
|
|
'Linux#i[2-6]?86#32#att' : '',
|
|
'Linux#i[2-6]?86#32#intel' : '-m i386:intel',
|
|
'Linux#x86_64#32#att' : '',
|
|
'Linux#x86_64#32#intel' : '-m i386:intel',
|
|
'Linux#x86_64#64#att' : '',
|
|
'Linux#x86_64#64#intel' : '-m i386:x86-64:intel',
|
|
'Linux#arm.*#32#.*' : '',
|
|
'FreeBSD#i[2-6]?86#32#.*' : '',
|
|
'FreeBSD#amd64#32#att' : ''
|
|
}
|
|
|
|
# {kernel-hardware-flag_64_bit-flag_intel}
|
|
as_options_map = {
|
|
'Linux#i[2-6]?86#32#att' : '',
|
|
'Linux#i[2-6]?86#32#intel' : '-msyntax=intel -mnaked-reg',
|
|
'Linux#x86_64#32#att' : '--32',
|
|
'Linux#x86_64#32#intel' : '--32 -msyntax=intel -mnaked-reg',
|
|
'Linux#x86_64#64#att' : '',
|
|
'Linux#x86_64#64#intel' : '-msyntax=intel -mnaked-reg',
|
|
'Linux#arm.*#32#.*' : '',
|
|
'FreeBSD#i[2-6]?86#32#.*' : '',
|
|
'FreeBSD#amd64#32#att' : ''
|
|
}
|
|
|
|
# {kernel-hardware-flag_64_bit-flag_intel}
|
|
ld_options_map = {
|
|
'Linux#i[2-6]?86#32#.*' : '',
|
|
'Linux#x86_64#32#.*' : '-m elf_i386',
|
|
'Linux#x86_64#64#.*' : '',
|
|
'Linux#arm.*#32#.*' : '',
|
|
'FreeBSD#i[2-6]?86#32#.*' : '-m elf_i386_fbsd',
|
|
'FreeBSD#amd64#32#att' : '-m elf_i386_fbsd'
|
|
}
|
|
|
|
# {kernel-hardware-flag_64_bit-flag_intel}
|
|
gcc_options_map = {
|
|
'Linux#i[2-6]?86#32#.*' : '',
|
|
'Linux#x86_64#32#.*' : '-m32',
|
|
'Linux#x86_64#64#.*' : '',
|
|
'Linux#arm.*#32#.*' : '',
|
|
'FreeBSD#i[2-6]?86#32#.*' : '-m elf_i386_fbsd'
|
|
}
|
|
|
|
# {kernel-hardware}
|
|
breakpoint_hex_map = {
|
|
'.*#i[2-6]?86' : 'cc',
|
|
'.*#x86_64' : 'cc'
|
|
}
|
|
|
|
# {kernel-hardware}
|
|
comment_as_char = {
|
|
'.*#i[2-6]?86' : '#',
|
|
'.*#x86_64' : '#',
|
|
'.*#arm.*' : '@',
|
|
}
|
|
|
|
# [hardware]
|
|
hw_with_align = ['arm.*']
|
|
|
|
shellcode_t = ('.section .text\n'
|
|
'%s\n'
|
|
)
|
|
|
|
completec_t = (
|
|
'''
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <errno.h>
|
|
#include <malloc.h>
|
|
|
|
%s
|
|
|
|
int len = (sizeof(shellcode) > 2048) ?sizeof(shellcode):2048;
|
|
|
|
int main() {
|
|
// make sure the memory is RWX to support self-modifying shellcode
|
|
char *target = (char *) memalign(4096, len);
|
|
mprotect(target, len, PROT_READ | PROT_WRITE | PROT_EXEC);
|
|
memcpy(target, shellcode, len);
|
|
(*(void (*)()) target)();
|
|
return 0;
|
|
}
|
|
'''
|
|
)
|
|
|
|
shellstorm_t = 'http://www.shell-storm.org/shellcode/files/shellcode-%s.php'
|
|
|
|
|
|
def __init__(self, flag_64_bit=False, flag_intel=False, with_breakpoint=False, verbose=0, keep_files=False):
|
|
self.shellnoob_fp = os.path.abspath(__file__)
|
|
self.flag_64_bit = '64' if flag_64_bit else '32'
|
|
self.flag_intel = 'intel' if flag_intel else 'att'
|
|
self.with_breakpoint = with_breakpoint
|
|
self.verbose = verbose
|
|
self.debug = True if self.verbose >= 4 else False
|
|
self.keep_files = keep_files
|
|
self.kernel = self.get_kernel()
|
|
self.hardware = self.get_hardware()
|
|
self.set_conv_functions()
|
|
self.check_compatibility()
|
|
|
|
def set_conv_functions(self):
|
|
for i in self.INPUT_FMT:
|
|
for o in self.OUTPUT_FMT:
|
|
func_name = '%s_to_%s' % (i, o)
|
|
if self.debug: print('Creating %s' % func_name, file=sys.stderr)
|
|
if i == o: continue
|
|
|
|
if func_name not in ShellNoob.__dict__:
|
|
# conversion not implemented: let's go through hex
|
|
setattr(ShellNoob, func_name, self.gen_conv_function(i, o))
|
|
|
|
def gen_conv_function(self, input_fmt, output_fmt):
|
|
# generate on-the-fly a conversion function going through the "hex" format
|
|
to_hex_func_name = '%s_to_hex' % input_fmt
|
|
from_hex_func_name = 'hex_to_%s' % output_fmt
|
|
to_hex = ShellNoob.__dict__[to_hex_func_name]
|
|
from_hex = ShellNoob.__dict__[from_hex_func_name]
|
|
|
|
def conv(self, input_s, with_breakpoint=False):
|
|
_hex = to_hex(self, input_s, with_breakpoint)
|
|
_output = from_hex(self, _hex)
|
|
return _output
|
|
|
|
return conv
|
|
|
|
def check_compatibility(self):
|
|
try:
|
|
self.get_objdump_options()
|
|
except ShellNoobException as e:
|
|
print('ERROR: %s' % e.message, file=sys.stderr)
|
|
sys.exit(2)
|
|
try:
|
|
self.get_as_options()
|
|
except ShellNoobException as e:
|
|
print('ERROR: %s' % e.message, file=sys.stderr)
|
|
sys.exit(2)
|
|
try:
|
|
self.get_ld_options()
|
|
except ShellNoobException as e:
|
|
print('ERROR: %s' % e.message, file=sys.stderr)
|
|
sys.exit(2)
|
|
if self.with_breakpoint:
|
|
try:
|
|
self.get_breakpoint_hex()
|
|
except ShellNoobException as e:
|
|
print('ERROR: %s' % e.message, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
|
|
def get_objdump_options(self, kernel=None, hardware=None, flag_64_bit=None, flag_intel=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
flag_64_bit = flag_64_bit if flag_64_bit is not None else self.flag_64_bit
|
|
flag_intel = flag_intel if flag_intel is not None else self.flag_intel
|
|
|
|
for entry, options in self.objdump_options_map.items():
|
|
e_kernel, e_hardware, e_64, e_intel = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if not re.search(e_64, flag_64_bit): continue
|
|
if not re.search(e_intel, flag_intel): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, options), file=sys.stderr)
|
|
return options
|
|
raise ShellNoobException('objdump_options not found for the current setup')
|
|
|
|
|
|
def get_as_options(self, kernel=None, hardware=None, flag_64_bit=None, flag_intel=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
flag_64_bit = flag_64_bit if flag_64_bit is not None else self.flag_64_bit
|
|
flag_intel = flag_intel if flag_intel is not None else self.flag_intel
|
|
|
|
for entry, options in self.as_options_map.items():
|
|
e_kernel, e_hardware, e_64, e_intel = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if not re.search(e_64, flag_64_bit): continue
|
|
if not re.search(e_intel, flag_intel): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, options), file=sys.stderr)
|
|
return options
|
|
raise ShellNoobException('as_options not found for the current setup')
|
|
|
|
|
|
def get_ld_options(self, kernel=None, hardware=None, flag_64_bit=None, flag_intel=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
flag_64_bit = flag_64_bit if flag_64_bit is not None else self.flag_64_bit
|
|
flag_intel = flag_intel if flag_intel is not None else self.flag_intel
|
|
|
|
for entry, options in self.ld_options_map.items():
|
|
e_kernel, e_hardware, e_64, e_intel = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if not re.search(e_64, flag_64_bit): continue
|
|
if not re.search(e_intel, flag_intel): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, options), file=sys.stderr)
|
|
return options
|
|
raise ShellNoobException('ld_options not found for the current setup')
|
|
|
|
|
|
def get_gcc_options(self, kernel=None, hardware=None, flag_64_bit=None, flag_intel=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
flag_64_bit = flag_64_bit if flag_64_bit is not None else self.flag_64_bit
|
|
flag_intel = flag_intel if flag_intel is not None else self.flag_intel
|
|
|
|
for entry, options in self.gcc_options_map.items():
|
|
e_kernel, e_hardware, e_64, e_intel = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if not re.search(e_64, flag_64_bit): continue
|
|
if not re.search(e_intel, flag_intel): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, options), file=sys.stderr)
|
|
return options
|
|
raise ShellNoobException('gcc_options not found for the current setup')
|
|
|
|
|
|
def get_breakpoint_hex(self, kernel=None, hardware=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
|
|
for entry, _hex in self.breakpoint_hex_map.items():
|
|
e_kernel, e_hardware = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if self.debug: print('MATCH with %s-%s ~> %s' % (e_kernel, e_hardware, _hex), file=sys.stderr)
|
|
return _hex
|
|
raise ShellNoobException('the breakpoint feature is not supported in the current configuration')
|
|
|
|
|
|
def get_comment_as_char(self, kernel=None, hardware=None):
|
|
# use the passed settings, if specified
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
for entry, comment_char in self.comment_as_char.items():
|
|
e_kernel, e_hardware = entry.split('#')
|
|
if not re.search(e_kernel, kernel): continue
|
|
if not re.search(e_hardware, hardware): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, comment_char), file=sys.stderr)
|
|
return comment_char
|
|
|
|
|
|
######################
|
|
# standalone plugins #
|
|
######################
|
|
|
|
def do_resolve_syscall(self, syscall, kernel=None, hardware=None):
|
|
global cstr
|
|
kernel = kernel if kernel is not None else self.kernel
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
|
|
if (kernel, hardware) == ('Linux', 'x86_64'):
|
|
platforms = {'i386' : ['asm/unistd_32.h'],
|
|
'x86_64' : ['asm/unistd_64.h']
|
|
}
|
|
symbol = '__NR_%s' % syscall
|
|
else:
|
|
platforms = {'i386' : ['sys/syscall.h']}
|
|
symbol = 'SYS_%s' % syscall
|
|
body = 'printf("%%d", %s); return 0;' % (symbol)
|
|
|
|
for platform, includes in reversed(sorted(platforms.items())):
|
|
try:
|
|
tmp_exe_fp = self.include_and_body_to_exe_fp(includes, body)
|
|
except ShellNoobException:
|
|
print('ERROR: syscall %s not found for platform %s' % (syscall, platform), file=sys.stderr)
|
|
continue
|
|
|
|
p = Popen(tmp_exe_fp, stdout=PIPE)
|
|
output, error = p.communicate()
|
|
retval = p.returncode
|
|
if retval == 0:
|
|
print('%s ~> %s' % (platform, cstr(output, "utf-8")))
|
|
else:
|
|
print('ERROR: reval %s while resolving syscall %s' % (retval, syscall), file=sys.stderr)
|
|
if not self.keep_files:
|
|
os.unlink(tmp_exe_fp)
|
|
|
|
|
|
def do_resolve_const(self, const):
|
|
includes = ['sys/types.h',
|
|
'sys/stat.h',
|
|
'sys/mman.h',
|
|
'fcntl.h',
|
|
]
|
|
body = 'printf("%%d", %s); return 0;' % (const)
|
|
|
|
try:
|
|
tmp_exe_fp = self.include_and_body_to_exe_fp(includes, body)
|
|
except ShellNoobException:
|
|
print('ERROR: constant %s not found' % const, file=sys.stderr)
|
|
return
|
|
|
|
p = Popen(tmp_exe_fp, stdout=PIPE)
|
|
output, error = p.communicate()
|
|
retval = p.returncode
|
|
if retval == 0:
|
|
print('%s ~> %s' % (const, int(output)))
|
|
else:
|
|
print('ERROR: reval %s while resolving const %s' % (retval, const), file=sys.stderr)
|
|
if not self.keep_files:
|
|
os.unlink(tmp_exe_fp)
|
|
|
|
|
|
def do_resolve_errno(self, errno):
|
|
global cstr
|
|
includes = ['string.h']
|
|
|
|
body = 'printf("%%s", strerror(%s)); return 0;' % (errno)
|
|
|
|
try:
|
|
tmp_exe_fp = self.include_and_body_to_exe_fp(includes, body)
|
|
except ShellNoobException:
|
|
print('ERROR: errno %s not found' % const, file=sys.stderr)
|
|
return
|
|
|
|
p = Popen(tmp_exe_fp, stdout=PIPE)
|
|
output, error = p.communicate()
|
|
retval = p.returncode
|
|
if retval == 0:
|
|
print('%s ~> %s' % (errno, cstr(output, "utf-8")))
|
|
else:
|
|
print('ERROR: reval %s while resolving errno %s' % (retval, errno), file=sys.stderr)
|
|
if not self.keep_files:
|
|
os.unlink(tmp_exe_fp)
|
|
|
|
|
|
def do_interactive_mode(self, args):
|
|
global cbytes
|
|
asm_to_opcode_flag = None
|
|
if '--to-opcode' in args:
|
|
asm_to_opcode_flag = True
|
|
elif '--to-asm' in args:
|
|
asm_to_opcode_flag = False
|
|
if asm_to_opcode_flag is None:
|
|
print('asm_to_opcode (1) or opcode_to_asm (2)?: ', end='')
|
|
answer = input()
|
|
while answer != '1' and answer != '2':
|
|
print('seriously? dude, choose between 1 and 2: ', end='')
|
|
answer = input()
|
|
asm_to_opcode_flag = True if answer == '1' else False
|
|
assert asm_to_opcode_flag is not None
|
|
if asm_to_opcode_flag:
|
|
print('asm_to_opcode selected (type "quit" or ^C to end)')
|
|
ins = ''
|
|
quit = False
|
|
while not quit:
|
|
while not ins:
|
|
print('>> ', end='')
|
|
ins = input().strip(' \t\n')
|
|
if ins.lower() == 'quit':
|
|
quit = True
|
|
if quit: continue
|
|
try:
|
|
_hex = self.ins_to_hex(ins)
|
|
print('%s ~> %s' % (ins, _hex))
|
|
except Exception as e:
|
|
print('ERROR: %s' % e, file=sys.stderr)
|
|
if self.verbose >= 3:
|
|
print(traceback.format_exc(), file=sys.stderr)
|
|
print('--------------------------', file=sys.stderr)
|
|
ins = ''
|
|
else:
|
|
print('opcode_to_asm selected (type "quit" or ^C to end)')
|
|
_hex = ''
|
|
quit = False
|
|
while not quit:
|
|
while not _hex:
|
|
print('>> ', end='')
|
|
_hex = input().strip(' \t\n')
|
|
if _hex.lower() == 'quit':
|
|
quit = True
|
|
if quit: continue
|
|
try:
|
|
_hex = _hex.replace(' ','').strip(' \t\n')
|
|
asm = self.hex_to_pretty(_hex)
|
|
print('%s ~> %s' % (cbytes(_hex), asm))
|
|
except Exception as e:
|
|
print('ERROR: %s' % e, file=sys.stderr)
|
|
if self.verbose >= 3:
|
|
print(traceback.format_exc(), file=sys.stderr)
|
|
print('--------------------------', file=sys.stderr)
|
|
_hex = ''
|
|
|
|
|
|
def do_conversion(self, input_fp, output_fp, input_fmt, output_fmt):
|
|
global cbytes
|
|
if self.verbose >= 0:
|
|
if input_fmt == '-':
|
|
msg = 'Converting from stdin (%s) ' % input_fmt
|
|
else:
|
|
msg = 'Converting %s (%s) ' % (input_fp, input_fmt)
|
|
if output_fp == '-':
|
|
msg += 'to stdout (%s)' % output_fmt
|
|
else:
|
|
msg += 'into %s (%s)' % (output_fp, output_fmt)
|
|
print(msg, file=sys.stderr)
|
|
|
|
# reading the input
|
|
if input_fp == '-':
|
|
_input = sys.stdin.read()
|
|
else:
|
|
if input_fmt == 'shellstorm':
|
|
_input = input_fp # shellcode id
|
|
else:
|
|
_input = open(input_fp, 'rb').read()
|
|
|
|
conv_func_name = '%s_to_%s' % (input_fmt, output_fmt)
|
|
try:
|
|
_output = getattr(self, conv_func_name)(_input)
|
|
except AttributeError as err:
|
|
print('ERROR: conversion mode "%s" is not supported.' % conv_func_name, file=sys.stderr)
|
|
if self.verbose >= 3: print(traceback.format_exc(), end='')
|
|
sys.exit(2)
|
|
except ShellNoobException as err:
|
|
print('%s' % err, file=sys.stderr)
|
|
if self.verbose >= 3: print(traceback.format_exc(), end='')
|
|
sys.exit(2)
|
|
|
|
if not isinstance(_output, bytes):
|
|
_output = cbytes(_output)
|
|
# writing the output
|
|
if output_fp == '-':
|
|
sys.stdout.write(_output.decode(sys.stdout.encoding))
|
|
else:
|
|
open(output_fp, 'wb').write(_output)
|
|
|
|
if output_fmt == 'exe' and output_fp != '-':
|
|
# chmod 700
|
|
os.chmod(output_fp, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
|
|
|
def do_strace(self, input_fp, input_fmt):
|
|
if self.verbose >= 3: print('IN do_strace', file=sys.stderr)
|
|
|
|
exe_fp = mktemp()
|
|
|
|
self.do_conversion(input_fp, exe_fp, input_fmt, 'exe')
|
|
|
|
p = Popen('strace %s' % exe_fp, shell=True)
|
|
p.wait()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(exe_fp)
|
|
if self.verbose >= 3: print('OUT do_strace', file=sys.stderr)
|
|
|
|
def do_gdb(self, input_fp, input_fmt):
|
|
if self.verbose >= 3: print('IN do_gdb', file=sys.stderr)
|
|
|
|
exe_fp = mktemp()
|
|
|
|
self.do_conversion(input_fp, exe_fp, input_fmt, 'exe')
|
|
|
|
start_addr = None
|
|
try:
|
|
start_addr = self.get_start_address(exe_fp)
|
|
except:
|
|
print('WARNING: failed to get the start address :-(', file=sys.stderr)
|
|
print(traceback.format_exc(), file=sys.stderr)
|
|
print('------------------------', file=sys.stderr)
|
|
|
|
if start_addr:
|
|
cmd = 'gdb -ex "break *%s" -q %s' % (start_addr, exe_fp)
|
|
else:
|
|
cmd = 'gdb -q %s' % exe_fp
|
|
p = Popen(cmd, shell=True)
|
|
p.wait()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(exe_fp)
|
|
|
|
if self.verbose >= 3: print('OUT do_gdb', file=sys.stderr)
|
|
|
|
|
|
#############################
|
|
# executable patching utils #
|
|
#############################
|
|
|
|
def get_bits(self, exe_fp):
|
|
bits = None
|
|
if '32-bit' in os.popen('file %s' % exe_fp).read():
|
|
bits = 32
|
|
elif '64-bit' in os.popen('file %s' % exe_fp).read():
|
|
bits = 64
|
|
assert bits is not None
|
|
return bits
|
|
|
|
def get_text_section_info(self, exe_fp):
|
|
bits = self.get_bits(exe_fp)
|
|
vm_address, file_offset, size = None, None, None
|
|
lines = os.popen('readelf -S %s' % exe_fp).read().split('\n')
|
|
if bits == 32:
|
|
for line in lines:
|
|
m = re.search(r'.text\s+\w+\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)', line)
|
|
if not m: continue
|
|
vm_address = int(m.group(1), 16)
|
|
file_offset = int(m.group(2), 16)
|
|
size = int(m.group(3), 16)
|
|
break
|
|
elif bits == 64:
|
|
for line in lines:
|
|
if vm_address is None and file_offset is None:
|
|
m = re.search(r'.text\s+\w+\s+([0-9a-f]+)\s+([0-9a-f]+)', line)
|
|
if not m: continue
|
|
vm_address = int(m.group(1), 16)
|
|
file_offset = int(m.group(2), 16)
|
|
continue
|
|
else:
|
|
m = re.search(r'\s+([0-9a-f]+)\s+[0-9a-f]+', line)
|
|
if not m: raise Exception('error while parsing readelf -S (64bit)')
|
|
size = int(m.group(1), 16)
|
|
break
|
|
else:
|
|
raise Exception('weird number of bits')
|
|
|
|
assert vm_address is not None and file_offset is not None and size is not None
|
|
|
|
return vm_address, file_offset, size
|
|
|
|
def get_file_offset_from_vm_address(self, exe_fp, vm_address):
|
|
start_vm, start_file, size = self.get_text_section_info(exe_fp)
|
|
assert start_vm <= vm_address <= start_vm + size
|
|
return vm_address - start_vm + start_file
|
|
|
|
def do_fork_nopper(self, exe_fp):
|
|
lines = os.popen('objdump -d %s' % exe_fp).read().split('\n')
|
|
for line in lines:
|
|
if self.verbose >= 1:
|
|
print(line)
|
|
m = re.search(r'([0-9a-f]+):\s+[0-9a-f ]+\s+call.*fork', line)
|
|
if not m: continue
|
|
vm_address = int(m.group(1), 16)
|
|
file_offset = self.get_file_offset_from_vm_address(exe_fp, vm_address)
|
|
print('Found call to fork @ 0x%x (file offset 0x%x)' % (vm_address, file_offset))
|
|
self.do_exe_patch(exe_fp, b'\x90\x90\x90\x31\xc0', file_offset)
|
|
|
|
def do_exe_patch(self, exe_fp, data, file_offset=None, vm_address=None, replace=True):
|
|
if not replace:
|
|
raise Exception('unsupported')
|
|
|
|
if file_offset is None and vm_address is None:
|
|
raise Exception('you need to specify at least one of the two ;)')
|
|
|
|
if file_offset is None:
|
|
file_offset = self.get_file_offset_from_vm_address(exe_fp, vm_address)
|
|
|
|
f = open(exe_fp, 'rb+')
|
|
f.seek(file_offset)
|
|
f.write(data)
|
|
f.close()
|
|
|
|
|
|
###################
|
|
### conversions ###
|
|
###################
|
|
|
|
def asm_to_hex(self, asm, with_breakpoint=None):
|
|
global cstr
|
|
if self.verbose >= 3: print('IN asm_to_hex', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
obj = self.asm_to_obj(asm, with_breakpoint)
|
|
_hex = self.obj_to_hex(obj, with_breakpoint=False)
|
|
|
|
if self.verbose >= 3: print('OUT asm_to_hex', file=sys.stderr)
|
|
return cstr(_hex)
|
|
|
|
def bin_to_hex(self, _bin, with_breakpoint=None):
|
|
global cbytes
|
|
if self.verbose >= 3: print('IN bin_to_hex', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
if self.verbose >= 3: print('OUT bin_to_hex', file=sys.stderr)
|
|
return cbytes(prepend) + binascii.hexlify(_bin)
|
|
|
|
def obj_to_hex(self, obj, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN obj_to_hex', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
tmp_obj_f = NamedTemporaryFile(delete=False)
|
|
tmp_obj_fp = tmp_obj_f.name
|
|
tmp_obj_f.write(obj)
|
|
tmp_obj_f.close()
|
|
|
|
tmp_bin_fp = mktemp()
|
|
|
|
cmd = 'objcopy -O binary %s %s' % (tmp_obj_fp, tmp_bin_fp)
|
|
retval = self.exec_cmd(cmd, 'obj_to_hex')
|
|
try:
|
|
assert retval == 0
|
|
except:
|
|
raise Exception('Error while converting from obj_to_hex. Not valid ELF?')
|
|
|
|
_bin = open(tmp_bin_fp, 'rb').read()
|
|
_hex = self.bin_to_hex(_bin, with_breakpoint)
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_obj_fp)
|
|
os.unlink(tmp_bin_fp)
|
|
|
|
if self.verbose >= 3: print('OUT obj_to_hex', file=sys.stderr)
|
|
return _hex
|
|
|
|
def c_to_hex(self, c, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN c_to_hex', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
print('WARNING: c_to_hex just extracts the \\xXX looking parts. Check that everything it\'s fine!', file=sys.stderr)
|
|
|
|
def get_next_hex(buf):
|
|
slash_x_idx = buf.find('\\x')
|
|
if slash_x_idx == -1:
|
|
return '', ''
|
|
return buf[slash_x_idx+2:slash_x_idx+4], buf[slash_x_idx+4:]
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
|
|
_hex = ''
|
|
_next = c
|
|
while _next:
|
|
hex_byte, _next = get_next_hex(_next)
|
|
_hex += hex_byte
|
|
_hex = prepend + _hex
|
|
|
|
if self.verbose >= 3: print('OUT c_to_hex', file=sys.stderr)
|
|
return _hex
|
|
|
|
def shellstorm_to_hex(self, shellstorm_id, with_breakpoint=None):
|
|
global cstr, urlread
|
|
if self.verbose >= 3: print('IN shellstorm_to_hex', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
print('WARNING: shellstorm_to_hex just extracts the \\xXX looking parts. Check that everything it\'s fine!', file=sys.stderr)
|
|
|
|
shellstorm_url = self.shellstorm_t % shellstorm_id
|
|
try:
|
|
content = cstr(urlread(shellstorm_url))
|
|
except HTTPError as err:
|
|
raise ShellNoobException('ERROR: failed fetching shellcode from %s (%s)' % (shellstorm_url, err))
|
|
|
|
# prefilter some html stuff
|
|
after_pre_idx = content.find('<pre>') + len('<pre>')
|
|
before_body_idx = content.find('<body>')
|
|
content = content[after_pre_idx:before_body_idx]
|
|
|
|
_hex = self.c_to_hex(content, with_breakpoint)
|
|
|
|
if self.verbose >= 3: print('OUT shellstorm_to_hex', file=sys.stderr)
|
|
return _hex
|
|
|
|
|
|
|
|
################
|
|
### hex_to_* ###
|
|
################
|
|
|
|
def hex_to_asm(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_asm', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
obj = self.hex_to_obj(_hex, with_breakpoint)
|
|
asm = self.obj_to_asm(obj, with_breakpoint=False)
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_asm', file=sys.stderr)
|
|
return asm
|
|
|
|
def hex_to_obj(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_obj', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if not isinstance(_hex, str):
|
|
_hex = cstr(_hex)
|
|
if len(_hex) != 0 and _hex.endswith('\n'):
|
|
_hex = _hex.rstrip('\n')
|
|
print('Warning: stripped a \'\\n\' at the end of the hex', file=sys.stderr)
|
|
if len(_hex) == 0 or len(_hex) % 2 != 0:
|
|
raise Exception('Not valid _hex: %s' % _hex)
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
_hex = prepend + _hex
|
|
|
|
asm = self.hex_to_asm_bytes(_hex)
|
|
obj = self.asm_to_obj(asm, with_breakpoint=False)
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_obj', file=sys.stderr)
|
|
return obj
|
|
|
|
def hex_to_exe(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_exe', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
completec = self.hex_to_completec(_hex, with_breakpoint)
|
|
exe = self.c_to_exe(completec, with_breakpoint=False)
|
|
if self.verbose >= 3: print('OUT hex_to_exe', file=sys.stderr)
|
|
return exe
|
|
|
|
def hex_to_bin(self, _hex, with_breakpoint=None):
|
|
global cstr
|
|
if self.verbose >= 3: print('IN hex_to_bin', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if not isinstance(_hex, str):
|
|
_hex = cstr(_hex)
|
|
if len(_hex) != 0 and _hex.endswith('\n'):
|
|
_hex = _hex.rstrip('\n')
|
|
print('Warning: stripped a \'\\n\' at the end of the hex', file=sys.stderr)
|
|
if len(_hex) == 0 or len(_hex) % 2 != 0:
|
|
raise Exception('Not valid _hex: %s' % _hex)
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
_hex = prepend + _hex
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_bin', file=sys.stderr)
|
|
return binascii.unhexlify(_hex)
|
|
|
|
def hex_to_c(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_c', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if not isinstance(_hex, str):
|
|
_hex = cstr(_hex)
|
|
if len(_hex) != 0 and _hex.endswith('\n'):
|
|
_hex = _hex.rstrip('\n')
|
|
print('Warning: stripped a \'\\n\' at the end of the hex', file=sys.stderr)
|
|
if len(_hex) == 0 or len(_hex) % 2 != 0:
|
|
raise Exception('Not valid _hex: %s' % _hex)
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
_hex = prepend + _hex
|
|
|
|
template = 'char shellcode[] = "%s";'
|
|
content = ''
|
|
for idx in range(0, len(_hex), 2):
|
|
content += '\\x%s' % _hex[idx:idx+2]
|
|
out = template % content
|
|
if self.verbose >= 3: print('OUT hex_to_c', file=sys.stderr)
|
|
return out
|
|
|
|
def hex_to_python(self, _hex, with_breakpoint=None):
|
|
global cstr
|
|
if self.verbose >= 3: print('IN hex_to_python', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if not isinstance(_hex, str):
|
|
_hex = cstr(_hex)
|
|
if len(_hex) != 0 and _hex.endswith('\n'):
|
|
_hex = _hex.rstrip('\n')
|
|
print('Warning: stripped a \'\\n\' at the end of the hex', file=sys.stderr)
|
|
if len(_hex) == 0 or len(_hex) % 2 != 0:
|
|
raise Exception('Not valid _hex: %s' % _hex)
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
_hex = prepend + _hex
|
|
|
|
template = '%s'
|
|
content = ''
|
|
for idx in range(0, len(_hex), 2):
|
|
content += '\\x%s' % _hex[idx:idx+2]
|
|
out = template % content
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_python', file=sys.stderr)
|
|
return out
|
|
|
|
def hex_to_bash(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_bash', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
out = self.hex_to_python(_hex, with_breakpoint)
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_bash', file=sys.stderr)
|
|
return out
|
|
|
|
class AreYouFuckingKiddingMeException(Exception):
|
|
pass
|
|
|
|
def hex_to_ruby(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_ruby', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
# I'm not a ruby fan, and I really needed to put an easter egg :-)
|
|
raise AreYouFuckingKiddingMeException()
|
|
|
|
def hex_to_pretty(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_pretty', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
obj = self.hex_to_obj(_hex, with_breakpoint)
|
|
exe = self.obj_to_pretty(obj, with_breakpoint=False)
|
|
if self.verbose >= 3: print('OUT hex_to_pretty', file=sys.stderr)
|
|
return exe
|
|
|
|
def obj_to_pretty(self, obj, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN obj_to_pretty', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if with_breakpoint:
|
|
raise Exception('the with_breakpoint option is NOT supported in obj_to_exe')
|
|
|
|
if self.need_to_align():
|
|
_hex = self.obj_to_hex(obj)
|
|
if self.debug: print('hex lenght: ',len(_hex), file=sys.stderr)
|
|
aligned_hex = self.align_hex(_hex)
|
|
if self.debug: print('aligned hex lenght: ' , len(aligned_hex), file=sys.stderr)
|
|
if _hex != aligned_hex:
|
|
obj = self.hex_to_obj(aligned_hex, with_breakpoint=False)
|
|
|
|
tmp_obj_f = NamedTemporaryFile(delete=False)
|
|
tmp_obj_fp = tmp_obj_f.name
|
|
tmp_obj_f.write(obj)
|
|
tmp_obj_f.close()
|
|
|
|
tmp_pretty_fp = mktemp()
|
|
|
|
objdump_options = self.get_objdump_options()
|
|
cmd = 'objdump -d %s %s > %s' % (objdump_options,
|
|
tmp_obj_fp,
|
|
tmp_pretty_fp
|
|
)
|
|
self.exec_cmd(cmd, caller='obj_to_pretty')
|
|
|
|
pretty = open(tmp_pretty_fp).read()
|
|
|
|
started = False
|
|
lines = []
|
|
for line in pretty.split('\n'):
|
|
if not started and 'Disassembly of section .text:' in line:
|
|
started = True
|
|
if not started: continue
|
|
lines.append(line)
|
|
pretty = '\n'.join(lines)
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_obj_fp)
|
|
os.unlink(tmp_pretty_fp)
|
|
|
|
if self.verbose >= 3: print('OUT obj_to_pretty', file=sys.stderr)
|
|
return pretty
|
|
|
|
|
|
#########################
|
|
### additional blocks ###
|
|
#########################
|
|
|
|
def asm_to_obj(self, asm, with_breakpoint=None):
|
|
global cstr
|
|
if self.verbose >= 3: print('IN asm_to_obj', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if isinstance(asm, bytes):
|
|
asm = cstr(asm)
|
|
prepend = self.hex_to_asm_bytes(self.get_breakpoint_hex()) if with_breakpoint else ''
|
|
|
|
asm = prepend + asm + '\n'
|
|
tmp_asm_f = NamedTemporaryFile(delete=False)
|
|
tmp_asm_fp = tmp_asm_f.name
|
|
tmp_asm_f.write(asm.encode("utf-8"))
|
|
tmp_asm_f.close()
|
|
|
|
tmp_obj_fp = mktemp()
|
|
|
|
as_options = self.get_as_options()
|
|
|
|
cmd = 'as %s -o %s %s' % (as_options, tmp_obj_fp, tmp_asm_fp)
|
|
self.exec_cmd(cmd, caller='asm_to_obj')
|
|
if not os.path.isfile(tmp_obj_fp):
|
|
raise Exception("not valid shellcode (asm_to_obj)")
|
|
|
|
# delete all the symbols
|
|
cmd = 'strip %s' % tmp_obj_fp
|
|
self.exec_cmd(cmd, caller='asm_to_obj')
|
|
|
|
obj = open(tmp_obj_fp, 'rb').read()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_asm_fp)
|
|
os.unlink(tmp_obj_fp)
|
|
|
|
if self.verbose >= 3: print('OUT asm_to_obj', file=sys.stderr)
|
|
return obj
|
|
|
|
def obj_to_asm(self, obj, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN obj_to_asm', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if self.need_to_align():
|
|
_hex = self.obj_to_hex(obj)
|
|
aligned_hex = self.align_hex(_hex)
|
|
if _hex != aligned_hex:
|
|
obj = self.hex_to_obj(aligned_hex, with_breakpoint=False)
|
|
|
|
tmp_obj_f = NamedTemporaryFile(delete=False)
|
|
tmp_obj_fp = tmp_obj_f.name
|
|
tmp_obj_f.write(obj)
|
|
tmp_obj_f.close()
|
|
|
|
objdump_options = self.get_objdump_options()
|
|
cmd = 'objdump -d %s %s | tr -s " "' % (objdump_options,
|
|
tmp_obj_fp,
|
|
)
|
|
if self.verbose >= 2: print('(obj_to_asm) Executing: %s' % cmd, file=sys.stderr)
|
|
obj_out = os.popen(cmd).read()
|
|
lines = obj_out.split('\n')
|
|
started = False
|
|
|
|
prepend = hex_to_asm_bytes(self.get_breakpoint_hex()) if with_breakpoint else ''
|
|
|
|
out_lines = []
|
|
max_asm_len, max_help_asm_len = 0, 0
|
|
for line in lines:
|
|
if not started and 'Disassembly of section .text:' in line:
|
|
started = True
|
|
continue
|
|
if not started: continue
|
|
|
|
comment_char = self.get_comment_as_char()
|
|
|
|
# asm started
|
|
m = re.search(r'[0-9a-f]+:\s+([0-9a-f ]+)\t(.*)$', line)
|
|
if not m:
|
|
continue
|
|
_hex = m.group(1).replace(' ', '').strip(' \t\n')
|
|
help_asm = self.hex_to_asm_bytes(_hex).rstrip('\n')
|
|
try:
|
|
_ascii = '.ascii "%s"' % _hex
|
|
_ascii = _ascii.strip(' \t\n')
|
|
except UnicodeDecodeError:
|
|
_ascii = ''
|
|
asm = m.group(2).strip(' \t\n')
|
|
sc_idx = asm.find(';')
|
|
if sc_idx != -1:
|
|
asm = asm[:sc_idx]
|
|
|
|
if len(asm) > max_asm_len:
|
|
max_asm_len = len(asm)
|
|
if len(help_asm) > max_help_asm_len:
|
|
max_help_asm_len = len(help_asm)
|
|
|
|
out_line = (asm, help_asm, _ascii)
|
|
out_lines.append(out_line)
|
|
|
|
out = prepend
|
|
out_fmt = ' {:<%d}\t{:} {:<%d} {:} {:}\n' % (max_asm_len, max_help_asm_len)
|
|
for (asm, help_asm, _ascii) in out_lines:
|
|
out += out_fmt.format(asm, comment_char, help_asm, comment_char, _ascii)
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_obj_fp)
|
|
|
|
shellcode = self.shellcode_t % out
|
|
|
|
if self.verbose >= 3: print('OUT obj_to_asm', file=sys.stderr)
|
|
return shellcode
|
|
|
|
def asm_to_exe(self, asm, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN asm_to_exe', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
_hex = self.asm_to_hex(asm, with_breakpoint)
|
|
exe = self.hex_to_exe(_hex, with_breakpoint=False)
|
|
|
|
if self.verbose >= 3: print('OUT asm_to_exe', file=sys.stderr)
|
|
return exe
|
|
|
|
def obj_to_exe(self, obj, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN obj_to_exe', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if with_breakpoint:
|
|
raise Exception('the with_breakpoint option is NOT supported in obj_to_exe')
|
|
|
|
tmp_obj_f = NamedTemporaryFile(delete=False)
|
|
tmp_obj_fp = tmp_obj_f.name
|
|
tmp_obj_f.write(obj)
|
|
tmp_obj_f.close()
|
|
|
|
tmp_exe_fp = mktemp()
|
|
|
|
ld_options = self.get_ld_options()
|
|
|
|
# note: ld -V to list all the emulations
|
|
cmd = 'ld -N %s %s -o %s' % (ld_options, tmp_obj_fp, tmp_exe_fp)
|
|
retval = self.exec_cmd(cmd, True, caller='obj_to_exe')
|
|
|
|
exe = open(tmp_exe_fp, 'rb').read()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_obj_fp)
|
|
os.unlink(tmp_exe_fp)
|
|
|
|
if self.verbose >= 3: print('OUT obj_to_exe', file=sys.stderr)
|
|
return exe
|
|
|
|
def hex_to_safeasm(self, _hex, with_breakpoint=None):
|
|
global cstr
|
|
if self.verbose >= 3: print('IN hex_to_safeasm', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
if not isinstance(_hex, str):
|
|
_hex = cstr(_hex)
|
|
if len(_hex) != 0 and _hex.endswith('\n'):
|
|
_hex = _hex.rstrip('\n')
|
|
print('Warning: stripped a \'\\n\' at the end of the hex', file=sys.stderr)
|
|
if len(_hex) == 0 or len(_hex) % 2 != 0:
|
|
raise Exception('Not valid _hex: %s' % _hex)
|
|
|
|
prepend = self.get_breakpoint_hex() if with_breakpoint else ''
|
|
_hex = prepend + _hex
|
|
|
|
asm = self.hex_to_asm_bytes(_hex)
|
|
shellcode = self.shellcode_t % asm
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_safeasm', file=sys.stderr)
|
|
return shellcode
|
|
|
|
def hex_to_completec(self, _hex, with_breakpoint=None):
|
|
if self.verbose >= 3: print('IN hex_to_completec', file=sys.stderr)
|
|
with_breakpoint = with_breakpoint if with_breakpoint is not None else self.with_breakpoint
|
|
|
|
c = self.hex_to_c(_hex, with_breakpoint)
|
|
completec = self.completec_t % c
|
|
|
|
if self.verbose >= 3: print('OUT hex_to_completec', file=sys.stderr)
|
|
return completec
|
|
|
|
def c_to_exe(self, c, with_breakpoint=None):
|
|
global cbytes
|
|
# NOTE assumption: the input is "compileable C"
|
|
if self.verbose >= 3: print('IN c_to_exe', file=sys.stderr)
|
|
|
|
if with_breakpoint:
|
|
raise Exception('the with_breakpoint option is NOT supported in c_to_exe')
|
|
|
|
if not isinstance(c, bytes):
|
|
c = cbytes(c)
|
|
tmp_c_f = NamedTemporaryFile(suffix='.c', delete=False)
|
|
tmp_c_fp = tmp_c_f.name
|
|
tmp_c_f.write(c)
|
|
tmp_c_f.close()
|
|
|
|
tmp_exe_fp = mktemp()
|
|
|
|
gcc_options = self.get_gcc_options()
|
|
cmd = 'gcc %s -o %s %s' % (gcc_options, tmp_exe_fp, tmp_c_fp)
|
|
retval = self.exec_cmd(cmd, True, caller='c_to_exe')
|
|
|
|
exe = open(tmp_exe_fp, 'rb').read()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_c_fp)
|
|
os.unlink(tmp_exe_fp)
|
|
|
|
if self.verbose >= 3: print('OUT c_to_exe', file=sys.stderr)
|
|
return exe
|
|
|
|
|
|
########################
|
|
# additional functions #
|
|
########################
|
|
|
|
def ins_to_hex(self, ins):
|
|
asm = self.inss_to_asm([ins])
|
|
_hex = self.asm_to_hex(asm)
|
|
return _hex
|
|
|
|
def hex_to_inss(self, _hex):
|
|
asm = self.hex_to_asm(_hex)
|
|
inss = asm.split('\n')[1:]
|
|
inss = filter(lambda i:i.strip(' \t'), inss)
|
|
inss = map(lambda i:i.split('#')[0], inss)
|
|
inss = map(lambda i:i.strip(' \t'), inss)
|
|
return list(inss)
|
|
|
|
def inss_to_asm(self, inss):
|
|
out = '\n'.join(inss)
|
|
shellcode = self.shellcode_t % out
|
|
return shellcode
|
|
|
|
def asm_to_inss(self, asm):
|
|
inss = []
|
|
for i in asm.split('\n'):
|
|
i = i.strip(' \t\n')
|
|
if not i: continue
|
|
inss.append(i)
|
|
return inss
|
|
|
|
|
|
###########
|
|
# helpers #
|
|
###########
|
|
|
|
def hex_to_asm_bytes(self, _hex):
|
|
hex_list = ['0x%s' % _hex[i:i+2] for i in range(0, len(_hex), 2)]
|
|
asm = '.byte ' + ','.join(hex_list) + '\n'
|
|
return asm
|
|
|
|
def include_and_body_to_exe_fp(self, includes, body):
|
|
global cbytes
|
|
std_includes = set(('stdio.h', 'stdlib.h', 'errno.h'))
|
|
includes = set(includes)
|
|
includes.update(std_includes)
|
|
|
|
c_prog = ''
|
|
for inc in includes:
|
|
c_prog += '#include<%s>\n' % inc
|
|
|
|
c_prog += 'int main() {\n'
|
|
c_prog += body
|
|
c_prog += '}\n'
|
|
|
|
tmp_c_fp = mktemp() + '.c'
|
|
tmp_exe_fp = mktemp()
|
|
|
|
with open(tmp_c_fp, 'wb') as f:
|
|
f.write(cbytes(c_prog))
|
|
|
|
cmd = 'gcc %s -o %s' % (tmp_c_fp, tmp_exe_fp)
|
|
retval = self.exec_cmd(cmd, 'include_and_body_to_exe_fp')
|
|
if retval != 0:
|
|
output = ''
|
|
raise ShellNoobException()
|
|
|
|
if not self.keep_files:
|
|
os.unlink(tmp_c_fp)
|
|
|
|
return tmp_exe_fp
|
|
|
|
def get_start_address(self, exe_fp):
|
|
cmd = 'objdump -f %s' % exe_fp
|
|
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
|
_out, _err = p.communicate()
|
|
assert p.returncode == 0
|
|
|
|
_out = cstr(_out)
|
|
for line in _out.split('\n'):
|
|
line = line.strip(' \t\n')
|
|
m = re.search(r'^start address (0x[0-9a-f]+)$', line)
|
|
if not m: continue
|
|
start_addr = m.group(1)
|
|
return start_addr
|
|
|
|
raise Exception('start address not found for %s' % exe_fp)
|
|
|
|
def exec_cmd(self, cmd, redirect_stderr=False, caller=None):
|
|
if self.verbose >= 2: print('(exec_cmd: "%s") Executing: "%s"' % (caller, cmd), file=sys.stderr)
|
|
|
|
if redirect_stderr:
|
|
with open('/dev/null', 'wb') as f:
|
|
retval = call(cmd, stderr=f, shell=True)
|
|
else:
|
|
retval = call(cmd, shell=True)
|
|
|
|
if self.verbose >= 2: print('(exec_cmd: "%s") Ret value: %s' % (caller, retval), file=sys.stderr)
|
|
return retval
|
|
|
|
def do_objdump_switch(self):
|
|
# do we need to invert the bytes from objdump?
|
|
return self.get_hardware().startswith('arm')
|
|
|
|
def switch_bytes(self, _hex):
|
|
# input: a hex string, like 34ab01ac
|
|
|
|
# group them by 2 chars
|
|
_hex = [_hex[i:i+2] for i in range(0, len(_hex), 2)]
|
|
# reverse the list
|
|
_hex = list(reversed(_hex))
|
|
# build a string
|
|
_hex = ''.join(_hex)
|
|
return _hex
|
|
|
|
def need_to_align(self, hardware=None):
|
|
# use the passed settings, if specified
|
|
hardware = hardware if hardware is not None else self.hardware
|
|
for e_hardware in self.hw_with_align:
|
|
if not re.search(e_hardware, hardware): continue
|
|
if self.debug: print('MATCH with %s ~> %s' % (entry, options), file=sys.stderr)
|
|
return True
|
|
return False
|
|
|
|
def align_hex(self, _hex):
|
|
assert len(_hex) % 2 == 0
|
|
if (len(_hex)/2) % 4 != 0:
|
|
_hex = _hex + '00'*(4 - ((len(_hex)/2) % 4))
|
|
assert len(_hex) % 8 == 0
|
|
return _hex
|
|
|
|
|
|
@staticmethod
|
|
def get_kernel():
|
|
return os.popen('uname -s').read().strip()
|
|
|
|
@staticmethod
|
|
def get_hardware():
|
|
return os.popen('uname -m').read().strip()
|
|
|
|
@staticmethod
|
|
def do_install(force=False):
|
|
if os.getuid() != 0:
|
|
print('ERROR: I need root!', file=sys.stderr)
|
|
sys.exit(1)
|
|
install_dir = '/usr/local/bin'
|
|
shellnoob_fp = os.path.join(install_dir, 'snoob')
|
|
print('This will copy shellnoob into %s' % shellnoob_fp, file=sys.stderr)
|
|
if not force:
|
|
input('Press a key to proceed..')
|
|
shutil.copyfile(__file__, shellnoob_fp)
|
|
os.chmod(shellnoob_fp, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
|
|
print('SUCCESS. "snoob -h" should display shellnoob\'s help', file=sys.stderr)
|
|
|
|
@staticmethod
|
|
def do_uninstall(force=False):
|
|
if os.getuid() != 0:
|
|
print('ERROR: I need root!', file=sys.stderr)
|
|
sys.exit(1)
|
|
install_dir = '/usr/local/bin'
|
|
shellnoob_fp = os.path.join(install_dir, 'snoob')
|
|
print('This will delete shellnoob from %s' % shellnoob_fp, file=sys.stderr)
|
|
if not force:
|
|
input('Press a key to proceed..')
|
|
os.unlink(shellnoob_fp)
|
|
|
|
|
|
class ShellNoobException(Exception):
|
|
pass
|
|
|
|
def main():
|
|
|
|
args = sys.argv[1:]
|
|
|
|
########################################################################
|
|
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING #
|
|
# This is the most awful CLI argument parsing ever. I started to do #
|
|
# it manually as I wanted a uber flexible CLI interface. I'm pretty #
|
|
# sure it's possible to achieve the same with optparse/argparse, but #
|
|
# I'm too noob for that. Somebody should write a OptParseNoob tool :-) #
|
|
########################################################################
|
|
|
|
# check for help
|
|
if '-h' in args or '--help' in args or len(args) == 0:
|
|
print_usage()
|
|
sys.exit(0)
|
|
|
|
# check for install/uninstall
|
|
if '--install' in args:
|
|
force = True if '--force' in args else False
|
|
ShellNoob.do_install(force)
|
|
sys.exit(0)
|
|
elif '--uninstall' in args:
|
|
force = True if '--force' in args else False
|
|
ShellNoob.do_uninstall(force)
|
|
sys.exit(0)
|
|
|
|
|
|
# parse the switches
|
|
flag_64_bit = False
|
|
if '--64' in args:
|
|
flag_64_bit = True
|
|
args.remove('--64')
|
|
flag_intel = False
|
|
if '--intel' in args:
|
|
flag_intel = True
|
|
args.remove('--intel')
|
|
with_breakpoint = False
|
|
if '-c' in args:
|
|
with_breakpoint = True
|
|
args.remove('-c')
|
|
keep_files = False
|
|
if '-k' in args:
|
|
keep_files = True
|
|
args.remove('-k')
|
|
verbose = 0
|
|
if '-vvvv' in args:
|
|
verbose = 4
|
|
args.remove('-vvvv')
|
|
if '-vvv' in args:
|
|
verbose = 3
|
|
args.remove('-vvv')
|
|
elif '-vv' in args:
|
|
verbose = 2
|
|
args.remove('-vv')
|
|
elif '-v' in args:
|
|
verbose = 1
|
|
args.remove('-v')
|
|
if '-q' in args:
|
|
verbose = -1
|
|
args.remove('-q')
|
|
|
|
snoob = ShellNoob(flag_64_bit=flag_64_bit, flag_intel=flag_intel,
|
|
with_breakpoint=with_breakpoint, verbose=verbose,
|
|
keep_files=keep_files
|
|
)
|
|
|
|
# parse the plugin switches
|
|
if '-i' in args:
|
|
snoob.do_interactive_mode(args)
|
|
sys.exit(0)
|
|
if '--get-const' in args:
|
|
idx = args.index('--get-const')
|
|
const = args[idx+1]
|
|
snoob.do_resolve_const(const)
|
|
sys.exit(0)
|
|
if '--get-sysnum' in args:
|
|
idx = args.index('--get-sysnum')
|
|
syscall = args[idx+1]
|
|
snoob.do_resolve_syscall(syscall)
|
|
sys.exit(0)
|
|
if '--get-errno' in args:
|
|
idx = args.index('--get-errno')
|
|
errno = args[idx+1]
|
|
snoob.do_resolve_errno(errno)
|
|
sys.exit(0)
|
|
do_strace_flag = False
|
|
if '--to-strace' in args:
|
|
do_strace_flag = True
|
|
args.remove('--to-strace')
|
|
do_gdb_flag = False
|
|
if '--to-gdb' in args:
|
|
do_gdb_flag = True
|
|
args.remove('--to-gdb')
|
|
|
|
if '--fork-nopper' in args:
|
|
idx = args.index('--fork-nopper')
|
|
exe_fp = args[idx+1]
|
|
snoob.do_fork_nopper(exe_fp)
|
|
sys.exit(0)
|
|
if '--file-patch' in args:
|
|
idx = args.index('--file-patch')
|
|
exe_fp = args[idx+1]
|
|
file_offset = int(args[idx+2], 16)
|
|
data = binascii.unhexlify(args[idx+3])
|
|
snoob.do_exe_patch(exe_fp, data, file_offset=file_offset)
|
|
sys.exit(0)
|
|
if '--vm-patch' in args:
|
|
idx = args.index('--vm-patch')
|
|
exe_fp = args[idx+1]
|
|
vm_address = int(args[idx+2], 16)
|
|
data = binascii.unhexlify(args[idx+3])
|
|
snoob.do_exe_patch(exe_fp, data, vm_address=vm_address)
|
|
sys.exit(0)
|
|
|
|
# parse the conversion arguments
|
|
|
|
curr_arg_idx = 0
|
|
|
|
# determine the input format
|
|
input_fmt = None
|
|
curr_arg = args[curr_arg_idx]
|
|
if curr_arg.startswith('--from-'):
|
|
input_fmt = curr_arg[len('--from-'):].lower()
|
|
if input_fmt not in snoob.INPUT_FMT:
|
|
raise Exception('input format "%s" not supported' % input_fmt)
|
|
curr_arg_idx += 1
|
|
|
|
input_fp = args[curr_arg_idx]
|
|
if input_fmt != 'shellstorm' and input_fp != '-':
|
|
input_fp = os.path.abspath(input_fp)
|
|
if not os.path.isfile(input_fp):
|
|
raise Exception('file %s does NOT exist' % input_fp)
|
|
curr_arg_idx += 1
|
|
|
|
if not input_fmt:
|
|
# ok, let's guess the input fmt
|
|
dot_idx = input_fp.rfind('.')
|
|
if dot_idx == -1:
|
|
raise Exception('Failed to guess the input format :/')
|
|
ext = input_fp[dot_idx+1:]
|
|
if ext in ['asm', 's', 'S', 'shell']:
|
|
input_fmt = 'asm'
|
|
elif ext in ['o', 'obj']:
|
|
input_fmt = 'obj'
|
|
elif ext in ['bin']:
|
|
input_fmt = 'bin'
|
|
elif ext in ['hex']:
|
|
input_fmt = 'hex'
|
|
elif ext in ['c', 'C']:
|
|
input_fmt = 'c'
|
|
else:
|
|
raise Exception('Input format "%s" is not supported' % input_fmt)
|
|
|
|
|
|
if do_strace_flag:
|
|
if snoob.verbose >= 1: print('do_strace mode selected', file=sys.stderr)
|
|
snoob.do_strace(input_fp, input_fmt)
|
|
sys.exit(0)
|
|
if do_gdb_flag:
|
|
if snoob.verbose >= 1: print('do_gdb mode selected', file=sys.stderr)
|
|
snoob.do_gdb(input_fp, input_fmt)
|
|
sys.exit(0)
|
|
|
|
|
|
output_fmt = None
|
|
curr_arg = args[curr_arg_idx]
|
|
if curr_arg.startswith('--to-'):
|
|
output_fmt = curr_arg[len('--to-'):].lower()
|
|
if output_fmt not in snoob.OUTPUT_FMT:
|
|
raise Exception('output format "%s" not supported' % output_fmt)
|
|
curr_arg_idx += 1
|
|
|
|
output_fp = None
|
|
if curr_arg_idx < len(args):
|
|
output_fp = args[curr_arg_idx]
|
|
curr_arg_idx += 1
|
|
|
|
if not output_fmt:
|
|
# ok, let's guess the output fmt
|
|
dot_idx = output_fp.rfind('.')
|
|
if dot_idx == -1:
|
|
raise Exception('Failed to guess the output format :/')
|
|
ext = output_fp[dot_idx+1:]
|
|
if ext in ['asm', 's', 'S', 'shell']:
|
|
output_fmt = 'asm'
|
|
elif ext in ['o', 'obj']:
|
|
output_fmt = 'obj'
|
|
elif ext in ['exe']:
|
|
output_fmt = 'exe'
|
|
elif ext in ['bin']:
|
|
output_fmt = 'bin'
|
|
elif ext in ['hex']:
|
|
output_fmt = 'hex'
|
|
elif ext in ['c', 'C']:
|
|
output_fmt = 'c'
|
|
elif ext in ['py', 'python']:
|
|
output_fmt = 'python'
|
|
elif ext in ['sh', 'bash']:
|
|
output_fmt = 'bash'
|
|
elif ext in ['rb', 'ruby']:
|
|
output_fmt = 'ruby'
|
|
elif ext in ['pretty']:
|
|
output_fmt = 'pretty'
|
|
elif ext in ['safeasm']:
|
|
output_fmt = 'safeasm'
|
|
else:
|
|
raise Exception('Output format "%s" is not supported' % output_fmt)
|
|
|
|
if output_fp == '-':
|
|
pass
|
|
elif output_fp != None:
|
|
output_fp = os.path.abspath(output_fp)
|
|
elif input_fp and input_fp != '-':
|
|
# choose the output_fp starting from the input_fp and input_fmt
|
|
input_pfx = input_fp[:input_fp.rfind('.')]
|
|
if output_fmt == 'completec':
|
|
output_ext = 'c'
|
|
else:
|
|
output_ext = output_fmt
|
|
output_fp = '%s.%s' % (input_pfx, output_ext)
|
|
else:
|
|
raise Exception('Specify the input/output filename!')
|
|
|
|
snoob.do_conversion(input_fp, output_fp, input_fmt, output_fmt)
|
|
|
|
|
|
if __name__== '__main__':
|
|
main()
|