pax_global_header00006660000000000000000000000064122330375010014506gustar00rootroot0000000000000052 comment=22d8875b6d986f8c31e906f4c3cb479b5e2cb03a CHANGELOG000066400000000000000000000011251223303750100122630ustar00rootroot000000000000000.3 --- Add -s option to mkdns323fw, to specify the type of firmware to build. New tool, splitdns323fw, to parse a firmware image, verify checksums, print IDs, and write out the various parts of the firmware image. Support DNS-321 ("Chopper") and DNS-343 ("Gandolf") firmware images. 0.2 --- Fix a manpage copy-paste error, -c is for custom_id, -m is for model_id, and never the twain shall meet. Remove debugging output that wasn't properly tidied up before release. Thanks to Luk Claes for pointing these problems out. 0.1 --- initial release, makes firmware images, nothing special. README.md000066400000000000000000000013361223303750100123340ustar00rootroot00000000000000# dns323-firmware-tools This package contains programs for manipulating the firmware images used by the D-Link DNS-323 and similar devices (firmwares sometimes referred to as "FrodoII" firmwares, due to the magic string used to identify them). These firmware images are what are used to "bundle" the kernel, initrd, and other data when uploading new firmware images using the "stock" interface. If you are already running a custom firmware, it is likely that these tools will be of no use to you, and you will need to use whatever update mechanism is provided by your firmware. ## Requirements The only requirements to run these tools is an installation of Ruby 1.8, and the input files (kernel/initrd) to make into a firmware. dns323fw-tool000077500000000000000000000234441223303750100133330ustar00rootroot00000000000000#!/usr/bin/ruby # Create and dismantle firmware files suitable for upload to a range of # D-Link (and compatible) NAS devices. # # Copyright (C) 2008,2012 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 # # This tool is based on information provided by Leschinsky Oleg. Many # thanks go to him for deciphering (and publishing) his analysis of the file # formats involved. # # This file should not be called as dns323fw-tool; create links to this file # named 'mkdns323fw' and 'splitdns323fw'. require 'optparse' # This class handles all the grunt work for decoding and encoding firmware # files. class DnsFirmware # This class can be initialized two ways: # # - with a single string argument, which is the filename of an existing # firmware file to be dissected; or # # - with a hash containing seven (symbol) keys: # * :kernel_file # * :initrd_file # * :defaults_file (optional; can be empty) # * :product_id # * :custom_id # * :model_id # * :compat_id (optional; defaults to 255) # * :signature # def initialize(opts) if opts.is_a? String @firmware_file = opts parse_header elsif opts.is_a? Hash @kernel = opts[:kernel_file] or raise ArgumentError.new("No :kernel_file provided") @initrd = opts[:initrd_file] or raise ArgumentError.new("No :initrd_file provided") @defaults = opts[:defaults_file] @product_id = opts[:product_id] or raise ArgumentError.new("No :product_id provided") @custom_id = opts[:custom_id] or raise ArgumentError.new("No :custom_id provided") @model_id = opts[:model_id] or raise ArgumentError.new("No :model_id provided") @compat_id = opts[:compat_id].nil? ? 255 : opts[:compat_id] @subcompat_id = opts[:subcompat_id].nil? ? 255 : opts[:subcompat_id] @signature = opts[:signature] or raise ArgumentError.new("No :signature provided") else raise ArgumentError.new("Incorrect type passed to DnsFirmware#initialize. String or Hash expected, got #{opts.class}") end end attr_reader :product_id, :custom_id, :model_id, :compat_id, :subcompat_id, :k_size, :i_size, :d_size # Return the signature of this firmware file. def signature if @signature =~ /^\x55\xAA(.{7})\0\x55\xAA$/ return $1 else raise RuntimeError.new("Unparseable signature string: #{@signature.inspect}") end end def write_kernel_file(destfile) write_data_file(destfile, @k_size, @k_offset) end def write_initrd_file(destfile) write_data_file(destfile, @i_size, @i_offset) end def write_defaults_file(destfile) write_data_file(destfile, @d_size, @d_offset) end def verify_kernel_checksum verify_checksum(@k_size, @k_offset, @k_sum) end def verify_initrd_checksum verify_checksum(@i_size, @i_offset, @i_sum) end def verify_defaults_checksum verify_checksum(@d_size, @d_offset, @d_sum) end # This method works from the kernel/initrd/defaults/etc data and writes out # a complete firmware file to the destfile of your choosing. def write_firmware_file(destfile) k_size = File.stat(@kernel).size i_size = File.stat(@initrd).size d_size = File.stat(@defaults).size rescue 0 header = [ 64, k_size, 64 + k_size, i_size, 64 + k_size + i_size, d_size, checksum(File.read(@kernel)), checksum(File.read(@initrd)), d_size == 0 ? 0 : checksum(File.read(@defaults)), "\x55\xAA#{@signature}\0\x55\xAA", @product_id, @custom_id, @model_id, @compat_id, @subcompat_id, "\x00" * 7, 0 ].pack("V9a12c5a7V") File.write(destfile, header) File.write(destfile, File.read(@kernel), 64) File.write(destfile, File.read(@initrd), 64+k_size) File.write(destfile, File.read(@defaults), 64+k_size+i_size) unless d_size == 0 end private def parse_header return if @header_parsed must_have_firmware_file @k_offset, @k_size, @i_offset, @i_size, @d_offset, @d_size, @k_sum, @i_sum, @d_sum, @signature, @product_id, @custom_id, @model_id, @compat_id, @subcompat_id, unused3, unused4 = File.read(@firmware_file, 64).unpack("V9a12c5a7V") @header_parsed = true end def must_have_firmware_file if @firmware_file.nil? raise RuntimeError.new("Attempted to get a firmware parameter without a firmware file. Not cool, man.") end end def write_data_file(dest, size, offset) data = File.read(@firmware_file, size, offset) File.write(dest, data) end def verify_checksum(size, offset, sum) data = File.read(@firmware_file, size, offset) checksum(data) == sum end def checksum(s) s.unpack("V*").inject(0) { |v, i| v ^= i } end end def mkdns323fw(args) opts = OptionParser.new optargs = {} opts.on('-h', '--help', "Print this help") { puts opts.to_s; exit 0 } opts.on('-k KERNEL', '--kernel KERNEL', "Specify the kernel to include in the firmware image", String) { |k| optargs[:kernel_file] = k } opts.on('-i INITRD', '--initrd INITRD', "Specify the initrd to include in the firmware image", String) { |i| optargs[:initrd_file] = i } opts.on('-d DEFAULTS', '--defaults DEFAULTS', "Specify the defaults.tar.gz to include in the firmware image (optional)", String) { |d| optargs[:defaults_file] = d } opts.on('-o OUTPUT', '--output OUTPUT', "Specify where to put the resulting firmware image", String) { |o| optargs[:output] = o } opts.on('-p PROD_ID', '--product-id PROD_ID', "The product ID to embed in the firmware image", Integer) { |p| optargs[:prod_id] = p } opts.on('-c CUSTOM_ID', '--custom-id CUSTOM_ID', "The custom ID to embed in the firmware image", Integer) { |c| optargs[:custom_id] = c } opts.on('-m MODEL_ID', '--model-id MODEL_ID', "The model ID to embed in the firmware image", Integer) { |m| optargs[:model_id] = m } opts.on('-s SIGNATURE', '--signature SIGNATURE', "The firmware signature type (either FrodoII, Chopper or Gandolf)", String) { |s| optargs[:signature] = s } opts.parse(args) opts = optargs %w{kernel_file initrd_file output prod_id custom_id model_id}.each do |k| if opts[k.to_sym].nil? $stderr.puts "Missing required argument #{k}" exit 1 end end %w{kernel_file initrd_file}.each do |k| if !File.exist?(opts[k.to_sym]) $stderr.puts "#{k} file (#{opts[k.to_sym]}) doesn't exist!" exit 1 end if !is_uboot(opts[k.to_sym]) $stderr.puts "#{k} file #{opts[k.to_sym]} is not a uboot file" exit 1 end end if opts[:defaults] and !File.exist?(opts[:defaults]) $stderr.puts "default file (#{opts[:defaults]}) doesn't exist!" exit 1 end opts[:signature] ||= "FrodoII" begin fw = DnsFirmware.new(opts) fw.write_firmware_file(opts[:output]) rescue StandardError => e $stderr.puts "Firmware generation failed: #{e.class}: #{e.message}" e.backtrace.each { |l| puts " #{l}" } else puts "Firmware generation completed successfully." end end def splitdns323fw(args) opts = OptionParser.new optargs = {} opts.on('-h', '--help', "Print this help") { $stderr.puts opts.to_s; exit 0 } opts.on('-k KERNEL', '--kernel KERNEL', "Write out the kernel to the specified file", String) { |k| optargs[:kernel] = k } opts.on('-i INITRD', '--initrd INITRD', "Write out the initrd to the specified file", String) { |i| optargs[:initrd] = i } opts.on('-d DEFAULTS', '--defaults DEFAULTS', "Write out the defaults.tar.gz to the specified file", String) { |d| optargs[:defaults] = d } image = opts.parse(args) 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 image = image[0] fw = DnsFirmware.new(image) puts "#{fw.signature} firmware signature found" fw.verify_kernel_checksum or $stderr.puts "Kernel data failed checksum verification" puts "Kernel is #{fw.k_size} bytes" fw.verify_initrd_checksum or $stderr.puts "Initrd data failed checksum verification" puts "initrd is #{fw.i_size} bytes" fw.verify_defaults_checksum or $stderr.puts "Defaults data failed checksum verification" puts "defaults.tar.gz is #{fw.d_size} bytes" puts "Product ID: #{fw.product_id}" puts "Custom ID: #{fw.custom_id}" puts "Model ID: #{fw.model_id}" if optargs[:kernel] fw.write_kernel_file(optargs[:kernel]) puts "Kernel data written to #{optargs[:kernel]}" unless is_uboot(optargs[:kernel]) puts "WARNING: kernel data does not appear to be a uBoot file" end end if optargs[:initrd] fw.write_initrd_file(optargs[:initrd]) puts "initrd data written to #{optargs[:initrd]}" unless is_uboot(optargs[:initrd]) puts "WARNING: initrd data does not appear to be a uBoot file" end end if optargs[:defaults] fw.write_defaults_file(optargs[:defaults]) puts "defaults.tar.gz written to #{optargs[:defaults]}" end end def is_uboot(file) File.read(file, 4) == "\x27\x05\x19\x56" end if $0 == __FILE__ case File.basename($0) when 'mkdns323fw' then mkdns323fw(ARGV) when 'splitdns323fw' then splitdns323fw(ARGV) else $stderr.puts "Please call me as either 'mkdns323fw' or 'splitdns323fw'; symlinks are good" exit 1 end end mkdns323fw000077700000000000000000000000001223303750100151402dns323fw-toolustar00rootroot00000000000000mkdns323fw.1000066400000000000000000000102431223303750100130350ustar00rootroot00000000000000.TH MKDNS323FW "1" "October 2008" "dns323-firmware-tools 0.1" "User Commands" .SH NAME mkdns323fw \- build firmware images for the DNS-323 from a kernel and initrd .SH SYNOPSIS .B mkdns323fw -k KERNEL -i INITRD [-d DEFAULTS] -p PRODUCT_ID -c CUSTOM_ID -m MODEL_ID -o OUTPUTFILE .SH DESCRIPTION mkdns323fw creates firmware images suitable for upload to the D-link DNS-323 and other, similar devices based on the same basic firmware image, such as the Conceptronics CH3SNAS. These firmware images contain a kernel and initrd, as well as various product-specific values and checksums. .PP This command can be very dangerous; although it attempts to do some very, very basic sanity checking, it is still quite easy to generate a firmware file that, when loaded into your device, will kill it stone dead. This program is not able to check that you're uploading valid data to your device; if you brick it, you're on your own. .HP \fB\-k\fR kernel, \fB\-\-kernel\fR=\fIkernel\fR .IP specify the file containing the kernel image to embed in the firmware image. This must be a uBoot image file, as produced by .BR mkimage (1) with appropriate options to specify it as a kernel image. Attempts to provide a non-uBoot file will fail, while specifying a non-kernel uBoot file may well brick your device. This option is required. .HP \fB\-i\fR initrd, \fB\-\-initrd\fR=\fIinitrd\fR .IP the initrd file to embed in the firmware image. This must be a uBoot image file, as produced by .BR mkimage (1) with appropriate options to specify it as a ramdisk. The tool will refuse to embed a non-uBoot file, however a dodgy ramdisk will likely brick your device. .HP \fB\-d\fR defaults.tar.gz, \fB\-\-defaults\fR=\fIdefaults.tar.gz\fR .IP The firmware format has the ability to embed a tarball with a default configuration; if you want to do this, you may use this option to do so. However, the devices that the author has dealt with do not require such a thing, and leaving it out still produces a valid firmware (and one that is a bit smaller, to boot). .HP \fB\-s\fR signature, \fB\-\-defaults\fR=\fIsignature\fR .IP For reasons that will probably remain unknown until the ends of time, there are (at least) two different firmware signatures running around that are otherwise identical in their internal structure, which are used for different devices. This option exists to allow you to specify the signature that you want to use in your firmware build. Valid values for this option are currently .I FrodoII .R (the default if this option is not specified), .I Chopper .R or .I Gandolf .R which is used in some devices. See the table at the top of the script if you don't know which value to use for your device. .HP \fB\--p product_id\fR, \fB\-\-product-id\fR=\fIproduct_id\fR .HP \fB\--c custom_id\fR, \fB\-\-custom-id\fR=\fIcustom_id\fR .HP \fB\--m model_id\fR, \fB\-\-model-id\fR=\fImodel_id\fR .IP Specify the product, custom, and model ID that this firmware image is intended for. As several different devices share the same firmware format, the intended device type is encoded in these fields. If you do not specify the correct values for the device that you are targetting with your firmware, it is quite likely that the device will refuse the upload. .IP Known-good values for various devices are provided in the header of the script; please look there for more information. You can also obtain the values you need by downloading an existing firmware for the device you're targetting and doing a bit of digging. .HP \fB\-o\fR outputfile, \fB\-\-output\fR=\fIoutputfile\fR .IP Where to write the completed firmware image. Will overwrite any existing file of the same name. .SH BUGS .PP E-mail bug reports to .BR theshed+dns323-firmware-tools@hezmatt.org . I don't guarantee to be able to help, but I'll give it a shot. Patches are far more helpful. .SH AUTHOR .BR mkdns323fw was written by Matt Palmer, based on reverse-engineering work done by Leschinsky Oleg. .SH COPYRIGHT Copyright \(co 2008 Matt Palmer. .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. .SH "SEE ALSO" .BR mkimage (1), .BR splitdns323fw (1). splitdns323fw000077700000000000000000000000001223303750100156642dns323fw-toolustar00rootroot00000000000000splitdns323fw.1000066400000000000000000000032041223303750100135600ustar00rootroot00000000000000.TH SPLITDNS323FW "1" "November 2008" "dns323-firmware-tools 0.3" "User Commands" .SH NAME splitdns323fw \- extract data from DNS-323 (and other) firmware images .SH SYNOPSIS .B splitdns323fw [-k KERNEL] [-i INITRD] [-d DEFAULTS] firmwarefile .SH DESCRIPTION splitdns323fw parses an existing firmware image for a device such as the D-Link DNS-323, DNS-321, and Conceptronics CH3SNAS. .PP By default, if only the .I firmwarefile .R option is given, the script verifies the checksums of the included components and prints a bunch of useful info about the image. If you specify one or more of the other options, however, the corresponding section of the firmware image will be written to that file. .HP \fB\-k\fR kernel, \fB\-\-kernel\fR=\fIkernel\fR .IP Write the kernel part of the firmware image to the specified file. .HP \fB\-i\fR initrd, \fB\-\-initrd\fR=\fIinitrd\fR .IP Write the initrd part of the firmware image to the specified file. .HP \fB\-d\fR defaults.tar.gz, \fB\-\-defaults\fR=\fIdefaults.tar.gz\fR .IP Write the defaults part of the firmware image to the specified file. .SH BUGS .PP E-mail bug reports to .BR theshed+dns323-firmware-tools@hezmatt.org . I don't guarantee to be able to help, but I'll give it a shot. Patches are far more helpful. .SH AUTHOR .BR splitdns323fw was written by Matt Palmer, based on reverse-engineering work done by Leschinsky Oleg. .SH COPYRIGHT Copyright \(co 2008 Matt Palmer. .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. .SH "SEE ALSO" .BR mkimage (1), .BR mkdns323fw (1).