From 64f3faf799f434a9ec49a869932696dc8192cd71 Mon Sep 17 00:00:00 2001 From: AMMOnium Date: Mon, 25 Jun 2012 02:23:21 +0400 Subject: [PATCH 1/8] add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c1d984 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +Thumbs.db \ No newline at end of file From b38f144f773a449a2e48360a8991cf145ef35ff0 Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Tue, 27 Jan 2015 22:46:37 +0100 Subject: [PATCH 2/8] Unicode support --- draytools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/draytools.py b/draytools.py index c55b7fd..4b6597f 100644 --- a/draytools.py +++ b/draytools.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # DrayTek Vigor password recovery, config & firmware tools # @@ -730,4 +731,4 @@ def spkeygen(mac): else: print '[ERR]:\tPlease enter a valid MAC address, e.g '\ '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' -# EOF \ No newline at end of file +# EOF From 592dfd1ce924e0c7b7a5c9148e2215119e54f9df Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Tue, 27 Jan 2015 22:49:45 +0100 Subject: [PATCH 3/8] fileformat DOS->unix --- draytools.py | 1468 +++++++++++++++++++++++++------------------------- 1 file changed, 734 insertions(+), 734 deletions(-) diff --git a/draytools.py b/draytools.py index 4b6597f..555f8cb 100644 --- a/draytools.py +++ b/draytools.py @@ -1,734 +1,734 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# DrayTek Vigor password recovery, config & firmware tools -# -# https://github.com/ammonium/draytools/ -# -# draytools Copyright (C) 2011 AMMOnium -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# - -import sys -import os -import re -import math - -from struct import pack, unpack -from binascii import hexlify, unhexlify -from collections import defaultdict - -from pydelzo import pydelzo, LZO_ERROR - - -class draytools: - """DrayTek Vigor password recovery, config & firmware tools""" - __version__ = "v0.41" - copyright = \ - "draytools Copyright (C) 2011 AMMOnium " - - CFG_RAW = 0 - CFG_LZO = 1 - CFG_ENC = 2 - - verbose = False - modelprint = True - force_smart_guess = True - - class fs: - """Draytek filesystem utilities""" - def __init__(self, data, test=False, echo=False): - self.cdata = data - self.test = test - self.echo = echo - - def get_fname(self,i): - """Return full filename of the file #i from FS header""" - addr = 0x10+i*44 - return str(self.cdata[addr : addr+0x20].strip('\x00')) - - def get_hash(self,i): - """Return currently unknown hash for the file #i from FS header""" - addr = 0x10+i*44 + 0x20 - return unpack(" 1: - if not os.path.exists(ppp) and not self.test: - os.makedirs(ppp) - nfname = os.sep.join(pp) - # size of uncompressed file - rawfs = -1 - if not self.test: - ff = file(nfname,'wb') - # perform extraction, some file types are not compressed - if fs>0: - if pp[-1].split('.')[-1].lower() \ - in ['gif','jpg','cgi','cab','txt','jar']: - rawfdata = fdata - else: - try: - rawfdata = pydelzo.decompress('\xF0' \ - + pack(">L",fs*64)+fdata) - except LZO_ERROR as lze: - print '[ERR]:\tFile "'+ fname \ - + '" is damaged or uncompressed [' \ - + str(lze) \ - + '], RAW DATA WRITTEN' - rawfdata = fdata - else: - rawfdata = '' - rawfs = len(rawfdata) - if not self.test: - ff.write(rawfdata) - ff.close() - # print some debug info for each file - if self.echo: - print '%08X "' % ds + fname + '" %08X' % fs \ - + ' %08X' % rawfs - return (fs, rawfs) - - def save_all(self, path): - """Extract all files from current FS""" - self.path = path - numfiles = unpack("> 2 - if len(data) < 4: - return 0xFFFFFFFF - if len(data) % 4: - data += '\x00' * (4 - len(data) % 4) - - pos = 0 - v0 = 0 - a0 = 0 - a2 = 0 - - while a1 > 0: - v0 = unpack(">L", data[pos+a0:pos+a0+4])[0] - a0 += 4 - a2 += v0 - a1 -= 1 - - v0 = unpack(">L",data[pos+a0:pos+a0+4])[0] - a2 = ~a2 - v0 ^= a2 - return v0 & 0xFFFFFFFF - - @staticmethod - def get_modelid(data): - """Extract a model ID from config file header""" - modelid = data[0x0C:0x0E] - return modelid - - @staticmethod - def decompress_cfg(data): - """Decompress a config file""" - modelstr = "V" + format(unpack(">H", - draytools.get_modelid(data))[0],"04X") - if draytools.verbose and draytools.modelprint: - print 'Model is :\t' + modelstr - else: - draytools.modelprint = True - rawcfgsize = 0x00100000 - lzocfgsize = unpack(">L", data[0x24:0x28])[0] - raw = data[:0x2D] + '\x00' + data[0x2E:0x100] \ - + pydelzo.decompress('\xF0' + pack(">L",rawcfgsize) \ - + data[0x100:0x100+lzocfgsize]) - return raw - - @staticmethod - def make_key(modelstr): - """Construct a key out of a model string (like 'V2710')""" - sum = 0 - for c in modelstr: - sum += ord(c) - return (0xFF & sum) - - @staticmethod - def enc(c, key): - c ^= key - c -= key - c = 0xFF & (c >> 5 | c << 3) - return c - - @staticmethod - def dec(c, key): - c = (c << 5 | c >> 3) - c += key - c ^= key - c &= 0xFF - return c - - @staticmethod - def decrypt(data, key): - """Decrypt a block of data using given key""" - rdata = '' - for i in xrange(len(data)): - rdata += chr(draytools.dec(ord(data[i]), key)) - return rdata -# return ''.join(map(lambda od:chr(draytools.dec(ord(od),key)),data)) - - @staticmethod - def brute_cfg(data): - """Check all possible keys until data looks like decrypted""" - rdata = None - key = 0 - for i in xrange(256): - rdata = draytools.decrypt(data, i) - if draytools.smart_guess(rdata) == draytools.CFG_LZO: - key = i - break - if draytools.verbose: - print 'Found key:\t[0x%02X]' % key - return rdata - - @staticmethod - def decrypt_cfg(data): - """Decrypt config, bruteforce if default key fails""" - modelstr = "V" + format(unpack(">H", - draytools.get_modelid(data))[0],"04X") - if draytools.verbose: - print 'Model is :\t' + modelstr - draytools.modelprint = False - ckey = draytools.make_key(modelstr) - rdata = draytools.decrypt(data[0x100:], ckey) - # if the decrypted data does not look good, bruteforce - if draytools.smart_guess(rdata) != draytools.CFG_LZO: - rdata = draytools.brute_cfg(data[0x100:]) - elif draytools.verbose: - print 'Used key :\t[0x%02X]' % ckey - return data[:0x2D] + '\x01' + data[0x2E:0x100] + rdata - - @staticmethod - def get_credentials(data): - """Extract admin credentials from config""" - login = data[0x100+0x28:0x100+0x40].split('\x00')[0] - password = data[0x100+0x40:0x100+0x58].split('\x00')[0] - return [login, password] - - @staticmethod - def guess(data): - """Return CFG type - raw(0), compressed(1), encrypted(2)""" - return ord(data[0x2D]) - - @staticmethod - def smart_guess(data): - """Guess is the cfg block compressed or not""" - # Uncompressed block is large and has low entropy - if draytools.entropy(data) < 1.0 or len(data) > 0x10000: - return draytools.CFG_RAW - # Compressed block still has pieces of cleartext at the beginning - if "Vigor" in data and ("Series" in data or "draytek" in data): - return draytools.CFG_LZO - return draytools.CFG_ENC - - @staticmethod - def de_cfg(data): - """Get raw config data from raw /compressed/encrypted & comressed""" - if draytools.force_smart_guess: - g = draytools.smart_guess(data) - else: - g = draytools.guess(data) - if g == draytools.CFG_RAW: - if draytools.verbose: - print 'File is :\tnot compressed, not encrypted' - return g, data - elif g == draytools.CFG_LZO: - if draytools.verbose: - print 'File is :\tcompressed, not encrypted' - return g, draytools.decompress_cfg(data) - elif g == draytools.CFG_ENC: - if draytools.verbose: - print 'File is :\tcompressed, encrypted' - return g, draytools.decompress_cfg(draytools.decrypt_cfg(data)) - - @staticmethod - def decompress_firmware(data): - """Decompress firmware""" - flen = len(data) - sigstart = data.find('\xA5\xA5\xA5\x5A\xA5\x5A') - # Try an alternative signature - if sigstart <= 0: - sigstart = data.find('\x5A\x5A\xA5\x5A\xA5\x5A') - # Compressed FW block found, now decompress - if sigstart > 0: - if draytools.verbose: - print 'Signature found at [0x%08X]' % sigstart - lzosizestart = sigstart + 6 - lzostart = lzosizestart + 4 - lzosize = unpack('>L', data[lzosizestart:lzostart])[0] - return data[0x100:sigstart+2] \ - + pydelzo.decompress('\xF0' + pack(">L",0x1000000) \ - + data[lzostart:lzostart+lzosize]) - else: - print '[ERR]:\tCompressed FW signature not found!' - raise Exception('Compressed FW signature not found') - return '' - - @staticmethod - def decompress_fs(data, path, test = False): - """Decompress filesystem""" - lzofsdatalen = unpack('>L', data[4:8])[0] - if draytools.verbose: - print 'Compressed FS length: %d [0x%08X]' % (lzofsdatalen, - lzofsdatalen) - # stupid assumption of raw FS length. Seems OK for now - fsdatalen = 0x800000 - fs_raw = pydelzo.decompress('\xF0' + pack(">L", fsdatalen) \ - + data[0x08:0x08 + lzofsdatalen]) - cfs = draytools.fs(fs_raw, test, draytools.verbose) - return (lzofsdatalen, cfs.save_all(path)) - - @staticmethod - def decompress_fs_only(data, path, test = False): - """Decompress filesystem""" - fsstart = unpack('>L', data[:4])[0] - if draytools.verbose: - print 'FS block start at: %d [0x%08X]' % (fsstart, fsstart) - return draytools.decompress_fs(data[fsstart:], path, test) - - @staticmethod - def entropy(data): - """Calculate Shannon entropy (in bits per byte)""" - flist = defaultdict(int) - dlen = len(data) - data = map(ord, data) - # count occurencies - for byte in data: - flist[byte] += 1 - ent = 0.0 - # convert count of occurencies into frequency - for freq in flist.values(): - if freq > 0: - ffreq = float(freq)/dlen - # actual entropy calcualtion - ent -= ffreq * math.log(ffreq, 2) - return ent - - @staticmethod - def spkeygen(mac): - """Generate a master key like 'AbCdEfGh' from MAC address""" - # stupid translation from MIPS assembly, but works - atu = 'WAHOBXEZCLPDYTFQMJRVINSUGK' - atl = 'kgusnivrjmqftydplczexbohaw' - res = ['\x00'] * 8 - st = [0] * 8 - # compute 31*(31*(31*(31*(31*m0+m1)+m2)+m3)+m4)+m5, sign-extend mac bytes - a3 = 0 - for i in mac: - v1 = a3 << 5 - v1 &= 0xFFFFFFFF - a0 = ord(i) - if a0 >= 0x80: - a0 |= 0xFFFFFF00 - v1 -= a3 - v1 &= 0xFFFFFFFF - a3 = v1 + a0 - a3 &= 0xFFFFFFFF - # Divide by 13 :) Old assembly trick, I leave it here - # 0x4EC4EC4F is a multiplicative inverse for 13 - ck = 0x4EC4EC4F * a3 - v1 = (ck & 0xFFFFFFFF00000000) >> 32 - # shift by two - v1 >>= 3 - v0 = v1 << 1 - v0 &= 0xFFFFFFFF - # trick ends here and now v0 = a3 / 13 - v0 += v1 - v0 <<= 2 - v0 &= 0xFFFFFFFF - v0 += v1 - v0 <<= 1 - v0 -= a3 - # v0 &= 0xFFFFFFFF - st[0] = a3 - res[0] = atu[abs(v0)] - - for i in xrange(1,8): - v1 = st[i-1] - a0 = ord(res[0]) - t0 = ord(res[1]) - v0 = (v1 << 5) & 0xFFFFFFFF - a1 = ord(res[2]) - v0 -= v1 - v0 += a0 - a2 = ord(res[3]) - a3 = ord(res[4]) - v0 += t0 - v0 += a1 - t0 = ord(res[5]) - v1 = ord(res[6]) - v0 += a2 - a0 = ord(res[7]) - v0 += a3 - v0 += t0 - v0 += v1 - # v0 here is a 32-bit sum of currently computed key chars - v0 &= 0xFFFFFFFF - a3 = v0 + a0 - # Again divide by 13 - i1 = a3 * 0x4EC4EC4F - a0 = i & 1 - st[i] = a3 - v1 = (i1 & 0xFFFFFFFF00000000) >> 32 - v1 >>= 3 - v0 = v1 << 1 - # here v0 = a3 / 13 - v0 += v1 - v0 <<= 2 - v0 += v1 - v0 <<= 1 - v0 = a3 - v0 - a1 += v0 - v0 &= 0xFFFFFFFF - if a0 == 0: - v1 = atu[abs(v0)] - else: - v1 = atl[abs(v0)] - res[i] = v1 - v0 = 0 - return ''.join(res) - - - -if __name__ == '__main__': - import optparse - - usage = \ -"""usage: %prog [options] file -DrayTek Vigor V2xxx/V3xxx password recovery, config & firmware tools""" - -# initialize cmdline option parser - optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog - parser = optparse.OptionParser(usage=usage, \ - version="%prog "+draytools.__version__, \ - epilog= -""" -Examples: - -To print login&password from the config file: -# python draytools.py -p config.cfg - Login and password will be displayed - -To decrypt & decompress the config file: -# python draytools.py -c config.cfg - Raw config file "config.cfg.out" will be produced - -To extract firmware and filesystem contents -# python draytools.py -F firmware.all - Uncompressed firmware will be written to file "firmware.all.out" - Filesystem will be extracted to "fs_out" folder. -""") - - cfggroup = optparse.OptionGroup(parser, "Config file (*.cfg) commands", - "To be used on config files only") - fwgroup = optparse.OptionGroup(parser, \ - "Firmware file (*.all, *.rst, *.bin) commands", - "To be used on firmware files only") - - mgroup = optparse.OptionGroup(parser, "Miscellaneous commands", - "Some other useful stuff") - - parser.add_option('-o', '--output', - action="store", dest="outfile", - help="Output file name, %INPUTFILE%.out if omitted", default="") - - parser.add_option('-t', '--test', - action="store_true", dest="test", help= -"""Test mode, do not write anything to disk, only try to parse files""", - default=False) - - parser.add_option('-v', '--verbose', - action="store_true", dest="verbose", - help="Verbose output", default=False) - -# config file option group for cmdline option parser - - cfggroup.add_option('-c', '--config', - action="store_true", dest="config", - help="Decrypt and decompress config", default=False) - - cfggroup.add_option('-d', '--decompress', - action="store_true", dest="decompress", - help="Decompress an unenrypted config file", default=False) - - cfggroup.add_option('-y', '--decrypt', - action="store_true", dest="decrypt", - help="Decrypt config file", default=False) - - cfggroup.add_option('-p', '--password', - action="store_true", dest="password", - help="Retrieve admin login and password from config file", - default=False) - -# firmware/fs option group for cmdline option parser - - fwgroup.add_option('-f', '--firmware', - action="store_true", dest="firmware", - help="Decompress firmware", default=False) - - fwgroup.add_option('-F', '--firmware-all', - action="store_true", dest="fw_all", - help="Decompress firmware and extract filesystem", default=False) - - fwgroup.add_option('-s', '--fs', - action="store_true", dest="fs", - help="Extract filesystem", default=False) - - fwgroup.add_option('-O', '--out-dir', - action="store", dest="outdir", - help= - "Output directory for filesystem contents, \"fs_out\" by default", - default="fs_out") - -# miscellaneous option group for cmdline option parser - mgroup.add_option('-m', '--master-key', - action="store", dest="mac", - help="Generate FTP master key for given router MAC address. " - "To login to FTP enter \"admin\" as username and generated " - "master key as password", - default=None) - - - parser.add_option_group(cfggroup) - parser.add_option_group(fwgroup) - parser.add_option_group(mgroup) - - - options, args = parser.parse_args() - - draytools.verbose = options.verbose - -# default output filename is input filename + '.out' - outfname = options.outfile is not None and options.outfile \ - or (len(args) > 0 and args[0]+'.out' or 'file.out') - outdir = options.outdir - - - infile = None - data = None - indata = None - outdata = None - - if len(args) > 1: - print '[ERR]:\tToo much arguments, only input file name expected' - print 'Run "draytools --help"' - sys.exit(1) - elif len(args) < 1 and not options.mac: - print '[ERR]:\tInput file name expected' - print 'Run "draytools --help"' - sys.exit(1) - -# open input file - if not options.mac: - try: - infile = file(args[0],'rb') - indata = infile.read() - # if default FS extraction path is used, put it near input file - if outdir == 'fs_out': - outdir = os.path.join(os.path.dirname( - os.path.abspath(args[0])),'fs_out') - - except IOError: - print '[ERR]:\tInput file open failed' - sys.exit(2) - -# Command: get raw config file - if options.config: - g = -1 - try: - g, outdata = draytools.de_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - if g == draytools.CFG_RAW: - print '[ERR]:\tNothing to do. '\ - 'Config file is already not encrypted and not compressed.' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - outfile.close() - else: - print 'CFG decryption/decompression test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - -# Command: decrypt config file - elif options.decrypt: - try: - outdata = draytools.decrypt_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - cksum = draytools.v2k_checksum(str(outdata)) - if options.verbose: - print 'V2kCheckSum = %08X ' % \ - cksum + ((cksum == 0) and 'OK' or 'FAIL') - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'CFG decryption test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - -# Command: decompress config file - elif options.decompress: - try: - outdata = draytools.decompress_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - cksum = draytools.v2k_checksum(str(indata)) - if options.verbose: - print 'V2kCheckSum = %08X ' % \ - cksum + ((cksum == 0) and 'OK' or 'FAIL') - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'CFG decompression test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - -# Command: extract admin credentials from config file - if options.password and \ - not (True in [options.firmware, options.fw_all, options.fs]): - g = -1 - try: - g, outdata = draytools.de_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - creds = draytools.get_credentials(outdata) - print "Login :\t" + (creds[0] == "" and "admin" or creds[0]) - print "Password :\t" + (creds[1] == "" and "admin" or creds[1]) - sys.exit(0) - -# Command: extract firmware - if options.firmware: - try: - outdata = draytools.decompress_firmware(indata) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'FW extraction test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - -# Command: extract firmware and filesystem - elif options.fw_all: - try: - outdata = draytools.decompress_firmware(indata) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'FW extraction test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - - try: - fss, nf = draytools.decompress_fs_only(indata, outdir, - options.test) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - if not options.test: - print 'FS extracted to [' + outdir + '], %d files extracted' % nf - else: - print 'FS extraction test OK, %d files extracted' % nf - - -# Command: extract filesystem - elif options.fs: - try: - fss, nf = draytools.decompress_fs_only(indata, outdir, - options.test) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - if not options.test: - print 'FS extracted to [' + outdir + '], %d files extracted' % nf - else: - print 'FS extraction test OK, %d files extracted' % nf - -# Command: generate master password - elif options.mac is not None: - # validate mac address (hex, delimited by colons, dashes or nothing) - xr = re.compile(\ - r'^([a-fA-F0-9]{2}([:-]?)[a-fA-F0-9]{2}(\2[a-fA-F0-9]{2}){4})$') - rr = xr.match(options.mac) - if rr: - xmac = unhexlify(re.sub('[:\-]', '', options.mac)) - print 'Username :\t' + "admin" - print 'Master key:\t' + draytools.spkeygen(xmac) - else: - print '[ERR]:\tPlease enter a valid MAC address, e.g '\ - '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' -# EOF +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# DrayTek Vigor password recovery, config & firmware tools +# +# https://github.com/ammonium/draytools/ +# +# draytools Copyright (C) 2011 AMMOnium +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# + +import sys +import os +import re +import math + +from struct import pack, unpack +from binascii import hexlify, unhexlify +from collections import defaultdict + +from pydelzo import pydelzo, LZO_ERROR + + +class draytools: + """DrayTek Vigor password recovery, config & firmware tools""" + __version__ = "v0.41" + copyright = \ + "draytools Copyright (C) 2011 AMMOnium " + + CFG_RAW = 0 + CFG_LZO = 1 + CFG_ENC = 2 + + verbose = False + modelprint = True + force_smart_guess = True + + class fs: + """Draytek filesystem utilities""" + def __init__(self, data, test=False, echo=False): + self.cdata = data + self.test = test + self.echo = echo + + def get_fname(self,i): + """Return full filename of the file #i from FS header""" + addr = 0x10+i*44 + return str(self.cdata[addr : addr+0x20].strip('\x00')) + + def get_hash(self,i): + """Return currently unknown hash for the file #i from FS header""" + addr = 0x10+i*44 + 0x20 + return unpack(" 1: + if not os.path.exists(ppp) and not self.test: + os.makedirs(ppp) + nfname = os.sep.join(pp) + # size of uncompressed file + rawfs = -1 + if not self.test: + ff = file(nfname,'wb') + # perform extraction, some file types are not compressed + if fs>0: + if pp[-1].split('.')[-1].lower() \ + in ['gif','jpg','cgi','cab','txt','jar']: + rawfdata = fdata + else: + try: + rawfdata = pydelzo.decompress('\xF0' \ + + pack(">L",fs*64)+fdata) + except LZO_ERROR as lze: + print '[ERR]:\tFile "'+ fname \ + + '" is damaged or uncompressed [' \ + + str(lze) \ + + '], RAW DATA WRITTEN' + rawfdata = fdata + else: + rawfdata = '' + rawfs = len(rawfdata) + if not self.test: + ff.write(rawfdata) + ff.close() + # print some debug info for each file + if self.echo: + print '%08X "' % ds + fname + '" %08X' % fs \ + + ' %08X' % rawfs + return (fs, rawfs) + + def save_all(self, path): + """Extract all files from current FS""" + self.path = path + numfiles = unpack("> 2 + if len(data) < 4: + return 0xFFFFFFFF + if len(data) % 4: + data += '\x00' * (4 - len(data) % 4) + + pos = 0 + v0 = 0 + a0 = 0 + a2 = 0 + + while a1 > 0: + v0 = unpack(">L", data[pos+a0:pos+a0+4])[0] + a0 += 4 + a2 += v0 + a1 -= 1 + + v0 = unpack(">L",data[pos+a0:pos+a0+4])[0] + a2 = ~a2 + v0 ^= a2 + return v0 & 0xFFFFFFFF + + @staticmethod + def get_modelid(data): + """Extract a model ID from config file header""" + modelid = data[0x0C:0x0E] + return modelid + + @staticmethod + def decompress_cfg(data): + """Decompress a config file""" + modelstr = "V" + format(unpack(">H", + draytools.get_modelid(data))[0],"04X") + if draytools.verbose and draytools.modelprint: + print 'Model is :\t' + modelstr + else: + draytools.modelprint = True + rawcfgsize = 0x00100000 + lzocfgsize = unpack(">L", data[0x24:0x28])[0] + raw = data[:0x2D] + '\x00' + data[0x2E:0x100] \ + + pydelzo.decompress('\xF0' + pack(">L",rawcfgsize) \ + + data[0x100:0x100+lzocfgsize]) + return raw + + @staticmethod + def make_key(modelstr): + """Construct a key out of a model string (like 'V2710')""" + sum = 0 + for c in modelstr: + sum += ord(c) + return (0xFF & sum) + + @staticmethod + def enc(c, key): + c ^= key + c -= key + c = 0xFF & (c >> 5 | c << 3) + return c + + @staticmethod + def dec(c, key): + c = (c << 5 | c >> 3) + c += key + c ^= key + c &= 0xFF + return c + + @staticmethod + def decrypt(data, key): + """Decrypt a block of data using given key""" + rdata = '' + for i in xrange(len(data)): + rdata += chr(draytools.dec(ord(data[i]), key)) + return rdata +# return ''.join(map(lambda od:chr(draytools.dec(ord(od),key)),data)) + + @staticmethod + def brute_cfg(data): + """Check all possible keys until data looks like decrypted""" + rdata = None + key = 0 + for i in xrange(256): + rdata = draytools.decrypt(data, i) + if draytools.smart_guess(rdata) == draytools.CFG_LZO: + key = i + break + if draytools.verbose: + print 'Found key:\t[0x%02X]' % key + return rdata + + @staticmethod + def decrypt_cfg(data): + """Decrypt config, bruteforce if default key fails""" + modelstr = "V" + format(unpack(">H", + draytools.get_modelid(data))[0],"04X") + if draytools.verbose: + print 'Model is :\t' + modelstr + draytools.modelprint = False + ckey = draytools.make_key(modelstr) + rdata = draytools.decrypt(data[0x100:], ckey) + # if the decrypted data does not look good, bruteforce + if draytools.smart_guess(rdata) != draytools.CFG_LZO: + rdata = draytools.brute_cfg(data[0x100:]) + elif draytools.verbose: + print 'Used key :\t[0x%02X]' % ckey + return data[:0x2D] + '\x01' + data[0x2E:0x100] + rdata + + @staticmethod + def get_credentials(data): + """Extract admin credentials from config""" + login = data[0x100+0x28:0x100+0x40].split('\x00')[0] + password = data[0x100+0x40:0x100+0x58].split('\x00')[0] + return [login, password] + + @staticmethod + def guess(data): + """Return CFG type - raw(0), compressed(1), encrypted(2)""" + return ord(data[0x2D]) + + @staticmethod + def smart_guess(data): + """Guess is the cfg block compressed or not""" + # Uncompressed block is large and has low entropy + if draytools.entropy(data) < 1.0 or len(data) > 0x10000: + return draytools.CFG_RAW + # Compressed block still has pieces of cleartext at the beginning + if "Vigor" in data and ("Series" in data or "draytek" in data): + return draytools.CFG_LZO + return draytools.CFG_ENC + + @staticmethod + def de_cfg(data): + """Get raw config data from raw /compressed/encrypted & comressed""" + if draytools.force_smart_guess: + g = draytools.smart_guess(data) + else: + g = draytools.guess(data) + if g == draytools.CFG_RAW: + if draytools.verbose: + print 'File is :\tnot compressed, not encrypted' + return g, data + elif g == draytools.CFG_LZO: + if draytools.verbose: + print 'File is :\tcompressed, not encrypted' + return g, draytools.decompress_cfg(data) + elif g == draytools.CFG_ENC: + if draytools.verbose: + print 'File is :\tcompressed, encrypted' + return g, draytools.decompress_cfg(draytools.decrypt_cfg(data)) + + @staticmethod + def decompress_firmware(data): + """Decompress firmware""" + flen = len(data) + sigstart = data.find('\xA5\xA5\xA5\x5A\xA5\x5A') + # Try an alternative signature + if sigstart <= 0: + sigstart = data.find('\x5A\x5A\xA5\x5A\xA5\x5A') + # Compressed FW block found, now decompress + if sigstart > 0: + if draytools.verbose: + print 'Signature found at [0x%08X]' % sigstart + lzosizestart = sigstart + 6 + lzostart = lzosizestart + 4 + lzosize = unpack('>L', data[lzosizestart:lzostart])[0] + return data[0x100:sigstart+2] \ + + pydelzo.decompress('\xF0' + pack(">L",0x1000000) \ + + data[lzostart:lzostart+lzosize]) + else: + print '[ERR]:\tCompressed FW signature not found!' + raise Exception('Compressed FW signature not found') + return '' + + @staticmethod + def decompress_fs(data, path, test = False): + """Decompress filesystem""" + lzofsdatalen = unpack('>L', data[4:8])[0] + if draytools.verbose: + print 'Compressed FS length: %d [0x%08X]' % (lzofsdatalen, + lzofsdatalen) + # stupid assumption of raw FS length. Seems OK for now + fsdatalen = 0x800000 + fs_raw = pydelzo.decompress('\xF0' + pack(">L", fsdatalen) \ + + data[0x08:0x08 + lzofsdatalen]) + cfs = draytools.fs(fs_raw, test, draytools.verbose) + return (lzofsdatalen, cfs.save_all(path)) + + @staticmethod + def decompress_fs_only(data, path, test = False): + """Decompress filesystem""" + fsstart = unpack('>L', data[:4])[0] + if draytools.verbose: + print 'FS block start at: %d [0x%08X]' % (fsstart, fsstart) + return draytools.decompress_fs(data[fsstart:], path, test) + + @staticmethod + def entropy(data): + """Calculate Shannon entropy (in bits per byte)""" + flist = defaultdict(int) + dlen = len(data) + data = map(ord, data) + # count occurencies + for byte in data: + flist[byte] += 1 + ent = 0.0 + # convert count of occurencies into frequency + for freq in flist.values(): + if freq > 0: + ffreq = float(freq)/dlen + # actual entropy calcualtion + ent -= ffreq * math.log(ffreq, 2) + return ent + + @staticmethod + def spkeygen(mac): + """Generate a master key like 'AbCdEfGh' from MAC address""" + # stupid translation from MIPS assembly, but works + atu = 'WAHOBXEZCLPDYTFQMJRVINSUGK' + atl = 'kgusnivrjmqftydplczexbohaw' + res = ['\x00'] * 8 + st = [0] * 8 + # compute 31*(31*(31*(31*(31*m0+m1)+m2)+m3)+m4)+m5, sign-extend mac bytes + a3 = 0 + for i in mac: + v1 = a3 << 5 + v1 &= 0xFFFFFFFF + a0 = ord(i) + if a0 >= 0x80: + a0 |= 0xFFFFFF00 + v1 -= a3 + v1 &= 0xFFFFFFFF + a3 = v1 + a0 + a3 &= 0xFFFFFFFF + # Divide by 13 :) Old assembly trick, I leave it here + # 0x4EC4EC4F is a multiplicative inverse for 13 + ck = 0x4EC4EC4F * a3 + v1 = (ck & 0xFFFFFFFF00000000) >> 32 + # shift by two + v1 >>= 3 + v0 = v1 << 1 + v0 &= 0xFFFFFFFF + # trick ends here and now v0 = a3 / 13 + v0 += v1 + v0 <<= 2 + v0 &= 0xFFFFFFFF + v0 += v1 + v0 <<= 1 + v0 -= a3 + # v0 &= 0xFFFFFFFF + st[0] = a3 + res[0] = atu[abs(v0)] + + for i in xrange(1,8): + v1 = st[i-1] + a0 = ord(res[0]) + t0 = ord(res[1]) + v0 = (v1 << 5) & 0xFFFFFFFF + a1 = ord(res[2]) + v0 -= v1 + v0 += a0 + a2 = ord(res[3]) + a3 = ord(res[4]) + v0 += t0 + v0 += a1 + t0 = ord(res[5]) + v1 = ord(res[6]) + v0 += a2 + a0 = ord(res[7]) + v0 += a3 + v0 += t0 + v0 += v1 + # v0 here is a 32-bit sum of currently computed key chars + v0 &= 0xFFFFFFFF + a3 = v0 + a0 + # Again divide by 13 + i1 = a3 * 0x4EC4EC4F + a0 = i & 1 + st[i] = a3 + v1 = (i1 & 0xFFFFFFFF00000000) >> 32 + v1 >>= 3 + v0 = v1 << 1 + # here v0 = a3 / 13 + v0 += v1 + v0 <<= 2 + v0 += v1 + v0 <<= 1 + v0 = a3 - v0 + a1 += v0 + v0 &= 0xFFFFFFFF + if a0 == 0: + v1 = atu[abs(v0)] + else: + v1 = atl[abs(v0)] + res[i] = v1 + v0 = 0 + return ''.join(res) + + + +if __name__ == '__main__': + import optparse + + usage = \ +"""usage: %prog [options] file +DrayTek Vigor V2xxx/V3xxx password recovery, config & firmware tools""" + +# initialize cmdline option parser + optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog + parser = optparse.OptionParser(usage=usage, \ + version="%prog "+draytools.__version__, \ + epilog= +""" +Examples: + +To print login&password from the config file: +# python draytools.py -p config.cfg + Login and password will be displayed + +To decrypt & decompress the config file: +# python draytools.py -c config.cfg + Raw config file "config.cfg.out" will be produced + +To extract firmware and filesystem contents +# python draytools.py -F firmware.all + Uncompressed firmware will be written to file "firmware.all.out" + Filesystem will be extracted to "fs_out" folder. +""") + + cfggroup = optparse.OptionGroup(parser, "Config file (*.cfg) commands", + "To be used on config files only") + fwgroup = optparse.OptionGroup(parser, \ + "Firmware file (*.all, *.rst, *.bin) commands", + "To be used on firmware files only") + + mgroup = optparse.OptionGroup(parser, "Miscellaneous commands", + "Some other useful stuff") + + parser.add_option('-o', '--output', + action="store", dest="outfile", + help="Output file name, %INPUTFILE%.out if omitted", default="") + + parser.add_option('-t', '--test', + action="store_true", dest="test", help= +"""Test mode, do not write anything to disk, only try to parse files""", + default=False) + + parser.add_option('-v', '--verbose', + action="store_true", dest="verbose", + help="Verbose output", default=False) + +# config file option group for cmdline option parser + + cfggroup.add_option('-c', '--config', + action="store_true", dest="config", + help="Decrypt and decompress config", default=False) + + cfggroup.add_option('-d', '--decompress', + action="store_true", dest="decompress", + help="Decompress an unenrypted config file", default=False) + + cfggroup.add_option('-y', '--decrypt', + action="store_true", dest="decrypt", + help="Decrypt config file", default=False) + + cfggroup.add_option('-p', '--password', + action="store_true", dest="password", + help="Retrieve admin login and password from config file", + default=False) + +# firmware/fs option group for cmdline option parser + + fwgroup.add_option('-f', '--firmware', + action="store_true", dest="firmware", + help="Decompress firmware", default=False) + + fwgroup.add_option('-F', '--firmware-all', + action="store_true", dest="fw_all", + help="Decompress firmware and extract filesystem", default=False) + + fwgroup.add_option('-s', '--fs', + action="store_true", dest="fs", + help="Extract filesystem", default=False) + + fwgroup.add_option('-O', '--out-dir', + action="store", dest="outdir", + help= + "Output directory for filesystem contents, \"fs_out\" by default", + default="fs_out") + +# miscellaneous option group for cmdline option parser + mgroup.add_option('-m', '--master-key', + action="store", dest="mac", + help="Generate FTP master key for given router MAC address. " + "To login to FTP enter \"admin\" as username and generated " + "master key as password", + default=None) + + + parser.add_option_group(cfggroup) + parser.add_option_group(fwgroup) + parser.add_option_group(mgroup) + + + options, args = parser.parse_args() + + draytools.verbose = options.verbose + +# default output filename is input filename + '.out' + outfname = options.outfile is not None and options.outfile \ + or (len(args) > 0 and args[0]+'.out' or 'file.out') + outdir = options.outdir + + + infile = None + data = None + indata = None + outdata = None + + if len(args) > 1: + print '[ERR]:\tToo much arguments, only input file name expected' + print 'Run "draytools --help"' + sys.exit(1) + elif len(args) < 1 and not options.mac: + print '[ERR]:\tInput file name expected' + print 'Run "draytools --help"' + sys.exit(1) + +# open input file + if not options.mac: + try: + infile = file(args[0],'rb') + indata = infile.read() + # if default FS extraction path is used, put it near input file + if outdir == 'fs_out': + outdir = os.path.join(os.path.dirname( + os.path.abspath(args[0])),'fs_out') + + except IOError: + print '[ERR]:\tInput file open failed' + sys.exit(2) + +# Command: get raw config file + if options.config: + g = -1 + try: + g, outdata = draytools.de_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + if g == draytools.CFG_RAW: + print '[ERR]:\tNothing to do. '\ + 'Config file is already not encrypted and not compressed.' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + outfile.close() + else: + print 'CFG decryption/decompression test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + +# Command: decrypt config file + elif options.decrypt: + try: + outdata = draytools.decrypt_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + cksum = draytools.v2k_checksum(str(outdata)) + if options.verbose: + print 'V2kCheckSum = %08X ' % \ + cksum + ((cksum == 0) and 'OK' or 'FAIL') + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'CFG decryption test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + +# Command: decompress config file + elif options.decompress: + try: + outdata = draytools.decompress_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + cksum = draytools.v2k_checksum(str(indata)) + if options.verbose: + print 'V2kCheckSum = %08X ' % \ + cksum + ((cksum == 0) and 'OK' or 'FAIL') + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'CFG decompression test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + +# Command: extract admin credentials from config file + if options.password and \ + not (True in [options.firmware, options.fw_all, options.fs]): + g = -1 + try: + g, outdata = draytools.de_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + creds = draytools.get_credentials(outdata) + print "Login :\t" + (creds[0] == "" and "admin" or creds[0]) + print "Password :\t" + (creds[1] == "" and "admin" or creds[1]) + sys.exit(0) + +# Command: extract firmware + if options.firmware: + try: + outdata = draytools.decompress_firmware(indata) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'FW extraction test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + +# Command: extract firmware and filesystem + elif options.fw_all: + try: + outdata = draytools.decompress_firmware(indata) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'FW extraction test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + + try: + fss, nf = draytools.decompress_fs_only(indata, outdir, + options.test) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + if not options.test: + print 'FS extracted to [' + outdir + '], %d files extracted' % nf + else: + print 'FS extraction test OK, %d files extracted' % nf + + +# Command: extract filesystem + elif options.fs: + try: + fss, nf = draytools.decompress_fs_only(indata, outdir, + options.test) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + if not options.test: + print 'FS extracted to [' + outdir + '], %d files extracted' % nf + else: + print 'FS extraction test OK, %d files extracted' % nf + +# Command: generate master password + elif options.mac is not None: + # validate mac address (hex, delimited by colons, dashes or nothing) + xr = re.compile(\ + r'^([a-fA-F0-9]{2}([:-]?)[a-fA-F0-9]{2}(\2[a-fA-F0-9]{2}){4})$') + rr = xr.match(options.mac) + if rr: + xmac = unhexlify(re.sub('[:\-]', '', options.mac)) + print 'Username :\t' + "admin" + print 'Master key:\t' + draytools.spkeygen(xmac) + else: + print '[ERR]:\tPlease enter a valid MAC address, e.g '\ + '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' +# EOF From a51b6c5d15e7c6f1e15f28b2f3c3c3f66c9ad66e Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Wed, 28 Jan 2015 08:21:44 +0100 Subject: [PATCH 4/8] \t => 4 spaces (https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces) --- draytools.py | 1272 +++++++++++++++++++++++++------------------------- 1 file changed, 636 insertions(+), 636 deletions(-) diff --git a/draytools.py b/draytools.py index 555f8cb..765abbd 100644 --- a/draytools.py +++ b/draytools.py @@ -32,703 +32,703 @@ class draytools: - """DrayTek Vigor password recovery, config & firmware tools""" - __version__ = "v0.41" - copyright = \ - "draytools Copyright (C) 2011 AMMOnium " - - CFG_RAW = 0 - CFG_LZO = 1 - CFG_ENC = 2 - - verbose = False - modelprint = True - force_smart_guess = True - - class fs: - """Draytek filesystem utilities""" - def __init__(self, data, test=False, echo=False): - self.cdata = data - self.test = test - self.echo = echo - - def get_fname(self,i): - """Return full filename of the file #i from FS header""" - addr = 0x10+i*44 - return str(self.cdata[addr : addr+0x20].strip('\x00')) - - def get_hash(self,i): - """Return currently unknown hash for the file #i from FS header""" - addr = 0x10+i*44 + 0x20 - return unpack(" 1: - if not os.path.exists(ppp) and not self.test: - os.makedirs(ppp) - nfname = os.sep.join(pp) - # size of uncompressed file - rawfs = -1 - if not self.test: - ff = file(nfname,'wb') - # perform extraction, some file types are not compressed - if fs>0: - if pp[-1].split('.')[-1].lower() \ - in ['gif','jpg','cgi','cab','txt','jar']: - rawfdata = fdata - else: - try: - rawfdata = pydelzo.decompress('\xF0' \ - + pack(">L",fs*64)+fdata) - except LZO_ERROR as lze: - print '[ERR]:\tFile "'+ fname \ - + '" is damaged or uncompressed [' \ - + str(lze) \ - + '], RAW DATA WRITTEN' - rawfdata = fdata - else: - rawfdata = '' - rawfs = len(rawfdata) - if not self.test: - ff.write(rawfdata) - ff.close() - # print some debug info for each file - if self.echo: - print '%08X "' % ds + fname + '" %08X' % fs \ - + ' %08X' % rawfs - return (fs, rawfs) - - def save_all(self, path): - """Extract all files from current FS""" - self.path = path - numfiles = unpack("> 2 - if len(data) < 4: - return 0xFFFFFFFF - if len(data) % 4: - data += '\x00' * (4 - len(data) % 4) - - pos = 0 - v0 = 0 - a0 = 0 - a2 = 0 - - while a1 > 0: - v0 = unpack(">L", data[pos+a0:pos+a0+4])[0] - a0 += 4 - a2 += v0 - a1 -= 1 - - v0 = unpack(">L",data[pos+a0:pos+a0+4])[0] - a2 = ~a2 - v0 ^= a2 - return v0 & 0xFFFFFFFF - - @staticmethod - def get_modelid(data): - """Extract a model ID from config file header""" - modelid = data[0x0C:0x0E] - return modelid - - @staticmethod - def decompress_cfg(data): - """Decompress a config file""" - modelstr = "V" + format(unpack(">H", - draytools.get_modelid(data))[0],"04X") - if draytools.verbose and draytools.modelprint: - print 'Model is :\t' + modelstr - else: - draytools.modelprint = True - rawcfgsize = 0x00100000 - lzocfgsize = unpack(">L", data[0x24:0x28])[0] - raw = data[:0x2D] + '\x00' + data[0x2E:0x100] \ - + pydelzo.decompress('\xF0' + pack(">L",rawcfgsize) \ - + data[0x100:0x100+lzocfgsize]) - return raw - - @staticmethod - def make_key(modelstr): - """Construct a key out of a model string (like 'V2710')""" - sum = 0 - for c in modelstr: - sum += ord(c) - return (0xFF & sum) - - @staticmethod - def enc(c, key): - c ^= key - c -= key - c = 0xFF & (c >> 5 | c << 3) - return c - - @staticmethod - def dec(c, key): - c = (c << 5 | c >> 3) - c += key - c ^= key - c &= 0xFF - return c - - @staticmethod - def decrypt(data, key): - """Decrypt a block of data using given key""" - rdata = '' - for i in xrange(len(data)): - rdata += chr(draytools.dec(ord(data[i]), key)) - return rdata -# return ''.join(map(lambda od:chr(draytools.dec(ord(od),key)),data)) - - @staticmethod - def brute_cfg(data): - """Check all possible keys until data looks like decrypted""" - rdata = None - key = 0 - for i in xrange(256): - rdata = draytools.decrypt(data, i) - if draytools.smart_guess(rdata) == draytools.CFG_LZO: - key = i - break - if draytools.verbose: - print 'Found key:\t[0x%02X]' % key - return rdata - - @staticmethod - def decrypt_cfg(data): - """Decrypt config, bruteforce if default key fails""" - modelstr = "V" + format(unpack(">H", - draytools.get_modelid(data))[0],"04X") - if draytools.verbose: - print 'Model is :\t' + modelstr - draytools.modelprint = False - ckey = draytools.make_key(modelstr) - rdata = draytools.decrypt(data[0x100:], ckey) - # if the decrypted data does not look good, bruteforce - if draytools.smart_guess(rdata) != draytools.CFG_LZO: - rdata = draytools.brute_cfg(data[0x100:]) - elif draytools.verbose: - print 'Used key :\t[0x%02X]' % ckey - return data[:0x2D] + '\x01' + data[0x2E:0x100] + rdata - - @staticmethod - def get_credentials(data): - """Extract admin credentials from config""" - login = data[0x100+0x28:0x100+0x40].split('\x00')[0] - password = data[0x100+0x40:0x100+0x58].split('\x00')[0] - return [login, password] - - @staticmethod - def guess(data): - """Return CFG type - raw(0), compressed(1), encrypted(2)""" - return ord(data[0x2D]) - - @staticmethod - def smart_guess(data): - """Guess is the cfg block compressed or not""" - # Uncompressed block is large and has low entropy - if draytools.entropy(data) < 1.0 or len(data) > 0x10000: - return draytools.CFG_RAW - # Compressed block still has pieces of cleartext at the beginning - if "Vigor" in data and ("Series" in data or "draytek" in data): - return draytools.CFG_LZO - return draytools.CFG_ENC - - @staticmethod - def de_cfg(data): - """Get raw config data from raw /compressed/encrypted & comressed""" - if draytools.force_smart_guess: - g = draytools.smart_guess(data) - else: - g = draytools.guess(data) - if g == draytools.CFG_RAW: - if draytools.verbose: - print 'File is :\tnot compressed, not encrypted' - return g, data - elif g == draytools.CFG_LZO: - if draytools.verbose: - print 'File is :\tcompressed, not encrypted' - return g, draytools.decompress_cfg(data) - elif g == draytools.CFG_ENC: - if draytools.verbose: - print 'File is :\tcompressed, encrypted' - return g, draytools.decompress_cfg(draytools.decrypt_cfg(data)) - - @staticmethod - def decompress_firmware(data): - """Decompress firmware""" - flen = len(data) - sigstart = data.find('\xA5\xA5\xA5\x5A\xA5\x5A') - # Try an alternative signature - if sigstart <= 0: - sigstart = data.find('\x5A\x5A\xA5\x5A\xA5\x5A') - # Compressed FW block found, now decompress - if sigstart > 0: - if draytools.verbose: - print 'Signature found at [0x%08X]' % sigstart - lzosizestart = sigstart + 6 - lzostart = lzosizestart + 4 - lzosize = unpack('>L', data[lzosizestart:lzostart])[0] - return data[0x100:sigstart+2] \ - + pydelzo.decompress('\xF0' + pack(">L",0x1000000) \ - + data[lzostart:lzostart+lzosize]) - else: - print '[ERR]:\tCompressed FW signature not found!' - raise Exception('Compressed FW signature not found') - return '' - - @staticmethod - def decompress_fs(data, path, test = False): - """Decompress filesystem""" - lzofsdatalen = unpack('>L', data[4:8])[0] - if draytools.verbose: - print 'Compressed FS length: %d [0x%08X]' % (lzofsdatalen, - lzofsdatalen) - # stupid assumption of raw FS length. Seems OK for now - fsdatalen = 0x800000 - fs_raw = pydelzo.decompress('\xF0' + pack(">L", fsdatalen) \ - + data[0x08:0x08 + lzofsdatalen]) - cfs = draytools.fs(fs_raw, test, draytools.verbose) - return (lzofsdatalen, cfs.save_all(path)) - - @staticmethod - def decompress_fs_only(data, path, test = False): - """Decompress filesystem""" - fsstart = unpack('>L', data[:4])[0] - if draytools.verbose: - print 'FS block start at: %d [0x%08X]' % (fsstart, fsstart) - return draytools.decompress_fs(data[fsstart:], path, test) - - @staticmethod - def entropy(data): - """Calculate Shannon entropy (in bits per byte)""" - flist = defaultdict(int) - dlen = len(data) - data = map(ord, data) - # count occurencies - for byte in data: - flist[byte] += 1 - ent = 0.0 - # convert count of occurencies into frequency - for freq in flist.values(): - if freq > 0: - ffreq = float(freq)/dlen - # actual entropy calcualtion - ent -= ffreq * math.log(ffreq, 2) - return ent - - @staticmethod - def spkeygen(mac): - """Generate a master key like 'AbCdEfGh' from MAC address""" - # stupid translation from MIPS assembly, but works - atu = 'WAHOBXEZCLPDYTFQMJRVINSUGK' - atl = 'kgusnivrjmqftydplczexbohaw' - res = ['\x00'] * 8 - st = [0] * 8 - # compute 31*(31*(31*(31*(31*m0+m1)+m2)+m3)+m4)+m5, sign-extend mac bytes - a3 = 0 - for i in mac: - v1 = a3 << 5 - v1 &= 0xFFFFFFFF - a0 = ord(i) - if a0 >= 0x80: - a0 |= 0xFFFFFF00 - v1 -= a3 - v1 &= 0xFFFFFFFF - a3 = v1 + a0 - a3 &= 0xFFFFFFFF - # Divide by 13 :) Old assembly trick, I leave it here - # 0x4EC4EC4F is a multiplicative inverse for 13 - ck = 0x4EC4EC4F * a3 - v1 = (ck & 0xFFFFFFFF00000000) >> 32 - # shift by two - v1 >>= 3 - v0 = v1 << 1 - v0 &= 0xFFFFFFFF - # trick ends here and now v0 = a3 / 13 - v0 += v1 - v0 <<= 2 - v0 &= 0xFFFFFFFF - v0 += v1 - v0 <<= 1 - v0 -= a3 - # v0 &= 0xFFFFFFFF - st[0] = a3 - res[0] = atu[abs(v0)] - - for i in xrange(1,8): - v1 = st[i-1] - a0 = ord(res[0]) - t0 = ord(res[1]) - v0 = (v1 << 5) & 0xFFFFFFFF - a1 = ord(res[2]) - v0 -= v1 - v0 += a0 - a2 = ord(res[3]) - a3 = ord(res[4]) - v0 += t0 - v0 += a1 - t0 = ord(res[5]) - v1 = ord(res[6]) - v0 += a2 - a0 = ord(res[7]) - v0 += a3 - v0 += t0 - v0 += v1 - # v0 here is a 32-bit sum of currently computed key chars - v0 &= 0xFFFFFFFF - a3 = v0 + a0 - # Again divide by 13 - i1 = a3 * 0x4EC4EC4F - a0 = i & 1 - st[i] = a3 - v1 = (i1 & 0xFFFFFFFF00000000) >> 32 - v1 >>= 3 - v0 = v1 << 1 - # here v0 = a3 / 13 - v0 += v1 - v0 <<= 2 - v0 += v1 - v0 <<= 1 - v0 = a3 - v0 - a1 += v0 - v0 &= 0xFFFFFFFF - if a0 == 0: - v1 = atu[abs(v0)] - else: - v1 = atl[abs(v0)] - res[i] = v1 - v0 = 0 - return ''.join(res) - + """DrayTek Vigor password recovery, config & firmware tools""" + __version__ = "v0.41" + copyright = \ + "draytools Copyright (C) 2011 AMMOnium " + + CFG_RAW = 0 + CFG_LZO = 1 + CFG_ENC = 2 + + verbose = False + modelprint = True + force_smart_guess = True + + class fs: + """Draytek filesystem utilities""" + def __init__(self, data, test=False, echo=False): + self.cdata = data + self.test = test + self.echo = echo + + def get_fname(self,i): + """Return full filename of the file #i from FS header""" + addr = 0x10+i*44 + return str(self.cdata[addr : addr+0x20].strip('\x00')) + + def get_hash(self,i): + """Return currently unknown hash for the file #i from FS header""" + addr = 0x10+i*44 + 0x20 + return unpack(" 1: + if not os.path.exists(ppp) and not self.test: + os.makedirs(ppp) + nfname = os.sep.join(pp) + # size of uncompressed file + rawfs = -1 + if not self.test: + ff = file(nfname,'wb') + # perform extraction, some file types are not compressed + if fs>0: + if pp[-1].split('.')[-1].lower() \ + in ['gif','jpg','cgi','cab','txt','jar']: + rawfdata = fdata + else: + try: + rawfdata = pydelzo.decompress('\xF0' \ + + pack(">L",fs*64)+fdata) + except LZO_ERROR as lze: + print '[ERR]:\tFile "'+ fname \ + + '" is damaged or uncompressed [' \ + + str(lze) \ + + '], RAW DATA WRITTEN' + rawfdata = fdata + else: + rawfdata = '' + rawfs = len(rawfdata) + if not self.test: + ff.write(rawfdata) + ff.close() + # print some debug info for each file + if self.echo: + print '%08X "' % ds + fname + '" %08X' % fs \ + + ' %08X' % rawfs + return (fs, rawfs) + + def save_all(self, path): + """Extract all files from current FS""" + self.path = path + numfiles = unpack("> 2 + if len(data) < 4: + return 0xFFFFFFFF + if len(data) % 4: + data += '\x00' * (4 - len(data) % 4) + + pos = 0 + v0 = 0 + a0 = 0 + a2 = 0 + + while a1 > 0: + v0 = unpack(">L", data[pos+a0:pos+a0+4])[0] + a0 += 4 + a2 += v0 + a1 -= 1 + + v0 = unpack(">L",data[pos+a0:pos+a0+4])[0] + a2 = ~a2 + v0 ^= a2 + return v0 & 0xFFFFFFFF + + @staticmethod + def get_modelid(data): + """Extract a model ID from config file header""" + modelid = data[0x0C:0x0E] + return modelid + + @staticmethod + def decompress_cfg(data): + """Decompress a config file""" + modelstr = "V" + format(unpack(">H", + draytools.get_modelid(data))[0],"04X") + if draytools.verbose and draytools.modelprint: + print 'Model is :\t' + modelstr + else: + draytools.modelprint = True + rawcfgsize = 0x00100000 + lzocfgsize = unpack(">L", data[0x24:0x28])[0] + raw = data[:0x2D] + '\x00' + data[0x2E:0x100] \ + + pydelzo.decompress('\xF0' + pack(">L",rawcfgsize) \ + + data[0x100:0x100+lzocfgsize]) + return raw + + @staticmethod + def make_key(modelstr): + """Construct a key out of a model string (like 'V2710')""" + sum = 0 + for c in modelstr: + sum += ord(c) + return (0xFF & sum) + + @staticmethod + def enc(c, key): + c ^= key + c -= key + c = 0xFF & (c >> 5 | c << 3) + return c + + @staticmethod + def dec(c, key): + c = (c << 5 | c >> 3) + c += key + c ^= key + c &= 0xFF + return c + + @staticmethod + def decrypt(data, key): + """Decrypt a block of data using given key""" + rdata = '' + for i in xrange(len(data)): + rdata += chr(draytools.dec(ord(data[i]), key)) + return rdata +# return ''.join(map(lambda od:chr(draytools.dec(ord(od),key)),data)) + + @staticmethod + def brute_cfg(data): + """Check all possible keys until data looks like decrypted""" + rdata = None + key = 0 + for i in xrange(256): + rdata = draytools.decrypt(data, i) + if draytools.smart_guess(rdata) == draytools.CFG_LZO: + key = i + break + if draytools.verbose: + print 'Found key:\t[0x%02X]' % key + return rdata + + @staticmethod + def decrypt_cfg(data): + """Decrypt config, bruteforce if default key fails""" + modelstr = "V" + format(unpack(">H", + draytools.get_modelid(data))[0],"04X") + if draytools.verbose: + print 'Model is :\t' + modelstr + draytools.modelprint = False + ckey = draytools.make_key(modelstr) + rdata = draytools.decrypt(data[0x100:], ckey) + # if the decrypted data does not look good, bruteforce + if draytools.smart_guess(rdata) != draytools.CFG_LZO: + rdata = draytools.brute_cfg(data[0x100:]) + elif draytools.verbose: + print 'Used key :\t[0x%02X]' % ckey + return data[:0x2D] + '\x01' + data[0x2E:0x100] + rdata + + @staticmethod + def get_credentials(data): + """Extract admin credentials from config""" + login = data[0x100+0x28:0x100+0x40].split('\x00')[0] + password = data[0x100+0x40:0x100+0x58].split('\x00')[0] + return [login, password] + + @staticmethod + def guess(data): + """Return CFG type - raw(0), compressed(1), encrypted(2)""" + return ord(data[0x2D]) + + @staticmethod + def smart_guess(data): + """Guess is the cfg block compressed or not""" + # Uncompressed block is large and has low entropy + if draytools.entropy(data) < 1.0 or len(data) > 0x10000: + return draytools.CFG_RAW + # Compressed block still has pieces of cleartext at the beginning + if "Vigor" in data and ("Series" in data or "draytek" in data): + return draytools.CFG_LZO + return draytools.CFG_ENC + + @staticmethod + def de_cfg(data): + """Get raw config data from raw /compressed/encrypted & comressed""" + if draytools.force_smart_guess: + g = draytools.smart_guess(data) + else: + g = draytools.guess(data) + if g == draytools.CFG_RAW: + if draytools.verbose: + print 'File is :\tnot compressed, not encrypted' + return g, data + elif g == draytools.CFG_LZO: + if draytools.verbose: + print 'File is :\tcompressed, not encrypted' + return g, draytools.decompress_cfg(data) + elif g == draytools.CFG_ENC: + if draytools.verbose: + print 'File is :\tcompressed, encrypted' + return g, draytools.decompress_cfg(draytools.decrypt_cfg(data)) + + @staticmethod + def decompress_firmware(data): + """Decompress firmware""" + flen = len(data) + sigstart = data.find('\xA5\xA5\xA5\x5A\xA5\x5A') + # Try an alternative signature + if sigstart <= 0: + sigstart = data.find('\x5A\x5A\xA5\x5A\xA5\x5A') + # Compressed FW block found, now decompress + if sigstart > 0: + if draytools.verbose: + print 'Signature found at [0x%08X]' % sigstart + lzosizestart = sigstart + 6 + lzostart = lzosizestart + 4 + lzosize = unpack('>L', data[lzosizestart:lzostart])[0] + return data[0x100:sigstart+2] \ + + pydelzo.decompress('\xF0' + pack(">L",0x1000000) \ + + data[lzostart:lzostart+lzosize]) + else: + print '[ERR]:\tCompressed FW signature not found!' + raise Exception('Compressed FW signature not found') + return '' + + @staticmethod + def decompress_fs(data, path, test = False): + """Decompress filesystem""" + lzofsdatalen = unpack('>L', data[4:8])[0] + if draytools.verbose: + print 'Compressed FS length: %d [0x%08X]' % (lzofsdatalen, + lzofsdatalen) + # stupid assumption of raw FS length. Seems OK for now + fsdatalen = 0x800000 + fs_raw = pydelzo.decompress('\xF0' + pack(">L", fsdatalen) \ + + data[0x08:0x08 + lzofsdatalen]) + cfs = draytools.fs(fs_raw, test, draytools.verbose) + return (lzofsdatalen, cfs.save_all(path)) + + @staticmethod + def decompress_fs_only(data, path, test = False): + """Decompress filesystem""" + fsstart = unpack('>L', data[:4])[0] + if draytools.verbose: + print 'FS block start at: %d [0x%08X]' % (fsstart, fsstart) + return draytools.decompress_fs(data[fsstart:], path, test) + + @staticmethod + def entropy(data): + """Calculate Shannon entropy (in bits per byte)""" + flist = defaultdict(int) + dlen = len(data) + data = map(ord, data) + # count occurencies + for byte in data: + flist[byte] += 1 + ent = 0.0 + # convert count of occurencies into frequency + for freq in flist.values(): + if freq > 0: + ffreq = float(freq)/dlen + # actual entropy calcualtion + ent -= ffreq * math.log(ffreq, 2) + return ent + + @staticmethod + def spkeygen(mac): + """Generate a master key like 'AbCdEfGh' from MAC address""" + # stupid translation from MIPS assembly, but works + atu = 'WAHOBXEZCLPDYTFQMJRVINSUGK' + atl = 'kgusnivrjmqftydplczexbohaw' + res = ['\x00'] * 8 + st = [0] * 8 + # compute 31*(31*(31*(31*(31*m0+m1)+m2)+m3)+m4)+m5, sign-extend mac bytes + a3 = 0 + for i in mac: + v1 = a3 << 5 + v1 &= 0xFFFFFFFF + a0 = ord(i) + if a0 >= 0x80: + a0 |= 0xFFFFFF00 + v1 -= a3 + v1 &= 0xFFFFFFFF + a3 = v1 + a0 + a3 &= 0xFFFFFFFF + # Divide by 13 :) Old assembly trick, I leave it here + # 0x4EC4EC4F is a multiplicative inverse for 13 + ck = 0x4EC4EC4F * a3 + v1 = (ck & 0xFFFFFFFF00000000) >> 32 + # shift by two + v1 >>= 3 + v0 = v1 << 1 + v0 &= 0xFFFFFFFF + # trick ends here and now v0 = a3 / 13 + v0 += v1 + v0 <<= 2 + v0 &= 0xFFFFFFFF + v0 += v1 + v0 <<= 1 + v0 -= a3 + # v0 &= 0xFFFFFFFF + st[0] = a3 + res[0] = atu[abs(v0)] + + for i in xrange(1,8): + v1 = st[i-1] + a0 = ord(res[0]) + t0 = ord(res[1]) + v0 = (v1 << 5) & 0xFFFFFFFF + a1 = ord(res[2]) + v0 -= v1 + v0 += a0 + a2 = ord(res[3]) + a3 = ord(res[4]) + v0 += t0 + v0 += a1 + t0 = ord(res[5]) + v1 = ord(res[6]) + v0 += a2 + a0 = ord(res[7]) + v0 += a3 + v0 += t0 + v0 += v1 + # v0 here is a 32-bit sum of currently computed key chars + v0 &= 0xFFFFFFFF + a3 = v0 + a0 + # Again divide by 13 + i1 = a3 * 0x4EC4EC4F + a0 = i & 1 + st[i] = a3 + v1 = (i1 & 0xFFFFFFFF00000000) >> 32 + v1 >>= 3 + v0 = v1 << 1 + # here v0 = a3 / 13 + v0 += v1 + v0 <<= 2 + v0 += v1 + v0 <<= 1 + v0 = a3 - v0 + a1 += v0 + v0 &= 0xFFFFFFFF + if a0 == 0: + v1 = atu[abs(v0)] + else: + v1 = atl[abs(v0)] + res[i] = v1 + v0 = 0 + return ''.join(res) + if __name__ == '__main__': - import optparse + import optparse - usage = \ + usage = \ """usage: %prog [options] file DrayTek Vigor V2xxx/V3xxx password recovery, config & firmware tools""" # initialize cmdline option parser - optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog - parser = optparse.OptionParser(usage=usage, \ - version="%prog "+draytools.__version__, \ - epilog= + optparse.OptionParser.format_epilog = lambda self, formatter: self.epilog + parser = optparse.OptionParser(usage=usage, \ + version="%prog "+draytools.__version__, \ + epilog= """ Examples: To print login&password from the config file: # python draytools.py -p config.cfg - Login and password will be displayed + Login and password will be displayed To decrypt & decompress the config file: # python draytools.py -c config.cfg - Raw config file "config.cfg.out" will be produced + Raw config file "config.cfg.out" will be produced To extract firmware and filesystem contents # python draytools.py -F firmware.all - Uncompressed firmware will be written to file "firmware.all.out" - Filesystem will be extracted to "fs_out" folder. + Uncompressed firmware will be written to file "firmware.all.out" + Filesystem will be extracted to "fs_out" folder. """) - cfggroup = optparse.OptionGroup(parser, "Config file (*.cfg) commands", - "To be used on config files only") - fwgroup = optparse.OptionGroup(parser, \ - "Firmware file (*.all, *.rst, *.bin) commands", - "To be used on firmware files only") - - mgroup = optparse.OptionGroup(parser, "Miscellaneous commands", - "Some other useful stuff") - - parser.add_option('-o', '--output', - action="store", dest="outfile", - help="Output file name, %INPUTFILE%.out if omitted", default="") - - parser.add_option('-t', '--test', - action="store_true", dest="test", help= + cfggroup = optparse.OptionGroup(parser, "Config file (*.cfg) commands", + "To be used on config files only") + fwgroup = optparse.OptionGroup(parser, \ + "Firmware file (*.all, *.rst, *.bin) commands", + "To be used on firmware files only") + + mgroup = optparse.OptionGroup(parser, "Miscellaneous commands", + "Some other useful stuff") + + parser.add_option('-o', '--output', + action="store", dest="outfile", + help="Output file name, %INPUTFILE%.out if omitted", default="") + + parser.add_option('-t', '--test', + action="store_true", dest="test", help= """Test mode, do not write anything to disk, only try to parse files""", - default=False) + default=False) - parser.add_option('-v', '--verbose', - action="store_true", dest="verbose", - help="Verbose output", default=False) + parser.add_option('-v', '--verbose', + action="store_true", dest="verbose", + help="Verbose output", default=False) # config file option group for cmdline option parser - cfggroup.add_option('-c', '--config', - action="store_true", dest="config", - help="Decrypt and decompress config", default=False) + cfggroup.add_option('-c', '--config', + action="store_true", dest="config", + help="Decrypt and decompress config", default=False) - cfggroup.add_option('-d', '--decompress', - action="store_true", dest="decompress", - help="Decompress an unenrypted config file", default=False) + cfggroup.add_option('-d', '--decompress', + action="store_true", dest="decompress", + help="Decompress an unenrypted config file", default=False) - cfggroup.add_option('-y', '--decrypt', - action="store_true", dest="decrypt", - help="Decrypt config file", default=False) + cfggroup.add_option('-y', '--decrypt', + action="store_true", dest="decrypt", + help="Decrypt config file", default=False) - cfggroup.add_option('-p', '--password', - action="store_true", dest="password", - help="Retrieve admin login and password from config file", - default=False) + cfggroup.add_option('-p', '--password', + action="store_true", dest="password", + help="Retrieve admin login and password from config file", + default=False) # firmware/fs option group for cmdline option parser - fwgroup.add_option('-f', '--firmware', - action="store_true", dest="firmware", - help="Decompress firmware", default=False) + fwgroup.add_option('-f', '--firmware', + action="store_true", dest="firmware", + help="Decompress firmware", default=False) - fwgroup.add_option('-F', '--firmware-all', - action="store_true", dest="fw_all", - help="Decompress firmware and extract filesystem", default=False) + fwgroup.add_option('-F', '--firmware-all', + action="store_true", dest="fw_all", + help="Decompress firmware and extract filesystem", default=False) - fwgroup.add_option('-s', '--fs', - action="store_true", dest="fs", - help="Extract filesystem", default=False) + fwgroup.add_option('-s', '--fs', + action="store_true", dest="fs", + help="Extract filesystem", default=False) - fwgroup.add_option('-O', '--out-dir', - action="store", dest="outdir", - help= - "Output directory for filesystem contents, \"fs_out\" by default", - default="fs_out") + fwgroup.add_option('-O', '--out-dir', + action="store", dest="outdir", + help= + "Output directory for filesystem contents, \"fs_out\" by default", + default="fs_out") # miscellaneous option group for cmdline option parser - mgroup.add_option('-m', '--master-key', - action="store", dest="mac", - help="Generate FTP master key for given router MAC address. " - "To login to FTP enter \"admin\" as username and generated " - "master key as password", - default=None) + mgroup.add_option('-m', '--master-key', + action="store", dest="mac", + help="Generate FTP master key for given router MAC address. " + "To login to FTP enter \"admin\" as username and generated " + "master key as password", + default=None) - parser.add_option_group(cfggroup) - parser.add_option_group(fwgroup) - parser.add_option_group(mgroup) + parser.add_option_group(cfggroup) + parser.add_option_group(fwgroup) + parser.add_option_group(mgroup) - - options, args = parser.parse_args() + + options, args = parser.parse_args() - draytools.verbose = options.verbose + draytools.verbose = options.verbose # default output filename is input filename + '.out' - outfname = options.outfile is not None and options.outfile \ - or (len(args) > 0 and args[0]+'.out' or 'file.out') - outdir = options.outdir + outfname = options.outfile is not None and options.outfile \ + or (len(args) > 0 and args[0]+'.out' or 'file.out') + outdir = options.outdir - infile = None - data = None - indata = None - outdata = None + infile = None + data = None + indata = None + outdata = None - if len(args) > 1: - print '[ERR]:\tToo much arguments, only input file name expected' - print 'Run "draytools --help"' - sys.exit(1) - elif len(args) < 1 and not options.mac: - print '[ERR]:\tInput file name expected' - print 'Run "draytools --help"' - sys.exit(1) + if len(args) > 1: + print '[ERR]:\tToo much arguments, only input file name expected' + print 'Run "draytools --help"' + sys.exit(1) + elif len(args) < 1 and not options.mac: + print '[ERR]:\tInput file name expected' + print 'Run "draytools --help"' + sys.exit(1) # open input file - if not options.mac: - try: - infile = file(args[0],'rb') - indata = infile.read() - # if default FS extraction path is used, put it near input file - if outdir == 'fs_out': - outdir = os.path.join(os.path.dirname( - os.path.abspath(args[0])),'fs_out') - - except IOError: - print '[ERR]:\tInput file open failed' - sys.exit(2) + if not options.mac: + try: + infile = file(args[0],'rb') + indata = infile.read() + # if default FS extraction path is used, put it near input file + if outdir == 'fs_out': + outdir = os.path.join(os.path.dirname( + os.path.abspath(args[0])),'fs_out') + + except IOError: + print '[ERR]:\tInput file open failed' + sys.exit(2) # Command: get raw config file - if options.config: - g = -1 - try: - g, outdata = draytools.de_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - if g == draytools.CFG_RAW: - print '[ERR]:\tNothing to do. '\ - 'Config file is already not encrypted and not compressed.' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - outfile.close() - else: - print 'CFG decryption/decompression test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - + if options.config: + g = -1 + try: + g, outdata = draytools.de_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + if g == draytools.CFG_RAW: + print '[ERR]:\tNothing to do. '\ + 'Config file is already not encrypted and not compressed.' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + outfile.close() + else: + print 'CFG decryption/decompression test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + # Command: decrypt config file - elif options.decrypt: - try: - outdata = draytools.decrypt_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - cksum = draytools.v2k_checksum(str(outdata)) - if options.verbose: - print 'V2kCheckSum = %08X ' % \ - cksum + ((cksum == 0) and 'OK' or 'FAIL') - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'CFG decryption test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) + elif options.decrypt: + try: + outdata = draytools.decrypt_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + cksum = draytools.v2k_checksum(str(outdata)) + if options.verbose: + print 'V2kCheckSum = %08X ' % \ + cksum + ((cksum == 0) and 'OK' or 'FAIL') + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'CFG decryption test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) # Command: decompress config file - elif options.decompress: - try: - outdata = draytools.decompress_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - cksum = draytools.v2k_checksum(str(indata)) - if options.verbose: - print 'V2kCheckSum = %08X ' % \ - cksum + ((cksum == 0) and 'OK' or 'FAIL') - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'CFG decompression test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) + elif options.decompress: + try: + outdata = draytools.decompress_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + cksum = draytools.v2k_checksum(str(indata)) + if options.verbose: + print 'V2kCheckSum = %08X ' % \ + cksum + ((cksum == 0) and 'OK' or 'FAIL') + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'CFG decompression test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) # Command: extract admin credentials from config file - if options.password and \ - not (True in [options.firmware, options.fw_all, options.fs]): - g = -1 - try: - g, outdata = draytools.de_cfg(indata) - except LZO_ERROR: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - creds = draytools.get_credentials(outdata) - print "Login :\t" + (creds[0] == "" and "admin" or creds[0]) - print "Password :\t" + (creds[1] == "" and "admin" or creds[1]) - sys.exit(0) + if options.password and \ + not (True in [options.firmware, options.fw_all, options.fs]): + g = -1 + try: + g, outdata = draytools.de_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + creds = draytools.get_credentials(outdata) + print "Login :\t" + (creds[0] == "" and "admin" or creds[0]) + print "Password :\t" + (creds[1] == "" and "admin" or creds[1]) + sys.exit(0) # Command: extract firmware - if options.firmware: - try: - outdata = draytools.decompress_firmware(indata) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'FW extraction test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) + if options.firmware: + try: + outdata = draytools.decompress_firmware(indata) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'FW extraction test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) # Command: extract firmware and filesystem - elif options.fw_all: - try: - outdata = draytools.decompress_firmware(indata) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - ol = len(outdata) - if not options.test: - outfile = file(outfname, 'wb') - outfile.write(outdata) - outfile.close() - print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) - else: - print 'FW extraction test OK, ' \ - 'output size %d [0x%08X] bytes' % (ol,ol) - - try: - fss, nf = draytools.decompress_fs_only(indata, outdir, - options.test) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - if not options.test: - print 'FS extracted to [' + outdir + '], %d files extracted' % nf - else: - print 'FS extraction test OK, %d files extracted' % nf - + elif options.fw_all: + try: + outdata = draytools.decompress_firmware(indata) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + ol = len(outdata) + if not options.test: + outfile = file(outfname, 'wb') + outfile.write(outdata) + outfile.close() + print outfname + ' written, %d [0x%08X] bytes' % (ol,ol) + else: + print 'FW extraction test OK, ' \ + 'output size %d [0x%08X] bytes' % (ol,ol) + + try: + fss, nf = draytools.decompress_fs_only(indata, outdir, + options.test) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + if not options.test: + print 'FS extracted to [' + outdir + '], %d files extracted' % nf + else: + print 'FS extraction test OK, %d files extracted' % nf + # Command: extract filesystem - elif options.fs: - try: - fss, nf = draytools.decompress_fs_only(indata, outdir, - options.test) - except: - print '[ERR]:\tInput file corrupted or not supported' - sys.exit(3) - - if not options.test: - print 'FS extracted to [' + outdir + '], %d files extracted' % nf - else: - print 'FS extraction test OK, %d files extracted' % nf + elif options.fs: + try: + fss, nf = draytools.decompress_fs_only(indata, outdir, + options.test) + except: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + + if not options.test: + print 'FS extracted to [' + outdir + '], %d files extracted' % nf + else: + print 'FS extraction test OK, %d files extracted' % nf # Command: generate master password - elif options.mac is not None: - # validate mac address (hex, delimited by colons, dashes or nothing) - xr = re.compile(\ - r'^([a-fA-F0-9]{2}([:-]?)[a-fA-F0-9]{2}(\2[a-fA-F0-9]{2}){4})$') - rr = xr.match(options.mac) - if rr: - xmac = unhexlify(re.sub('[:\-]', '', options.mac)) - print 'Username :\t' + "admin" - print 'Master key:\t' + draytools.spkeygen(xmac) - else: - print '[ERR]:\tPlease enter a valid MAC address, e.g '\ - '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' + elif options.mac is not None: + # validate mac address (hex, delimited by colons, dashes or nothing) + xr = re.compile(\ + r'^([a-fA-F0-9]{2}([:-]?)[a-fA-F0-9]{2}(\2[a-fA-F0-9]{2}){4})$') + rr = xr.match(options.mac) + if rr: + xmac = unhexlify(re.sub('[:\-]', '', options.mac)) + print 'Username :\t' + "admin" + print 'Master key:\t' + draytools.spkeygen(xmac) + else: + print '[ERR]:\tPlease enter a valid MAC address, e.g '\ + '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' # EOF From 410d84006c77966211d8ad2b4c576acbebf9fb7a Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Wed, 28 Jan 2015 08:32:44 +0100 Subject: [PATCH 5/8] add -e option (config variables extraction) - now it's tested on 2820Vn --- draytools.py | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) diff --git a/draytools.py b/draytools.py index 765abbd..bcb1862 100644 --- a/draytools.py +++ b/draytools.py @@ -249,6 +249,263 @@ def get_credentials(data): password = data[0x100+0x40:0x100+0x58].split('\x00')[0] return [login, password] + @staticmethod + def get_params(data): + """Extract parameters from config""" + desc = [] + packstring = ">296s"# ??? do 296 + desc.append("???") + + packstring += "24s"# login do 320 + desc.append("login") + + packstring += "24s"# password 344 + desc.append("System Maintenance >> Administrator Password Setup >> Administrator Password") + + packstring += "24s"# Router Name 368 + desc.append("System Maintenance >> Management >> Management Setup >> Roouter Name") + + packstring += "160s"# ??? 598 + desc.append("???") + + packstring += 'i'# System Maintenance >> Management >> Management Setup >> Management Port Setup >> Telnet Port + desc.append("System Maintenance >> Management >> Management Setup >> Management Port Setup >> Telnet Port") + + packstring += 'i'# System Maintenance >> Management >> Management Setup >> Management Port Setup >> HTTP Port + desc.append("System Maintenance >> Management >> Management Setup >> Management Port Setup >> HTTP Port") + + packstring += 'i'# System Maintenance >> Management >> Management Setup >> Management Port Setup >> FTP Port + desc.append("System Maintenance >> Management >> Management Setup >> Management Port Setup >> FTP Port") + + packstring += 'i'# System Maintenance >> Management >> Management Setup >> Management Port Setup >> HTTPS Port + desc.append("System Maintenance >> Management >> Management Setup >> Management Port Setup >> HTTPS Port") + + packstring += "32s" + desc.append("???") + + for i in range(20): + packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Public Port + desc.append("NAT >> Port Redirection >> Index No. %02d >> Public Port"% (i+1)) + packstring += "2s" # ???? + desc.append("???") + packstring += "16s"# NAT >> Port Redirection >> Index No. %02d >> Private IP + desc.append("NAT >> Port Redirection >> Index No. %02d >> Private IP"% (i+1)) + packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Private Port + desc.append("NAT >> Port Redirection >> Index No. %02d >> Private Port"% (i+1)) + packstring += "24s"# NAT >> Port Redirection >> Index No. %02d >> Service Name + desc.append("NAT >> Port Redirection >> Index No. %02d >> Service Name"% (i+1)) + packstring += "4s" # ???? + desc.append("???") + + packstring += "2344s"# ??? 392 + desc.append("???") + + packstring += "24s"# System Maintenance >> Management >> Management Setup >> SNMP Setup >> Trap Community 3944 + desc.append("System Maintenance >> Management >> Management Setup >> SNMP Setup >> Trap Community") + + packstring += "24s"# System Maintenance >> Management >> Management Setup >> SNMP Setup >> Get Community 3968 + desc.append("System Maintenance >> Management >> Management Setup >> SNMP Setup >> Get Community") + + packstring += "24s"# System Maintenance >> Management >> Management Setup >> SNMP Setup >> Set Community 3992 + desc.append("System Maintenance >> Management >> Management Setup >> SNMP Setup >> Set Community") + + packstring += "88s"# ??? 408 + desc.append("???") + + packstring += "32s"# System Maintenance >> Time Date >> Time Setup >> Server IP Address 4112 + desc.append("System Maintenance >> Time Date >> Time Setup >> Server IP Address") + + packstring += "11928s"# ??? 16040 + desc.append("???") + + for i in range(2): + packstring += "24s"# Interface WAN %d Display Name + desc.append("Interface WAN %d Display Name"% (i+1)) + packstring += "600s"# ??? + desc.append("???") + + packstring += "19716s"# ??? 37004 + desc.append("???") + + for i in range(12): + packstring += "24s"# Filter Set %02d Comment + desc.append("Filter Set %02d Comment"% (i+1)) + packstring += "32s"# ??? + desc.append("???") + for j in range(1,8): + packstring += "24s"# Filter Set %02d Rule %02d Comment + desc.append("Filter Set %02d Rule %02d Comment"% (i+1,j)) + packstring += "120s"# ??? + desc.append("???") # 49772 + + packstring += "200s"# ??? + desc.append("???") # 49972 + + for i in range(192): + packstring += "16s"# Object Setting >> IP Object >> Profile Index : %03d >> Name + desc.append("Object Setting >> IP Object >> Profile Index : %03d >> Name"% (i+1)) + packstring += "20s"# ??? + desc.append("???") # 56884 + + + + for i in range(20): + packstring += "16s"# Object Setting >> IP Group >> Profile Index : %02d >> Name + desc.append("Object Setting >> IP Group >> Profile Index : %02d >> Name"% (i+1)) + packstring += "16s"# ??? + desc.append("???") # 57524 + + packstring += "384s"# ??? 57908 + desc.append("???") + + for i in range(96): + packstring += "16s"# "Object Setting >> Service Type Object >> Profile Index : %02d >> Name + desc.append("Object Setting >> Service Type Object >> Profile Index : %02d >> Name"% (i+1)) + packstring += "12s"# ??? + desc.append("???") # 60596 + + packstring += "23588s"# ??? 84184 + desc.append("???") + + packstring += "256s"# CSM >> URL Content Filter Profile >> Administration Message 84440 + desc.append("CSM >> URL Content Filter Profile >> Administration Message") + + packstring += "12s"# ??? 84452 + desc.append("???") + + for i in range(8): + packstring += "16s"# FIX: CSM >> URL Content Filter Profile >> Profile Index : %01d >> Profile Name 84580 + desc.append("CSM >> URL Content Filter Profile >> Profile Index : %01d >> Profile Name"% (i+1)) + packstring += "108s"# ??? + desc.append("???"); + + for i in range(8): + packstring += "8s"# [FIX] Object Setting >> File Extension Object >> Profile Index : %0d >> Profile Name + desc.append("Object Setting >> File Extension Object >> Profile Index : %0d >> Profile Name"% (i+1)) + packstring += "160s"# ??? + desc.append("???") + + packstring += "4s" + desc.append("???") + + packstring += "256s"# CSM >> Web Content Filter Profile >> Administration Message 87048 + desc.append("CSM >> Web Content Filter Profile >> Administration Message") + + packstring += "1292s"# ??? 88340 + desc.append("???") + + packstring += "256s"# Bandwidth Management >> Session Limit >> Administration Message 88596 + desc.append("Bandwidth Management >> Session Limit >> Administration Message") + + packstring += "4s"# ??? 88600 + desc.append("???") + + for i in range(32): + packstring += "12s"# LAN >> Bind IP to MAC >> IP Bind List >> Index %02d >> Comment 88984 + desc.append("LAN >> Bind IP to MAC >> IP Bind List >> Index %02d >> Commen"% (i+1)) + packstring += "14080s"# ??? 103064 + desc.append("???") + + for i in range(3): + packstring += "12s"# Bandwidth Management >> Quality of Service >> Class index #%01d >> Name + desc.append("Bandwidth Management >> Quality of Service >> Class index #%01d >> Name"% (i+1)) + packstring += "1040s"# ??? + desc.append("???") + + packstring += "4660s"# ??? 110880 + desc.append("???") + + for i in range(4): + packstring += "32s"# Wireless LAN >> General Setup >> General Setting (IEEE 802.11) >> %01d >> SSID + desc.append("Wireless LAN >> General Setup >> General Setting (IEEE 802.11) >> %01d >> SSID"% (i+1)) + packstring += "472s"# ??? + desc.append("???") # 118296 + packstring += "64s"# Wireless LAN >> Security Settings >> SSID %01d >> Pre-Shared Key (PSK) + desc.append("Wireless LAN >> Security Settings >> SSID %01d >> Pre-Shared Key (PSK)"% (i+1)) + packstring += "1286s"# ??? + desc.append("???") # 118296 + + #packstring += "59776s"# ??? 164616 + packstring += "46272s"# ??? 164616 + desc.append("???") + + for i in range(32): + packstring += "48s"# VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-In Settings >> Peer ID + desc.append("VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-In Settings >> Peer ID"% (i+1)) + + packstring += "11968s"# ??? 178072 + desc.append("???") + + for i in range(32): + packstring += "10s"# VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Common Settings >> Profile Name + desc.append("VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Common Settings >> Profile Name"% (i+1)) + packstring += "10s"# ??? + desc.append("???") + packstring += "12s"# VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-In Settings >> Username + desc.append("VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-In Settings >> Username"% (i+1)) + packstring += "60s"# ??? + desc.append("???") + packstring += "50s"# VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-Out Settings >> Username + desc.append("VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-Out Settings >> Username"% (i+1)) + packstring += "14s"# ??? + desc.append("???") + packstring += "16s"# VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-Out Settings >> Password + desc.append("VPN and Remote Access >> LAN to LAN >> Profile Index : %02d >> Dial-Out Settings >> Password"% (i+1)) + packstring += "44s"# ??? + desc.append("???") + + packstring += "81476s"# ??? + desc.append("???") + + for i in range(200): + packstring += "16s"# Object Setting >> Keyword Object >> Profile Index : %03d >> Name + desc.append("Object Setting >> Keyword Object >> Profile Index : %03d >> Name"% (i+1)) + packstring += "64s"# Object Setting >> Keyword Object >> Profile Index : %03d >> Contents + desc.append("Object Setting >> Keyword Object >> Profile Index : %03d >> Contents"% (i+1)) + packstring += "64s"# Object Setting >> Keyword Object >> Profile Index : %03d >> Contents + desc.append("???") + + for i in range(32): + packstring += "16s"# Object Setting >> Keyword Group Setup >> Profile Index : %03d >> Name + desc.append("Object Setting >> Keyword Object >> Profile Index : %03d >> Name"% (i+1)) + packstring += "48s"# ??? + desc.append("???") + + packstring += "1920s"# 299228 + desc.append("???") + + for i in range(32): + packstring += "16s"# CSM >> APP Enforcement Profile >> Profile Index : %0d >> Profile Name + desc.append("CSM >> APP Enforcement Profile >> Profile Index : %02d >> Profile Name"% (i+1)) + packstring += "160s"# ??? + desc.append("???") + + packstring += "27916s"# 332776 + desc.append("???") + + packstring += "16s"# USB General Settings Workgroup Name 332792 + desc.append("USB General Settings Workgroup Name") + + packstring += "31712s"# ??? 364504 + desc.append("???") + + packstring += "32s"# "System Maintenance >> Login Page Greeting >> Login Page Title 364536 + desc.append("System Maintenance >> Login Page Greeting >> Login Page Title") + + packstring += "384s"# ??? 364920 + desc.append("???") + + packstring += "512s"# "System Maintenance >> Login Page Greeting >> Login Page Title 365432 + desc.append("System Maintenance >> Login Page Greeting >> Login Page Title") + + variables = list(unpack(packstring, data[:365432])) + for i in range(0,len(variables)): + if type(variables[i]) == type(str()): + variables[i]=variables[i].split('\x00')[0] + dict_list = zip (desc, variables) + + return dict(dict_list) + @staticmethod def guess(data): """Return CFG type - raw(0), compressed(1), encrypted(2)""" @@ -504,6 +761,12 @@ def spkeygen(mac): help="Retrieve admin login and password from config file", default=False) + cfggroup.add_option('-e', '--extract', + action="store_true", dest="text", + help="Extract text config from config file", + default=False) + + # firmware/fs option group for cmdline option parser fwgroup.add_option('-f', '--firmware', @@ -656,6 +919,22 @@ def spkeygen(mac): print "Password :\t" + (creds[1] == "" and "admin" or creds[1]) sys.exit(0) +# Command: extract parameters from config file + if options.text and \ + not (True in [options.firmware, options.fw_all, options.fs]): + g = -1 + try: + g, outdata = draytools.de_cfg(indata) + except LZO_ERROR: + print '[ERR]:\tInput file corrupted or not supported' + sys.exit(3) + params = draytools.get_params(outdata) + for k,v in sorted(params.iteritems()): + if v: + print "%s => %s" % (k, v) + + sys.exit(0) + # Command: extract firmware if options.firmware: try: @@ -732,3 +1011,4 @@ def spkeygen(mac): print '[ERR]:\tPlease enter a valid MAC address, e.g '\ '00-11-22-33-44-55 or 00:DE:AD:BE:EF:00 or 1337babecafe' # EOF + From b1f5dd138f13342ae462f80ece5b4c41e824b77e Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Thu, 29 Jan 2015 01:17:57 +0100 Subject: [PATCH 6/8] added some variables, MAC, DNS etc. --- draytools.py | 62 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/draytools.py b/draytools.py index bcb1862..077d091 100644 --- a/draytools.py +++ b/draytools.py @@ -281,21 +281,23 @@ def get_params(data): desc.append("System Maintenance >> Management >> Management Setup >> Management Port Setup >> HTTPS Port") packstring += "32s" - desc.append("???") + desc.append("???") # 576 for i in range(20): packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Public Port desc.append("NAT >> Port Redirection >> Index No. %02d >> Public Port"% (i+1)) - packstring += "2s" # ???? - desc.append("???") + packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Public Port + desc.append("NAT >> Port Redirection >> Index No. %02d >> Protocol" % (i+1)) packstring += "16s"# NAT >> Port Redirection >> Index No. %02d >> Private IP desc.append("NAT >> Port Redirection >> Index No. %02d >> Private IP"% (i+1)) packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Private Port desc.append("NAT >> Port Redirection >> Index No. %02d >> Private Port"% (i+1)) packstring += "24s"# NAT >> Port Redirection >> Index No. %02d >> Service Name desc.append("NAT >> Port Redirection >> Index No. %02d >> Service Name"% (i+1)) - packstring += "4s" # ???? - desc.append("???") + packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> Public Port Count + desc.append("NAT >> Port Redirection >> Index No. %02d >> Public Port Count" % (i+1)) + packstring += "H" # NAT >> Port Redirection >> Index No. %02d >> WAN IP + desc.append("NAT >> Port Redirection >> Index No. %02d >> WAN IP" % (i+1)) packstring += "2344s"# ??? 392 desc.append("???") @@ -321,10 +323,48 @@ def get_params(data): for i in range(2): packstring += "24s"# Interface WAN %d Display Name desc.append("Interface WAN %d Display Name"% (i+1)) - packstring += "600s"# ??? + packstring += "236s"# ??? + desc.append("???") + for j in range (4): + packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d + desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d "% (i+1,j+1)) + packstring += "16s"# ??? + desc.append("???") + for j in range (4): + packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d + desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> IP Address %d "% (i+1,j+1)) + for j in range (4): + packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d + desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Subnet Mask %d "% (i+1,j+1)) + packstring += "192s"# ??? + desc.append("???") + for j in range (4): + packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> DNS Server IP Address >> Primary IP Address + desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> DNS Server IP Address >> Primary IP Address %d "% (i+1,j+1)) + for j in range (4): + packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> DNS Server IP Address >> Secondary IP Address + desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> DNS Server IP Address >> Secondary IP Address %d "% (i+1,j+1)) + packstring += "144s"# ??? desc.append("???") - packstring += "19716s"# ??? 37004 + packstring += "11224s"# ??? 28528 + desc.append("???") + + for i in range(7): + for j in range(4): + packstring += "B"# WAN >> Internet Access >> WAN 2 >> Static or Dynamic IP >> WAN IP Network Settings >> WAN IP Alias %d + desc.append("WAN >> Internet Access >> WAN 2 >> Static or Dynamic IP >> WAN IP Network Settings >> WAN IP Alias %d %d"% (i+1,j+1)) + + packstring += "6516s"# ??? 35072 + desc.append("???") + for i in range(10): + for j in range(6): + packstring += "B"# Bind IP to MAC >> IP Bind List >> Mac address %d + desc.append("LAN >> Bind IP to MAC >> IP Bind List >> Mac address %d %d" % (i+1,j+1)) + +# variables = list(unpack(packstring, data[:365432])) + + packstring += "1872s"# ??? 37004 desc.append("???") for i in range(12): @@ -364,7 +404,13 @@ def get_params(data): packstring += "12s"# ??? desc.append("???") # 60596 - packstring += "23588s"# ??? 84184 + for i in range(32): + packstring += "16s"# "Object Setting >> Service Type Group >> Profile Index : %02d >> Name + desc.append("Object Setting >> Service Type Group >> Profile Index : %02d >> Name"% (i+1)) + packstring += "16s"# ??? + desc.append("???") # + + packstring += "22564s"# ??? 84184 desc.append("???") packstring += "256s"# CSM >> URL Content Filter Profile >> Administration Message 84440 From e045736228b721ffcd4e96649b33aa3b945bc38b Mon Sep 17 00:00:00 2001 From: Tomasz Brzezina Date: Thu, 29 Jan 2015 22:26:16 +0100 Subject: [PATCH 7/8] factory setting for 2850 --- V2850_20150129.cfg | Bin 0 -> 7200 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 V2850_20150129.cfg diff --git a/V2850_20150129.cfg b/V2850_20150129.cfg new file mode 100644 index 0000000000000000000000000000000000000000..a7cd72297b4521851a367506ade9b69fe82b3769 GIT binary patch literal 7200 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 Date: Wed, 7 Dec 2016 11:28:27 +0100 Subject: [PATCH 8/8] Set new variables: * WAN >> Internet Access >> WAN x >> PPPoE/PPPoA >> ISP Access Setup >> Username * WAN >> Internet Access >> WAN x >> PPPoE/PPPoA >> ISP Access Setup >> Password --- draytools.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) mode change 100644 => 100755 draytools.py diff --git a/draytools.py b/draytools.py old mode 100644 new mode 100755 index 077d091..c7fe9ca --- a/draytools.py +++ b/draytools.py @@ -322,9 +322,20 @@ def get_params(data): for i in range(2): packstring += "24s"# Interface WAN %d Display Name - desc.append("Interface WAN %d Display Name"% (i+1)) - packstring += "236s"# ??? + desc.append("WAN >> General Setup >> WAN %d >> Display Name"% (i+1)) + + packstring += "64s" # jeszcze nie wiem desc.append("???") + + packstring += "64s"# ??? + desc.append("WAN >> Internet Access >> WAN %d >> PPPoE/PPPoA >> ISP Access Setup >> Username" % (i+1)) + + packstring += "64s"# ??? + desc.append("WAN >> Internet Access >> WAN %d >> PPPoE/PPPoA >> ISP Access Setup >> Password" % (i+1)) + + packstring += "44s"# ??? + desc.append("???") + for j in range (4): packstring += "B"# WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d desc.append("WAN >> Internet Access >> WAN %d >> Static or Dynamic IP >> WAN IP Network Settings >> Gateway IP Address %d "% (i+1,j+1))