Lets look at D-Link DNS-323 firmware structure and building procedures.
(You can get parseFirmware.py util here.)
Internally firmware binary contains three parts: - u-boot kernel image - u-boot rootfs image - archive with default parameters (default.tar.gz)
Layout of firmware binary is defined by this C structure:
typedef struct _CONTROL_HEADER_
{
unsigned long offset_1;
unsigned long len_1;
unsigned long offset_2;
unsigned long len_2;
unsigned long offset_3;
unsigned long len_3;
unsigned long checksum_1;
unsigned long checksum_2;
unsigned long checksum_3;
unsigned char magic_num[12];
unsigned char product_id;
unsigned char custom_id;
unsigned char model_id;
unsigned char sub_id;
unsigned char NewVersion;
unsigned char reserved[7]; //all structure is 64 bytes
unsigned long Next_offset;
}
Where magic_num contains this string:
//the string is "0x55 0xaa FrodoII 0x00 0x55 0xaa"
unsigned char FRODO2_MAGIC_NUM[12] =
{0x55,0xaa,0x46,0x72,0x6f,0x64,0x6f,0x49,0x49,0x00,0x55,0xaa};
This is common for D-Link and Conceptronic devices.
Where they differ is a product, custom, model and sub ids.
Those are read from custom.h file located at goahead Web-server sources in goahead/LINUX directory. Each manufacturer puts own Web-interface code for device including custom.h.
What we need looks like this.
#define PRODUCT_ID 7
#define CUSTOM_ID 1
#define MODEL_ID 1
I have already posted here how they get those values from custom.h. Just look at posts with tag "wtf".
We can translate C structure to Python struct string and see offsets, lengths and checksums of its components.
import struct
import sys
try:
f = open(sys.argv[1], mode='rb')
# We read 9 unsigned longs from file
# unsigned long -- 4 bytes
bytes = f.read(36)
except IOError:
print "Unable to read header from firmware file"
sys.exit()
(kern_offset, kern_length, ramdisk_offset,
ramdisk_length, defaults_offset, defaults_length,
kernel_checksum, ramdisk_checksum,
defaults_checksum) = struct.unpack('LLLLLLLLL', bytes)
print """Kernel offset: %s
Kernel length: %s
Ramdisk offset: %s
Ramdisk length: %s
Defaults offset: %s
Defaults length: %s
Kernel checksum: %s
Ramsdisk checksum: %s
Defaults checksum: %s""" % (kern_offset, kern_length, ramdisk_offset,
ramdisk_length, defaults_offset,
defaults_length, kernel_checksum,
ramdisk_checksum, defaults_checksum)
So to get kernel, ramdisk and defaults archive we must read number of bytes from specified location and write them in separate files.
To slice components from firmware binary image run command like this.
$ ./parseFirmware.py <path to firmware image>
for example:
$ ./parseFirmware.py ~/devel/DNS323.1.04
parseFirmware.py will create three files:
We can't do much with kernel image as there are no modifications possible without recompilation.
As for defaults archive, we can completely ignore it and use only to build our firmware binary (why it can be ignored will be explained later).
All that left is ramdisk image. I made some shortcut in parseFirmware.py to get gzip-ed ramdisk ext2fs image instead of u-boot image so you can gunzip it and mount like this.
# gunzip Ramdisk.gz
# mkdir ./ramdisk
# mount -o loop Ramdisk ./ramdisk
Ramdisk image contains normal directory structure and cramfs image image.cfs.
# ls ./ramdisk
bin etc image.cfs lost+found proc sbin tmp var welcome.msg
dev home lib mnt root sys usr web
#
Mount image.cfs to look inside.
# mkdir ./cramfsimage
# mount -o loop ./ramdisk/image.cfs ./cramfsimage/
# ls ./cramfsimage
bin default language lltd samba scsi upnp web_page
codepages etc_codepage lib LPRng sbin shared_name web
We won't touch those directories for now. What's looks interesting is a default directory. Apparently its content similar to default.tar.gz content.
To understand what function each of them serving we can look at source (in goahead/webs.c function extractUploadedFileContent) where firmware is unpacked and written into flash memory. It's obvious from the source that only kernel and ramdisk are flashed to device and default.tar.gz is only get its checksum checked.
As for default directory at cramfs it is used at startup.
In ./ramdisk/etc/fstab:
/image.cfs /sys/crfs cramfs loop 0 0
In ./ramdisk/etc/rc.sh:
ln -s /sys/crfs/default /default
Looks like cramfs image contains default parameters which are used after "Reset To Factory Defaults" command is issued from Web-interface.