From: Pawel Dembicki Date: Thu, 1 Mar 2018 19:11:01 +0000 (+0100) Subject: firmware: add JBOOT based devices config extractor X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=5323477184cf39f4f20003142ce320ac1d626863;p=librecmc%2Flibrecmc.git firmware: add JBOOT based devices config extractor Adds tool to extract MAC and pre-calibration data required for JBOOT based D-Link routers. Signed-off-by: Pawel Dembicki --- diff --git a/package/utils/jboot-tools/Makefile b/package/utils/jboot-tools/Makefile new file mode 100644 index 0000000000..ce9758ba3a --- /dev/null +++ b/package/utils/jboot-tools/Makefile @@ -0,0 +1,28 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=jboot-tools +PKG_RELEASE:=1 +CMAKE_INSTALL:=1 +PKG_FLAGS:=nonshared + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/jboot-tools + SECTION:=firmware + CATEGORY:=Firmware + DEPENDS:=@TARGET_ramips + TITLE:=Utilites for accessing JBOOT based D-Link devices Calibration data +endef + +define Package/jboot-tools/description + This package contains: + jboot_config_read.c: partially read the config partition of JBOOT based D-Link devices. +endef + +define Package/jboot-tools/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/jboot_config_read $(1)/usr/bin/ +endef + +$(eval $(call BuildPackage,jboot-tools)) diff --git a/package/utils/jboot-tools/README.md b/package/utils/jboot-tools/README.md new file mode 100644 index 0000000000..0d1aeac9d1 --- /dev/null +++ b/package/utils/jboot-tools/README.md @@ -0,0 +1,46 @@ +Userspace utilties for jboot based devices config partition read + +## Building + +``` +mkdir build +cd build +cmake /path/to/jboot-tools +make +``` + +## Usage + +All command line parameters are documented: +``` +jboot_config_read -h +``` + +Show all stored MACs: +``` +jboot_config_read -m -i PATH_TO_CONFIG_PARTITIO +``` + +Extract wifi eeprom data: +``` +jboot_config_read -i PATH_TO_CONFIG_PARTITION -e OUTPUT_PATH +``` + + +## LICENSE + +See `LICENSE`: + + 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; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/package/utils/jboot-tools/src/CMakeLists.txt b/package/utils/jboot-tools/src/CMakeLists.txt new file mode 100644 index 0000000000..98fbab38dc --- /dev/null +++ b/package/utils/jboot-tools/src/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(jboot-tools C) +ADD_DEFINITIONS(-Wall -Werror --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +ADD_EXECUTABLE(jboot_config_read jboot_config_read.c) +TARGET_LINK_LIBRARIES(jboot_config_read) + +INSTALL(TARGETS jboot_config_read RUNTIME DESTINATION bin) diff --git a/package/utils/jboot-tools/src/jboot_config_read.c b/package/utils/jboot-tools/src/jboot_config_read.c new file mode 100644 index 0000000000..c65b0917a1 --- /dev/null +++ b/package/utils/jboot-tools/src/jboot_config_read.c @@ -0,0 +1,427 @@ +/* + * jboot_config_read + * + * Copyright (C) 2018 Paweł Dembicki + * + * This tool is based on mkdlinkfw. + * Copyright (C) 2018 Paweł Dembicki + * Copyright (C) 2009 Gabor Juhos + * Copyright (C) 2008,2009 Wang Jian + * + * 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. + */ + +#include +#include +#include +#include +#include /* for unlink() */ +#include +#include /* for getopt() */ +#include +#include +#include +#include +#include + + + +#define ERR(fmt, ...) do { \ + fflush(0); \ + fprintf(stderr, "[%s] *** error: " fmt "\n", \ + progname, ## __VA_ARGS__); \ +} while (0) + +#define ERRS(fmt, ...) do { \ + int save = errno; \ + fflush(0); \ + fprintf(stderr, "[%s] *** error: " fmt ": %s\n", \ + progname, ## __VA_ARGS__, strerror(save)); \ +} while (0) + +#define VERBOSE(fmt, ...) do { \ + if (verbose) { \ + fprintf(stdout, "[%s] " fmt "\n", progname, ## __VA_ARGS__); \ + } \ +} while (0) + +#define STAG_SIZE 16 +#define STAG_MAGIC 0x2B24 +#define STAG_ID 0x02 + +#define CSXF_SIZE 16 +#define CSXF_MAGIC 0x5343 + +#define MAX_DATA_HEADER 128 +#define DATA_HEADER_UNKNOWN 0x8000 +#define DATA_HEADER_EEPROM 0xF5 +#define DATA_HEADER_CONFIG 0x42 +#define DATA_HEADER_SIZE 6 + +#define DATA_HEADER_ID_MAC 0x30 +#define DATA_HEADER_ID_CAL 0x0 + +/* ARM update header 2.0 + * used only in factory images to erase and flash selected area + */ +struct stag_header { /* used only of sch2 wrapped kernel data */ + uint8_t cmark; /* in factory 0xFF ,in sysuograde must be the same as id */ + uint8_t id; /* 0x04 */ + uint16_t magic; /* magic 0x2B24 */ + uint32_t time_stamp; /* timestamp calculated in jboot way */ + uint32_t image_length; /* lentgh of kernel + sch2 header */ + uint16_t image_checksum; /* negated jboot_checksum of sch2 + kernel */ + uint16_t tag_checksum; /* negated jboot_checksum of stag header data */ +}; + +struct csxf_header { + uint16_t magic; /* 0x5343, 'CS' in little endian */ + uint16_t checksum; /* checksum, include header & body */ + uint32_t body_length; /* length of body */ + uint8_t body_encoding; /* encoding method of body */ + uint8_t reserved[3]; + uint32_t raw_length; /* length of body before encoded */ +}; + +struct data_header { + uint8_t id; + uint8_t type; /* 0x42xx for config 0xF5xx for eeprom */ + uint16_t unknown; + uint16_t length; /* length of body */ + uint8_t data[]; /* encoding method of body */ +}; + +/* globals */ + +char *ofname; +char *ifname; +char *progname; + +uint8_t *buffer; +uint32_t config_size; + +uint32_t start_offset; +uint8_t mac_duplicate; +uint8_t mac_print; +uint8_t print_data; +uint8_t verbose; + +static void usage(int status) +{ + fprintf(stderr, "Usage: %s [OPTIONS...]\n", progname); + fprintf(stderr, + "\n" + "Options:\n" + " -i config partition file \n" + " -m print mac address\n" + " -e save eeprom calibration data image to the file \n" + " -o set start offset to \n" + " -p print config data\n" + " -v verbose\n" + " -h show this screen\n"); + + exit(status); +} + +static void print_data_header(struct data_header *printed_header) +{ + printf("id: 0x%02X " + "type: 0x%02X " + "unknown: 0x%04X " + "length: 0x%04X\n" + "data: ", + printed_header->id, + printed_header->type, + printed_header->unknown, printed_header->length); + + for (uint16_t i = 0; i < printed_header->length; i++) + printf("%02X ", printed_header->data[i]); + + printf("\n"); + +} + +static uint16_t jboot_checksum(uint16_t start_val, uint16_t *data, int size) +{ + uint32_t counter = start_val; + uint16_t *ptr = data; + + while (size > 1) { + counter += *ptr; + ++ptr; + while (counter >> 16) + counter = (uint16_t) counter + (counter >> 16); + size -= 2; + } + if (size > 0) { + counter += *(uint8_t *) ptr; + counter -= 0xFF; + } + while (counter >> 16) + counter = (uint16_t) counter + (counter >> 16); + return counter; +} + +static int find_header(uint8_t *buf, uint32_t buf_size, + struct data_header **data_table) +{ + uint8_t *tmp_buf = buf + start_offset; + uint8_t tmp_hdr[4] = { STAG_ID, STAG_ID, (STAG_MAGIC & 0xFF), (STAG_MAGIC >> 8) }; + struct csxf_header *tmp_csxf_header; + uint16_t tmp_checksum = 0; + uint16_t data_header_counter = 0; + int ret = EXIT_FAILURE; + + VERBOSE("Looking for STAG header!"); + + while ((uint32_t) tmp_buf - (uint32_t) buf <= buf_size) { + if (!memcmp(tmp_buf, tmp_hdr, 4)) { + if (((struct stag_header *)tmp_buf)->tag_checksum == + (uint16_t) ~jboot_checksum(0, (uint16_t *) tmp_buf, + STAG_SIZE - 2)) { + VERBOSE("Found proper STAG header at: 0x%X.", + tmp_buf - buf); + break; + } + } + tmp_buf++; + } + + tmp_csxf_header = (struct csxf_header *)(tmp_buf + STAG_SIZE); + if (tmp_csxf_header->magic != CSXF_MAGIC) { + ERR("CSXF magic incorrect! 0x%X != 0x%X", + tmp_csxf_header->magic, CSXF_MAGIC); + goto out; + } + VERBOSE("CSXF magic ok."); + tmp_checksum = tmp_csxf_header->checksum; + tmp_csxf_header->checksum = 0; + + tmp_csxf_header->checksum = + (uint16_t) ~jboot_checksum(0, (uint16_t *) (tmp_buf + STAG_SIZE), + tmp_csxf_header->raw_length + + CSXF_SIZE); + + if (tmp_checksum != tmp_csxf_header->checksum) { + ERR("CSXF checksum incorrect! Stored: 0x%X Calculated: 0x%X", + tmp_checksum, tmp_csxf_header->checksum); + goto out; + } + VERBOSE("CSXF image checksum ok."); + + tmp_buf = tmp_buf + STAG_SIZE + CSXF_SIZE; + + while ((uint32_t) tmp_buf - (uint32_t) buf <= buf_size) { + + struct data_header *tmp_data_header = + (struct data_header *)tmp_buf; + + if (tmp_data_header->unknown != DATA_HEADER_UNKNOWN) { + tmp_buf++; + continue; + } + if (tmp_data_header->type != DATA_HEADER_EEPROM + && tmp_data_header->type != DATA_HEADER_CONFIG) { + tmp_buf++; + continue; + } + + data_table[data_header_counter] = tmp_data_header; + tmp_buf += + DATA_HEADER_SIZE + data_table[data_header_counter]->length; + data_header_counter++; + + } + + ret = data_header_counter; + + out: + return ret; +} + +static int read_file(char *file_name) +{ + int ret = EXIT_FAILURE; + uint32_t file_size = 0; + FILE *fp; + + fp = fopen(file_name, "r"); + + if (!fp) { + ERR("Failed to open config input file %s", file_name); + goto out; + } + + fseek(fp, 0L, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0L, SEEK_SET); + + buffer = malloc(file_size); + VERBOSE("Allocated %d bytes.", file_size); + + if (fread(buffer, 1, file_size, fp) != file_size) { + ERR("Failed to read config input file %s", file_name); + goto out_free_buf; + } + + VERBOSE("Read %d bytes of config input file %s", file_size, file_name); + config_size = file_size; + ret = EXIT_SUCCESS; + goto out; + + out_free_buf: + free(buffer); + fclose(fp); + out: + return ret; +} + +static int write_file(const char *ofname, const uint8_t *data, int len) +{ + FILE *f; + int ret = EXIT_FAILURE; + + f = fopen(ofname, "w"); + if (f == NULL) { + ERRS("could not open \"%s\" for writing", ofname); + goto out; + } + + errno = 0; + fwrite(data, len, 1, f); + if (errno) { + ERRS("unable to write output file"); + goto out_flush; + } + + VERBOSE("firmware file \"%s\" completed", ofname); + + ret = EXIT_SUCCESS; + + out_flush: + fflush(f); + fclose(f); + if (ret != EXIT_SUCCESS) + unlink(ofname); + out: + return ret; +} + +static void print_mac(struct data_header **data_table, int cnt) +{ + + for (int i = 0; i < cnt; i++) { + if (data_table[i]->type == DATA_HEADER_CONFIG + && data_table[i]->id == DATA_HEADER_ID_MAC) { + int j; + for (j = 0; j < 5; j++) + printf("%02x:", data_table[i]->data[j]); + printf("%02x\n", data_table[i]->data[j]); + } + + } + +} + +static int write_eeprom(struct data_header **data_table, int cnt) +{ + int ret = EXIT_FAILURE; + + for (int i = 0; i < cnt; i++) { + if (data_table[i]->type == DATA_HEADER_EEPROM + && data_table[i]->id == DATA_HEADER_ID_CAL) { + ret = + write_file(ofname, data_table[i]->data, + data_table[i]->length); + break; + } + + } + + return ret; +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE; + int configs_counter = 0; + struct data_header *configs_table[MAX_DATA_HEADER]; + buffer = NULL; + config_size = 0; + + progname = basename(argv[0]); + start_offset = 0; + mac_print = 0; + print_data = 0; + verbose = 0; + ofname = NULL; + ifname = NULL; + + while (1) { + int c; + + c = getopt(argc, argv, "de:hi:mo:pv"); + if (c == -1) + break; + + switch (c) { + case 'm': + mac_print = 1; + break; + case 'i': + ifname = optarg; + break; + case 'e': + ofname = optarg; + break; + case 'o': + sscanf(optarg, "0x%x", &start_offset); + break; + case 'p': + print_data = 1; + break; + case 'v': + verbose = 1; + VERBOSE("Enable verbose!"); + break; + default: + usage(EXIT_FAILURE); + break; + } + } + + if (!ifname) + usage(EXIT_FAILURE); + + ret = read_file(ifname); + + if (ret || config_size <= 0) + goto out; + + configs_counter = find_header(buffer, config_size, configs_table); + + if (configs_counter <= 0) + goto out_free_buf; + + if (print_data || verbose) { + for (int i = 0; i < configs_counter; i++) + print_data_header(configs_table[i]); + } + + if (mac_print) + print_mac(configs_table, configs_counter); + + ret = EXIT_SUCCESS; + + if (ofname) + ret = write_eeprom(configs_table, configs_counter); + + out_free_buf: + free(buffer); + out: + return ret; + +}