-
Notifications
You must be signed in to change notification settings - Fork 0
/
pybna.py
executable file
·134 lines (124 loc) · 5.45 KB
/
pybna.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
#!/usr/bin/env python2
# This file is part of pybna.
#
# pybna is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pybna is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with pybna. If not, see <http://www.gnu.org/licenses/>.
#
# (C) 2012- by Laszlo Hammerl, <[email protected]>
import random
import ctypes
import urllib2
import time
import hmac
import hashlib
import struct
import csv
import os
from optparse import OptionParser
TOKEN_FILE = './tokens.csv'
RSA_KEY = 257
PUB_KEY = 104890018807986556874007710914205443157030159668034197186125678960287470894290830530618284943118405110896322835449099433232093151168250152146023319326491587651685252774820340995950744075665455681760652136576493028733914892166700899109836291180881063097461175643998356321993663868233366705340758102567742483097
M_URL = 'http://m.%s.mobileservice.blizzard.com'
M_ENROLL = '/enrollment/enroll.htm'
M_SYNC = '/enrollment/time.htm'
class Token(object):
def __init__(self, region = 'eu'):
self.secret = ''
self.serial = ''
self.region = region.upper()
self.offset = 0
def generate(self):
while True:
otp = ''.join(chr(random.randint(0, 255)) for i in range(37))
msg = '%d%s%s%s' % (1, otp, self.region, 'Motorola RAZR v3')
n = pow(int(msg.encode('hex'), 16), RSA_KEY, PUB_KEY)
a = ctypes.create_string_buffer(n.bit_length() // 8 + 1)
pyba = ctypes.pythonapi._PyLong_AsByteArray
pyba.argtypes = [ctypes.py_object, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_int, ctypes.c_int]
pyba(n, a, len(a), 0, 1)
req = urllib2.Request(M_URL % self.region + M_ENROLL)
req.add_header('Content-type', 'application/octet-stream')
req.add_header('Content-length', len(a.raw))
req.add_data(a.raw)
res = bytearray(urllib2.urlopen(req).read())
for i in range(37):
res[i+8] ^= ord(otp[i])
if str(res[28:31]) == '%s-' % (self.region):
self.serial = str(res[28:])
self.secret = ''.join(['%02X' % c for c in res[8:28]])
break
def set_token(self, serial, secret, region):
self.serial = serial
self.secret = secret
self.region = region
def get_time_offset(self):
res = urllib2.urlopen(M_URL % self.region + M_SYNC).read()
self.offset = int(time.time() * 1000) - sum(ord(res[7 - i]) << (i * 8) for i in range(8))
def get_password(self):
validity = (30 - int(time.time() + self.offset / 1000 ) % 30) - 1
t = (int(time.time() * 1000) + self.offset) / 30000
src = bytearray([0, 0, 0, 0] + [c for c in struct.pack('>L', t)])
key = bytearray(self.secret.decode('hex'))
raw_hmac = bytearray(hmac.new(key, src, hashlib.sha1).digest())
pos = raw_hmac[19] & 0x0f
auth = str(raw_hmac[pos:pos+4])
tmp = struct.unpack('>l', auth)[0]
code = (tmp & 0x7FFFFFFF) % (10**8)
return (code, validity)
if __name__ == '__main__':
usage = 'usage: %prog <option> <token_name>'
parser = OptionParser(usage)
parser.add_option('-n', '--new', action='store_true', help='request new token from server', default=False)
parser.add_option('-d', '--delete', action='store_true', help='delete the token', default=False)
parser.add_option('-g', '--generate', action='store_true', help='generate password for existing token', default=False)
parser.add_option('-l', '--list', action='store_true', help='get a list of existing tokens', default=False)
parser.add_option('-r', '--region', dest='region', help='set the region (default: %default)', default='eu')
(options, args) = parser.parse_args()
tokens = {}
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, 'rb') as f:
reader = csv.reader(f)
for row in reader:
tokens[row[0]] = (row[1], row[2], row[3])
f = open(TOKEN_FILE, 'ab')
if options.new:
t = Token()
t.generate()
writer = csv.writer(f)
writer.writerow([args[0], t.serial, t.secret, t.region])
elif options.delete:
if args[0] not in tokens.keys():
print '[!] Unknown token name'
else:
del(tokens[args[0]])
f.truncate(0)
writer = csv.writer(f)
for (k,v) in tokens.items():
writer.writerow([args[0]] + tokens[args[0]])
elif options.list:
print '%-24s%-24s' % ('Token name (region)', 'Token serial')
for (k, v) in tokens.items():
print '%-24s%-24s' % ('%s (%s)' % (k, v[2]), v[0])
elif options.generate:
if args[0] not in tokens.keys():
print '[!] Unknown token name'
else:
t = Token()
tmp = tokens[args[0]]
t.set_token(tmp[0], tmp[1], tmp[2])
t.get_time_offset()
pw = t.get_password()
print 'Password for %s: %s (valid for %d seconds)' % (args[0], pw[0], pw[1])
else:
parser.print_help()
f.close()