From 1eb30867f00df13f0d561ef6dfa4911e5996ced8 Mon Sep 17 00:00:00 2001 From: c vw Date: Fri, 5 Aug 2022 10:13:12 +0200 Subject: [PATCH] Added a bootloader for the "ancient" (TCP-snifffing) bootloader protocol. --- bootloader.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 bootloader.c diff --git a/bootloader.c b/bootloader.c new file mode 100644 index 0000000..09c42cc --- /dev/null +++ b/bootloader.c @@ -0,0 +1,510 @@ +/////////////////////////////////////////////////////////////////////////// +// +// HPSDR protocol-1 bootloader +// +// This program successfully communicates with the protocol-1 bootloader +// insider the SDR. +// It must be run as root since it "sniffs" on the Ethernet interface to +// look for packets from the radio (using the pcap library), +// so this is "spy-ware" but that's the requirement of the P1 bootloader. +// +// This is the reason why you cannot use this program accross a VPN or +// a managed switch. This is based on illegal (or let's say non-routable) +// data packets. You either have to use a "dump switch" or (better) +// a direct-cable-connection. +// +// For older ANAN radios, you have to place a jumper on the HPSDR board +// to put the board into "bootloader" mode. More recent radios are more +// comfortable, you simply have to change a switch (at the bottom or +// the back panel of the case) from "normal" to "bootloader" position. +// +// The reason to have a bootloader depending on "non-routable" packets is, +// that the radio probably does not even need an IP address to communicate +// with the bootloader. All packets to/from the radio are identified by +// the (bogus) MAC address 11:22:33:44:55:66 that the radio is using. The +// main advantage of this "archaic" protocol is that with most ANAN radios, +// you can use it even if the firmware that you have uploaded last time is +// corrupt. In the "community slang", this can be used to "un-brick" your +// radio. +// +// The core parts of the program have been taken from John Melton's +// "bootloader" code from the TAPR github repository, and converted +// to a command-line program for Linux and MacOS contained in a single file. +// +/////////////////////////////////////////////////////////////////////////// +// +// Arguments: +// +// -d Display IPv4 ethernet adapters present in the system, +// together with their IP and MAC address. +// +// -i Use specific adapter. The name must match +// one of the adapters displayed with -d +// +// -s Set IP addr of radio to +// +// -f The name of the firmware file to be uploaded +// +/////////////////////////////////////////////////////////////////////////// +// +// -d is the default, it is over-ridden if -i is given. +// +// Without specifying valid device using the -i option, the -s and -f options +// cannot work. +// +/////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// Forward declaration for the two functions that send something to the radio +// +void sendRawCommand(pcap_t *handle, unsigned char hw[6], unsigned char command, unsigned char *data, int datalen); +void sendRawData(pcap_t *handle, unsigned char hw[6], unsigned char *data, off_t ptr); + +// HPSDR P1 bootloader command codes (we don't need those related to JTAG) + +#define PROGRAM_METIS_FLASH 0x01 +#define ERASE_METIS_FLASH 0x02 +#define READ_METIS_MAC 0x03 +#define READ_METIS_IP 0x04 +#define WRITE_METIS_IP 0x05 + +// HPSDR bootloader reply codes + +#define ERASE_DONE 0x01 // Sent when the ERASE_METIS_FLASH command is completed. +#define SEND_MORE 0x02 // Sent when the data of the PROGRAM_METIS_FLASH command has been processed. +#define HAVE_MAC_ADDRESS 0x03 // Sent as response to READ_METIS_MAC +#define HAVE_IP_ADDRESS 0x04 // Sent as response to READ_METIS_IP + +// The state of our program. + +#define STATE_QUERYMAC 0 // Query MAC address of radio +#define STATE_QUERYIP 1 // Query IP address of radio +#define STATE_SETIP 2 // Set IP addr. Skipped if -s argument not given +#define STATE_ERASE 3 // Erase EEPROM. Skipped if -f argument not given +#define STATE_PROGRAM 4 // Upload rbf file. Skipped if -f argument not given +#define STATE_WAIT 5 // Wait for response from radio +#define STATE_DONE 6 // All done. + + +int main(int argc, char **argv) +{ + int i; + char *dev=NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t* descr; + struct pcap_pkthdr hdr; + const u_char *packet; + unsigned char mymac[6]; // Mac address of host + unsigned char myip[4]; // IP address of host + unsigned char hisip[4]; // IP address to burn into radio + + pcap_if_t *devlist,*ifp; + pcap_addr_t *addr; + struct sockaddr *sa; + struct sockaddr_dl *link; + + char string[256]; + int have_addr, have_mac; + int do_display, do_lookup; + char *rbffile; + int rbffd; + int timeout; + off_t rbflen,rbfxfr,rbfptr; + int state; + unsigned char *rbfcontent; // the whole .rbf file (+ padding) + + char *cp; + + do_display=1; + do_lookup=0; + hisip[0]=0; + rbffile=NULL; + + i=1; + while (i < argc) { + if (!strcmp(argv[i],"-d")) { + do_display=1; + do_lookup=0; + } + if (!strcmp(argv[i],"-i") && i+1 < argc) { + dev=argv[++i]; + do_display=0; + do_lookup=1; + } + if (!strcmp(argv[i],"-f") && i+1 < argc) { + rbffile=argv[++i]; + } + if (!strcmp(argv[i],"-s") && i+1 < argc) { + int i1,i2,i3,i4; + i++; + if (sscanf(argv[i],"%d.%d.%d.%d",&i1,&i2,&i3,&i4) < 4) { + printf("Could not determine IP addr from string %s\n", argv[i]); + } else { + hisip[0]=i1; + hisip[1]=i2; + hisip[2]=i3; + hisip[3]=i4; + } + } + i++; + } + + // + // list devices + // + if (pcap_findalldevs(&devlist, errbuf) == -1) { + fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf); + exit(1); + } + + ifp=devlist; + while (ifp != NULL) { + if (do_lookup && strcmp(ifp->name, dev)) { + // we are looking for a specific interface, skip all others + ifp=ifp->next; + continue; + } + have_addr=0; + have_mac=0; + addr=ifp->addresses; + while (addr != NULL) { + sa=addr->addr; + if (sa->sa_family == AF_INET) { + int i1, i2, i3, i4; + (void) inet_ntop(AF_INET, (void *)&(((struct sockaddr_in *)sa)->sin_addr), string, 256); + if (sscanf(string,"%d.%d.%d.%d",&i1,&i2,&i3,&i4) == 4) { + myip[0]=i1; + myip[1]=i2; + myip[2]=i3; + myip[3]=i4; + if (i > 127) have_addr=1; + } + } + if (sa->sa_family == AF_LINK) { + link=(struct sockaddr_dl *)sa->sa_data; + unsigned char mac[link->sdl_alen]; + memcpy(mac, LLADDR(link), link->sdl_alen); + if (link->sdl_alen == 6) { + mymac[0]=mac[0]; + mymac[1]=mac[1]; + mymac[2]=mac[2]; + mymac[3]=mac[3]; + mymac[4]=mac[4]; + mymac[5]=mac[5]; + } + if (link->sdl_alen > 6) { + // + // This happens on MacOS + // + mymac[0]=mac[1]; + mymac[1]=mac[2]; + mymac[2]=mac[3]; + mymac[3]=mac[4]; + mymac[4]=mac[5]; + mymac[5]=mac[6]; + } + have_mac=1; + } + addr=addr->next; + } + if (have_addr && have_mac) { + printf("Interface=%-10s Address=(%3d,%3d,%3d,%3d) MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", + ifp->name, + myip[0], myip[1], myip[2], myip[3], + mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]); + } + ifp=ifp->next; + } + pcap_freealldevs(devlist); + + // + // If no suitable interface has been specified + // (or if this is a "display" run), give up. + // + if (dev == NULL || !have_addr || !have_mac) { + return 8; + } + + // + // Check some assumptions about RBF file + // + rbffd = -1; + if (rbffile != NULL) { + rbffd=open(rbffile, O_RDONLY); + if (rbffd < 0) { + perror("Open RBF file"); + } + i=strlen(rbffile); + if (i < 5) { + rbffd = -1; + } else { + if (strcmp(rbffile+i-4,".rbf")) { + printf("Firmware file name does not end in .rbf, ignoring.\n"); + rbffd = -1; + } + } + if (rbffd >= 0) { + rbflen=lseek(rbffd, 0, SEEK_END); + if (rbflen < 100000UL) { + printf("RBF file seems unreasonably short!\n"); + close(rbffd); + rbffd=-1; + } else { + printf("RBF file %s length = %ld bytes\n", rbffile, (long) rbflen); + if (rbflen % 256 != 0) { + rbfxfr=256 * (rbflen/256 +1); + } else { + rbfxfr = rbflen; + } + printf("RBF transfer length = %ld bytes (%d blocks)\n", (long) rbfxfr, (int) (rbfxfr / 256)); + } + } + } + if (hisip[0] > 127) { + printf("Shall burn IP addr = (%d,%d,%d,%d) into radio.\n",hisip[0],hisip[1],hisip[2],hisip[3]); + } + if (hisip[0] > 127 || rbffd >= 0) { + printf("Is this OK (y/n)?\n"); + i=getchar(); + if (i != 'y' && i != 'Y') return 0; + } + // + // Read rbf file into memory + // + if (rbffd >= 0) { + rbfcontent=malloc(rbfxfr); + if (rbfcontent == NULL) { + perror("Allocation of memory for RBF file"); + return 0; + } + lseek(rbffd, 0, SEEK_SET); + if (read(rbffd, rbfcontent, rbflen) != rbflen) { + printf("RBF file read error, terminating\n"); + return 0; + } + close(rbffd); + // pad with 0xFF up to the next multiple of 256 + for (i=rbflen; i= 0) { + state=STATE_ERASE; + } + break; + case STATE_ERASE: + sendRawCommand(descr, mymac, ERASE_METIS_FLASH, NULL, 0); + printf("Erasing (THIS TAKES SOME TIME!)\n"); + timeout=18000; // up to 3 minutes + state=STATE_WAIT; + break; + case STATE_PROGRAM: + sendRawData(descr, mymac, rbfcontent, rbfptr); + rbfptr +=256; + if (rbfptr % 1024 == 0) { + printf("Data sent: %6ld k-Bytes\r", (long) rbfptr / 1024); + fflush(stdout); + } + timeout=50; + state=STATE_WAIT; + break; + case STATE_WAIT: + // + // Terminate program if timeout is reached. + // The "wait" state is changed if a valid response + // packet is captured. + // + timeout--; + if (timeout == 0) { + printf("TIMEOUT reached - terminating bootloader.\n"); + return(0); + } + break; + case STATE_DONE: + printf("All Done.\n"); + return (0); + } + + // + // Obtain next packet + // + packet = pcap_next(descr,&hdr); + if (packet == NULL) continue; // Nothing arrived within time-out + + /* determine if this is a packet from a bootloader to our computer */ + if (hdr.len > 22 && packet[0] == mymac[0] && packet[1] == mymac[1] && packet[2] == mymac[2] + && packet[3] == mymac[3] && packet[4] == mymac[4] && packet[5] == mymac[5] + && packet[6] == 0x11 && packet[7] == 0x22 && packet[8] == 0x33 + && packet[9] == 0x44 && packet[10]== 0x55 && packet[11]== 0x66 + && packet[12] == 0xef && packet[13] == 0xfe && packet[14] == 0x03) { + + switch (packet[15]) { + case HAVE_MAC_ADDRESS: + printf("HPSDR board detected, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", packet[16], packet[17], packet[18], packet[19], packet[20], packet[21]); + state=STATE_QUERYIP; + break; + case HAVE_IP_ADDRESS: + printf("Board has IP addr (%d,%d,%d,%d).\n", packet[16], packet[17], packet[18], packet[19]); + state=STATE_DONE; + if (rbffd >= 0) state=STATE_ERASE; // if there is something to upload + if (hisip[0] > 127) state=STATE_SETIP; // if we want to set an IP address + break; + case ERASE_DONE: + if (rbffd >= 0) { + printf("Board erased, about to upload new RBF file.\n"); + state=STATE_PROGRAM; + } else { + // this cannot happen + state=STATE_DONE; + } + break; + case SEND_MORE: + if (rbffd < 0 || rbfptr > rbfxfr) { + printf("\nRBF file upload complete.\n"); + state=STATE_DONE; + } else { + state=STATE_PROGRAM; + } + break; + default: + printf("UNKNOWN REPLY=%d\n", packet[15]); + break; + } + } + } +} + +// +// Send a 62-byte buffer to the radio containing a "command" +// +void sendRawCommand(pcap_t *handle, unsigned char hw[6], unsigned char command, unsigned char *data, int datalen) { + unsigned char buffer[62]; + int i; + + /*set the frame header*/ + buffer[0]=0x11; // use "bogus" radio MAC address for destination + buffer[1]=0x22; + buffer[2]=0x33; + buffer[3]=0x44; + buffer[4]=0x55; + buffer[5]=0x66; + + buffer[6]=hw[0]; // use own MAC address as source + buffer[7]=hw[1]; + buffer[8]=hw[2]; + buffer[9]=hw[3]; + buffer[10]=hw[4]; + buffer[11]=hw[5]; + + buffer[12]=0xEF; // protocol + buffer[13]=0xFE; + + buffer[14]=0x03; + buffer[15]=command; + + /*fill the frame with 0x00*/ + for(i=0;i<46;i++) { + buffer[i+16]=(unsigned char)0x00; + } + /* possibly fill in data */ + for (i=0; i