#!/usr/bin/ruby # Take a firmware file for various NAS devices, and write out it's # constituent parts. # # Copyright (C) 2008 Matt Palmer # # 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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # Script based on information provided by Leschinsky Oleg. # # Takes a firmware file image as it's sole mandatory argument, and verifies # that the signature and checksums are OK before extracting whichever # element(s) of the firmware you requested (if any). Also displays the # Product/Custom/Model ID, and the firmware signature type. require 'optparse' def main opts = parse_cmdline validate_command_line(opts) header = {} header[:k_offset], header[:k_size], header[:i_offset], header[:i_size], header[:d_offset], header[:d_size], header[:k_sum], header[:i_sum], header[:d_sum], header[:signature], header[:prod_id], header[:custom_id], header[:model_id], header[:unused_1], header[:unused_2], header[:unused_3], header[:unused_4] = File.read(opts[:image], 64).unpack("V9a12c5a7V") if header[:signature] == "\x55\xAAFrodoII\x00\x55\xAA" puts "FrodoII firmware signature found" elsif header[:signature] == "\x55\xAAChopper\x00\x55\xAA" puts "Chopper firmware signature found" elsif header[:signature] == "\x55\xAAGandolf\x00\x55\xAA" puts "Gandolf firmware signature found" else puts "ERROR: Unknown firmware signature (I got #{header[:signature].inspect}; perhaps it's new?)" exit 1 end verify_sum(File.read(opts[:image], header[:k_size], header[:k_offset]), header[:k_sum], "Kernel") puts "Kernel is #{header[:k_size]} bytes" verify_sum(File.read(opts[:image], header[:i_size], header[:i_offset]), header[:i_sum], "initrd") puts "initrd is #{header[:i_size]} bytes" verify_sum(File.read(opts[:image], header[:d_size], header[:d_offset]), header[:d_sum], "defaults.tar.gz") puts "defaults.tar.gz is #{header[:d_size]} bytes" puts "Product ID: #{header[:prod_id]}" puts "Custom ID: #{header[:custom_id]}" puts "Model ID: #{header[:model_id]}" if opts[:kernel] File.open(opts[:kernel], 'w') do |fd| fd.write(File.read(opts[:image], header[:k_size], header[:k_offset])) end puts "Kernel data written to #{opts[:kernel]}" unless is_uboot(opts[:kernel]) puts "WARNING: kernel data does not appear to be a uBoot file" end end if opts[:initrd] File.open(opts[:initrd], 'w') do |fd| fd.write(File.read(opts[:image], header[:i_size], header[:i_offset])) end puts "initrd data written to #{opts[:initrd]}" unless is_uboot(opts[:initrd]) puts "WARNING: initrd data does not appear to be a uBoot file" end end if opts[:defaults] File.open(opts[:defaults], 'w') do |fd| fd.write(File.read(opts[:image], header[:d_size], header[:d_offset])) end puts "defaults.tar.gz written to #{opts[:defaults]}" end end def validate_command_line(opts) if !File.exist?(opts[:image]) $stderr.puts "Firmware image file (#{opts[:image]}) doesn't exist!" exit 1 end end def is_uboot(file) File.read(file, 4) == "\x27\x05\x19\x56" end def checksum(s) sum = 0 sum end def verify_sum(s, expected_sum, desc) actual_sum = 0 s.unpack("V*").each { |v| actual_sum ^= v } if actual_sum == expected_sum puts "#{desc} checksum matches" else puts "WARNING: #{desc} checksum mismatch (expected #{expected_sum}, got #{actual_sum})" end end def parse_cmdline opts = OptionParser.new optargs = {} opts.on('-h', '--help', "Print this help") { optargs[:help] = true } opts.on('-k KERNEL', '--kernel KERNEL', "Write out the kernel to the specified file", String) { |optargs[:kernel]| } opts.on('-i INITRD', '--initrd INITRD', "Write out the initrd to the specified file", String) { |optargs[:initrd]| } opts.on('-d DEFAULTS', '--defaults DEFAULTS', "Write out the defaults.tar.gz to the specified file", String) { |optargs[:defaults]| } image = opts.parse(ARGV) if optargs[:help] $stderr.puts opts.to_s exit 0 end if image.nil? or image.empty? $stderr.puts "No firmware image provided!" exit 1 end if image.length > 1 $stderr.puts "I can only read one firmware image!" exit 1 end optargs[:image] = image[0] optargs end main