Merge branch 'master' of github.com:jclehner/nmrpflash

This commit is contained in:
Joseph C. Lehner 2020-07-06 18:15:44 +02:00
commit c0c951f4a8
8 changed files with 195 additions and 50 deletions

View file

@ -1,13 +1,14 @@
CC ?= gcc
PKG_CONFIG ?= pkg-config
PREFIX ?= /usr/local
VERSION = $(shell git describe --always | tail -c +2)
VERSION := $(shell if [ -d .git ] && which git 2>&1 > /dev/null; then git describe --always | tail -c +2; else echo $$STANDALONE_VERSION; fi)
LIBS = -lpcap
CFLAGS += -Wall -g -DNMRPFLASH_VERSION=\"$(VERSION)\"
LDFLAGS += $(LIBS)
ifeq ($(shell uname -s),Linux)
CFLAGS += $(shell pkg-config libnl-route-3.0 --cflags)
LIBS += $(shell pkg-config libnl-route-3.0 --libs)
CFLAGS += $(shell $(PKG_CONFIG) libnl-route-3.0 --cflags)
LIBS += $(shell $(PKG_CONFIG) libnl-route-3.0 --libs)
endif
ifeq ($(shell uname -s),Darwin)

33
README-R7000.md Normal file
View file

@ -0,0 +1,33 @@
Some helpful hints for putting firmware on the Netgear R7000
============================================================
* As of the writing of this, July 2020, the R7000's web interface does not let you downgrade its firmware, or run 3rd party firmware on it.
* Older versions of the R7000's firmware do allow you to flash 3rd party firmware.
* You can use nrmpflash to downgrade router's firmware, for example R7000-V1.0.3.56_1.1.25.
Here is an example set of steps
1. Plug in your router, go through the regular stock web interface setup. Note if the router's IP address is now 192.168.1.1 or 10.0.0.1
2. Connect computer your computer to LAN1 with an ethernet cable
3. At the command prompt on your computer, run:
`sudo nmrpflash -v -i YOUR_ADAPTER_NAME -f R7000-V1.0.3.56_1.1.25.chk -t 10000 -T 10000 -A 10.0.0.2 -a 10.0.0.1`
* Note 1: The instructions from README.md that tell you how to find YOUR_ADAPTER_NAME.
* Note 2: if your router's IP address was 192.168.1.1 then swap out 10.0.0.x with 192.168.1.x for the two IP addresses above
4. Right after running the command, power on your router. Your router checks for the nmrpflash server on boot. If all goes well you should see this:
```
sudo nmrpflash -v -i enp0s25 -f R7000-V1.0.3.56_1.1.25.chk -t 10000 -T 10000 -A 10.0.0.2 -a 10.0.0.1
Adding 10.0.0.2 to interface enp0s25.
Advertising NMRP server on enp0s25 ... /
Received configuration request from ab:cd:ef:12:34:56.
Sending configuration: 10.0.0.1/24.
Received upload request without filename.
Using remote filename 'R7000-V1.0.3.56_1.1.25.chk'.
Uploading R7000-V1.0.3.56_1.1.25.chk ... OK
Waiting for remote to respond.
Received keep-alive request (19).
Remote finished. Closing connection.
Reboot your device now.
```
5. Reboot the device. You now have old firwmare, congratulations.

View file

@ -3,7 +3,7 @@ nmrpflash - Netgear Unbrick Utility
`nmrpflash` uses Netgear's [NMRP protocol](http://www.chubb.wattle.id.au/PeterChubb/nmrp.html)
to flash a new firmware image to a compatible device. It has been successfully used on a Netgear
EX2700, DNG3700v2, R6220, R7000, D7000, WNR3500, R6400 and R6800, but is likely to be compatible
EX2700, EX6120, EX6150v2, DNG3700v2, R6220, R7000, D7000, WNR3500, R6400 and R6800, WNDR3800, but is likely to be compatible
with many other Netgear devices.
Prebuilt binaries for Linux, ~OS X~ macOS and Windows are available
@ -13,16 +13,17 @@ Prebuilt binaries for Linux, ~OS X~ macOS and Windows are available
```
Usage: nmrpflash [OPTIONS...]
Options (-i and -f and/or -c are mandatory):
Options (-i, and -f or -c are mandatory):
-a <ipaddr> IP address to assign to target device
-A <ipaddr> IP address to assign to interface
-A <ipaddr> IP address to assign to selected interface
-B Blind mode (don't wait for response packets)
-c <command> Command to run before (or instead of) TFTP upload
-f <firmware> Firmware file
-F <filename> Remote filename to use during TFTP upload
-i <interface> Network interface directly connected to device
-m <mac> MAC address of target device (xx:xx:xx:xx:xx:xx)
-M <netmask> Subnet mask to assign to target device
-t <timeout> Timeout (in milliseconds) for regular messages
-t <timeout> Timeout (in milliseconds) for NMRP packets
-T <timeout> Time (seconds) to wait after successfull TFTP upload
-p <port> Port to use for TFTP upload
-R <region> Set device region (NA, WW, GR, PR, RU, BZ, IN, KO, JP)
@ -38,6 +39,12 @@ Your Netgear router must be connected to your network using an
Ethernet cable. The device running `nmrpflash` must be connected
to the same network, using either Wi-Fi or Ethernet.
Usage sequence of events:
1. Turn off the router
2. Connect ethernet cable from computer to router's LAN1
3. Run nmrpflash on command line
4. Turn on the router.
All available network interfaces can be listed using
```
@ -93,9 +100,16 @@ C:\> net start npf
###### "No response after 60 seconds. Bailing out."
The router did not respond. Try rebooting the device and run `nmrpflash` again.
You could also try running `nmrpflash` with `-m` and specify your router's
MAC address. It's also possible that your device does not support the NMRP protocol.
The router did not respond. Always run `nmrpflash` in the sequence
described above!
If that still doesn't work, you can try "blind mode", which can be
invoked using `-B`. Note that you also have to specify your router's
mac address using `-m xx:xx:xx:xx:xx:xx`. Also beware that in this mode,
careful timing between running `nmrpflash` and turning on the router may
be required!
It's also possible that your device does not support the NMRP protocol.
###### "Timeout while waiting for ACK(0)/OACK."
@ -157,6 +171,36 @@ By default, file transfers using TFTP are limited to `65535 * 512` bytes
(almost 32 MiB). Uploading files exceeding this limit might fail, depending
on the device.
###### "Ignoring extra upload request."
Extraneous upload requests are usually sent by the device if the image validation
failed. Some possible causes are:
* If you downloaded a firmware that's contained in an archive (a `.zip` for
example), you must extract this file, and then use the contained firmware file
as the argument to the `-f` parameter. Some examples for file extensions used
for firmware: `.chk`, `.bin`, `.trx`, `.img`.
* Some devices prevent you from downgrading the firmware. See if it works with
the latest version available for your device. If you're already using the latest
version, it might be possible to patch the version info of the firmware file. A
future version of `nmrpflash` might incorporate an auto-patch feature for these
cases.
* Your device might expect a different image format for `nmrpflash` than when
flashing via the web interface.
###### "bind: Cannot assign requested address"
Specify the address of the router, and address of your computer, using
`-A` and `-a`. For example:
`-A 10.0.0.2 -a 10.0.0.1`
or
`-A 192.168.1.2 -a 192.168.1.1`
### Building and installing
###### Linux, Mac OS X, BSDs
@ -173,4 +217,9 @@ project file (`nmrpflash.dev`). Download the latest
and extract it into the root folder of the `nmrpflash` sources.
### Donate
You can [buy me a coffee](https://www.buymeacoffee.com/jclehner) if you want, but please consider
donating the money for charity instead - [Médecins Sans Frontiers](https://www.msf.org/donate) comes to mind,
but any other organization, local or international, that you think deserves support will do. Thank you!

27
main.c
View file

@ -28,16 +28,17 @@ void usage(FILE *fp)
fprintf(fp,
"Usage: nmrpflash [OPTIONS...]\n"
"\n"
"Options (-i, -f and/or -c are mandatory):\n"
"Options (-i, and -f or -c are mandatory):\n"
" -a <ipaddr> IP address to assign to target device\n"
" -A <ipaddr> IP address to assign to seleted interface\n"
" -A <ipaddr> IP address to assign to selected interface\n"
" -B Blind mode (don't wait for response packets)\n"
" -c <command> Command to run before (or instead of) TFTP upload\n"
" -f <firmware> Firmware file\n"
" -F <filename> Remote filename to use during TFTP upload\n"
" -i <interface> Network interface directly connected to device\n"
" -m <mac> MAC address of target device (xx:xx:xx:xx:xx:xx)\n"
" -M <netmask> Subnet mask to assign to target device\n"
" -t <timeout> Timeout (in milliseconds) for regular messages\n"
" -t <timeout> Timeout (in milliseconds) for NMRP packets\n"
" -T <timeout> Time (seconds) to wait after successfull TFTP upload\n"
" -p <port> Port to use for TFTP upload\n"
#ifdef NMRPFLASH_SET_REGION
@ -128,9 +129,9 @@ void require_admin()
int main(int argc, char **argv)
{
int c, val, max;
int list = 0;
bool list = false, have_dest_mac = false;
struct nmrpd_args args = {
.rx_timeout = 200,
.rx_timeout = 200 * 1000,
.ul_timeout = 5 * 60 * 1000,
.tftpcmd = NULL,
.file_local = NULL,
@ -143,6 +144,7 @@ int main(int argc, char **argv)
.op = NMRP_UPLOAD_FW,
.port = 69,
.region = NULL,
.blind = false,
};
#ifdef NMRPFLASH_WINDOWS
char *newpath = NULL;
@ -179,7 +181,7 @@ int main(int argc, char **argv)
opterr = 0;
while ((c = getopt(argc, argv, "a:A:c:f:F:i:m:M:p:R:t:T:hLVvU")) != -1) {
while ((c = getopt(argc, argv, "a:A:Bc:f:F:i:m:M:p:R:t:T:hLVvU")) != -1) {
max = 0x7fffffff;
switch (c) {
case 'a':
@ -188,6 +190,9 @@ int main(int argc, char **argv)
case 'A':
args.ipaddr_intf = optarg;
break;
case 'B':
args.blind = true;
break;
case 'c':
args.tftpcmd = optarg;
break;
@ -202,6 +207,7 @@ int main(int argc, char **argv)
break;
case 'm':
args.mac = optarg;
have_dest_mac = true;
break;
case 'M':
args.ipmask = optarg;
@ -227,7 +233,7 @@ int main(int argc, char **argv)
if (c == 'p') {
args.port = val;
} else if (c == 't') {
args.rx_timeout = val;
args.rx_timeout = val * 1000;
} else if (c == 'T') {
args.ul_timeout = val * 1000;
}
@ -241,7 +247,7 @@ int main(int argc, char **argv)
++verbosity;
break;
case 'L':
list = 1;
list = true;
break;
case 'h':
usage(stdout);
@ -267,6 +273,11 @@ int main(int argc, char **argv)
return 1;
}
if (args.blind && !have_dest_mac) {
fprintf(stderr, "Error: use of -B requires -m <mac>.\n");
return 1;
}
#ifndef NMRPFLASH_FUZZ
if (!list && ((!args.file_local && !args.tftpcmd) || !args.intf)) {
usage(stderr);

88
nmrp.c
View file

@ -357,11 +357,11 @@ int nmrp_do(struct nmrpd_args *args)
uint16_t region;
char *filename;
time_t beg;
int i, status, ulreqs, expect, upload_ok, autoip, kareqs;
int i, timeout, status, ulreqs, expect, upload_ok, autoip, ka_reqs, fake;
struct ethsock *sock;
struct ethsock_ip_undo *ip_undo = NULL;
struct ethsock_arp_undo *arp_undo = NULL;
uint32_t intf_addr;
uint32_t intf_addr = 0;
void (*sigh_orig)(int);
struct in_addr ipaddr;
struct in_addr ipmask;
@ -385,8 +385,13 @@ int nmrp_do(struct nmrpd_args *args)
if (!args->ipaddr) {
autoip = true;
/* The MAC of the device that was used to test this utility starts
* with a4:2b:8c, hence 164 (0xa4) and 183 (0x2b + 0x8c)
/* A random IP address. The MAC of the first device that was
* used to test this utility starts with a4:2b:8c, so we use
* 164 (0xa4) and 183 (0x2b + 0x8c).
*
* These addresses should not cause collisions on most networks,
* and if they do, the user is probably "poweruser" enough to
* be able to use the -a and -A options.
*/
args->ipaddr = "10.164.183.252";
@ -477,6 +482,8 @@ int nmrp_do(struct nmrpd_args *args)
i = 0;
upload_ok = 0;
fake = 0;
timeout = args->blind ? 10 : NMRP_INITIAL_TIMEOUT;
beg = time_monotonic();
while (!g_interrupted) {
@ -486,7 +493,6 @@ int nmrp_do(struct nmrpd_args *args)
i = (i + 1) & 3;
if (pkt_send(sock, &tx) < 0) {
xperror("sendto");
goto out;
}
@ -504,18 +510,34 @@ int nmrp_do(struct nmrpd_args *args)
} else {
/* because we don't want nmrpflash's exit status to be zero */
status = 1;
if ((time_monotonic() - beg) >= NMRP_INITIAL_TIMEOUT) {
printf("\nNo response after 60 seconds. Bailing out.\n");
goto out;
if ((time_monotonic() - beg) >= timeout) {
printf("\nNo response after %d seconds. ", timeout);
if (!args->blind) {
printf("Bailing out.\n");
goto out;
} else {
// we're blind, so fake a response from the MAC specified by -m
memcpy(rx.eh.ether_shost, dest, 6);
msg_init(&rx.msg, NMRP_C_CONF_REQ);
printf("Continuing blindly.");
fake = 1;
break;
}
}
}
}
printf("\n");
memcpy(tx.eh.ether_dhost, rx.eh.ether_shost, 6);
if (ethsock_arp_add(sock, rx.eh.ether_shost, ipaddr.s_addr, &arp_undo) != 0) {
goto out;
}
expect = NMRP_C_CONF_REQ;
ulreqs = 0;
kareqs = 0;
ka_reqs = 0;
while (!g_interrupted) {
if (expect != NMRP_C_NONE && rx.msg.code != expect) {
@ -537,18 +559,14 @@ int nmrp_do(struct nmrpd_args *args)
msg_mkconfack(&tx.msg, ipaddr.s_addr, ipmask.s_addr, region);
expect = NMRP_C_TFTP_UL_REQ;
printf("Received configuration request from %s.\n",
mac_to_str(rx.eh.ether_shost));
memcpy(tx.eh.ether_dhost, rx.eh.ether_shost, 6);
if (!fake) {
printf("Received configuration request from %s.\n",
mac_to_str(rx.eh.ether_shost));
}
printf("Sending configuration: %s/%d.\n",
args->ipaddr, bitcount(ipmask.s_addr));
if (ethsock_arp_add(sock, rx.eh.ether_shost, ipaddr.s_addr, &arp_undo) != 0) {
goto out;
}
break;
case NMRP_C_TFTP_UL_REQ:
if (!upload_ok) {
@ -575,7 +593,9 @@ int nmrp_do(struct nmrpd_args *args)
printf("Received upload request: filename '%s'.\n", filename);
} else if (!args->file_remote) {
args->file_remote = leafname(args->file_local);
printf("Received upload request without filename.\n");
if (!fake) {
printf("Received upload request without filename.\n");
}
}
status = 0;
@ -621,6 +641,10 @@ int nmrp_do(struct nmrpd_args *args)
}
if (!status) {
if (args->blind) {
goto out;
}
printf("Waiting for remote to respond.\n");
upload_ok = 1;
ethsock_set_timeout(sock, args->ul_timeout);
@ -636,7 +660,7 @@ int nmrp_do(struct nmrpd_args *args)
case NMRP_C_KEEP_ALIVE_REQ:
tx.msg.code = NMRP_C_KEEP_ALIVE_ACK;
ethsock_set_timeout(sock, args->ul_timeout);
printf("\rReceived keep-alive request (%d). ", ++kareqs);
printf("\rReceived keep-alive request (%d). ", ++ka_reqs);
break;
case NMRP_C_CLOSE_REQ:
tx.msg.code = NMRP_C_CLOSE_ACK;
@ -651,18 +675,13 @@ int nmrp_do(struct nmrpd_args *args)
}
if (tx.msg.code != NMRP_C_NONE) {
if (pkt_send(sock, &tx) < 0) {
xperror("sendto");
goto out;
}
if (tx.msg.code == NMRP_C_CLOSE_REQ) {
if (pkt_send(sock, &tx) != 0 || tx.msg.code == NMRP_C_CLOSE_REQ) {
goto out;
}
}
if (rx.msg.code == NMRP_C_CLOSE_REQ) {
if (kareqs) {
if (ka_reqs) {
printf("\n");
}
@ -673,10 +692,21 @@ int nmrp_do(struct nmrpd_args *args)
status = pkt_recv(sock, &rx);
if (status) {
if (status == 2) {
fprintf(stderr, "Timeout while waiting for %s.\n",
msg_code_str(expect));
if (!args->blind) {
fprintf(stderr, "Timeout while waiting for %s.\n",
msg_code_str(expect));
goto out;
}
// fake response
msg_init(&rx.msg, expect);
memcpy(rx.eh.ether_shost, tx.eh.ether_dhost, 6);
fake = 1;
} else {
goto out;
}
goto out;
} else {
fake = 0;
}
ethsock_set_timeout(sock, args->rx_timeout);

View file

@ -62,6 +62,10 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef PACKED
#define PACKED __attribute__((packed))
#endif
@ -92,6 +96,7 @@ struct nmrpd_args {
const char *intf;
const char *mac;
enum nmrp_op op;
bool blind;
uint16_t port;
const char *region;
};

19
tftp.c
View file

@ -317,6 +317,8 @@ inline bool tftp_is_valid_filename(const char *filename)
return strlen(filename) <= 255 && is_netascii(filename);
}
static const char *spinner = "\\|/-";
int tftp_put(struct nmrpd_args *args)
{
struct sockaddr_in addr;
@ -327,6 +329,8 @@ int tftp_put(struct nmrpd_args *args)
const char *file_remote = args->file_remote;
char *val, *end;
bool rollover;
const unsigned rx_timeout = MAX(args->rx_timeout / (args->blind ? 50 : 5), 2000);
const unsigned max_timeouts = args->blind ? 3 : 5;
sock = -1;
ret = -1;
@ -383,6 +387,7 @@ int tftp_put(struct nmrpd_args *args)
xperror("inet_addr");
goto cleanup;
}
addr.sin_port = htons(args->port);
blksize = 512;
@ -429,6 +434,10 @@ int tftp_put(struct nmrpd_args *args)
}
}
printf("%c ", spinner[block & 3]);
fflush(stdout);
printf("\b\b");
pkt_mknum(tx, DATA);
pkt_mknum(tx + 2, block);
len = read(fd, tx + 4, blksize);
@ -463,11 +472,17 @@ int tftp_put(struct nmrpd_args *args)
}
}
ret = tftp_recvfrom(sock, rx, &port, args->rx_timeout, blksize + 4);
ret = tftp_recvfrom(sock, rx, &port, rx_timeout, blksize + 4);
if (ret < 0) {
goto cleanup;
} else if (!ret) {
if (++timeouts < 5 || (!block && timeouts < 10)) {
if (++timeouts < max_timeouts || (!block && timeouts < (max_timeouts * 4))) {
continue;
} else if (args->blind) {
timeouts = 0;
// fake an ACK packet
pkt_mknum(rx, ACK);
pkt_mknum(rx + 2, block);
continue;
} else if (block) {
fprintf(stderr, "Timeout while waiting for ACK(%d).\n", block);

View file

@ -52,13 +52,14 @@ function nmrp_dissect_opt(opt, buffer, tree)
return
end
tree:add(opt_len_f, buffer(2, 2))
if opt == 0x01 or opt == 0x0181 then
tree:add(buffer(4), "Value: " .. buffer(4):string())
elseif opt == 0x02 then
tree:add(buffer(4, 4), "Address: " .. tostring(buffer(4, 4):ipv4()))
tree:add(buffer(8, 4), "Netmask: " .. tostring(buffer(8, 4):ipv4()))
else
tree:add(opt_len_f, buffer(2, 2))
tree:add(opt_data_f, buffer(4, buffer:len() - 4))
end
end