Skip to content

Instantly share code, notes, and snippets.

@aplocher
Last active May 20, 2021 20:19
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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)"
}
@geoffch
Copy link

geoffch commented Aug 25, 2016

Thank you for this script! Although I couldn't get it to work verbatim, I found by trial and error starting with your perl code that I could get this C program to do the job.

  1. /*
  2. * Convert a binary image that was created for WNDR3800 to use on a WNDR3800sw.
  3. * Usage: ./patch3800sw file.img
  4. * Note: file.img is converted in place.
  5. */
  6. #include <stdio.h>
  7. /*
  8. * 128-byte header for WNDR3800SW.
  9. */
  10. static char sw[] = {
  11. 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x3A, 0x57, 0x4E, 0x44, 0x52, 0x33, 0x38, 0x30, 0x30, 0x53,
    
  12. 0x57, 0x0A, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x56, 0x31, 0x2E, 0x30, 0x2E, 0x30,
    
  13. 0x2E, 0x39, 0x39, 0x53, 0x57, 0x0A, 0x72, 0x65, 0x67, 0x69, 0x6F, 0x6E, 0x3A, 0x0A, 0x68, 0x64,
    
  14. 0x5F, 0x69, 0x64, 0x3A, 0x32, 0x39, 0x37, 0x36, 0x33, 0x36, 0x35, 0x34, 0x2B, 0x31, 0x36, 0x2B,
    
  15. 0x31, 0x32, 0x38, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
  16. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
  17. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
  18. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    
  19. };
  20. main(int argc, char *argv[]) {
  21. unsigned int i; long unsigned int j, n; FILE *f;
    
  22. /*
    
  23.  \* Open the file.
    
  24.  */
    
  25. if (argc < 2) return 1;
    
  26. if ((f = fopen(argv[1], "r+")) == NULL) return 2;
    
  27. /*
    
  28.  \*  Replace the first 128 bytes with the header for WNDR3800SW.
    
  29.  */
    
  30. for (i=0; i<sizeof(sw); i++) fputc(sw[i], f);
    
  31. /*
    
  32.  \* Get the file size.
    
  33.  */
    
  34. if (fseek(f, 0L, SEEK_END) != 0) return 3; n = ftell(f);
    
  35. /*
    
  36.  \* Replace the last byte with one that results in a CRC of 0xFF.
    
  37.  */
    
  38. if (fseek(f, 0L, SEEK_SET) != 0) return 4;
    
  39. for (i=0, j=1; j<n; j++) i = (i + (unsigned int)fgetc(f)) % 0x100;
    
  40. fputc((char)(0xFF - i), f);
    
  41. /*
    
  42.  \* Close the file.
    
  43.  */
    
  44. if (fclose(f) != 0) return 5;
    
  45. printf("The image file %s was converted successfully\n", argv[1]);
    
  46. return 0;
    
  47. }

@zanedb
Copy link

zanedb commented Jan 9, 2017

Does this work for WNDR4300SW models? I was unable to get this to work. Do they possibly use a different 128-bit header?

@aplocher
Copy link
Author

Sorry for the very late response. I should have noted that I absolutely loathe Perl. Also, if I recall, I tried using this a few months after I threw it together with a plain jane perl setup from perl.org (I assume) and it didn't work. What I ended up using was Perl.exe included with Git (I THINK - I KNOW IT WAS INCLUDED WITH SOMETHING ELSE). So however Git pre-configured perl, was how I had to run it. There might have been some additional hacky steps too, sorry :-)

@aplocher
Copy link
Author

aplocher commented Mar 17, 2017

But to answer your question @zaneweb, as-is, it won't work with the 4300 because the bytes specifically mention "3800sw".

If you take a look at the beginning of a "4300sw" img file in a hex editor, you will see that the first 128 bytes (I think) have some identifying information that mentions "sw" or "surewest". Basically, those first 128 bytes must be included on all images before it'll let you install it. If it's like the 3800, you can copy these bytes with the hex editor to another img (latest update from Netgear).

Unfortunately, there's a checksum as well, which if I recall is the part that Perl is handling (or @geoff's C code). So after you replace the header, you must remove the last checksum byte from the img, recalc a new checksum, and append it to the img as the last byte(s?). Perhaps the checksum calculation is the same between the 3800 and 4300. If that's the case, then you might be able to just manually replace the header and then use the Perl or Geoff's C code in the previous comment to reapply a new checksum.

Don't kill me if I'm wrong, but I believe it's fairly safe to try. If it didn't get signed properly, it will not allow it to install. If it is something that bricks it, there's a recovery mode that you can load the source img from (just make sure you have the 4300sw img).

@geoff Thank you for the code, I would like to replace what I have here (preferably in a simple script that can be copied around). Although to be honest, I'll probably never touch this code again

@M0nty-Pyth0n
Copy link

M0nty-Pyth0n commented Jul 19, 2018

I know this is an older post but I was also looking for a way to upgrade my WNDR4300 router from SureWest that was practically useless and needing to be rebooted every 3 - 5 days with the default netgear firmware... And I figured that @zanedb would also be interested since the question was asked about adapting to the 4300 router.

Following on @aplocher 's comments, I looked into giving it a try.

Viewing the original WNDR4300SW-V1.0.0.6SW.img from SureWest in a hex editor I could see that the header includes:

device:WNDR4300SW
version:V1.0.0.6SW
region:
hd_id:29763948+0+128+128+2x2+3x3

and its corresponding 128 bytes are shown below.

I started to edit the perl script written by @aplocher and realized shortly afterwards that it was written for windows so I switched to the c program by @geoffch so I could just run it on my Linux PC -- thank you everyone for all of this :)

I replaced the data structures to use the 128 bytes for the WNDR4300sw:

For the perl script (for those interested in the perl script):

$newBytes = 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

And for the c program:

` 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

};`

I could include the c program here but it only involves changing the sw[] array and also replacing mentions of WNDR3800 with WNDR4300.

Basically only the beginning of @geoffch 's program was modified for WNDR4300SW 's 128 byte header:

` /*
* 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

};`

And the checksum is correctly updated by the c program.

@M0nty-Pyth0n
Copy link

M0nty-Pyth0n commented Jul 21, 2018

My Netgear WNDR4300v1 (SW) router is now happily running the latest OpenWRT-Lede firmware (lede-17.01.4-ar71xx-nand-wndr4300-ubi-factory).

I could see that surewest's last supplied firmware was about 5 years old... and was actually running a modified (but ancient) version of OpenWRT (MIPS OpenWrt Linux-2.6.31 vs MIPS OpenWrt Linux-4.4.92). It seems like SureWest only bothered to create one update (ever) to this router. Good riddens! I might also just look into changing my Netgear 6300 router now.

(Unrelated) I only had an initial scare with the 5G not showing a signal at all. From the admin GUI even though I enabled / configured 5G, it never showed a signal (strength or bars) -- although I could connect to it from a device. I rebooted and a after a few minutes, the interface (gui) showed a signal.

Related to what @aplocher mentioned, this should let you convert any Netgear WNDR4300 firmware img to work with WNDR4300SW. The speeds are much much better and I love OpenWRT-Lede's admin GUI and the possible configurations.

I hope this helps anyone else frustrated with their Netgear WNDR4300 (SW) router but unable to update to alternative firmware.
For those interested in the updated img firmware for the WNDR4300 (ie: not interested in updating and running a program to do the conversion) I could probably send the file to you.

@M0nty-Pyth0n
Copy link

Thanks again to @aplocher for starting this thread and to @geoffch for the c code! If possible, @aplocher could you add tags for patch4300SW or WNDR4300SW or the like? The openwrt page (https://openwrt.org/toh/netgear/wndr4300) only briefly mentions the need to change the header and checksum to update the surewest router with no additional info (I understand why though). This page helped out tremendously. Thanks again.

@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