Created
January 12, 2016 16:13
-
-
Save julien-duponchelle/50555048a495a710a74a to your computer and use it in GitHub Desktop.
IOS NVRAM extractor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
# Copyright (C) 2015 Bernhard Ehlers, GNS3 | |
# | |
# 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 2 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. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
# This utility is a stripped down version of dynamips' nvram_export, | |
# ported from C to Python, see https://github.com/GNS3/dynamips | |
# nvram_export is (c) 2013 Flávio J. Saraiva | |
""" | |
iou_export exports startup/private configuration from IOU NVRAM file. | |
usage: iou_export [-h] NVRAM startup-config [private-config] | |
positional arguments: | |
NVRAM NVRAM file | |
startup-config startup configuration | |
private-config private configuration | |
optional arguments: | |
-h, --help show this help message and exit | |
""" | |
import argparse | |
import sys | |
import struct | |
# Uncompress data in .Z file format. | |
# Ported from dynamips' fs_nvram.c to python | |
# Adapted from 7zip's ZDecoder.cpp, which is licensed under LGPL 2.1. | |
def uncompress_LZC(data): | |
LZC_NUM_BITS_MIN = 9 | |
LZC_NUM_BITS_MAX = 16 | |
in_data = bytearray(data) | |
in_len = len(in_data) | |
out_data = bytearray() | |
if in_len == 0: | |
return out_data | |
if in_len < 3: | |
raise ValueError('invalid length') | |
if in_data[0] != 0x1F or in_data[1] != 0x9D: | |
raise ValueError('invalid header') | |
maxbits = in_data[2] & 0x1F | |
numItems = 1 << maxbits | |
blockMode = (in_data[2] & 0x80) != 0 | |
if maxbits < LZC_NUM_BITS_MIN or maxbits > LZC_NUM_BITS_MAX: | |
raise ValueError('not supported') | |
parents = [0] * numItems | |
suffixes = [0] * numItems | |
in_pos = 3 | |
numBits = LZC_NUM_BITS_MIN | |
head = 256 | |
if blockMode: | |
head += 1 | |
needPrev = 0 | |
bitPos = 0 | |
numBufBits = 0 | |
parents[256] = 0 | |
suffixes[256] = 0 | |
buf_extend = bytearray([0] * 3) | |
while True: | |
# fill buffer, when empty | |
if numBufBits == bitPos: | |
buf_len = min(in_len - in_pos, numBits) | |
buf = in_data[in_pos:in_pos+buf_len] + buf_extend | |
numBufBits = buf_len << 3 | |
bitPos = 0 | |
in_pos += buf_len | |
# extract next symbol | |
bytePos = bitPos >> 3 | |
symbol = buf[bytePos] | buf[bytePos + 1] << 8 | buf[bytePos + 2] << 16 | |
symbol >>= bitPos & 7 | |
symbol &= (1 << numBits) - 1 | |
bitPos += numBits | |
# check for special conditions: end, bad data, re-initialize dictionary | |
if bitPos > numBufBits: | |
break | |
if symbol >= head: | |
raise ValueError('invalid data') | |
if blockMode and symbol == 256: | |
numBufBits = bitPos = 0 | |
numBits = LZC_NUM_BITS_MIN | |
head = 257 | |
needPrev = 0 | |
continue | |
# convert symbol to string | |
stack = [] | |
cur = symbol | |
while cur >= 256: | |
stack.append(suffixes[cur]) | |
cur = parents[cur] | |
stack.append(cur) | |
if needPrev: | |
suffixes[head - 1] = cur | |
if symbol == head - 1: | |
stack[0] = cur | |
stack.reverse() | |
out_data.extend(stack) | |
# update parents, check for numBits change | |
if head < numItems: | |
needPrev = 1 | |
parents[head] = symbol | |
head += 1 | |
if head > (1 << numBits): | |
if numBits < maxbits: | |
numBufBits = bitPos = 0 | |
numBits += 1 | |
else: | |
needPrev = 0 | |
return out_data | |
# export NVRAM | |
def nvram_export(nvram): | |
nvram = bytearray(nvram) | |
# extract startup config | |
offset = 0 | |
while struct.unpack_from(">HH", nvram, offset=offset) != (0xF0A5, 0xABCD): | |
offset += 2 | |
offset += 2 | |
# Startup config | |
# struct fs_nvram_header_startup_config { | |
# /** Magic value 0xABCD. */ | |
# m_uint16_t magic; | |
# | |
# /** Format of the data. | |
# * 0x0001 - raw data; | |
# * 0x0002 - .Z compressed (12 bits); | |
# */ | |
# m_uint16_t format; | |
# | |
# /** Checksum of filesystem data. (all data after the filesystem magic) */ | |
# m_uint16_t checksum; | |
# | |
# /** 0x0C04 - maybe maximum amount of free space that will be reserved? */ | |
# m_uint16_t unk1; | |
# | |
# /** Address of the data. */ | |
# m_uint32_t start; | |
# | |
# /** Address right after the data. */ | |
# m_uint32_t end; | |
# | |
# /** Length of block. */ | |
# m_uint32_t len; | |
# | |
# /** 0x00000000 */ | |
# m_uint32_t unk2; | |
# | |
# /** 0x00000000 if raw data, 0x00000001 if compressed */ | |
# m_uint32_t unk3; | |
# | |
# /** 0x0000 if raw data, 0x0001 if compressed */ | |
# m_uint16_t unk4; | |
# | |
# /** 0x0000 */ | |
# m_uint16_t unk5; | |
# | |
# /** Length of uncompressed data, 0 if raw data. */ | |
# m_uint32_t uncompressed_len; | |
# | |
# // startup-config data comes after this header | |
# } __attribute__((__packed__)); | |
(magic, fs_format, checksum, free_space, start, end, length, _, _, _, _, uncompressed_len) = struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=offset) | |
offset += 36 | |
if len(nvram) < offset + length: | |
raise ValueError('invalid length') | |
startup = nvram[offset:offset+length] | |
# compressed startup config | |
if format == 2: | |
try: | |
startup = uncompress_LZC(startup) | |
except ValueError as err: | |
raise ValueError('uncompress startup: ' + str(err)) | |
return (startup, None) | |
if __name__ == '__main__': | |
# Main program | |
parser = argparse.ArgumentParser(description='%(prog)s exports startup/private configuration from IOS NVRAM file.') | |
parser.add_argument('nvram', metavar='NVRAM', | |
help='NVRAM file') | |
parser.add_argument('startup', metavar='startup-config', | |
help='startup configuration') | |
args = parser.parse_args() | |
try: | |
fd = open(args.nvram, 'rb') | |
nvram = fd.read() | |
fd.close() | |
except (IOError, OSError) as err: | |
sys.stderr.write("Error reading file: {}\n".format(err)) | |
sys.exit(1) | |
try: | |
startup, private = nvram_export(nvram) | |
except ValueError as err: | |
sys.stderr.write("nvram_export: {}\n".format(err)) | |
sys.exit(3) | |
with open(args.startup, 'wb+') as f: | |
f.write(startup) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment