From 4d979c7828da8d8d8c8760a4b0731c0a599f165f Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Sun, 1 Jun 2014 22:13:48 +0200 Subject: [PATCH] Initial import --- .gitignore | 2 + Makefile | 54 +++++++ actions-firmware-extract.c | 348 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 actions-firmware-extract.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d891fd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +actions-firmware-extract.o +actions-firmware-extract diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2e65c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +CFLAGS ?= -std=c99 -pedantic -Wall -Wextra -O2 + +CFLAGS += -pedantic-errors + +# GCC >= 4.6 +CFLAGS += -Wunused-but-set-variable + +CFLAGS += -fno-common \ + -Wall \ + -Wextra \ + -Wformat=2 \ + -Winit-self \ + -Winline \ + -Wpacked \ + -Wp,-D_FORTIFY_SOURCE=2 \ + -Wpointer-arith \ + -Wlarger-than-65500 \ + -Wmissing-declarations \ + -Wmissing-format-attribute \ + -Wmissing-noreturn \ + -Wmissing-prototypes \ + -Wnested-externs \ + -Wold-style-definition \ + -Wredundant-decls \ + -Wsign-compare \ + -Wstrict-aliasing=2 \ + -Wstrict-prototypes \ + -Wswitch-enum \ + -Wundef \ + -Wunreachable-code \ + -Wunsafe-loop-optimizations \ + -Wwrite-strings + +# for some functions in endian.h +CFLAGS += -D_BSD_SOURCE + +# for getopt() +CFLAGS += -D_POSIX_C_SOURCE=2 + +PREFIX ?= /usr/local +bindir := $(PREFIX)/sbin + +actions-firmware-extract: actions-firmware-extract.o + +test: actions-firmware-extract + rm -rf PPX2230 && \ + ./actions-firmware-extract -d PPX2230 ppx2230_eu_fus_aen.bin + +install: actions-firmware-extract + install -d $(DESTDIR)$(bindir) + install -m 755 actions-firmware-extract $(DESTDIR)$(bindir) + +clean: + rm -rf *~ *.o actions-firmware-extract endian endian.h diff --git a/actions-firmware-extract.c b/actions-firmware-extract.c new file mode 100644 index 0000000..2deeb8f --- /dev/null +++ b/actions-firmware-extract.c @@ -0,0 +1,348 @@ +/* actions-firmware-extract - extract firmwares like the one for Acer K330 + * + * Copyright (C) 2014 Antonio Ospite + * + * 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 3 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Based on cdt_parse.c from OpenEZX: + * http://cgit.openezx.org/moto-boot-usb/tree/src/cdt_parse.c + */ + +#define ACTIONS_HEADER_DISK_SIZE 64 +struct actions_firmware_header { + uint8_t magic_string[16]; + uint8_t version_string[16]; + uint8_t checksum_string[16]; + uint32_t base_address; + uint32_t length; + uint32_t unknown0; + uint32_t unknown1; +}; + +#define ACTIONS_SECTION_DISK_SIZE 32 +struct actions_firmware_section { + uint8_t name[16]; + uint32_t start_address; + uint32_t length; + uint32_t unknown0; + uint32_t unknown1; +}; + +#define FIRM_TABLE_OFFSET 0x200 +#define FIRM_DATA_OFFSET 0x2000 + +static void get_string(uint8_t **srcp, uint8_t *dest, unsigned int len) +{ + memcpy(dest, *srcp, len); + dest[len - 1] = '\0'; + *srcp += len; +} + +static inline uint16_t get_le16(uint8_t **bufferp) +{ + uint16_t tmp; + + memcpy(&tmp, *bufferp, sizeof (tmp)); + *bufferp += sizeof (tmp); + + return le16toh(tmp); +} + +static inline uint32_t get_le32(uint8_t **bufferp) +{ + uint32_t tmp; + + memcpy(&tmp, *bufferp, sizeof (tmp)); + *bufferp += sizeof (tmp); + + return le32toh(tmp); +} + +static inline uint64_t get_le64(uint8_t **bufferp) +{ + uint64_t tmp; + + memcpy(&tmp, *bufferp, sizeof (tmp)); + *bufferp += sizeof (tmp); + + return le64toh(tmp); +} + +static int parse_header(uint8_t **bufferp, struct actions_firmware_header *header) +{ + get_string(bufferp, header->magic_string, 16); + + if (strncmp((char *)header->magic_string, "ActionsFirmware", 16) != 0) { + fprintf(stderr, "Not an Actions firmware.\n"); + return -EINVAL; + } + + get_string(bufferp, header->version_string, 16); + get_string(bufferp, header->checksum_string, 16); + + header->base_address = get_le32(bufferp); + header->length = get_le32(bufferp); + header->unknown0 = get_le32(bufferp); + header->unknown1 = get_le32(bufferp); + + return 0; +} + +static int parse_section(uint8_t **bufferp, struct actions_firmware_section *section) +{ + get_string(bufferp, section->name, 16); + + if (section->name[0] == '\0') + return -EINVAL; + + section->start_address = get_le32(bufferp); + section->length = get_le32(bufferp); + section->unknown0 = get_le32(bufferp); + section->unknown1 = get_le32(bufferp); + + return 0; +} + +static void print_header(struct actions_firmware_header *header) +{ + printf("Magic: %s\n" + "Version: %s\n" + "Checksum: %s\n" + "Base address: %d\n" + "Content length: %d\n" + "unknown0: 0x%08x (%u)\n" + "unknown1: 0x%08x (%u)\n" + "Complete file size: %d\n", + header->magic_string, + header->version_string, + header->checksum_string, + header->base_address, + header->length, + header->unknown0, header->unknown0, + header->unknown1, header->unknown1, + header->base_address + header->length); +} + +static void print_section(struct actions_firmware_section *section) +{ + printf("| %-16s | 0x%08x | 0x%08x | 0x%08x | 0x%08x |\n", + section->name, + section->start_address, + section->length, + section->unknown0, + section->unknown1); +} + +static int dump_section_data(const char *path_prefix, + const char *name_prefix, + uint8_t *buffer, + struct actions_firmware_section *section) +{ + int fd; + int ret; + char path[PATH_MAX] = { 0 }; + ssize_t written; + + snprintf(path, PATH_MAX, "%s/%s%s", + path_prefix, name_prefix ? name_prefix : "", (char *)section->name); + + fd = creat(path, 0644); + if (fd < 0) { + perror("creat"); + ret = -errno; + goto out; + } + + written = write(fd, buffer + section->start_address, section->length); + if (written < 0) { + perror("write"); + ret = -errno; + goto out_close_fd; + } + + ret = 0; + +out_close_fd: + close(fd); +out: + return ret; +} + +static void usage(const char *name) +{ + printf("usage: %s [OPTIONS] \n", name); + printf("OPTIONS:\n"); + printf("\t-d \t\tdump the firmware sections into the directory\n"); + printf("\t-h \t\t\tthis help message\n"); + printf("\nEXAMPLE OF USE:\n"); + printf("\t%s -d PPX2230 ppx2230_eu_fus_aen.bin\n", name); +} + +int main(int argc, char *argv[]) +{ + int ret; + int opt; + + int fd; + struct stat st; + size_t size; + uint8_t *buffer; + uint8_t *buffer_iterator; + struct actions_firmware_header header; + struct actions_firmware_section section; + unsigned int i; + const char *path_prefix = NULL; + + while ((opt = getopt(argc, argv, "d:h")) != -1) { + switch (opt) { + case 'd': + path_prefix = optarg; + ret = mkdir(path_prefix, 0755); + if (ret < 0) { + fprintf(stderr, "Cannot create \"%s\", if it exists remove it first.\n", path_prefix); + goto out; + } + break; + case 'h': + usage(argv[0]); + ret = 0; + goto out; + default: /* '?' */ + usage(argv[0]); + ret = -EINVAL; + goto out; + } + } + + if (argv[optind] == NULL) { + usage(argv[0]); + ret = -EINVAL; + goto out; + } + + fd = open(argv[optind], O_RDONLY); + if (fd < 0) { + perror("open"); + ret = -EINVAL; + goto out; + } + + ret = fstat(fd, &st); + if (ret < 0) { + perror("fstat"); + goto out_close_fd; + } + size = (size_t)st.st_size; + + buffer = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buffer == NULL) { + perror("mmap"); + ret = -errno; + goto out_close_fd; + } + + if (size < ACTIONS_HEADER_DISK_SIZE) { + fprintf(stderr, "File cannot contain an Actions firmware header.\n"); + ret = -EINVAL; + goto out_munmap; + } + + /* iterate over a copy of the pointer */ + buffer_iterator = buffer; + + ret = parse_header(&buffer_iterator, &header); + if (ret < 0) { + fprintf(stderr, "Cannot parse the header.\n"); + goto out_munmap; + } + + print_header(&header); + + i = 0; + while (parse_section(&buffer_iterator, §ion) == 0) { + print_section(§ion); + i++; + + if (strncmp((char *)section.name, "FIRM", 16) == 0) { + uint8_t *firm_buffer_iterator = buffer + section.start_address + FIRM_TABLE_OFFSET; + struct actions_firmware_section firm_section; + struct actions_firmware_section prev_section = { + .start_address = section.start_address + FIRM_DATA_OFFSET, + .length = 0, + }; + while (parse_section(&firm_buffer_iterator, &firm_section) == 0) { + /* + * unknown1 seems to be some form of checksum for + * firm sections, if a sections have the same + * checksum of the previous one they are not + * duplicated but refer to the same memory + * region, so do not increase the start + * address. + */ + if (firm_section.unknown1 == prev_section.unknown1) { + firm_section.start_address = prev_section.start_address; + } else { + firm_section.start_address = prev_section.start_address + prev_section.length; + } + + printf("\t"); + print_section(&firm_section); + + if (path_prefix) + dump_section_data(path_prefix, + "FIRM_", + buffer, + &firm_section); + + prev_section = firm_section; + } + } else if (strncmp((char *)section.name, "LINUX", 16) == 0) { + continue; + } + + if (path_prefix) + dump_section_data(path_prefix, NULL, buffer, §ion); + + } + printf("Found %d sections.\n", i); + + ret = 0; + +out_munmap: + ret = munmap(buffer, size); + if (ret < 0) + perror("munmap"); + +out_close_fd: + ret = close(fd); + if (ret < 0) + perror("close"); + +out: + return ret; +} -- 2.1.4