some small fixes

This commit is contained in:
Mari Wahl 2014-10-11 01:54:02 -04:00
parent 52067188ed
commit 9cdfa95054
89 changed files with 27401 additions and 0 deletions

View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
__all__ = ["args", "colors", "libcolors", "routine"]
__version__ = "0.96"

View file

@ -0,0 +1,28 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from docopt import docopt
from routine import parse_char
class ArgError(Exception):
pass
def parse_parameters(doc, version):
p = docopt(doc, version=version)
p = {k.lstrip("-"): v for k, v in p.items()}
try:
return {
"input_is_hex": bool(p["hex"]),
"max_key_length": int(p["max-keylen"]),
"known_key_length": int(p["key-length"]) if p["key-length"] else None,
"most_frequent_char": parse_char(p["char"]) if p["char"] else None,
"brute_chars": bool(p["brute-chars"]),
"brute_printable": bool(p["brute-printable"]),
"frequency_spread": 0, # to be removed
"filename": p["FILE"] if p["FILE"] else "-", # stdin by default
}
except ValueError as err:
raise ArgError(str(err))

View file

@ -0,0 +1,19 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from libcolors import color
C_RESET = color()
C_FATAL = color("red")
C_WARN = color("yellow")
C_KEYLEN = color("green")
C_PROB = color("white", attrs="")
C_BEST_KEYLEN = color("green", attrs="bold")
C_BEST_PROB = color("white", attrs="bold")
C_DIV = color(attrs="bold")
C_KEY = color("red", attrs="bold")
C_BOLD = color(attrs="bold")
C_COUNT = color("yellow", attrs="bold")

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import os
BASH_ATTRIBUTES = {"regular": "0",
"bold": "1", "underline": "4", "strike": "9",
"light": "1", "dark": "2",
"invert": "7"} # invert bg and fg
BASH_COLORS = {"black": "30", "red": "31", "green": "32", "yellow": "33",
"blue": "34", "purple": "35", "cyan": "36", "white": "37"}
BASH_BGCOLORS = {"black": "40", "red": "41", "green": "42", "yellow": "43",
"blue": "44", "purple": "45", "cyan": "46", "white": "47"}
def _main():
header = color("white", "black", "dark")
print
print header + " " + "Colors and backgrounds: " + color()
for c in _keys_sorted_by_values(BASH_COLORS):
c1 = color(c)
c2 = color("white" if c != "white" else "black", bgcolor=c)
print (c.ljust(10) +
c1 + "colored text" + color() + " " +
c2 + "background" + color())
print
print header + " " + "Attributes: " + color()
for c in _keys_sorted_by_values(BASH_ATTRIBUTES):
c1 = color("red", attrs=c)
c2 = color("white", attrs=c)
print (c.ljust(13) +
c1 + "red text" + color() + " " +
c2 + "white text" + color())
print
return
def color(color=None, bgcolor=None, attrs=None):
if not is_bash():
return ""
ret = "\x1b[0"
if attrs:
for attr in attrs.lower().split():
attr = attr.strip(",+|")
if attr not in BASH_ATTRIBUTES:
raise ValueError("Unknown color attribute: " + attr)
ret += ";" + BASH_ATTRIBUTES[attr]
if color:
if color in BASH_COLORS:
ret += ";" + BASH_COLORS[color]
else:
raise ValueError("Unknown color: " + color)
if bgcolor:
if bgcolor in BASH_BGCOLORS:
ret += ";" + BASH_BGCOLORS[bgcolor]
else:
raise ValueError("Unknown background color: " + bgcolor)
return ret + "m"
def is_bash():
return os.environ.get("SHELL", "unknown").endswith("bash")
def _keys_sorted_by_values(adict):
"""Return list of the keys of @adict sorted by values."""
return sorted(adict, key=adict.get)
if __name__ == "__main__":
_main()

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import os
import sys
import string
class MkdirError(Exception):
pass
def load_file(filename):
if filename == "-":
return sys.stdin.read()
fd = open(filename, "rb")
contents = fd.read()
fd.close()
return contents
def save_file(filename, data):
fd = open(filename, "wb")
fd.write(data)
fd.close()
return
def mkdir(dirname):
if os.path.exists(dirname):
return
try:
os.mkdir(dirname)
except BaseException as err:
raise MkdirError(str(err))
return
def rmdir(dirname):
if dirname[-1] == os.sep:
dirname = dirname[:-1]
if os.path.islink(dirname):
return # do not clear link - we can get out of dir
files = os.listdir(dirname)
for f in files:
if f == '.' or f == '..':
continue
path = dirname + os.sep + f
if os.path.isdir(path):
rmdir(path)
else:
os.unlink(path)
os.rmdir(dirname)
return
def decode_from_hex(text):
only_hex_digits = "".join([c for c in text if c in string.hexdigits])
return only_hex_digits.decode("hex")
def parse_char(ch):
"""
'A' or '\x41' or '41'
"""
if len(ch) == 1:
return ord(ch)
if ch[0:2] == "\\x":
ch = ch[2:]
if not ch:
raise ValueError("Empty char")
return ord(chr(int(ch, 16)))
def dexor(text, key):
ret = list(text)
mod = len(key)
for index, char in enumerate(ret):
ret[index] = chr(ord(char) ^ ord(key[index % mod]))
return "".join(ret)
def die(exitMessage, exitCode=1):
print exitMessage
sys.exit(exitCode)
def is_linux():
return sys.platform.startswith("linux")
def alphanum(s):
lst = list(s)
for index, char in enumerate(lst):
if char in (string.letters + string.digits):
continue
lst[index] = char.encode("hex")
return "".join(lst)

View file

@ -0,0 +1,381 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
xortool
A tool to do some xor analysis:
- guess the key length (based on count of equal chars)
- guess the key (base on knowledge of most frequent char)
Usage:
xortool [-x] [-m MAX-LEN] [FILE]
xortool [-x] [-l LEN] [-c CHAR | -b | -o] [FILE]
xortool [-x] [-m MAX-LEN| -l LEN] [-c CHAR | -b | -o] [FILE]
xortool [-h | --help]
xortool --version
Options:
-x --hex input is hex-encoded str
-l LEN, --key-length=LEN length of the key
-m MAX-LEN, --max-keylen=MAX-LEN maximum key length to probe [default: 65]
-c CHAR, --char=CHAR most frequent char (one char or hex code)
-b --brute-chars brute force all possible most frequent chars
-o --brute-printable same as -b but will only check printable chars
-h --help show this help
Examples:
xortool file.bin
xortool -l 11 -c 20 file.bin
xortool -x -c ' ' file.hex
"""
from operator import itemgetter
import os
import string
import xortool
from xortool.colors import *
from xortool.routine import *
from xortool.args import parse_parameters, ArgError
DIRNAME = 'xortool_out' # here plaintexts will be placed
PARAMETERS = dict()
class AnalysisError(Exception):
pass
def main():
global PARAMETERS
try:
PARAMETERS = parse_parameters(__doc__, xortool.__version__)
ciphertext = get_ciphertext()
update_key_length(ciphertext)
if PARAMETERS["brute_chars"]:
try_chars = range(256)
elif PARAMETERS["brute_printable"]:
try_chars = map(ord, string.printable)
elif PARAMETERS["most_frequent_char"] != None:
try_chars = [PARAMETERS["most_frequent_char"]]
else:
die(C_WARN +
"Most possible char is needed to guess the key!" +
C_RESET)
(probable_keys,
key_char_used) = guess_probable_keys_for_chars(ciphertext, try_chars)
print_keys(probable_keys)
produce_plaintexts(ciphertext, probable_keys, key_char_used)
except IOError as err:
print C_FATAL + "[ERROR] Can't load file:\n\t", err, C_RESET
except ArgError as err:
print C_FATAL + "[ERROR] Bad argument:\n\t", err, C_RESET
except MkdirError as err:
print C_FATAL + "[ERROR] Can't create directory:\n\t", err, C_RESET
except AnalysisError as err:
print C_FATAL + "[ERROR] Analysis error:\n\t", err, C_RESET
else:
return
cleanup()
# -----------------------------------------------------------------------------
# LOADING CIPHERTEXT
# -----------------------------------------------------------------------------
def get_ciphertext():
"""
Load ciphertext from a file or stdin and hex-decode if needed
"""
ciphertext = load_file(PARAMETERS["filename"])
if PARAMETERS["input_is_hex"]:
ciphertext = decode_from_hex(ciphertext)
return ciphertext
# -----------------------------------------------------------------------------
# KEYLENGTH GUESSING SECTION
# -----------------------------------------------------------------------------
def update_key_length(text):
"""
Guess length of the key if it's not set. (Updates PARAMETERS)
"""
global PARAMETERS
if PARAMETERS["known_key_length"]:
return
PARAMETERS["known_key_length"] = guess_key_length(text)
return
def guess_key_length(text):
"""
Try key lengths from 1 to max_key_length and print local maximums.
Set key_length to the most possible if it's not set by user.
"""
fitnesses = calculate_fitnesses(text)
if not fitnesses:
raise AnalysisError("No candidates for key length found! Too small file?")
print_fitnesses(fitnesses)
guess_and_print_divisors(fitnesses)
return get_max_fitnessed_key_length(fitnesses)
def calculate_fitnesses(text):
"""
Calc. fitnesses for each keylen
"""
prev = 0
pprev = 0
fitnesses = []
for key_length in range(1, PARAMETERS["max_key_length"] + 1):
fitness = count_equals(text, key_length)
# smaller key-length with nearly the same fitness is preferable
fitness = (float(fitness) /
(PARAMETERS["max_key_length"] + key_length ** 1.5))
if pprev < prev and prev > fitness: # local maximum
fitnesses += [(key_length - 1, prev)]
pprev = prev
prev = fitness
if pprev < prev:
fitnesses += [(key_length - 1, prev)]
return fitnesses
def print_fitnesses(fitnesses):
print "The most probable key lengths:"
# top sorted by fitness, but print sorted by length
fitnesses.sort(key=itemgetter(1), reverse=True)
top10 = fitnesses[:10]
best_fitness = top10[0][1]
top10.sort(key=itemgetter(0))
fitness_sum = calculate_fitness_sum(top10)
for key_length, fitness in top10:
s1 = str(key_length).rjust(4, " ")
s2 = str(round(100 * fitness * 1.0 / fitness_sum, 1)) + "%"
if fitness == best_fitness:
print (C_BEST_KEYLEN + s1 + C_RESET + ": "
+ C_BEST_PROB + s2 + C_RESET)
else:
print (C_KEYLEN + s1 + C_RESET + ": "
+ C_PROB + s2 + C_RESET)
return
def calculate_fitness_sum(fitnesses):
return sum([f[1] for f in fitnesses])
def count_equals(text, key_length):
"""
count equal chars count for each offset and sum them
"""
equals_count = 0
if key_length >= len(text):
return 0
for offset in range(key_length):
chars_count = chars_count_at_offset(text, key_length, offset)
equals_count += max(chars_count.values()) - 1 # why -1? don't know
return equals_count
def guess_and_print_divisors(fitnesses):
"""
Prints common divisors and returns the most common divisor
"""
divisors_counts = [0] * (PARAMETERS["max_key_length"] + 1)
for key_length, fitness in fitnesses:
for number in range(3, key_length + 1):
if key_length % number == 0:
divisors_counts[number] += 1
max_divisors = max(divisors_counts)
limit = 3
ret = 2
for number, divisors_count in enumerate(divisors_counts):
if divisors_count == max_divisors:
print "Key-length can be " + C_DIV + str(number) + "*n" + C_RESET
ret = number
limit -= 1
if limit == 0:
return ret
return ret
def get_max_fitnessed_key_length(fitnesses):
max_fitness = 0
max_fitnessed_key_length = 0
for key_length, fitness in fitnesses:
if fitness > max_fitness:
max_fitness = fitness
max_fitnessed_key_length = key_length
return max_fitnessed_key_length
def chars_count_at_offset(text, key_length, offset):
chars_count = dict()
for pos in range(offset, len(text), key_length):
c = text[pos]
if c in chars_count:
chars_count[c] += 1
else:
chars_count[c] = 1
return chars_count
# -----------------------------------------------------------------------------
# KEYS GUESSING SECTION
# -----------------------------------------------------------------------------
def guess_probable_keys_for_chars(text, try_chars):
"""
Guess keys for list of characters.
"""
probable_keys = []
key_char_used = {}
for c in try_chars:
keys = guess_keys(text, c)
for key in keys:
key_char_used[key] = c
if key not in probable_keys:
probable_keys.append(key)
return probable_keys, key_char_used
def guess_keys(text, most_char):
"""
Generate all possible keys for key length
and the most possible char
"""
key_length = PARAMETERS["known_key_length"]
key_possible_bytes = [[] for _ in range(key_length)]
for offset in range(key_length): # each byte of key<
chars_count = chars_count_at_offset(text, key_length, offset)
max_count = max(chars_count.values())
for char in chars_count:
if chars_count[char] >= max_count:
key_possible_bytes[offset].append(chr(ord(char) ^ most_char))
return all_keys(key_possible_bytes)
def all_keys(key_possible_bytes, key_part="", offset=0):
"""
Produce all combinations of possible key chars
"""
keys = []
if offset >= len(key_possible_bytes):
return [key_part]
for c in key_possible_bytes[offset]:
keys += all_keys(key_possible_bytes, key_part + c, offset + 1)
return keys
def print_keys(keys):
if not keys:
print "No keys guessed!"
return
s1 = C_COUNT + str(len(keys)) + C_RESET
s2 = C_COUNT + str(len(keys[0])) + C_RESET
print "{} possible key(s) of length {}:".format(s1, s2)
for key in keys[:5]:
print C_KEY + repr(key)[1:-1] + C_RESET
if len(keys) > 10:
print "..."
# -----------------------------------------------------------------------------
# RETURNS PERCENTAGE OF PRINTABLE CHARS
# -----------------------------------------------------------------------------
def percentage_printable(text):
x = 0.0
for c in text:
if c in string.printable:
x += 1
return x / len(text)
# -----------------------------------------------------------------------------
# PRODUCE OUTPUT
# -----------------------------------------------------------------------------
def produce_plaintexts(ciphertext, keys, key_char_used):
"""
Produce plaintext variant for each possible key,
creates csv files with keys, percentage of printable
characters and used most frequent character
"""
cleanup()
mkdir(DIRNAME)
# this is split up in two files since the
# key can contain all kinds of characters
fn_key_mapping = "filename-key.csv"
fn_perc_mapping = "filename-char_used-perc_printable.csv"
key_mapping = open(os.path.join(DIRNAME, fn_key_mapping), "w")
perc_mapping = open(os.path.join(DIRNAME, fn_perc_mapping), "w")
key_mapping.write("file_name;key_repr\n")
perc_mapping.write("file_name;char_used;perc_printable\n")
threshold_printable = 95
count_printable = 0
for index, key in enumerate(keys):
key_index = str(index).rjust(len(str(len(keys) - 1)), "0")
key_repr = repr(key)[1:-1].replace("/", "\\x2f")
if not is_linux():
key_repr = alphanum(key)
file_name = os.path.join(DIRNAME, key_index + ".out")
dexored = dexor(ciphertext, key)
perc = round(100 * percentage_printable(dexored))
if perc > threshold_printable:
count_printable += 1
key_mapping.write("{};{}\n".format(file_name, key_repr))
perc_mapping.write("{};{};{}\n".format(file_name,
repr(key_char_used[key]),
perc))
f = open(file_name, "wb")
f.write(dexored)
f.close()
key_mapping.close()
perc_mapping.close()
s1 = C_COUNT + str(count_printable) + C_RESET
s2 = C_COUNT + str(round(threshold_printable)) + C_RESET
print "Found {} plaintexts with {}%+ printable characters".format(s1, s2)
print "See files {}, {}".format(fn_key_mapping, fn_perc_mapping)
return
def cleanup():
if os.path.exists(DIRNAME):
rmdir(DIRNAME)
return
if __name__ == "__main__":
main()

View file

@ -0,0 +1,116 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
xor strings
options:
-s - string with \\xAF escapes
-r - raw string
-h - hex-encoded string (non-letterdigit chars are stripped)
-f - read data from file (- for stdin)
-n - no newline at the end
--no-cycle / --nc - pad smaller strings with null bytes
example: xor -s lol -h 414243 -f /etc/passwd
author: hellman ( hellman1908@gmail.com )
"""
import sys
import string
import getopt
DATA_OPTS = "s:r:h:f:"
HEXES = set("0123456789abcdefABCDEF")
def main():
nocycle = False
nonewline = False
try:
opts, args = getopt.getopt(sys.argv[1:], "n" + DATA_OPTS, ["no-cycle", "nc"])
datas = []
for c, val in opts:
if c in ("--no-cycle", "--nc"):
nocycle = True
elif c == "-n":
nonewline = True
else:
v = arg_data(c, val)
if v is None:
raise getopt.GetoptError("unknown option %s" % c)
datas.append(v)
if not datas:
raise getopt.GetoptError("no data given")
except getopt.GetoptError as e:
print >>sys.stderr, "error:", e
print >>sys.stderr, __doc__
quit()
sys.stdout.write(xor(datas, nocycle=nocycle))
if not nonewline:
sys.stdout.write("\n")
def xor(args, nocycle=False):
maxlen = max(map(len, args))
res = [0] * maxlen
if nocycle:
for s in args:
for i in xrange(len(s)):
res[i] ^= ord(s[i])
else:
for s in args:
slen = len(s)
for i in xrange(maxlen):
res[i] ^= ord(s[i % slen])
return "".join(map(chr, res))
def from_str(s):
res = ""
i = 0
while True:
if i + 4 > len(s):
break
if s[i+1] == "x" and s[i+2] in HEXES and s[i+3] in HEXES:
res += chr(int(s[i+2:i+4], 16))
i += 4
else:
res += s[i]
i += 1
res += s[i:]
return res
def from_hex(s):
res = ""
for c in s:
if c in HEXES:
res += c
elif c in string.ascii_letters:
raise ValueError("Bad splitters (alphanum)")
return res.decode("hex")
def from_file(s):
if s == "-":
return sys.stdin.read()
return open(s, "rb").read()
def arg_data(opt, s):
if opt == "-s":
return from_str(s)
elif opt == "-r":
return s
elif opt == "-h":
return from_hex(s)
elif opt == "-f":
return from_file(s)
return None
if __name__ == '__main__':
main()