12

I have a Fritz!Box 7490 residential DSL/Modem/Router by AVM.

One can dump the router's current configuration as a file (under "System > Backup > Save Tab"). The result is a simple structured text file, not JSON, but nearly so (Excellent design decision, AVM!)

I want to unconventionally modify it and upload it again to change the router's configuration.

What do I want to modify? First, empty the now outrageously large DHCP cache - doing this via the interface takes ages (hours of clicking). Second, modify the DNS servers passed to the DHCP clients on the LAN, options servercfg.user_dns1_for_ipv4 and servercfg.user_dns2_for_ipv4, which are not accessible via the 7490 interface.

The configuration upload facility of the router ("System > Backup > Restore Tab") however verifies the file integrity. Apparently it computes checksums from the contents and verifies these against checksums embedded in the file to upload. This is the following line at the very end:

**** END OF EXPORT 0428BE3C ****

If there is a mismatch, the upload is rejected with "The specified file is not a valid import file." (If there is a match, the upload is applied without further ado and the router is rebooted. Ouch!)

In the model 7390, one could disable the integrity check by adding NoChecks=yes before the intro string **** CFGFILE:ar7.cfg at the very top (see for example Changing the DNS on your Fritzbox) This apparently no longer works in the 7490 (too many people busting their devices?)

Is there another workaround to make this modern-day version of POKE work?

4
  • 1
    A colleague has actually found a checksum computation algorithm at Fritz!Box VBScripte. Hmmmm... time to test this (and rewrite it to Perl?) Oct 7, 2015 at 20:26
  • You can accept also your own answered question.
    – peterh
    Aug 25, 2017 at 10:31
  • Is it possible to set settings that are rejected by the UI? For example, using the entire Unicode space in the SSID?
    – nikeee
    Oct 11, 2019 at 11:11
  • @nikeee I have no idea. Quite possibly. Oct 11, 2019 at 11:33

1 Answer 1

13
+50

Here is a script which seems to correctly compute the checksum, which turns out to be a CRC32 checksum. In the end, I didn not succeed to clear the DHCP cache of the Fritz!Box via dump/modify/restore of the configuration. Sigh.

#!/usr/bin/perl

# Read Fritz!Box configuration dump file and compute its checksum using CRC32.
# The problem is only knowing what to checksum exactly, and in this case its not pretty.
# Inspired from the very compact Visual Basic script by Michael Engelke available at
# http://www.mengelke.de/Projekte/FritzBoxVBScript 

# The Fritz!Box accepts a modified file where the checksum has been changed 
# manually to the output of this program in the last line. I have no idea what 
# happens if there is a syntax error anywhere inside the config file, so beware.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use strict;
use warnings;

# ---
# Compute CRC start values
# ---

sub build_crc_table() {
   my @crctbl = (); # Accepts 256 values

   for (my $a = 0; $a < 256; $a++) {
      my $c = $a;
      for (my $b = 0; $b < 8; $b++) {
         my $d = $c & 0x01;
         $c = ($c >> 1) & 0x7FFFFFFF;
         if ($d) { 
            $c = $c ^ 0xEDB88320;
         }
      }
      push @crctbl, $c
   }

   my $print = 0;

   if ($print) {
      my $i = 0;
      foreach my $x (@crctbl) {
         print sprintf("CRC table value $i: %08x\n", $x);
         $i++
      }
   }

   return @crctbl
}

# ---
# Transform a string into a vector of its ASCII code points
# ---

sub numerize {
   my ($str) = @_;
   my @res = ();
   foreach my $ch (split('',$str)) {
      push @res, ord($ch)
   }
   return @res;
}

# ---
# Transform a hexstring into a vector of its 8-bit values
# ---

sub hexnumerize {
   my ($str) = @_;
   my @res = ();
   my $tmp;
   my $i = 0;
   foreach my $ch (split('',$str)) {
      if ($i == 0) { $tmp = $ch; $i++; }
      else         { $tmp.= $ch; $i=0; push @res,hex($tmp) } 
   }
   if ($i != 0) {
      die "Irregular hex string: $str\n";
   }
   return @res;
}

# ---
# Compute CRC-32 checksum
# See https://en.wikipedia.org/wiki/Cyclic_redundancy_check
# This must yield 414fa339:
# print sprintf("%08Xi\n", compute_crc32(numerize("The quick brown fox jumps over the lazy dog")));
# ---

sub compute_crc32 {
   my(@data)  = @_;
   my @crctbl = build_crc_table(); # not very efficient on multiple calls
   my $crc = 0xFFFFFFFF;
   my $i = 0;
   foreach my $x (@data) {
      my $index = ($crc ^ $x) & 0xFF;
      $crc = $crctbl[$index] ^ ($crc >> 8); # ">>" is zero-filling in Perl
      $crc = $crc & 0xFFFFFFFF;             # format to 32 bit
      #if ($i > 98700 && $i < 99000) {
      #   print sprintf("$i %c : $x => %08X\n",$x,$crc); 
      #}
      $i++
   }
   return $crc ^ 0xFFFFFFFF
}

# ---
# The name of the configuration file may have been given on the command line. 
# If so slurp the file.
# If nothing has been given, slurp STDIN.
# After that, the input can be found in "@lines" (line terminators are still in there)
# ---

my @lines;

if ($ARGV[0]) {
   open(my $fh,'<',$ARGV[0]) or die "Could not open file '$ARGV[0]': $!";
   @lines = <$fh>;
   close($fh)
}
else {
   @lines = <>;
}

# ---
# Stateful analysis.
# If there are lines after "END OF EXPORT" we will disregard them
# ---

my $firmware;       # will capture firmware version
my $fritzbox;       # will capture name string in header
my @data = ();      # will capture 8-bit values to be CRC-checksummed later
my $cursum;         # will capture the current CRC checksum in the text

# "state" indicates where we are in the file

my $state = 'NOTSTARTED';

# we consider the lines as a stack and push/pop to the stack

@lines = reverse @lines;

while (@lines && $state ne 'END') {

   my $line = pop @lines;

   if ($state eq 'NOTSTARTED') {
      if ($line =~ /^\*{4} (.*?) CONFIGURATION EXPORT/) {
         $fritzbox = $1;
         $state    = 'HEADER'
      }
      else {
         chomp $line;
         die "Expected 'CONFIGURATION EXPORT' in NOTSTARTED state, got '$line'"
      }
      next
   }

   if ($state eq 'HEADER') {
      chomp $line;
      if ($line =~ /(\w+)=([\w\$\.]+)$/) {
         my $key = $1;
         my $val = $2;
         if ($key eq 'FirmwareVersion') { $firmware = $val }
         push @data,numerize($key);
         push @data,numerize($val);
         push @data,0;
      }
      elsif ($line =~ /^\*{4}/) {
         $state = 'NEXTSECTION';
         push @lines,$line
      }
      else {
         die "Unexpected line in HEADER state: '$line'"
      }
      next
   }

   if ($state eq 'NEXTSECTION') {
      chomp $line;
      if ($line =~ /^\*{4} (?:CRYPTED)?(CFG|BIN)FILE:(\S+)/) {
         $state = "INSIDESECTION_$1";
         my $secname = $2;
         print "Section $line\n";
         push @data,numerize($secname);
         push @data,0
      }   
      elsif ($line =~ /^\*{4} END OF EXPORT (\w{8}) \*{4}/) {
         $state  = 'END';
         $cursum = $1
      }
      else {
          die "Unexpected line in NEXTSECTION state: '$line'"
      }
      next
   }

   if ($state eq 'INSIDESECTION_BIN') {
      chomp $line;
      if ($line =~ /^\*{4} END OF FILE \*{4}/) {
         $state = 'NEXTSECTION'
      } 
      else {
         push @data,hexnumerize($line)
      } 
      next
   }

   if ($state eq 'INSIDESECTION_CFG') {
      if ($line =~ /^\*{4} END OF FILE \*{4}/) {
         # Something unbelievably dirty: All section-internal linefeeds (and carriage returns)
         # are kept as is for checksumming, except for the last one before the "END OF FILE"
         if ($data[-1] == ord("\n")) {
            pop @data;
            if ($data[-1] == ord("\r")) {
               pop @data;
            }
         }
         $state = 'NEXTSECTION'
      }
      else {
         # More dirty stuff: Double backspaces are replaced by single ones for some reason.
         $line =~ s/\\\\/\\/g;
         push @data,numerize($line)
      }
      next
   } 

   die "Unexpected state $state"

}

if ($state ne 'END') {
   die 'Did not find proper end of configuration'
}
if (@lines) {
   my $n = @lines;
   print "There are still $n lines left that will be disregarded"
}

my $crc = compute_crc32(@data);
my $newsum = sprintf("%08X", $crc);

print "$fritzbox with firmware $firmware\n";

if ($newsum eq $cursum) {
   print "Checksum is OK: $cursum\n"
}
else {
   print "Found new checksum: $newsum\n";
   print "Checksum embedded in file is $cursum\n"
}
5
  • 1
    Tested the script with my Fritz!Box 7390 and was able to upload modified config.
    – galaxy
    Oct 24, 2015 at 8:57
  • excellent tool! confirming (as galaxy does) it is still working on the 7390. My firmware: 6.30 :)
    – Madivad
    Mar 3, 2016 at 3:35
  • 4
    Check the python version (includes pre-built win32 exe gui) at github.com/mementum/fritzchecksum
    – mementum
    Jun 7, 2016 at 13:43
  • 1
    @David really nice, thank you very much. I can confirm that this is working on the current LAB-BETA (6.69) of the F!Box 7490
    – Kev Inski
    Aug 18, 2016 at 13:22
  • I'm unsure why exactly, but on my 7530 a password is mandatory when exporting and importing the config, but it never accepts the password. Instead, I removed the entire password line, and then just put "a" during import. When restoring all settings this caused the router to factory reset and not import any information, but when selecting "Select manually the settings to be restored" I could pick individual settings and it would work.
    – Baa
    May 9 at 13:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .