Last active
December 21, 2015 01:19
-
-
Save zeroaltitude/6226930 to your computer and use it in GitHub Desktop.
Timestamp cracker, Python, version zeroaltitude
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python -tt -3 | |
# -*- coding: utf-8 -*- | |
# This code should be python 2 and python 3 safe -- use range() instead of xrange(), though | |
from __future__ import unicode_literals, print_function | |
import sys | |
import hashlib | |
from itertools import chain, product, permutations | |
from timeit import default_timer as timer | |
import cProfile | |
from struct import unpack | |
# | |
# Cracker for Ingress timestamps. YMMV. BBIAB. KK. | |
# See this page for example codes to test this with: https://gist.github.com/zeroaltitude/6213498 | |
# | |
# Usage: python ./fun.py <cipher> <hour> <trial number> <otp hash> | |
# | |
# E.g. | |
# | |
# $ python ./fun.py ISGDVWWJXCYSDRXDLSSOVOKIEUJNTVQLJSXQCLCYLRFEFYWVTZILMLACAKVTCCZJNOBYOJ 2 2 7527D47FE0E8F422EE269B666C1A2CA2 | |
# !!!!!!!!!!! FOUND OTP/TIMESTAMP: WOGLBFSXTPFVKVJGDAVOCRRMQXVQRKCJZVSCIUFMDELLBGZVGWLGYRJJCNILPYCRJMNLLR/MEASUREMENTXTWOXISXATXTWOXOXCLOCKXFOURXMINUTESXANDXFOURTYXNINEXSECONDS | |
# | |
# $ python ./fun.py SUZGNEKXBNORVSNVEACAVDHFYXZGDWPISLOVOXLAGESFLAMAN 3 3 6b7ca401083b34628db954c1e8b1c5de | |
# !!!!!!!!!!! FOUND OTP/TIMESTAMP: VBSPJANJEQMGHQDYQNYDJVULFTCGQTSDKGVXRSDFCHABJMZXV/XTHREEXOXXCLOCKXONEXMINUTEXANDXFIFTYXFIVEXSECONDS | |
# | |
# Lots of performance work to still be done. My best runs seem to be using PyPy. YMMV. | |
# | |
# by zeroaltitude of the Boston Englightened | |
# | |
WORDY_NUMBERS = { | |
0: "zero", | |
1: "one", | |
2: "two", | |
3: "three", | |
4: "four", | |
5: "five", | |
6: "six", | |
7: "seven", | |
8: "eight", | |
9: "nine", | |
} | |
WORDY_TENS = { | |
10: "ten", | |
11: "eleven", | |
12: "twelve", | |
13: "thirteen", | |
14: "fourteen", | |
15: "fifteen", | |
16: "sixteen", | |
17: "seventeen", | |
18: "eighteen", | |
19: "nineteen", | |
20: "twenty", | |
30: "thirty", | |
# USE FOR US: | |
# 40: "forty", | |
# USE FOR AUS: | |
40: "fourty", | |
50: "fifty", | |
} | |
PADS = ["x"] | |
PREFIXES = [ | |
"", | |
] | |
WPREFIXES = [ | |
"measurement %s is at ", | |
] | |
TIME_FORMATS = { | |
'one': "%s%s minute%s %s second%s past %s o clock", | |
'two': "%s%s o clock %s minute%s and %s second%s", | |
} | |
MAX_PADDING = 2 # 6 | |
MINUTES = 5 # 5 | |
SECONDS = 60 # 60 | |
PROFILING = True | |
try: | |
_range = xrange | |
except: | |
_range = range | |
# only for numbers between [0 and 60) | |
def number_to_wordy_number(num): | |
if num < 10: | |
return WORDY_NUMBERS[num] | |
elif num <= 20: | |
return WORDY_TENS[num] | |
else: | |
if num % 10 == 0: | |
return WORDY_TENS[num] | |
else: | |
mod = num % 10 | |
base = num - mod | |
return "%s %s" % (WORDY_TENS[base], WORDY_NUMBERS[mod]) | |
def generate_pads(pad, parts, parts_len, accum='', depth=0, remaining=0): | |
if depth == 0: | |
start = 0 | |
elif depth == parts_len: | |
# the minimum start value is more than zero if there are more remaining than we can fit | |
# minus one to disclude the one we're in | |
remaining_depths = parts_len + 1 - depth - 1 | |
overflow = remaining / MAX_PADDING | |
if overflow >= 1: | |
start = remaining % MAX_PADDING | |
else: | |
start = 0 | |
# but also, if remaining_depths is zero, set start to remaining | |
if remaining_depths == 0: | |
start = remaining | |
else: | |
start = 1 | |
if depth > parts_len: | |
yield accum | |
else: | |
# end is the lesser of remaining and max padding: | |
if (MAX_PADDING + 1) >= remaining: | |
# plus one because we're using range | |
end = remaining + 1 | |
else: | |
end = MAX_PADDING + 1 | |
for i in _range(start, end): | |
if depth == parts_len: | |
part = '' | |
else: | |
part = parts[depth] | |
for gen in generate_pads(pad, parts, parts_len, accum=(accum + (pad * i) + part), depth=depth + 1, remaining=remaining - i): | |
yield gen | |
def sequential_pads(basestr, lcipher): | |
# if we can detect that the cipher length is not equal to the padded base string, we can | |
# avoid making overhead calls | |
parts = basestr.split(' ') | |
strlen = sum(len(y) for y in parts) | |
# yield each possible padding | |
parts_len = len(parts) | |
ret = [] | |
for pad in PADS: | |
ret.append(generate_pads(pad, parts, parts_len, remaining=(lcipher - strlen))) | |
return ret | |
def plural_ending(num): | |
if num == 0 or num > 1: | |
return 's' | |
return '' | |
def get_prefixes(trial): | |
for prefix in PREFIXES: | |
yield prefix | |
for prefix in WPREFIXES: | |
yield prefix % number_to_wordy_number(trial) | |
# trial is "measurement 1" or 2 or whatever | |
def generate_timestamps(hour, trial, lcipher): | |
# for every second between boundary hour:00:00 and boundary_hour:05:00, generate all the possible | |
# timestamps | |
for prefix in get_prefixes(trial): | |
for minute in _range(MINUTES): | |
for second in _range(SECONDS): | |
wsecond = number_to_wordy_number(second) | |
wminute = number_to_wordy_number(minute) | |
whour = number_to_wordy_number(hour) | |
ws_plural = plural_ending(second) | |
wm_plural = plural_ending(minute) | |
one = sequential_pads(TIME_FORMATS['one'] % (prefix, wminute, wm_plural, wsecond, ws_plural, whour), lcipher) | |
two = sequential_pads(TIME_FORMATS['two'] % (prefix, whour, wminute, wm_plural, wsecond, ws_plural), lcipher) | |
yield chain(one, two) | |
PADARRAY = [0] * ((26 * 26) + 1) | |
alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] | |
def build_padmap(): | |
i = 0 | |
j = 0 | |
for letter in alphabet: | |
for xletter in alphabet: | |
clear_int = ord(letter) | |
cipher_int = ord(xletter) | |
if cipher_int > clear_int: | |
diff = cipher_int - clear_int | |
elif cipher_int == clear_int: | |
diff = 0 | |
else: | |
diff = 26 - (clear_int - cipher_int) | |
PADARRAY[(i * 26) + j] = chr(ord('A') + diff) | |
j += 1 | |
i += 1 | |
j = 0 | |
def generate_otp(timestamp, cipher, lcipher): | |
# calculate the one time pad that rotates timestamp to cipher, | |
# represented as all caps alpha, A=0, ... Z=25, etc | |
index = 0 | |
test_o = '' | |
itimestamp = unpack(b'B'*len(timestamp), timestamp) | |
icipher = unpack(b'B'*len(cipher), cipher) | |
for item in timestamp: | |
test_o += PADARRAY[((itimestamp[index] - 65) * 26) + (icipher[index] - 65)] | |
index += 1 | |
return test_o | |
def main(cipher, hour, trial, target_hash): | |
build_padmap() | |
# Find the timestamp | |
lcipher = len(cipher) | |
global output | |
output = [0] * lcipher | |
target_hash = target_hash.upper() | |
end = False | |
for stamp_list in generate_timestamps(hour, trial, lcipher): | |
if end: | |
break | |
for pad in stamp_list: | |
if end: | |
break | |
for timestamp in pad: | |
test_ts = timestamp.upper() | |
otp = generate_otp(test_ts, cipher, lcipher) | |
if otp is not None: | |
m = hashlib.md5() | |
m.update(otp.encode('ascii')) | |
otp_hash = m.hexdigest() | |
#if test_ts[0:15] == 'XTHREEXOXXCLOCK': #XCLOCKXONEXMINUTEXANDXFIFTYXFIVEXSECONDS': | |
# print("(test_ts)%s:(otp)%s:(target_hash)%s:(otp_hash)%s" % (test_ts, otp, target_hash, otp_hash.upper())) | |
if target_hash == otp_hash.upper(): | |
print("!!!!!!!!!!! FOUND OTP/TIMESTAMP: %s/%s" % (otp, test_ts)) | |
end = True | |
break | |
if __name__ == "__main__": | |
try: | |
ciphertext = str(sys.argv[1]) | |
hour = int(sys.argv[2]) | |
trial = int(sys.argv[3]) | |
otphash = str(sys.argv[4]) | |
s = timer() | |
if PROFILING: | |
cProfile.runctx('main(ciphertext, hour, trial, otphash)', globals(), locals(), filename='fun.py.profile') | |
import pstats | |
p = pstats.Stats('fun.py.profile') | |
p.sort_stats('time').print_stats(50) | |
else: | |
main(ciphertext, hour, trial, otphash) | |
e = timer() | |
print("Took %s s\n" % (e - s)) | |
except Exception as e: | |
print("Usage: fun.py CIPHERTEXT HOUR TRIAL OTPHASH") | |
raise |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment