diff --git a/Makefile b/Makefile index 80793df..76c1f81 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/README-R7000.md b/README-R7000.md new file mode 100644 index 0000000..9c6ef3c --- /dev/null +++ b/README-R7000.md @@ -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. + diff --git a/README.md b/README.md index ee9e516..c16efc3 100644 --- a/README.md +++ b/README.md @@ -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 IP address to assign to target device - -A IP address to assign to interface + -A IP address to assign to selected interface + -B Blind mode (don't wait for response packets) -c Command to run before (or instead of) TFTP upload -f Firmware file -F Remote filename to use during TFTP upload -i Network interface directly connected to device -m MAC address of target device (xx:xx:xx:xx:xx:xx) -M Subnet mask to assign to target device - -t Timeout (in milliseconds) for regular messages + -t Timeout (in milliseconds) for NMRP packets -T Time (seconds) to wait after successfull TFTP upload -p Port to use for TFTP upload -R 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! diff --git a/main.c b/main.c index b21fee4..3e7968d 100644 --- a/main.c +++ b/main.c @@ -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 IP address to assign to target device\n" - " -A IP address to assign to seleted interface\n" + " -A IP address to assign to selected interface\n" + " -B Blind mode (don't wait for response packets)\n" " -c Command to run before (or instead of) TFTP upload\n" " -f Firmware file\n" " -F Remote filename to use during TFTP upload\n" " -i Network interface directly connected to device\n" " -m MAC address of target device (xx:xx:xx:xx:xx:xx)\n" " -M Subnet mask to assign to target device\n" - " -t Timeout (in milliseconds) for regular messages\n" + " -t Timeout (in milliseconds) for NMRP packets\n" " -T Time (seconds) to wait after successfull TFTP upload\n" " -p 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 .\n"); + return 1; + } + #ifndef NMRPFLASH_FUZZ if (!list && ((!args.file_local && !args.tftpcmd) || !args.intf)) { usage(stderr); diff --git a/nmrp.c b/nmrp.c index 41cc07d..a476c5f 100644 --- a/nmrp.c +++ b/nmrp.c @@ -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); diff --git a/nmrpd.h b/nmrpd.h index 0773777..d65c811 100644 --- a/nmrpd.h +++ b/nmrpd.h @@ -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; }; diff --git a/tftp.c b/tftp.c index 3cbf7c6..16fa591 100644 --- a/tftp.c +++ b/tftp.c @@ -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); diff --git a/wireshark-nmrp.lua b/wireshark-nmrp.lua index 6463ccf..234d9eb 100644 --- a/wireshark-nmrp.lua +++ b/wireshark-nmrp.lua @@ -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