1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
import collections
import random
import re
import string
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.fiddling import hexdump
log = getLogger(__name__)
class Encoder(object):
_encoders = collections.defaultdict(lambda: [])
#: Architecture which this encoder works on
arch = None
#: Blacklist of bytes which are known not to be supported
blacklist = set()
def __init__(self):
"""Shellcode encoder class
Implements an architecture-specific shellcode encoder
"""
Encoder._encoders[self.arch].append(self)
def __call__(self, raw_bytes, avoid, pcreg):
"""avoid(raw_bytes, avoid)
Arguments:
raw_bytes(str):
String of bytes to encode
avoid(set):
Set of bytes to avoid
pcreg(str):
Register which contains the address of the shellcode.
May be necessary for some shellcode.
"""
raise NotImplementedError()
@LocalContext
def encode(raw_bytes, avoid=None, expr=None, force=0, pcreg=''):
"""encode(raw_bytes, avoid, expr, force) -> str
Encode shellcode ``raw_bytes`` such that it does not contain
any bytes in ``avoid`` or ``expr``.
Arguments:
raw_bytes(str): Sequence of shellcode bytes to encode.
avoid(str): Bytes to avoid
expr(str): Regular expression which matches bad characters.
force(bool): Force re-encoding of the shellcode, even if it
doesn't contain any bytes in ``avoid``.
"""
orig_avoid = avoid
avoid = set(avoid or '')
if expr:
for char in all_chars:
if re.search(expr, char):
avoid.add(char)
if not (force or avoid & set(raw_bytes)):
return raw_bytes
encoders = Encoder._encoders[context.arch]
random.shuffle(encoders)
for encoder in encoders:
if encoder.blacklist & avoid:
continue
try:
v = encoder(raw_bytes, bytes(avoid), pcreg)
except NotImplementedError:
continue
if avoid & set(v):
log.warning_once("Encoder %s did not succeed" % encoder)
continue
return v
avoid_errmsg = ''
if orig_avoid and expr:
avoid_errmsg = '%r and %r' % (orig_avoid, expr)
elif expr:
avoid_errmsg = repr(expr)
else:
avoid_errmsg = repr(bytes(avoid))
args = (context.arch, avoid_errmsg, hexdump(raw_bytes))
msg = "No encoders for %s which can avoid %s for\n%s" % args
msg = msg.replace('%', '%%')
log.error(msg)
all_chars = list(chr(i) for i in range(256))
re_alphanumeric = r'[^A-Za-z0-9]'
re_printable = r'[^\x21-\x7e]'
re_whitespace = r'\s'
re_null = r'\x00'
re_line = r'[\s\x00]'
@LocalContext
def null(raw_bytes, *a, **kw):
"""null(raw_bytes) -> str
Encode the shellcode ``raw_bytes`` such that it does not
contain any NULL bytes.
Accepts the same arguments as :func:`encode`.
"""
return encode(raw_bytes, expr=re_null, *a, **kw)
@LocalContext
def line(raw_bytes, *a, **kw):
"""line(raw_bytes) -> str
Encode the shellcode ``raw_bytes`` such that it does not
contain any NULL bytes or whitespace.
Accepts the same arguments as :func:`encode`.
"""
return encode(raw_bytes, expr=re_whitespace, *a, **kw)
@LocalContext
def alphanumeric(raw_bytes, *a, **kw):
"""alphanumeric(raw_bytes) -> str
Encode the shellcode ``raw_bytes`` such that it does not
contain any bytes except for [A-Za-z0-9].
Accepts the same arguments as :func:`encode`.
"""
return encode(raw_bytes, expr=re_alphanumeric, *a, **kw)
@LocalContext
def printable(raw_bytes, *a, **kw):
"""printable(raw_bytes) -> str
Encode the shellcode ``raw_bytes`` such that it only contains
non-space printable bytes.
Accepts the same arguments as :func:`encode`.
"""
return encode(raw_bytes, expr=re_printable, *a, **kw)
@LocalContext
def scramble(raw_bytes, *a, **kw):
"""scramble(raw_bytes) -> str
Encodes the input data with a random encoder.
Accepts the same arguments as :func:`encode`.
"""
return encode(raw_bytes, force=1, *a, **kw)
|