/* * firmware cutter for broadcom 43xx wireless driver files * * Copyright (c) 2005 Martin Langer , * 2005 Michael Buesch * 2005 Alex Beregszaszi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include typedef unsigned char byte; #define DRIVER_UNSUPPORTED 0x01 /* no support for this driver file */ #define BYTE_ORDER_BIG_ENDIAN 0x02 /* ppc driver files */ #define BYTE_ORDER_LITTLE_ENDIAN 0x04 /* x86, mips driver files */ #define MISSING_INITVAL_08 0x10 /* initval 8 is missing */ #define MISSING_INITVAL_80211_A 0x20 /* initvals 3,7,9,10 (802.11a cards) are empty */ #define FIRMWARE_UCODE_OFFSET 100 #define FIRMWARE_UNDEFINED 0 #define FIRMWARE_PCM_4 4 #define FIRMWARE_PCM_5 5 #define FIRMWARE_UCODE_2 (FIRMWARE_UCODE_OFFSET + 2) #define FIRMWARE_UCODE_4 (FIRMWARE_UCODE_OFFSET + 4) #define FIRMWARE_UCODE_5 (FIRMWARE_UCODE_OFFSET + 5) #define FIRMWARE_UCODE_11 (FIRMWARE_UCODE_OFFSET + 11) #define fwcutter_stringify_1(x) #x #define fwcutter_stringify(x) fwcutter_stringify_1(x) #define FWCUTTER_VERSION fwcutter_stringify(FWCUTTER_VERSION_) #include "md5.h" #include "fwcutter_list.h" struct cmdline_args { const char *infile; const char *postfix; const char *target_dir; int identify_only; }; static struct cmdline_args cmdargs; int big_endian_cpu; static void write_little_endian(FILE *f, byte *buffer, int len) { byte swapbuf[4]; while (len > 0) { swapbuf[0] = buffer[3]; swapbuf[1] = buffer[2]; swapbuf[2] = buffer[1]; swapbuf[3] = buffer[0]; fwrite(swapbuf, 4, 1, f); buffer = buffer + 4; len = len - 4; } } static void write_big_endian(FILE *f, byte *buffer, int len) { while (len > 0) { fwrite(buffer, 4, 1, f); buffer = buffer + 4; len = len - 4; } } static void write_fw(const char *outfilename, uint8_t flags, byte *data, int len) { FILE* fw; char outfile[2048]; snprintf(outfile, sizeof(outfile), "%s/%s", cmdargs.target_dir, outfilename); fw = fopen(outfile, "w"); if (!fw) { perror(outfile); exit(1); } if (flags & BYTE_ORDER_LITTLE_ENDIAN) write_little_endian(fw, data, len); else if (flags & BYTE_ORDER_BIG_ENDIAN) write_big_endian(fw, data, len); else printf("unknown byteorder...\n"); fflush(fw); fclose(fw); } static void write_iv(uint8_t flags, byte *data) { FILE* fw; char ivfilename[2048]; int i; for (i = 1; i <= 10; i++) { if ((flags & MISSING_INITVAL_08) && (i==8)) { printf("*****: Sorry, initval08 is not available in driver file \"%s\".\n", cmdargs.infile); printf("*****: Extracting firmware from an old driver is bad. Choose a more recent one.\n"); printf("*****: Luckily bcm43xx driver doesn't include initval08 uploads at the moment.\n"); printf("*****: But this can be added in the future...\n"); i++; } snprintf(ivfilename, sizeof(ivfilename), "%s/bcm43xx_initval%02d%s.fw", cmdargs.target_dir, i, cmdargs.postfix); fw = fopen(ivfilename, "w"); if (!fw) { perror(ivfilename); exit(1); } printf("extracting bcm43xx_initval%02d%s.fw ...\n", i, cmdargs.postfix); while (1) { if ((data[0]==0xff) && (data[1]==0xff) && (data[2]==0x00) && (data[3]==0x00)) { data = data + 8; break; } if (flags & BYTE_ORDER_LITTLE_ENDIAN) fprintf(fw, "%c%c%c%c%c%c%c%c", data[1], data[0], /* offset */ data[3], data[2], /* size */ data[7], data[6], data[5], data[4]); /* value */ else if (flags & BYTE_ORDER_BIG_ENDIAN) fprintf(fw, "%c%c%c%c%c%c%c%c", data[0], data[1], /* offset */ data[2], data[3], /* size */ data[4], data[5], data[6], data[7]); /* value */ else { printf("unknown byteorder...\n"); exit(1); } data = data + 8; } fflush(fw); fclose(fw); } } static byte* read_file(const char* filename) { FILE* file; long len; byte* data; file = fopen(filename, "rb"); if (!file) { perror(filename); exit(1); } if (fseek(file, 0, SEEK_END)) { perror("cannot seek"); exit(1); } len = ftell(file); fseek(file, 0, SEEK_SET); data = malloc(len); if (!data) { fputs("out of memory\n", stderr); exit(1); } if (fread(data, 1, len, file) != len) { perror("cannot read"); exit(1); } fclose(file); return data; } static void extract_fw(uint8_t fwtype, uint8_t flags, uint32_t pos, uint32_t length) { byte* filedata; char outfile[1024]; switch (fwtype) { case FIRMWARE_UCODE_2: case FIRMWARE_UCODE_4: case FIRMWARE_UCODE_5: case FIRMWARE_UCODE_11: snprintf(outfile, sizeof(outfile), "bcm43xx_microcode%i%s.fw", fwtype - FIRMWARE_UCODE_OFFSET, cmdargs.postfix); break; case FIRMWARE_PCM_4: case FIRMWARE_PCM_5: snprintf(outfile, sizeof(outfile), "bcm43xx_pcm%i%s.fw", fwtype, cmdargs.postfix); break; default: snprintf(outfile, sizeof(outfile), "bcm43xx_unknown.fw"); } if (length > 0) { printf("extracting %s ...\n", outfile); filedata = read_file(cmdargs.infile); write_fw(outfile, flags, filedata + pos, length); free(filedata); } else { printf("*****: Sorry, it's not posible to extract \"%s\".\n", outfile); printf("*****: Extracting firmware from an old driver is bad. Choose a more recent one.\n"); switch (fwtype) { case FIRMWARE_UCODE_2: printf("*****: bcm43xx driver will not work with with core revision 2.\n"); break; case FIRMWARE_UCODE_4: printf("*****: bcm43xx driver will not work with with core revision 4.\n"); break; case FIRMWARE_UCODE_5: printf("*****: bcm43xx driver will not work with with core revision 5 or higher.\n"); break; case FIRMWARE_UCODE_11: printf("*****: Luckily bcm43xx driver doesn't include microcode11 uploads at the moment.\n"); printf("*****: But this can be added in the future...\n"); break; case FIRMWARE_PCM_4: printf("*****: bcm43xx driver will not work with with core revision 4 or smaller.\n"); break; case FIRMWARE_PCM_5: printf("*****: bcm43xx driver will not work with with core revision 5 or higher.\n"); break; } } } static void extract_iv(uint8_t flags, uint32_t pos) { byte* filedata; if (pos > 0) { filedata = read_file(cmdargs.infile); write_iv(flags, filedata + pos); free(filedata); } } static void print_banner(void) { printf("fwcutter " FWCUTTER_VERSION "\n"); } static void print_file(const struct file *file) { printf("%s\t", file->name); if (strlen(file->name) < 8) printf("\t"); printf("%s\t", file->version); if (strlen(file->version) < 8) printf("\t"); if (strlen(file->version) < 16) printf("\t"); if (!(file->flags & DRIVER_UNSUPPORTED)) { if (file->flags & MISSING_INITVAL_80211_A) printf("b/g "); else printf("a/b/g"); } printf(" %s", file->md5); printf("\n"); } static void print_supported_files(void) { int i; print_banner(); printf("\nExtracting firmware is possible from these binary driver files:\n\n"); printf("\t\t <802.11>\n\n"); for (i = 0; i < FILES; i++) { if (files[i].flags & DRIVER_UNSUPPORTED) continue; print_file(&files[i]); } printf("\n\nExtracting firmware is IMPOSSIBLE from these binary driver files:\n\n"); printf("\t\t \n\n"); for (i = 0; i < FILES; i++) { if (!(files[i].flags & DRIVER_UNSUPPORTED)) continue; print_file(&files[i]); } } static const struct file * find_file(FILE *fd) { unsigned char buffer[16384], signature[16]; struct MD5Context md5c; char md5sig[33]; int i; MD5Init(&md5c); while ((i = (int) fread(buffer, 1, sizeof(buffer), fd)) > 0) MD5Update(&md5c, buffer, (unsigned) i); MD5Final(signature, &md5c); snprintf(md5sig, sizeof(md5sig), "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", signature[0], signature[1], signature[2], signature[3], signature[4], signature[5], signature[6], signature[7], signature[8], signature[9], signature[10], signature[11], signature[12], signature[13], signature[14], signature[15]); for (i = 0; i < FILES; ++i) { if (strcasecmp(md5sig, files[i].md5) == 0) { if (files[i].flags & DRIVER_UNSUPPORTED) { printf("Extracting firmware from this file is IMPOSSIBLE. (too old)\n"); return 0; } printf("fwcutter can cut the firmware out of %s\n", cmdargs.infile); printf(" filename : %s\n", files[i].name); printf(" version : %s\n", files[i].version); printf(" MD5 : %s\n\n", files[i].md5); if (files[i].flags & MISSING_INITVAL_80211_A) { printf("WARNING! This firmware doesn't include support for 802.11a cards.\n"); printf("WARNING! Use this firmware only for 802.11b/g cards.\n\n"); } return &(files[i]); } } printf("Sorry, the input file is either wrong or not supported by fwcutter.\n"); printf("I can't find the MD5sum %s :(\n", md5sig); return 0; } static void get_endianess(void) { const unsigned char x[] = { 0xde, 0xad, 0xbe, 0xef, }; const uint32_t *p = (uint32_t *)x; if (*p == 0xdeadbeef) { big_endian_cpu = 1; } else if (*p == 0xefbeadde) { big_endian_cpu = 0; } else { printf("Confused: NUXI endian machine??\n"); exit(-1); } } static void print_usage(int argc, char *argv[]) { print_banner(); printf("\nUsage: %s [OPTION] [driver.sys]\n", argv[0]); printf(" -l|--list List supported driver versions\n"); printf(" -i|--identify Only identify the driver file (don't extract)\n"); printf(" -w|--target-dir DIR Extract and write firmware to DIR\n"); printf(" -p|--postfix \".FOO\" Postfix for firmware filenames (.FOO.fw)\n"); printf(" -v|--version Print fwcutter version\n"); printf(" -h|--help Print this help\n"); printf("\nExample: %s bcmwl5.sys\n" " to extract the firmware blobs from bcmwl5.sys\n", argv[0]); } #define ARG_MATCH 0 #define ARG_NOMATCH 1 #define ARG_ERROR -1 static int do_cmp_arg(char **argv, int *pos, const char *template, int allow_merged, char **param) { char *arg; char *next_arg; size_t arg_len, template_len; arg = argv[*pos]; next_arg = argv[*pos + 1]; arg_len = strlen(arg); template_len = strlen(template); if (param) { /* Maybe we have a merged parameter here. * A merged parameter is "-pfoobar" for example. */ if (allow_merged && arg_len > template_len) { if (memcmp(arg, template, template_len) == 0) { *param = arg + template_len; return ARG_MATCH; } return ARG_NOMATCH; } else if (arg_len != template_len) return ARG_NOMATCH; *param = next_arg; } if (strcmp(arg, template) == 0) { if (param) { /* Skip the parameter on the next iteration. */ (*pos)++; if (*param == 0) { printf("%s needs a parameter\n", arg); return ARG_ERROR; } } return ARG_MATCH; } return ARG_NOMATCH; } /* Simple and lean command line argument parsing. */ static int cmp_arg(char **argv, int *pos, const char *long_template, const char *short_template, char **param) { int err; if (long_template) { err = do_cmp_arg(argv, pos, long_template, 0, param); if (err == ARG_MATCH || err == ARG_ERROR) return err; } err = ARG_NOMATCH; if (short_template) err = do_cmp_arg(argv, pos, short_template, 1, param); return err; } static int parse_args(int argc, char *argv[]) { int i, res; char *param; if (argc < 2) goto out_usage; for (i = 1; i < argc; i++) { res = cmp_arg(argv, &i, "--list", "-l", 0); if (res == ARG_MATCH) { print_supported_files(); return 1; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--version", "-v", 0); if (res == ARG_MATCH) { print_banner(); return 1; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--help", "-h", 0); if (res == ARG_MATCH) goto out_usage; else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--identify", "-i", 0); if (res == ARG_MATCH) { cmdargs.identify_only = 1; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--target-dir", "-w", ¶m); if (res == ARG_MATCH) { cmdargs.target_dir = param; continue; } else if (res == ARG_ERROR) goto out; res = cmp_arg(argv, &i, "--postfix", "-p", ¶m); if (res == ARG_MATCH) { cmdargs.postfix = param; continue; } else if (res == ARG_ERROR) goto out; cmdargs.infile = argv[i]; break; } if (!cmdargs.infile) goto out_usage; return 0; out_usage: print_usage(argc, argv); out: return -1; } int main(int argc, char *argv[]) { FILE *fd; const struct file *file; int err; get_endianess(); cmdargs.target_dir = "."; cmdargs.postfix = ""; err = parse_args(argc, argv); if (err == 1) return 0; else if (err != 0) return err; fd = fopen(cmdargs.infile, "rb"); if (!fd) { fprintf(stderr, "Cannot open input file %s\n", cmdargs.infile); return 2; } err = -1; file = find_file(fd); if (!file) goto out_close; if (cmdargs.identify_only) { err = 0; goto out_close; } extract_fw(FIRMWARE_UCODE_2, file->flags, file->uc2_pos, file->uc2_length); extract_fw(FIRMWARE_UCODE_4, file->flags, file->uc4_pos, file->uc4_length); extract_fw(FIRMWARE_UCODE_5, file->flags, file->uc5_pos, file->uc5_length); extract_fw(FIRMWARE_UCODE_11, file->flags, file->uc11_pos, file->uc11_length); extract_fw(FIRMWARE_PCM_4, file->flags, file->pcm4_pos, file->pcm4_length); extract_fw(FIRMWARE_PCM_5, file->flags, file->pcm5_pos, file->pcm5_length); extract_iv(file->flags, file->iv_pos); err = 0; out_close: fclose(fd); return err; }