Skip to content

Instantly share code, notes, and snippets.

@zeroaltitude
Last active December 21, 2015 01:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeroaltitude/6226930 to your computer and use it in GitHub Desktop.
Save zeroaltitude/6226930 to your computer and use it in GitHub Desktop.
Timestamp cracker, Python, version zeroaltitude
#!/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