forked from SkyLined/alpha3
-
Notifications
You must be signed in to change notification settings - Fork 12
/
encode.py
176 lines (166 loc) · 7.12 KB
/
encode.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
167
168
169
170
171
172
173
174
175
176
import ALPHA3
import math
# A generic encoding routine used by various decoders:
def encodeData(decoder, data, encoding_function, valid_values, pre_xor=True, terminating_char=""):
assert decoder, "Decoder is missing or empty"
assert data.find("\0") == -1, "Shellcode must be NULL free"
# Older encoders use a terminating char:
if terminating_char != "":
terminating_value = ord(terminating_char)
if terminating_value in valid_values:
valid_values.remove(terminating_value)
data += "\0" # A NULL will mark the end of the shellcode.
# The shellcode is encoded after the decoder. But because the last byte of the
# decoder is the negative offset of a jump that restarts the decoder loop and
# negative offsets translate into non-alphanumeric chars, this byte is also
# encoded (and will be the first to be decoded, so the jump will be decoded
# when the codepath hits it). All this means that the last two bytes of the
# decoder are part of the encoded data:
encoded_data = decoder[-2:]
decoder = decoder[:-2]
destination_index = 1
for c in data:
data_byte = ord(c);
# pre-xor A with B and encode the result into two bytes:
status = "encoding data (%d%%)" % int(100 * destination_index / len(data))
if pre_xor:
# The decoder stores decoded data using the XOR instruction. Originally, the
# decoder would pre-xor each data byte (A) with the value of the byte where it
# should be stored (B) before storing the byte. This meant that A would become
# A ^ B before the decoder stores it B ^= A ^ B (== A). However, this can be
# done while encoding, as well, saving a few bytes in the decoder:
pre_xor_byte = ord(encoded_data[destination_index])
bx, by = encoding_function(data_byte ^ pre_xor_byte, status, valid_values)
else:
bx, by = encoding_function(data_byte, status, valid_values)
encoded_data += chr(bx) + chr(by)
destination_index += 1
return decoder + encoded_data + terminating_char
# An encoding using in x86 ascii mixedcase decoders
def bx_IMUL_30_XOR_by(value, status, valid_values):
for bx in valid_values:
bx_IMUL_30 = (bx * 0x30) & 0xFF
for by in valid_values:
bx_IMUL_30_XOR_by = bx_IMUL_30 ^ by
if bx_IMUL_30_XOR_by == value:
ALPHA3.PrintVerboseStatus()
return [bx, by]
ALPHA3.PrintVerboseStatus(status, "%02X * 30 ^ %02X == %02X" % (
bx, by, bx_IMUL_30_XOR_by))
raise AssertionError("Cannot encode %02X" % (value,))
# An encoding used by old x86 unicode decoders:
def bx_IMUL_10_ADD_by(value, status, valid_values):
for bx in valid_values:
bx_IMUL_10 = (bx * 0x10) & 0xFF
for by in valid_values:
bx_IMUL_10_ADD_by = (bx_IMUL_10 + by) & 0xFF
if bx_IMUL_10_ADD_by == value:
ALPHA3.PrintVerboseStatus()
return [bx, by]
ALPHA3.PrintVerboseStatus(status, "%02X * 10 + %02X == %02X" % (
bx, by, bx_IMUL_10_ADD_by))
raise AssertionError("Cannot encode %02X" % (value,))
def wyx_IMUL_30_SHR_8_XOR_bx(value, status, valid_values):
for bx in valid_values:
for by in valid_values:
wyx = (by << 8) + bx
wyx_IMUL_30 = (wyx * 0x30) & 0xFFFF
wyx_IMUL_30_SHR_8 = wyx_IMUL_30 >> 8
wyx_IMUL_30_SHR_8_XOR_bx = wyx_IMUL_30_SHR_8 ^ bx
if wyx_IMUL_30_SHR_8_XOR_bx == value:
ALPHA3.PrintVerboseStatus()
return [bx, by]
ALPHA3.PrintVerboseStatus(status, "%02X %02X: ((%04X * 30) >> 8) ^ %02X == %02X" % (
bx, by, wyx, bx, wyx_IMUL_30_SHR_8_XOR_bx))
raise AssertionError("Cannot encode %02X" % (value,))
def dwx_IMUL_by(value, status, valid_values):
for by in valid_values:
dwx, carry = makeValid(0, 4, valid_values)
while not carry:
dwx_IMUL_by = (dwx * by) & 0xFFFFFFFF
if dwx_IMUL_by == value:
ALPHA3.PrintVerboseStatus()
return [dwx, by]
ALPHA3.PrintVerboseStatus(status, "%08X * %02X == %08X" % (
dwx, by, dwx_IMUL_by))
# How far off the desired value is the result now?
diff = value - dwx_IMUL_by
if diff < 0:
# the result is too small:
if diff % by == 0:
# It is a whole number of times by smaller: increase dwxX to get
# the right result:
dwx -= int(diff / by)
else:
# It is a off by an ammount that cannot lead to the solution easily.
# Cause another integer overflow to try another solution:
dwx += int(math.floor((diff + 0x100000000) / by))
else:
# It is too large, cause an integer overflow to resolve that:
dwx += int(math.floor((0x100000000 - diff) / by))
# Whatever modifications have been made to dwx may have caused it to
# become non-alphanumeric. If so, find the smallest alphanumeric number
# that is larger than dwx and use that:
dwx, carry = makeValid(int(dwx), 4, valid_values)
def dwx_IMUL_30_XOR_dwy(value, status, valid_values):
dwx, carry = makeValid(0, 4, valid_values)
dwy, carry = makeValid(0, 4, valid_values)
for byte in range(0, 4):
byte_mask = 0xFF << (byte * 8)
while 1:
dwx_IMUL_30_XOR_dwy = ((dwx * 0x30) ^ dwy) & 0xFFFFFFFF
ALPHA3.PrintVerboseStatus(status, "%08X * 30 ^ %08X == %08X" % (
dwx, dwy, dwx_IMUL_30_XOR_dwy))
if (dwx_IMUL_30_XOR_dwy & byte_mask) == (value & byte_mask):
break # OK: next byte
x_byte = (dwx & byte_mask) >> (byte * 8)
next_x_byte, carry = makeValid(x_byte + 1, 1, valid_values)
dwx = (dwx & (0xFFFFFFFF ^ byte_mask)) ^ (next_x_byte << (byte * 8))
if carry:
y_byte = (dwy & byte_mask) >> (byte * 8)
next_y_byte, carry = makeValid(y_byte + 1, 1, valid_values)
dwy = (dwy & (0xFFFFFFFF ^ byte_mask)) ^ (next_y_byte << (byte * 8))
assert carry == 0, "Cannot encode value!" # Should never happen!
ALPHA3.PrintVerboseStatus()
return [dwx, dwy]
def isValid(value, bytes, valid_values):
for i in range(0, bytes):
byte = (value >> (i * 8)) & 0xFF
if byte not in valid_values:
return False
return True
def makeValid(value, bytes, valid_values):
carry = 0
result = 0
for i in range(0, bytes):
byte = ((value >> (i * 8)) & 0xFF) + carry
carry = 0
if byte not in valid_values:
for valid_byte in valid_values:
if valid_byte > byte:
byte = valid_byte
break
else:
byte = 0x30
carry = 1
result += byte << (i * 8)
if (value >> (bytes * 8)) > 0:
carry = 1
return result, carry
def toString(value, bytes):
s = ""
for i in range(0, bytes):
byte = (value >> (i * 8)) & 0xFF
s += chr(byte)
return s
def injectCodes(data, codes):
for code in codes:
start_code = data.find("@")
assert start_code != -1, "More codes than places to store them!"
end_code = start_code + 1
while data[end_code] == "@":
end_code += 1
encoded_code = toString(code, end_code - start_code)
data = data[:start_code] + encoded_code + data[end_code:]
assert data.find("@") == -1, "Not enough codes to satisfy decoder!"
return data