mirror of
https://github.com/mgeeky/Penetration-Testing-Tools.git
synced 2024-11-22 02:21:36 +01:00
Improved auto-decoding algorithm to be sensible about final output format - whether it should be text or binary.
This commit is contained in:
parent
bbffec490c
commit
45a1188f76
194
web/reencode.py
194
web/reencode.py
@ -21,6 +21,7 @@
|
|||||||
# Mariusz B., 2018
|
# Mariusz B., 2018
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import jwt
|
import jwt
|
||||||
@ -34,10 +35,21 @@ import binascii
|
|||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================
|
||||||
|
# RE-ENCODER'S IMPLEMENTATION
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
class ReEncoder:
|
class ReEncoder:
|
||||||
|
|
||||||
# Switch this to show some verbose informations about decoding process.
|
# Switch this to show some verbose informations about decoding process.
|
||||||
DEBUG = False
|
DEBUG = True
|
||||||
|
|
||||||
|
PREFER_AUTO = 0 # Automatically determine final output format
|
||||||
|
PREFER_TEXT = 1 # Prefer text/printable final output format
|
||||||
|
PREFER_BINARY = 2 # Prefer binary final output format
|
||||||
|
|
||||||
class Utils:
|
class Utils:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -129,7 +141,10 @@ class ReEncoder:
|
|||||||
def check(self, data):
|
def check(self, data):
|
||||||
try:
|
try:
|
||||||
if base64.b64encode(base64.b64decode(data)) == data:
|
if base64.b64encode(base64.b64decode(data)) == data:
|
||||||
return True
|
m = re.match('^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$', data, re.I)
|
||||||
|
if m:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
@ -147,7 +162,10 @@ class ReEncoder:
|
|||||||
def check(self, data):
|
def check(self, data):
|
||||||
try:
|
try:
|
||||||
if base64.urlsafe_b64encode(base64.urlsafe_b64decode(data)) == data:
|
if base64.urlsafe_b64encode(base64.urlsafe_b64decode(data)) == data:
|
||||||
return True
|
m = re.match('^(?:[A-Za-z0-9\-_]{4})*(?:[A-Za-z0-9\-_]{2}==|[A-Za-z0-9\-_]{3}=|[A-Za-z0-9\-_]{4})$', data, re.I)
|
||||||
|
if m:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
@ -221,6 +239,7 @@ class ReEncoder:
|
|||||||
)
|
)
|
||||||
self.encodersMap = {}
|
self.encodersMap = {}
|
||||||
self.data = ''
|
self.data = ''
|
||||||
|
self.preferredOutputFormat = ReEncoder.PREFER_AUTO
|
||||||
|
|
||||||
for encoder in self.encoders:
|
for encoder in self.encoders:
|
||||||
self.encodersMap[encoder.name()] = encoder
|
self.encodersMap[encoder.name()] = encoder
|
||||||
@ -328,13 +347,37 @@ class ReEncoder:
|
|||||||
return ent
|
return ent
|
||||||
|
|
||||||
def evaluateEncodingTree(self, root):
|
def evaluateEncodingTree(self, root):
|
||||||
weights = {
|
(printableEncodings, printableCandidate) = self.evaluateEncodingTreePicker(root, False)
|
||||||
'printableChars' : 10.0,
|
(binaryEncodings, binaryCandidate) = self.evaluateEncodingTreePicker(root, True)
|
||||||
'highEntropy' : 4.0,
|
|
||||||
'length' : 1.0
|
if self.preferredOutputFormat == ReEncoder.PREFER_TEXT:
|
||||||
}
|
ReEncoder.log('Returning text/printable output format as requested preferred one.')
|
||||||
|
return printableEncodings
|
||||||
|
elif self.preferredOutputFormat == ReEncoder.PREFER_BINARY:
|
||||||
|
ReEncoder.log('Returning binary output format as requested preferred one.')
|
||||||
|
return binaryEncodings
|
||||||
|
else:
|
||||||
|
ReEncoder.log('Trying to determine preferred output format...')
|
||||||
|
|
||||||
|
ReEncoder.log('\n---------------------------------------')
|
||||||
|
ReEncoder.log('[>] Winning printable encoding path scored: {} points.'.format(
|
||||||
|
printableCandidate[2]
|
||||||
|
))
|
||||||
|
ReEncoder.log('[>] Winning binary encoding path scored: {} points.'.format(
|
||||||
|
binaryCandidate[2]
|
||||||
|
))
|
||||||
|
|
||||||
|
if(printableCandidate[2] >= binaryCandidate[2]):
|
||||||
|
ReEncoder.log('\n[+] Choosing all-time winner: PRINTABLE output format.')
|
||||||
|
return printableEncodings
|
||||||
|
|
||||||
|
ReEncoder.log('\n[+] Choosing all-time winner: BINARY output format.')
|
||||||
|
ReEncoder.log('---------------------------------------\n')
|
||||||
|
return binaryEncodings
|
||||||
|
|
||||||
|
def evaluateEncodingTreePicker(self, root, preferBinary):
|
||||||
|
candidates = self._evaluateEncodingTreeWorker(root, preferBinary)
|
||||||
|
|
||||||
candidates = self.formEncodingCandidates(root)
|
|
||||||
maxCandidate = 0
|
maxCandidate = 0
|
||||||
|
|
||||||
for i in range(len(candidates)):
|
for i in range(len(candidates)):
|
||||||
@ -344,38 +387,6 @@ class ReEncoder:
|
|||||||
decoded = candidate[1]
|
decoded = candidate[1]
|
||||||
points = float(candidate[2])
|
points = float(candidate[2])
|
||||||
|
|
||||||
ReEncoder.log('[=] Evaluating candidate: {} (data: "{}")'.format(
|
|
||||||
name, decoded
|
|
||||||
))
|
|
||||||
|
|
||||||
# Step 1: Adding points for printable percentage.
|
|
||||||
printables = sum([int(x in string.printable) for x in decoded])
|
|
||||||
printablePoints = weights['printableChars'] * (float(printables) / float(len(decoded)))
|
|
||||||
ReEncoder.log('\tAdding {} points for printable characters.'.format(printablePoints))
|
|
||||||
points += printablePoints
|
|
||||||
|
|
||||||
# Step 4: If encoder is Base64 and was previously None
|
|
||||||
# - then length and entropy of previous values should be of slighly lower weights
|
|
||||||
if name.lower() == 'none' \
|
|
||||||
and len(candidates) > i+1 \
|
|
||||||
and candidates[i+1][0].lower().startswith('base64'):
|
|
||||||
entropyPoints = ReEncoder.entropy(decoded) * (weights['highEntropy'] * 0.75)
|
|
||||||
lengthPoints = float(len(decoded)) * (weights['length'] * 0.75)
|
|
||||||
else:
|
|
||||||
entropyPoints = ReEncoder.entropy(decoded) * weights['highEntropy']
|
|
||||||
lengthPoints = float(len(decoded)) * weights['length']
|
|
||||||
|
|
||||||
# Step 2: Add points for entropy
|
|
||||||
ReEncoder.log('\tAdding {} points for high entropy.'.format(entropyPoints))
|
|
||||||
points += entropyPoints
|
|
||||||
|
|
||||||
# Step 3: Add points for length
|
|
||||||
ReEncoder.log('\tAdding {} points for length.'.format(lengthPoints))
|
|
||||||
points += lengthPoints
|
|
||||||
|
|
||||||
ReEncoder.log('\tScored in total: {} points.'.format(points))
|
|
||||||
candidates[i][2] = points
|
|
||||||
|
|
||||||
if points > candidates[maxCandidate][2]:
|
if points > candidates[maxCandidate][2]:
|
||||||
maxCandidate = i
|
maxCandidate = i
|
||||||
|
|
||||||
@ -389,11 +400,87 @@ class ReEncoder:
|
|||||||
ReEncoder.log('[?] Other equally good candidate paths:\n' + str(winningPaths))
|
ReEncoder.log('[?] Other equally good candidate paths:\n' + str(winningPaths))
|
||||||
winningPath = winningPaths[0]
|
winningPath = winningPaths[0]
|
||||||
|
|
||||||
ReEncoder.log('[+] Winning decode path is:\n{}'.format(str(winningPath)))
|
preferred = 'printable'
|
||||||
|
if preferBinary:
|
||||||
|
preferred = 'binary'
|
||||||
|
|
||||||
|
ReEncoder.log('[+] Winning decode path for {} output is:\n{}'.format(
|
||||||
|
preferred,
|
||||||
|
str(winningPath))
|
||||||
|
)
|
||||||
|
|
||||||
encodings = [x.name for x in winningPath.path if x != 'None']
|
encodings = [x.name for x in winningPath.path if x != 'None']
|
||||||
|
return (encodings, winningCandidate)
|
||||||
|
|
||||||
|
def _evaluateEncodingTreeWorker(self, root, preferBinary = False):
|
||||||
|
weights = {
|
||||||
|
'unreadableChars' : 0.0,
|
||||||
|
'printableChars' : 9.6,
|
||||||
|
'entropyScore' : 4.0,
|
||||||
|
'length' : 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if preferBinary:
|
||||||
|
weights['unreadableChars'] = 24.0
|
||||||
|
weights['printableChars'] = 0.0
|
||||||
|
weights['entropyScore'] = 2.666667
|
||||||
|
|
||||||
|
candidates = self.formEncodingCandidates(root)
|
||||||
|
|
||||||
|
for i in range(len(candidates)):
|
||||||
|
candidate = candidates[i]
|
||||||
|
|
||||||
|
name = candidate[0]
|
||||||
|
decoded = candidate[1]
|
||||||
|
points = float(candidate[2])
|
||||||
|
entropy = ReEncoder.entropy(decoded)
|
||||||
|
printables = sum([int(x in string.printable) for x in decoded])
|
||||||
|
nonprintables = len(decoded) - printables
|
||||||
|
|
||||||
|
ReEncoder.log('[=] Evaluating candidate: {} (entropy: {}, data: "{}")'.format(
|
||||||
|
name, entropy, decoded
|
||||||
|
))
|
||||||
|
|
||||||
|
# Step 1: Adding points for printable percentage.
|
||||||
|
printablePoints = float(weights['printableChars']) * (float(printables) / float(len(decoded)))
|
||||||
|
nonPrintablePoints = float(weights['unreadableChars']) * (float(nonprintables) / float(len(decoded)))
|
||||||
|
|
||||||
|
# Step 2: If encoder is Base64 and was previously None
|
||||||
|
# - then length and entropy of previous values should be of slighly lower weights
|
||||||
|
if name.lower() == 'none' \
|
||||||
|
and len(candidates) > i+1 \
|
||||||
|
and candidates[i+1][0].lower().startswith('base64'):
|
||||||
|
ReEncoder.log('\tAdding fine for being base64')
|
||||||
|
entropyPoints = entropy * (weights['entropyScore'] * 0.666666)
|
||||||
|
lengthPoints = float(len(decoded)) * (weights['length'] * 0.666666)
|
||||||
|
else:
|
||||||
|
entropyPoints = entropy * weights['entropyScore']
|
||||||
|
lengthPoints = float(len(decoded)) * weights['length']
|
||||||
|
|
||||||
|
if printables > nonprintables:
|
||||||
|
ReEncoder.log('More printable chars than binary ones.')
|
||||||
|
ReEncoder.log('\tAdding {} points for printable entropy.'.format(entropyPoints))
|
||||||
|
|
||||||
|
ReEncoder.log('\tAdding {} points for printable characters.'.format(printablePoints))
|
||||||
|
points += printablePoints
|
||||||
|
else:
|
||||||
|
ReEncoder.log('More binary chars than printable ones.')
|
||||||
|
ReEncoder.log('\tAdding {} points for binary entropy.'.format(entropyPoints))
|
||||||
|
|
||||||
|
ReEncoder.log('\tAdding {} points for binary characters.'.format(nonPrintablePoints))
|
||||||
|
points += nonPrintablePoints
|
||||||
|
|
||||||
|
points += entropyPoints
|
||||||
|
|
||||||
|
# Step 4: Add points for length
|
||||||
|
ReEncoder.log('\tAdding {} points for length.'.format(lengthPoints))
|
||||||
|
points += lengthPoints
|
||||||
|
|
||||||
|
ReEncoder.log('\tScored in total: {} points.'.format(points))
|
||||||
|
candidates[i][2] = points
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
|
||||||
return encodings
|
|
||||||
|
|
||||||
def getWinningDecodePath(self, root):
|
def getWinningDecodePath(self, root):
|
||||||
return [x for x in self.evaluateEncodingTree(root) if x != 'None']
|
return [x for x in self.evaluateEncodingTree(root) if x != 'None']
|
||||||
@ -420,7 +507,16 @@ class ReEncoder:
|
|||||||
self.encodings = self.getWinningDecodePath(root)
|
self.encodings = self.getWinningDecodePath(root)
|
||||||
ReEncoder.log('[+] Selected encodings: {}'.format(str(self.encodings)))
|
ReEncoder.log('[+] Selected encodings: {}'.format(str(self.encodings)))
|
||||||
|
|
||||||
def decode(self, data, encodings = []):
|
def decode(self, data, preferredOutputFormat = PREFER_AUTO, encodings = []):
|
||||||
|
self.preferredOutputFormat = preferredOutputFormat
|
||||||
|
|
||||||
|
if preferredOutputFormat != ReEncoder.PREFER_AUTO and \
|
||||||
|
preferredOutputFormat != ReEncoder.PREFER_TEXT and \
|
||||||
|
preferredOutputFormat != ReEncoder.PREFER_BINARY:
|
||||||
|
raise Exception('Unknown preferred output format specified in decode(): {}'.format(
|
||||||
|
preferredOutputFormat
|
||||||
|
))
|
||||||
|
|
||||||
if not encodings:
|
if not encodings:
|
||||||
self.process(data)
|
self.process(data)
|
||||||
else:
|
else:
|
||||||
@ -445,20 +541,26 @@ class ReEncoder:
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
# Sample 1: ZLIB -> Base64 -> URLEncode
|
# Sample 1: ZLIB -> Base64 -> URLEncode
|
||||||
sample = 'eJzzSM3JyVcozy%2FKSVFIK8rPVQhKdc1Lzk9JLVIEAIr8Cck%3D'
|
sample = 'eJzzSM3JyVcozy%2FKSVFIK8rPVQhKdc1Lzk9JLVIEAIr8Cck%3D'
|
||||||
|
|
||||||
# Sample 2: URLEncode -> Base64 -> HexEncode
|
# Sample 2: URLEncode -> Base64 -> HexEncode
|
||||||
sample = '4a5451344a5459314a545a6a4a545a6a4a545a6d4a5449774a5463334a545a6d4a5463794a545a6a4a5459304a5449784a5449774a544e684a544a6b4a544935'
|
#sample = '4a5451344a5459314a545a6a4a545a6a4a545a6d4a5449774a5463334a545a6d4a5463794a545a6a4a5459304a5449784a5449774a544e684a544a6b4a544935'
|
||||||
|
|
||||||
if len(argv) != 2:
|
if len(argv) != 2:
|
||||||
print('Usage: reencode.py <text>')
|
print('Usage: reencode.py <text|file>')
|
||||||
print('Using sample: "{}"'.format(sample))
|
print('Using sample: "{}"'.format(sample))
|
||||||
text = sample
|
text = sample
|
||||||
else:
|
else:
|
||||||
text = argv[1]
|
text = argv[1]
|
||||||
|
|
||||||
|
if os.path.isfile(text):
|
||||||
|
f = open(text, 'rb')
|
||||||
|
text = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
decoder = ReEncoder()
|
decoder = ReEncoder()
|
||||||
decoded = decoder.decode(text)
|
decoded = decoder.decode(text)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user