Skip to content

Instantly share code, notes, and snippets.

@aplocher
Last active May 20, 2021 20:19
Show Gist options
  • Save aplocher/7d717925fe00c6602bed to your computer and use it in GitHub Desktop.
Save aplocher/7d717925fe00c6602bed to your computer and use it in GitHub Desktop.
Patch router firmware files for WNDR3800SW (SureWest specific) router model. Can convert any Netgear WNDR3800 firmware img to work with WNDR3800SW. Requires perl.exe currently.
== patch3800sw.ps1 ==
Requires perl.exe to be in the same folder or in the environment PATH (for generating checksum)!
Patch firmware .img files used for Netgear WNDR3800 routers to work with Netgear WNDR3800SW
The SW model routers are specfic to SureWest Communications (ISP from northern CA)
The routers are identical, but require a different header on the .img file. This should work with any
.img file that works with a WNDR3800, including DD-WRT, OpenWRT, and the Netgear factory firmware
-- Example Command (PowerShell):
PS C:\Temp> .\patch3800sw.ps1 "C:\users\adam\Downloads\openwrt-ar71xx-generic-wndr3800-squashfs-factory(2).img" "C:\temp\test333.img"
-- Example Result:
===========================================================
PATCHING INPUT FILE FOR SUREWEST NETGEAR WNDR3800SW ROUTERS
===========================================================
-Reading input file
-Removing existing checksum byte
-Patching first 128 bytes of file with WNDR3800SW signature
-Attempting to generate new checksum byte
-Perl.exe found, generating and executing script
* Append checksum => file : C:\Users\adam\AppData\Local\Temp\tmp5794.tmp, len : 0x340084, checksum : 0x63
-Finished
-New file - C:\temp\test333.img
-Patched and ready to flash on WNDR3800SW (if there weren't any errors)
# REQUIRES PERL.EXE!
#
# Patch firmware .img files used for Netgear WNDR3800 routers to work with Netgear WNDR3800SW
# The SW model routers are specfic to SureWest Communications (ISP from northern CA)
# The routers are identical, but require a different header on the .img file. This should work with any
# .img file that works with a WNDR3800, including DD-WRT, OpenWRT, and the Netgear factory firmware
#
## Usage: .\patch3800sw <input img file path> <output img file path>
#
## Example: .\patch3800sw openwrt-ar71xx-generic-wndr3800-squashfs-factory.img openwrt-wndr3800sw.img
#
## Requires: perl.exe must be installed or in the same folder as this script!
#
## Find updates at http://gist.github.com/aplocher
#
## All actual reverse engineering/hacking/whatevering was done by the folks over at OpenWRT.org. They
## deserve all the real credit https://forum.openwrt.org/viewtopic.php?id=39142
#
## If anyone would like to port the perl script portion over to PowerShell, I would be happy to update
## this with your contributions. I hate requiring perl.exe!
Param(
[string]$fwImgInputPath,
[string]$fwImgOutputPath
)
Write-Output "==========================================================="
Write-Output "PATCHING INPUT FILE FOR SUREWEST NETGEAR WNDR3800SW ROUTERS"
Write-Output "==========================================================="
$fwImgInputPath = Convert-Path $fwImgInputPath
if ($fwImgInputPath -eq $null -or $fwImgInputPath -eq "" -or -not (Test-Path $fwImgInputPath)) { Write-Error "File not found"; Exit; }
$newBytes = 0x64,0x65,0x76,0x69,0x63,0x65,0x3A,0x57,0x4E,0x44,0x52,0x33,0x38,0x30,0x30,0x53,0x57,0x0A,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x3A,0x56,0x31,
0x2E,0x30,0x2E,0x30,0x2E,0x39,0x39,0x53,0x57,0x0A,0x72,0x65,0x67,0x69,0x6F,0x6E,0x3A,0x0A,0x68,0x64,0x5F,0x69,0x64,0x3A,0x32,0x39,0x37,0x36,
0x33,0x36,0x35,0x34,0x2B,0x31,0x36,0x2B,0x31,0x32,0x38,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
Write-Output "-Reading input file"
$inputFileBytes = [System.IO.File]::ReadAllBytes($fwImgInputPath)
$bytes = $inputFileBytes[1..($inputFileBytes.Length-1)]
Write-Output "-Removing existing checksum byte"
[array]::Copy($inputFileBytes, $bytes, $bytes.Length)
Write-Output "-Patching first 128 bytes of file with WNDR3800SW signature"
[array]::Copy($newBytes,0,$bytes,0,128)
New-Item $fwImgOutputPath -Force -ea SilentlyContinue | Out-Null
$fwImgOutputPath = Convert-Path $fwImgOutputPath
[System.IO.File]::WriteAllBytes($fwImgOutputPath, $bytes)
Write-Output "-Attempting to generate new checksum byte"
$perlExe = "perl.exe"
if (Get-Command ".\perl.exe" -ea SilentlyContinue) {$perlExe = ".\perl.exe"}
if ((Get-Command "$($perlExe)" -ea SilentlyContinue) -ne $null) {
Write-Output "-Perl.exe found, generating and executing script"
$perlPath = (Get-Command $perlExe).Path
$perlScript = "#!/usr/bin/perl -w`
`
use strict;`
`
my `$TempCompFileName = shift(@ARGV);`
my `$OutFileName = shift(@ARGV);`
`
my (`$start_addr, `$appd_cks, `$appd_len) = (0);`
`
sub appendCks`
{`
my (`$fileName) = @_;`
my (`$cks, `$len) = (0, 0);`
`
open APPENDCKS_FH, `"+<`$fileName`" or die `"fail to open file `$fileName : `$!`";`
until ( eof APPENDCKS_FH ) {`
`$cks = ( `$cks + ord( getc( APPENDCKS_FH )) ) % 0x100 ;`
`$len++ ;`
}`
`
`$cks = 0xFF - `$cks;`
close APPENDCKS_FH;`
`
printf (`" * Append checksum => file : %s, len : 0x%X, checksum : 0x%X \n`", , `$fileName, `$len, `$cks);`
`$appd_cks =`$cks;`
`$appd_len =`$len;`
}`
`
`
appendCks(`$TempCompFileName);`
`
# Then append Checksum (include the 4bytes starting address) `
open INS_FH, `"`$TempCompFileName`" or die `"fail to open file `$TempCompFileName : `$!`";`
open OUT_FH, `">`$OutFileName`" or die `"fail to Append file `$OutFileName: `$!`";`
while (<INS_FH>)`
{`
print OUT_FH `$_;`
}`
print OUT_FH chr(`$appd_cks); `
close INS_FH;`
close OUT_FH;"
$checksumPlPath = Join-Path $env:TEMP "GenChecksum.pl"
$newPerlPath = Join-Path $env:TEMP "newperl.exe"
Copy-Item -Force $perlPath $newPerlPath
$perlScript | Out-File $checksumPlPath -Force
$tmp = New-TemporaryFile
Move-Item "$($fwImgOutputPath)" "$($tmp)" -Force
c:\windows\system32\cmd.exe /c ""$perlPath" "$($checksumPlPath)" "$($tmp)" "$($fwImgOutputPath)""
Write-Output "-Finished"
Write-Output "-New file - $($fwImgOutputPath)"
Write-Output "-Patched and ready to flash on WNDR3800SW (if there weren't any errors)"
}
else {
Write-Warning "-Perl.exe not found. New file generated but no valid checksum byte created"
Write-Output "-New file: $($fwImgOutputPath)"
}
@jphein
Copy link

jphein commented Oct 19, 2018

@dpelletier, I have the same WNDR4300SW router. I'd love to install a new firmware, but I'm stuck at compiling the C program. I'm using Linux as well. Can you provide a precompiled file? Or even a precompiled image?
@geoff, Here is the code I tried to compile:

/*
* Convert a binary image that was created for WNDR4300 to use on a WNDR4300sw.
* Usage: ./patch4300V1.0.0.6SW file.img
* Note: file.img is converted in place.
/
#include <stdio.h>
/
* 128-byte header for WNDR4300SW.
*/

static char sw[] = {
0x64,0x65,0x76,0x69,0x63,0x65,0x3A,0x57,0x4E,0x44,0x52,0x34,0x33,0x30,0x30,0x53,
0x57,0x0A,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x3A,0x56,0x31,0x2E,0x30,0x2E,0x30,
0x2E,0x36,0x53,0x57,0x0A,0x72,0x65,0x67,0x69,0x6F,0x6E,0x3A,0x0A,0x68,0x64,0x5F,
0x69,0x64,0x3A,0x32,0x39,0x37,0x36,0x33,0x39,0x34,0x38,0x2B,0x30,0x2B,0x31,0x32,
0x38,0x2B,0x31,0x32,0x38,0x2B,0x32,0x78,0x32,0x2B,0x33,0x78,0x33,0x0A,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

main(int argc, char *argv[]) {
    
unsigned int i; long unsigned int j, n; FILE *f;

    /*

     \* Open the file.

     */

    if (argc < 2) return 1;

    if ((f = fopen(argv[1], "r+")) == NULL) return 2;

    /*

     \*  Replace the first 128 bytes with the header for WNDR3800SW.

     */

    for (i=0; i<sizeof(sw); i++) fputc(sw[i], f);

    /*

     \* Get the file size.

     */

    if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);

    /*

     \* Replace the last byte with one that results in a CRC of 0xFF.

     */

    if (fseek(f, 0L, SEEK_SET) != 0) return 4;

    for (i=0, j=1; j<n; j++) i = (i + (unsigned int)fgetc(f)) % 0x100;

    fputc((char)(0xFF - i), f);

    /*

     \* Close the file.

     */

    if (fclose(f) != 0) return 5;

    printf("The image file %s was converted successfully\n", argv[1]);

    return 0;

}

Here are the errors I encountered when running: gcc -o 4300sw wndr4300sw.c

wndr4300sw.c:23:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main(int argc, char *argv[]) {
 ^
wndr4300sw.c: In function ‘main’:
wndr4300sw.c:25:41: error: unknown type name ‘FILE’
 unsigned int i; long unsigned int j, n; FILE *f;
                                         ^
wndr4300sw.c:35:14: warning: implicit declaration of function ‘fopen’ [-Wimplicit-function-declaration]
     if ((f = fopen(argv[1], "r+")) == NULL) return 2;
              ^
wndr4300sw.c:35:12: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
     if ((f = fopen(argv[1], "r+")) == NULL) return 2;
            ^
wndr4300sw.c:35:39: error: ‘NULL’ undeclared (first use in this function)
     if ((f = fopen(argv[1], "r+")) == NULL) return 2;
                                       ^
wndr4300sw.c:35:39: note: each undeclared identifier is reported only once for each function it appears in
wndr4300sw.c:43:34: warning: implicit declaration of function ‘fputc’ [-Wimplicit-function-declaration]
     for (i=0; i<sizeof(sw); i++) fputc(sw[i], f);
                                  ^
wndr4300sw.c:51:9: warning: implicit declaration of function ‘fseek’ [-Wimplicit-function-declaration]
     if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);
         ^
wndr4300sw.c:51:22: error: ‘SEEK_END’ undeclared (first use in this function)
     if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);
                      ^
wndr4300sw.c:51:52: warning: implicit declaration of function ‘ftell’ [-Wimplicit-function-declaration]
     if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);
                                                    ^
wndr4300sw.c:59:22: error: ‘SEEK_SET’ undeclared (first use in this function)
     if (fseek(f, 0L, SEEK_SET) != 0) return 4;
                      ^
wndr4300sw.c:61:53: warning: implicit declaration of function ‘fgetc’ [-Wimplicit-function-declaration]
     for (i=0, j=1; j<n; j++) i = (i + (unsigned int)fgetc(f)) % 0x100;
                                                     ^
wndr4300sw.c:71:9: warning: implicit declaration of function ‘fclose’ [-Wimplicit-function-declaration]
     if (fclose(f) != 0) return 5;
         ^
wndr4300sw.c:73:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
     printf("The image file %s was converted successfully\n", argv[1]);
     ^
wndr4300sw.c:73:5: warning: incompatible implicit declaration of built-in function ‘printf’
wndr4300sw.c:73:5: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’

@jphein
Copy link

jphein commented Oct 21, 2018

For anyone concerned, there were some missing *'s in @dpelletier's code. The correct C program for modifying firmware for the WNDR4300SW is below:


/*
* Convert a binary image that was created for WNDR4300 to use on a WNDR4300sw.
* Usage: ./patch4300V1.0.0.6SW file.img
* Note: file.img is converted in place.
*/

#include <stdio.h>

/*
* 128-byte header for WNDR4300SW.
*/

static char sw[] = {
0x64,0x65,0x76,0x69,0x63,0x65,0x3A,0x57,0x4E,0x44,0x52,0x34,0x33,0x30,0x30,0x53,
0x57,0x0A,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x3A,0x56,0x31,0x2E,0x30,0x2E,0x30,
0x2E,0x36,0x53,0x57,0x0A,0x72,0x65,0x67,0x69,0x6F,0x6E,0x3A,0x0A,0x68,0x64,0x5F,
0x69,0x64,0x3A,0x32,0x39,0x37,0x36,0x33,0x39,0x34,0x38,0x2B,0x30,0x2B,0x31,0x32,
0x38,0x2B,0x31,0x32,0x38,0x2B,0x32,0x78,0x32,0x2B,0x33,0x78,0x33,0x0A,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

main(int argc, char *argv[]) {
    
unsigned int i; long unsigned int j, n; FILE* f;

    /*

     \* Open the file.

     */

    if (argc < 2) return 1;

    if ((f = fopen(argv[1], "r+")) == NULL) return 2;

    /*

     \*  Replace the first 128 bytes with the header for WNDR3800SW.

     */

    for (i=0; i<sizeof(sw); i++) fputc(sw[i], f);

    /*

     \* Get the file size.

     */

    if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);

    /*

     \* Replace the last byte with one that results in a CRC of 0xFF.

     */

    if (fseek(f, 0L, SEEK_SET) != 0) return 4;

    for (i=0, j=1; j<n; j++) i = (i + (unsigned int)fgetc(f)) % 0x100;

    fputc((char)(0xFF - i), f);

    /*

     \* Close the file.

     */

    if (fclose(f) != 0) return 5;

    printf("The image file %s was converted successfully\n", argv[1]);

    return 0;

}

@jphein
Copy link

jphein commented Oct 21, 2018

Thanks so much! I just wanted to add a bit of information:
The stock WNDR4300SW default credentials are admin/cciadmin. NOT admin/password like the WNDR4300.
I have successfully installed the latest DDWRT firmware for the WNDR4300 on this device.

@unclemiltie
Copy link

Sorry to be late to the party but...

I got my hands on one of those WNDR4300SW routers with a business I just bought and wanted to see if I could update it and make it a bit more reliable. The CCI software seems flakey

Anyway, I downloaded and compiled the above program and ran it against the latest OpenWRT for the 4300, the program completes and says that the file was converted successfully

BUT it won't load. The 4300 SW (using its built in firmware upgrade method) says the the firmware is the incorrect firmware.

Any suggestions (other than buying another router, that's the easy way out!)

@aplocher
Copy link
Author

It's been quite a while since I threw this together but if I recall those first bytes were extracted from one of the (old outdated) 3800sw firmware files and then written over the first bytes of the new 3800 (non sw) firmwares. That's essentially all this is doing. I don't recall exactly the number of bytes but if you open it in a hex editor I think it's pretty obvious. Maybe 1024 bytes or something makes up the header. I'm on my phone, but when I get to my computer tomorrow I can provide a little more info.

Without this script you can literally just use a hex editor to copy and paste the first bytes from the sw file over the bytes in the newer non sw file and the router will allow you to flash it... At least with the 3800sw.

@aplocher
Copy link
Author

Ahh I forgot about the checksum byte too (just read the old comment history)... Last byte of the file must be removed and then calc a new checksum that gets added to the end (1 byte).

@unclemiltie
Copy link

Mine's a 4300 and not a 3800, and I used the modified version of your code, by jphein above to do the work.

If I use my rudimentary C skills I see that it does build a checksum and changes it at the end

is there a way to download the image that is in the router so that I can compare the headers on the two files to see what is different? or where could I get my hands on the img file for the 4300SW so that I could compare them?

Thanks for the response!

@uragit
Copy link

uragit commented May 20, 2021

Nice! I used geoffch's C version. Saved my ass when I ended up with a used WNDR3800 from ebay that turned out to be a WNDR3800SW. Worked flawlessly. Thanks guys!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment