-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
encoder.py
166 lines (124 loc) · 4.34 KB
/
encoder.py
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)