Skip to content

Instantly share code, notes, and snippets.

@julien-duponchelle
Created January 12, 2016 16:13
Show Gist options
  • Save julien-duponchelle/50555048a495a710a74a to your computer and use it in GitHub Desktop.
Save julien-duponchelle/50555048a495a710a74a to your computer and use it in GitHub Desktop.
IOS NVRAM extractor
#!/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