69df56504e
Change-Id: Ib78f0fe58db5cec86f043d3e9e1ca14e69297ba0 Reviewed-on: http://gerrit.rockbox.org/911 Reviewed-by: Marcin Bukat <marcin.bukat@gmail.com>
1208 lines
34 KiB
C
1208 lines
34 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2013 Amaury Pouly
|
|
*
|
|
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#define _ISOC99_SOURCE /* snprintf() */
|
|
#define _POSIX_C_SOURCE 200809L /* for strdup */
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <stdarg.h>
|
|
#include <strings.h>
|
|
#include <getopt.h>
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
#include <lauxlib.h>
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
|
|
#if LUA_VERSION_NUM < 502
|
|
#warning You need at least lua 5.2
|
|
#endif
|
|
|
|
#include "crypto.h"
|
|
#include "elf.h"
|
|
#include "sb.h"
|
|
#include "sb1.h"
|
|
#include "misc.h"
|
|
#include "md5.h"
|
|
|
|
#define ARRAYLEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
|
|
|
lua_State *g_lua;
|
|
bool g_exit = false;
|
|
|
|
/**
|
|
* FW object library
|
|
*/
|
|
|
|
enum fw_type_t
|
|
{
|
|
FW_UNK, FW_ELF, FW_SB1, FW_SB2, FW_BIN, FW_EDOC
|
|
};
|
|
|
|
enum crc_type_t
|
|
{
|
|
CRC_RKW
|
|
};
|
|
|
|
struct crc_type_desc_t
|
|
{
|
|
enum crc_type_t type;
|
|
const char *lua_name;
|
|
unsigned (*fn)(uint8_t *buf, size_t len);
|
|
};
|
|
|
|
struct bin_file_t
|
|
{
|
|
size_t size;
|
|
void *data;
|
|
};
|
|
|
|
struct edoc_section_t
|
|
{
|
|
uint32_t addr;
|
|
size_t size;
|
|
void *data;
|
|
};
|
|
|
|
struct edoc_file_t
|
|
{
|
|
int nr_sections;
|
|
struct edoc_section_t *sections;
|
|
};
|
|
|
|
struct edoc_header_t
|
|
{
|
|
char magic[4];
|
|
uint32_t total_size;
|
|
uint32_t zero;
|
|
} __attribute__((packed));
|
|
|
|
struct edoc_section_header_t
|
|
{
|
|
uint32_t addr;
|
|
uint32_t size;
|
|
uint32_t checksum;
|
|
} __attribute__((packed));
|
|
|
|
uint32_t edoc_checksum(void *buffer, size_t size)
|
|
{
|
|
uint32_t c = 0;
|
|
uint32_t *p = buffer;
|
|
while(size >= 4)
|
|
{
|
|
c += *p + (*p >> 16);
|
|
p++;
|
|
size -= 4;
|
|
}
|
|
if(size != 0)
|
|
printf("[Checksum section size is not a multiple of 4 bytes !]\n");
|
|
return c & 0xffff;
|
|
}
|
|
|
|
#define FWOBJ_MAGIC ('F' | 'W' << 8 | 'O' << 16 | 'B' << 24)
|
|
|
|
struct fw_object_t
|
|
{
|
|
uint32_t magic;
|
|
enum fw_type_t type;
|
|
union
|
|
{
|
|
struct sb_file_t *sb;
|
|
struct sb1_file_t *sb1;
|
|
struct elf_params_t *elf;
|
|
struct bin_file_t *bin;
|
|
struct edoc_file_t *edoc;
|
|
}u;
|
|
};
|
|
|
|
typedef struct fw_addr_t
|
|
{
|
|
uint32_t addr;
|
|
const char *section;
|
|
}fw_addr_t;
|
|
|
|
#define INVALID_FW_ADDR ((struct fw_addr_t){.addr = (uint32_t)-1, .section = ""})
|
|
#define IS_VALID_FW_ADDR(x) !(x.addr == (uint32_t)-1 && x.section && strlen(x.section) == 0)
|
|
|
|
typedef struct fw_sym_addr_t
|
|
{
|
|
const char *name;
|
|
const char *section;
|
|
}fw_sym_addr_t;
|
|
|
|
struct fw_section_info_t
|
|
{
|
|
uint32_t addr;
|
|
uint32_t size;
|
|
};
|
|
|
|
static inline struct fw_addr_t make_addr(uint32_t addr, const char *section)
|
|
{
|
|
return (struct fw_addr_t){.addr = addr, .section = section};
|
|
}
|
|
|
|
static enum fw_type_t fw_guess(const char *filename)
|
|
{
|
|
enum sb_version_guess_t ver = guess_sb_version(filename);
|
|
if(ver == SB_VERSION_ERR)
|
|
{
|
|
printf("Cannot open/read SB file: %m\n");
|
|
return FW_UNK;
|
|
}
|
|
if(ver == SB_VERSION_1) return FW_SB1;
|
|
if(ver == SB_VERSION_2) return FW_SB2;
|
|
FILE *fd = fopen(filename, "rb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("Cannot open '%s' for reading: %m\n", filename);
|
|
return FW_UNK;
|
|
}
|
|
uint8_t sig[4];
|
|
if(fread(sig, 4, 1, fd) == 1)
|
|
{
|
|
if(sig[0] == 'E' && sig[1] == 'D' && sig[2] == 'O' && sig[3] == 'C')
|
|
return FW_EDOC;
|
|
}
|
|
bool is_elf = elf_guess(elf_std_read, fd);
|
|
fclose(fd);
|
|
return is_elf ? FW_ELF : FW_BIN;
|
|
}
|
|
|
|
static const char *fw_type_name(enum fw_type_t t)
|
|
{
|
|
switch(t)
|
|
{
|
|
case FW_SB1: return "SB1";
|
|
case FW_SB2: return "SB2";
|
|
case FW_ELF: return "ELF";
|
|
case FW_BIN: return "binary";
|
|
case FW_EDOC: return "EDOC";
|
|
default: return "<unk>";
|
|
}
|
|
}
|
|
|
|
static struct fw_object_t *fw_load(const char *filename, enum fw_type_t force)
|
|
{
|
|
if(force == FW_UNK)
|
|
force = fw_guess(filename);
|
|
if(force == FW_UNK)
|
|
{
|
|
printf("Cannot guess type of file: %m. This probably indicates the file cannot be read.\n");
|
|
return NULL;
|
|
}
|
|
|
|
struct fw_object_t *obj = malloc(sizeof(struct fw_object_t));
|
|
memset(obj, 0, sizeof(struct fw_object_t));
|
|
obj->magic = FWOBJ_MAGIC;
|
|
|
|
printf("Loading '%s' as %s file...\n", filename, fw_type_name(force));
|
|
color(OFF);
|
|
if(force == FW_SB2)
|
|
{
|
|
enum sb_error_t err;
|
|
obj->type = FW_SB2;
|
|
obj->u.sb = sb_read_file(filename, false, NULL, generic_std_printf, &err);
|
|
if(obj->u.sb == NULL)
|
|
{
|
|
printf("SB read failed: %d\n", err);
|
|
goto Lerr;
|
|
}
|
|
return obj;
|
|
}
|
|
else if(force == FW_SB1)
|
|
{
|
|
enum sb1_error_t err;
|
|
obj->type = FW_SB1;
|
|
obj->u.sb1 = sb1_read_file(filename, NULL, generic_std_printf, &err);
|
|
if(obj->u.sb1 == NULL)
|
|
{
|
|
printf("SB1 read failed: %d\n", err);
|
|
goto Lerr;
|
|
}
|
|
return obj;
|
|
}
|
|
else if(force == FW_ELF)
|
|
{
|
|
FILE *fd = fopen(filename, "rb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("cannot open '%s' for reading: %m\n", filename);
|
|
goto Lerr;
|
|
}
|
|
obj->type = FW_ELF;
|
|
obj->u.elf = malloc(sizeof(struct elf_params_t));
|
|
elf_init(obj->u.elf);
|
|
bool loaded = elf_read_file(obj->u.elf, elf_std_read, generic_std_printf, fd);
|
|
fclose(fd);
|
|
if(!loaded)
|
|
{
|
|
printf("error loading elf file '%s'\n", filename);
|
|
free(obj->u.elf);
|
|
goto Lerr;
|
|
}
|
|
return obj;
|
|
}
|
|
else if(force == FW_EDOC)
|
|
{
|
|
FILE *fd = fopen(filename, "rb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("cannot open '%s' for reading: %m\n", filename);
|
|
goto Lerr;
|
|
}
|
|
struct edoc_header_t hdr;
|
|
if(fread(&hdr, sizeof(hdr), 1, fd) != 1)
|
|
{
|
|
printf("cannot read EDOC header: %m\n");
|
|
goto Lerr;
|
|
}
|
|
if(strncmp(hdr.magic, "EDOC", 4) != 0)
|
|
{
|
|
printf("EDOC signature mismatch\n");
|
|
goto Lerr;
|
|
}
|
|
struct edoc_file_t *file = xmalloc(sizeof(struct edoc_file_t));
|
|
memset(file, 0, sizeof(struct edoc_file_t));
|
|
for(size_t pos = sizeof(hdr); pos < hdr.total_size + 8;)
|
|
{
|
|
struct edoc_section_header_t shdr;
|
|
if(fread(&shdr, sizeof(shdr), 1, fd) != 1)
|
|
{
|
|
printf("cannot read EDOC section header: %m\n");
|
|
goto Lerr;
|
|
}
|
|
file->sections = realloc(file->sections, (file->nr_sections + 1) *
|
|
sizeof(struct edoc_section_t));
|
|
file->sections[file->nr_sections].addr = shdr.addr;
|
|
file->sections[file->nr_sections].size = shdr.size;
|
|
file->sections[file->nr_sections].data = xmalloc(shdr.size);
|
|
if(fread(file->sections[file->nr_sections].data, shdr.size, 1, fd) != 1)
|
|
{
|
|
printf("cannot read EDOC section: %m\n");
|
|
goto Lerr;
|
|
}
|
|
if(edoc_checksum(file->sections[file->nr_sections].data, shdr.size) != shdr.checksum)
|
|
{
|
|
printf("EDOC section checksum mismatch\n");
|
|
goto Lerr;
|
|
}
|
|
file->nr_sections++;
|
|
pos += sizeof(shdr) + shdr.size;
|
|
}
|
|
fclose(fd);
|
|
obj->type = FW_EDOC;
|
|
obj->u.edoc = file;
|
|
return obj;
|
|
}
|
|
else
|
|
{
|
|
FILE *fd = fopen(filename, "rb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("cannot open '%s' for reading: %m\n", filename);
|
|
goto Lerr;
|
|
}
|
|
obj->u.bin = malloc(sizeof(struct bin_file_t));
|
|
obj->type = FW_BIN;
|
|
fseek(fd, 0, SEEK_END);
|
|
obj->u.bin->size = ftell(fd);
|
|
fseek(fd, 0, SEEK_SET);
|
|
obj->u.bin->data = xmalloc(obj->u.bin->size);
|
|
if(fread(obj->u.bin->data, obj->u.bin->size, 1, fd) != 1)
|
|
{
|
|
printf("cannot read '%s': %m\n", filename);
|
|
free(obj->u.bin->data);
|
|
free(obj->u.bin);
|
|
goto Lerr;
|
|
}
|
|
fclose(fd);
|
|
return obj;
|
|
}
|
|
|
|
Lerr:
|
|
free(obj);
|
|
return NULL;
|
|
}
|
|
|
|
static bool fw_save(struct fw_object_t *obj, const char *filename)
|
|
{
|
|
if(obj->type == FW_ELF)
|
|
{
|
|
FILE *fd = fopen(filename, "wb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("Cannot open '%s' for writing: %m\n", filename);
|
|
return false;
|
|
}
|
|
elf_write_file(obj->u.elf, elf_std_write, generic_std_printf, fd);
|
|
fclose(fd);
|
|
return true;
|
|
}
|
|
else if(obj->type == FW_SB2)
|
|
{
|
|
/* sb_read_file will fill real key and IV but we don't want to override
|
|
* them when looping back otherwise the output will be inconsistent and
|
|
* garbage */
|
|
obj->u.sb->override_real_key = false;
|
|
obj->u.sb->override_crypto_iv = false;
|
|
enum sb_error_t err = sb_write_file(obj->u.sb, filename, NULL, generic_std_printf);
|
|
if(err != SB_SUCCESS)
|
|
{
|
|
printf("Cannot write '%s': %d\n", filename, err);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if(obj->type == FW_BIN)
|
|
{
|
|
FILE *fd = fopen(filename, "wb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("Cannot open '%s' for writing: %m\n", filename);
|
|
return false;
|
|
}
|
|
fwrite(obj->u.bin->data, 1, obj->u.bin->size, fd);
|
|
fclose(fd);
|
|
return true;
|
|
}
|
|
else if(obj->type == FW_EDOC)
|
|
{
|
|
FILE *fd = fopen(filename, "wb");
|
|
if(fd == NULL)
|
|
{
|
|
printf("Cannot open '%s' for writing: %m\n", filename);
|
|
return false;
|
|
}
|
|
struct edoc_header_t hdr;
|
|
strncpy(hdr.magic, "EDOC", 4);
|
|
hdr.zero = 0;
|
|
hdr.total_size = 4;
|
|
for(int i = 0; i < obj->u.edoc->nr_sections; i++)
|
|
hdr.total_size += sizeof(struct edoc_section_header_t) +
|
|
obj->u.edoc->sections[i].size;
|
|
fwrite(&hdr, sizeof(hdr), 1, fd);
|
|
for(int i = 0; i < obj->u.edoc->nr_sections; i++)
|
|
{
|
|
struct edoc_section_header_t shdr;
|
|
shdr.addr = obj->u.edoc->sections[i].addr;
|
|
shdr.size = obj->u.edoc->sections[i].size;
|
|
shdr.checksum = edoc_checksum(obj->u.edoc->sections[i].data, shdr.size);
|
|
fwrite(&shdr, sizeof(shdr), 1, fd);
|
|
fwrite(obj->u.edoc->sections[i].data, shdr.size, 1, fd);
|
|
}
|
|
fclose(fd);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
printf("Unimplemented fw_save\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void fw_free(struct fw_object_t *obj)
|
|
{
|
|
switch(obj->type)
|
|
{
|
|
case FW_SB1:
|
|
sb1_free(obj->u.sb1);
|
|
break;
|
|
case FW_SB2:
|
|
sb_free(obj->u.sb);
|
|
break;
|
|
case FW_ELF:
|
|
elf_release(obj->u.elf);
|
|
free(obj->u.elf);
|
|
break;
|
|
case FW_BIN:
|
|
free(obj->u.bin->data);
|
|
free(obj->u.bin);
|
|
case FW_EDOC:
|
|
for(int i = 0; i < obj->u.edoc->nr_sections; i++)
|
|
free(obj->u.edoc->sections[i].data);
|
|
free(obj->u.edoc->sections);
|
|
free(obj->u.edoc);
|
|
default:
|
|
break;
|
|
}
|
|
free(obj);
|
|
}
|
|
|
|
static struct elf_section_t *elf_find_section(struct elf_params_t *elf, fw_addr_t addr)
|
|
{
|
|
struct elf_section_t *match = NULL;
|
|
for(struct elf_section_t *sec = elf->first_section; sec; sec = sec->next)
|
|
{
|
|
if(addr.section && strcmp(addr.section, sec->name) != 0)
|
|
continue;
|
|
if(addr.addr < sec->addr || addr.addr >= sec->addr + sec->size)
|
|
continue;
|
|
if(match != NULL)
|
|
{
|
|
printf("Error: there are several match for address %#x@%s\n",
|
|
(unsigned)addr.addr, addr.section);
|
|
return NULL;
|
|
}
|
|
match = sec;
|
|
}
|
|
if(match == NULL)
|
|
{
|
|
printf("Error: there is no match for address %#x@%s\n", (unsigned)addr.addr,
|
|
addr.section);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static bool fw_elf_rw(struct elf_params_t *elf, fw_addr_t addr, void *buffer, size_t size, bool read)
|
|
{
|
|
struct elf_section_t *sec = elf_find_section(elf, addr);
|
|
if(sec == NULL)
|
|
return false;
|
|
if(addr.addr + size > sec->addr + sec->size)
|
|
{
|
|
printf("Unsupported read/write across section boundary in ELF firmware\n");
|
|
return false;
|
|
}
|
|
if(sec->type != EST_LOAD)
|
|
{
|
|
printf("Error: unimplemented read/write to a fill section (ELF)\n");
|
|
return false;
|
|
}
|
|
void *data = sec->section + addr.addr - sec->addr;
|
|
if(read)
|
|
memcpy(buffer, data, size);
|
|
else
|
|
memcpy(data, buffer, size);
|
|
return true;
|
|
}
|
|
|
|
static struct sb_inst_t *sb2_find_section(struct sb_file_t *sb_file, fw_addr_t addr)
|
|
{
|
|
struct sb_inst_t *match = NULL;
|
|
uint32_t sec_id = 0xffffffff;
|
|
int inst_nr = -1;
|
|
if(addr.section)
|
|
{
|
|
/* must be of the form name[.index] */
|
|
const char *mid = strchr(addr.section, '.');
|
|
char *end;
|
|
if(mid)
|
|
{
|
|
inst_nr = strtol(mid + 1, &end, 0);
|
|
if(*end)
|
|
{
|
|
printf("Warning: ignoring invalid section name '%s' (invalid inst nr)\n", addr.section);
|
|
goto Lscan;
|
|
}
|
|
}
|
|
else
|
|
mid = addr.section + strlen(addr.section);
|
|
if(mid - addr.section > 4)
|
|
{
|
|
printf("Warning: ignoring invalid section name '%s' (sec id too long)\n", addr.section);
|
|
goto Lscan;
|
|
}
|
|
sec_id = 0;
|
|
for(int i = 0; i < mid - addr.section; i++)
|
|
sec_id = sec_id << 8 | addr.section[i];
|
|
}
|
|
|
|
Lscan:
|
|
for(int i = 0; i < sb_file->nr_sections; i++)
|
|
{
|
|
struct sb_section_t *sec = &sb_file->sections[i];
|
|
if(addr.section && sec->identifier != sec_id)
|
|
continue;
|
|
int cur_blob = 0;
|
|
for(int j = 0; j < sec->nr_insts; j++)
|
|
{
|
|
struct sb_inst_t *inst = &sec->insts[j];
|
|
if(inst->inst == SB_INST_CALL || inst->inst == SB_INST_JUMP)
|
|
cur_blob++;
|
|
if(inst_nr >= 0 && cur_blob != inst_nr)
|
|
continue;
|
|
if(inst->inst != SB_INST_LOAD && inst->inst != SB_INST_FILL && inst->inst != SB_INST_DATA)
|
|
continue;
|
|
/* only consider data sections if section has been explicitely stated */
|
|
if(inst->inst == SB_INST_DATA && !addr.section)
|
|
continue;
|
|
/* for data sections, address will be 0 */
|
|
if(addr.addr < inst->addr || addr.addr > inst->addr + inst->size)
|
|
continue;
|
|
if(match != NULL)
|
|
{
|
|
printf("Error: there are several match for address %#x@%s\n",
|
|
(unsigned)addr.addr, addr.section);
|
|
return NULL;
|
|
}
|
|
match = inst;
|
|
}
|
|
}
|
|
if(match == NULL)
|
|
{
|
|
printf("Error: there is no match for address %#x@%s\n", (unsigned)addr.addr,
|
|
addr.section);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static bool fw_sb2_rw(struct sb_file_t *sb_file, fw_addr_t addr, void *buffer, size_t size, bool read)
|
|
{
|
|
struct sb_inst_t *inst = sb2_find_section(sb_file, addr);
|
|
if(inst == NULL)
|
|
return false;
|
|
if(addr.addr + size > inst->addr + inst->size)
|
|
{
|
|
printf("Unsupported read/write across instruction boundary in SB firmware\n");
|
|
return false;
|
|
}
|
|
if(inst->inst != SB_INST_LOAD && inst->inst != SB_INST_DATA)
|
|
{
|
|
printf("Error: unimplemented read/write to a fill instruction (SB)\n");
|
|
return false;
|
|
}
|
|
void *data = inst->data + addr.addr - inst->addr;
|
|
if(read)
|
|
memcpy(buffer, data, size);
|
|
else
|
|
memcpy(data, buffer, size);
|
|
return true;
|
|
}
|
|
|
|
static bool fw_bin_rw(struct bin_file_t *bin_file, fw_addr_t addr, void *buffer, size_t size, bool read)
|
|
{
|
|
if(addr.addr + size > bin_file->size)
|
|
{
|
|
printf("Unsupport read/write accross boundary in binary firmware\n");
|
|
return false;
|
|
}
|
|
void *data = bin_file->data + addr.addr;
|
|
if(read)
|
|
memcpy(buffer, data, size);
|
|
else
|
|
memcpy(data, buffer, size);
|
|
return true;
|
|
}
|
|
|
|
static bool fw_edoc_rw(struct edoc_file_t *edoc_file, fw_addr_t addr, void *buffer, size_t size, bool read)
|
|
{
|
|
for(int i = 0; i < edoc_file->nr_sections; i++)
|
|
{
|
|
if(addr.addr < edoc_file->sections[i].addr ||
|
|
addr.addr + size >= edoc_file->sections[i].addr + edoc_file->sections[i].size)
|
|
continue;
|
|
void *data = edoc_file->sections[i].data + addr.addr - edoc_file->sections[i].addr;
|
|
if(read)
|
|
memcpy(buffer, data, size);
|
|
else
|
|
memcpy(data, buffer, size);
|
|
return true;
|
|
}
|
|
printf("Unsupport read/write accross boundary in EDOC firmware\n");
|
|
return false;
|
|
}
|
|
|
|
static bool fw_rw(struct fw_object_t *obj, fw_addr_t addr, void *buffer, size_t size, bool read)
|
|
{
|
|
switch(obj->type)
|
|
{
|
|
case FW_ELF: return fw_elf_rw(obj->u.elf, addr, buffer, size, read);
|
|
case FW_SB2: return fw_sb2_rw(obj->u.sb, addr, buffer, size, read);
|
|
case FW_BIN: return fw_bin_rw(obj->u.bin, addr, buffer, size, read);
|
|
case FW_EDOC: return fw_edoc_rw(obj->u.edoc, addr, buffer, size, read);
|
|
default:
|
|
printf("Error: unimplemented read/write for type %d\n", obj->type);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool fw_read(struct fw_object_t *obj, fw_addr_t addr, void *buffer, size_t size)
|
|
{
|
|
return fw_rw(obj, addr, buffer, size, true);
|
|
}
|
|
|
|
static bool fw_write(struct fw_object_t *obj, fw_addr_t addr, const void *buffer, size_t size)
|
|
{
|
|
return fw_rw(obj, addr, (void *)buffer, size, false);
|
|
}
|
|
|
|
static bool elf_find_sym(struct elf_params_t *elf, fw_sym_addr_t addr, fw_addr_t *out_addr)
|
|
{
|
|
bool found = false;
|
|
for(struct elf_symbol_t *cur = elf->first_symbol; cur; cur = cur->next)
|
|
{
|
|
if(strcmp(cur->name, addr.name) != 0)
|
|
continue;
|
|
if(addr.section && strcmp(cur->section, addr.section) != 0)
|
|
continue;
|
|
if(found)
|
|
{
|
|
printf("Error: there are several match for symbol %s@%s\n", addr.name, addr.section);
|
|
return false;
|
|
}
|
|
out_addr->addr = cur->addr;
|
|
out_addr->section = cur->section;
|
|
found = true;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static bool fw_find_sym(struct fw_object_t *obj, fw_sym_addr_t addr, fw_addr_t *out_addr)
|
|
{
|
|
switch(obj->type)
|
|
{
|
|
case FW_ELF: return elf_find_sym(obj->u.elf, addr, out_addr);
|
|
case FW_SB2: case FW_SB1: case FW_BIN: return false;
|
|
default:
|
|
printf("Error: unimplemented find addr for type %d\n", obj->type);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool fw_bin_section_info(struct bin_file_t *obj, const char *sec, struct fw_section_info_t *out)
|
|
{
|
|
// the only valid section names are NULL and ""
|
|
if(sec != NULL && strlen(sec) != 0)
|
|
return false;
|
|
out->addr = 0;
|
|
out->size = obj->size;
|
|
return true;
|
|
}
|
|
|
|
static bool fw_section_info(struct fw_object_t *obj, const char *sec, struct fw_section_info_t *out)
|
|
{
|
|
switch(obj->type)
|
|
{
|
|
case FW_BIN: return fw_bin_section_info(obj->u.bin, sec, out);
|
|
default:
|
|
printf("Error: unimplemented get section info for type %d\n", obj->type);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* LUA library
|
|
*/
|
|
|
|
struct fw_object_t *my_lua_get_object(lua_State *state, int index)
|
|
{
|
|
struct fw_object_t *obj = lua_touserdata(state, index);
|
|
if(obj == NULL || obj->magic != FWOBJ_MAGIC)
|
|
luaL_error(state, "invalid parameter: not a firmware object");
|
|
return obj;
|
|
}
|
|
|
|
const char *my_lua_get_string(lua_State *state, int index)
|
|
{
|
|
return luaL_checkstring(state, index);
|
|
}
|
|
|
|
lua_Unsigned my_lua_get_unsigned(lua_State *state, int index)
|
|
{
|
|
lua_Integer i = luaL_checkinteger(state, index);
|
|
if(i < 0)
|
|
luaL_error(state, "invalid parameter: not an unsigned value");
|
|
return i;
|
|
}
|
|
|
|
fw_addr_t my_lua_get_addr(lua_State *state, int index)
|
|
{
|
|
if(!lua_istable(state, index))
|
|
luaL_error(state, "invalid parameter: not an address table");
|
|
lua_getfield(state, index, "addr");
|
|
if(lua_isnil(state, -1))
|
|
luaL_error(state, "invalid parameter: address has not field 'addr'");
|
|
uint32_t addr = my_lua_get_unsigned(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
char *sec = NULL;
|
|
lua_getfield(state, index, "section");
|
|
if(!lua_isnil(state, -1))
|
|
sec = strdup(my_lua_get_string(state, -1));
|
|
lua_pop(state, 1);
|
|
return make_addr(addr, sec);
|
|
}
|
|
|
|
void my_lua_pushbuffer(lua_State *state, void *buffer, size_t len)
|
|
{
|
|
uint8_t *p = buffer;
|
|
lua_createtable(state, len, 0);
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
lua_pushinteger(state, i + 1);
|
|
lua_pushinteger(state, p[i]);
|
|
lua_settable(state, -3);
|
|
}
|
|
}
|
|
|
|
void *my_lua_get_buffer(lua_State *state, int index, size_t *len)
|
|
{
|
|
if(!lua_istable(state, index))
|
|
luaL_error(state, "invalid parameter: not a data table");
|
|
*len = lua_rawlen(state, index);
|
|
uint8_t *buf = xmalloc(*len);
|
|
for(int i = 0; i < *len; i++)
|
|
{
|
|
lua_pushinteger(state, i + 1);
|
|
lua_gettable(state, index);
|
|
if(lua_isnil(state, -1))
|
|
{
|
|
free(buf);
|
|
luaL_error(state, "invalid parameter: not a data table, missing some fields");
|
|
}
|
|
int v = luaL_checkinteger(state, -1);
|
|
lua_pop(state, 1);
|
|
if(v < 0 || v > 0xff)
|
|
{
|
|
free(buf);
|
|
luaL_error(state, "invalid parameter: not a data table, field is not a byte");
|
|
}
|
|
buf[i] = v;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
int my_lua_load_file(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 1)
|
|
return luaL_error(state, "load_file takes one argument: a filename");
|
|
enum fw_type_t type = lua_tounsigned(state, lua_upvalueindex(1));
|
|
const char *filename = my_lua_get_string(state, 1);
|
|
struct fw_object_t *obj = fw_load(filename, type);
|
|
if(obj)
|
|
lua_pushlightuserdata(state, obj);
|
|
else
|
|
lua_pushnil(state);
|
|
return 1;
|
|
}
|
|
|
|
int my_lua_save_file(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 2)
|
|
return luaL_error(state, "load_file takes two arguments: a firmware and a filename");
|
|
struct fw_object_t *obj = my_lua_get_object(state, 1);
|
|
const char *filename = my_lua_get_string(state, 2);
|
|
lua_pushboolean(state, fw_save(obj, filename));
|
|
return 1;
|
|
}
|
|
|
|
int my_lua_read(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 3)
|
|
return luaL_error(state, "read takes three arguments: a firmware, an address and a length");
|
|
struct fw_object_t *obj = my_lua_get_object(state, 1);
|
|
fw_addr_t addr = my_lua_get_addr(state, 2);
|
|
size_t len = my_lua_get_unsigned(state, 3);
|
|
void *buffer = xmalloc(len);
|
|
bool ret = fw_read(obj, addr, buffer, len);
|
|
if(ret)
|
|
my_lua_pushbuffer(state, buffer, len);
|
|
else
|
|
lua_pushnil(state);
|
|
free(buffer);
|
|
return 1;
|
|
}
|
|
|
|
int my_lua_write(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 3)
|
|
return luaL_error(state, "write takes three arguments: a firmware, an address and a data table");
|
|
struct fw_object_t *obj = my_lua_get_object(state, 1);
|
|
fw_addr_t addr = my_lua_get_addr(state, 2);
|
|
size_t len;
|
|
void *buf = my_lua_get_buffer(state, 3, &len);
|
|
fw_write(obj, addr, buf, len);
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
|
|
int my_lua_section_info(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 2)
|
|
return luaL_error(state, "section_info takes two arguments: a firmware and a section name");
|
|
struct fw_object_t *obj = my_lua_get_object(state, 1);
|
|
const char *secname = my_lua_get_string(state, 2);
|
|
struct fw_section_info_t seci;
|
|
if(fw_section_info(obj, secname, &seci))
|
|
{
|
|
lua_createtable(state, 0, 0);
|
|
lua_pushinteger(state, seci.addr);
|
|
lua_setfield(state, -2, "addr");
|
|
lua_pushinteger(state, seci.size);
|
|
lua_setfield(state, -2, "size");
|
|
}
|
|
else
|
|
lua_pushnil(state);
|
|
return 1;
|
|
}
|
|
|
|
unsigned crc_rkw(uint8_t *buf, size_t len)
|
|
{
|
|
/* polynomial 0x04c10db7 */
|
|
static const uint32_t crc32_lookup[16] =
|
|
{ /* lookup table for 4 bits at a time is affordable */
|
|
0x00000000, 0x04C10DB7, 0x09821B6E, 0x0D4316D9,
|
|
0x130436DC, 0x17C53B6B, 0x1A862DB2, 0x1E472005,
|
|
0x26086DB8, 0x22C9600F, 0x2F8A76D6, 0x2B4B7B61,
|
|
0x350C5B64, 0x31CD56D3, 0x3C8E400A, 0x384F4DBD
|
|
};
|
|
|
|
uint32_t crc32 = 0;
|
|
unsigned char byte;
|
|
uint32_t t;
|
|
|
|
while (len--)
|
|
{
|
|
byte = *buf++; /* get one byte of data */
|
|
|
|
/* upper nibble of our data */
|
|
t = crc32 >> 28; /* extract the 4 most significant bits */
|
|
t ^= byte >> 4; /* XOR in 4 bits of data into the extracted bits */
|
|
crc32 <<= 4; /* shift the CRC register left 4 bits */
|
|
crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
|
|
|
|
/* lower nibble of our data */
|
|
t = crc32 >> 28; /* extract the 4 most significant bits */
|
|
t ^= byte & 0x0F; /* XOR in 4 bits of data into the extracted bits */
|
|
crc32 <<= 4; /* shift the CRC register left 4 bits */
|
|
crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
|
|
}
|
|
|
|
return crc32;
|
|
}
|
|
|
|
struct crc_type_desc_t crc_types[] =
|
|
{
|
|
{CRC_RKW, "RKW", crc_rkw}
|
|
};
|
|
|
|
int my_lua_crc_buf(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 2)
|
|
return luaL_error(state, "crc_buf takes two arguments: a crc type and a buffer");
|
|
unsigned type = lua_tounsigned(state, 1);
|
|
size_t len;
|
|
void *buf = my_lua_get_buffer(state, 2, &len);
|
|
for(int i = 0; i < ARRAYLEN(crc_types); i++)
|
|
if(crc_types[i].type == type)
|
|
{
|
|
lua_pushunsigned(state, crc_types[i].fn(buf, len));
|
|
free(buf);
|
|
return 1;
|
|
}
|
|
free(buf);
|
|
luaL_error(state, "crc_buf: unknown crc type");
|
|
return 0;
|
|
}
|
|
|
|
/* compute MD5 sum of a buffer */
|
|
static bool compute_md5sum_buf(void *buf, size_t sz, uint8_t file_md5sum[16])
|
|
{
|
|
md5_context ctx;
|
|
md5_starts(&ctx);
|
|
md5_update(&ctx, buf, sz);
|
|
md5_finish(&ctx, file_md5sum);
|
|
return true;
|
|
}
|
|
|
|
/* read a file to a buffer */
|
|
static bool read_file(const char *file, void **buffer, size_t *size)
|
|
{
|
|
FILE *f = fopen(file, "rb");
|
|
if(f == NULL)
|
|
{
|
|
printf("Error: cannot open file for reading: %m\n");
|
|
return false;
|
|
}
|
|
fseek(f, 0, SEEK_END);
|
|
*size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
*buffer = xmalloc(*size);
|
|
if(fread(*buffer, *size, 1, f) != 1)
|
|
{
|
|
printf("Error: cannot read file: %m\n");
|
|
free(*buffer);
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
|
|
/* compute MD5 of a file */
|
|
static bool compute_md5sum(const char *file, uint8_t file_md5sum[16])
|
|
{
|
|
void *buf;
|
|
size_t sz;
|
|
if(!read_file(file, &buf, &sz))
|
|
return false;
|
|
compute_md5sum_buf(buf, sz, file_md5sum);
|
|
free(buf);
|
|
return true;
|
|
}
|
|
|
|
int my_lua_md5sum(lua_State *state)
|
|
{
|
|
int n = lua_gettop(state);
|
|
if(n != 1)
|
|
return luaL_error(state, "md5sum takes one argument: a filename");
|
|
const char *filename = my_lua_get_string(state, 1);
|
|
uint8_t md5sum[16];
|
|
if(!compute_md5sum(filename, md5sum))
|
|
return luaL_error(state, "cannot compute md5sum of the file");
|
|
my_lua_pushbuffer(state, md5sum, sizeof(md5sum));
|
|
return 1;
|
|
}
|
|
|
|
static bool init_lua_hwp(void)
|
|
{
|
|
lua_pushunsigned(g_lua, FW_UNK);
|
|
lua_pushcclosure(g_lua, my_lua_load_file, 1);
|
|
lua_setfield(g_lua, -2, "load_file");
|
|
|
|
lua_pushunsigned(g_lua, FW_ELF);
|
|
lua_pushcclosure(g_lua, my_lua_load_file, 1);
|
|
lua_setfield(g_lua, -2, "load_elf_file");
|
|
|
|
lua_pushunsigned(g_lua, FW_SB2);
|
|
lua_pushcclosure(g_lua, my_lua_load_file, 1);
|
|
lua_setfield(g_lua, -2, "load_sb_file");
|
|
|
|
lua_pushunsigned(g_lua, FW_SB1);
|
|
lua_pushcclosure(g_lua, my_lua_load_file, 1);
|
|
lua_setfield(g_lua, -2, "load_sb1_file");
|
|
|
|
lua_pushunsigned(g_lua, FW_BIN);
|
|
lua_pushcclosure(g_lua, my_lua_load_file, 1);
|
|
lua_setfield(g_lua, -2, "load_bin_file");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_save_file);
|
|
lua_setfield(g_lua, -2, "save_file");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_read);
|
|
lua_setfield(g_lua, -2, "read");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_write);
|
|
lua_setfield(g_lua, -2, "write");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_section_info);
|
|
lua_setfield(g_lua, -2, "section_info");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_md5sum);
|
|
lua_setfield(g_lua, -2, "md5sum");
|
|
|
|
lua_newtable(g_lua);
|
|
for(int i = 0; i < ARRAYLEN(crc_types); i++)
|
|
{
|
|
lua_pushunsigned(g_lua, crc_types[i].type);
|
|
lua_setfield(g_lua, -2, crc_types[i].lua_name);
|
|
}
|
|
lua_setfield(g_lua, -2, "CRC");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_crc_buf);
|
|
lua_setfield(g_lua, -2, "crc_buf");
|
|
|
|
return true;
|
|
}
|
|
|
|
int my_lua_exit(lua_State *state)
|
|
{
|
|
g_exit = true;
|
|
return 0;
|
|
}
|
|
|
|
bool my_lua_create_arg(lua_State *state, int argc, char **argv)
|
|
{
|
|
lua_newtable(state); // arg
|
|
for(int i = 0; i < argc; i++)
|
|
{
|
|
lua_pushinteger(state, i + 1);
|
|
lua_pushstring(state, argv[i]);
|
|
lua_settable(state, -3);
|
|
}
|
|
lua_setglobal(state, "arg");
|
|
return true;
|
|
}
|
|
|
|
static bool init_lua(void)
|
|
{
|
|
g_lua = luaL_newstate();
|
|
if(g_lua == NULL)
|
|
{
|
|
printf("Cannot create lua state\n");
|
|
return 1;
|
|
}
|
|
// open all standard libraires
|
|
luaL_openlibs(g_lua);
|
|
|
|
lua_newtable(g_lua); // hwp
|
|
if(!init_lua_hwp())
|
|
return false;
|
|
lua_setglobal(g_lua, "hwp");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_exit);
|
|
lua_setglobal(g_lua, "exit");
|
|
|
|
lua_pushcfunction(g_lua, my_lua_exit);
|
|
lua_setglobal(g_lua, "quit");
|
|
|
|
return true;
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
printf("Usage: hwpatcher [options] [--] [arguments]\n");
|
|
printf("Options:\n");
|
|
printf(" -?/--help Display this message\n");
|
|
printf(" -d/--debug Enable debug output\n");
|
|
printf(" -n/--no-color Disable color output\n");
|
|
printf(" -i/--interactive Enter interactive mode after all files have run\n");
|
|
printf(" -f/--do-file <f> Do lua file\n");
|
|
printf(" -k <file> Add key file\n");
|
|
printf(" -z Add zero key\n");
|
|
printf(" --add-key <key> Add single key (hex)\n");
|
|
printf(" -x Use default sb1 key\n");
|
|
printf("All files executed are provided with the extra arguments in the 'arg' table\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
bool interactive = false;
|
|
if(argc <= 1)
|
|
usage();
|
|
if(!init_lua())
|
|
return 1;
|
|
char **do_files = xmalloc(argc * sizeof(char *));
|
|
int nr_do_files = 0;
|
|
while(1)
|
|
{
|
|
static struct option long_options[] =
|
|
{
|
|
{"help", no_argument, 0, '?'},
|
|
{"debug", no_argument, 0, 'd'},
|
|
{"no-color", no_argument, 0, 'n'},
|
|
{"interactive", no_argument, 0, 'i'},
|
|
{"do-file", required_argument, 0, 'f'},
|
|
{"add-key", required_argument, 0, 'a'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
int c = getopt_long(argc, argv, "?dif:zx", long_options, NULL);
|
|
if(c == -1)
|
|
break;
|
|
switch(c)
|
|
{
|
|
case -1:
|
|
break;
|
|
case 'n':
|
|
enable_color(false);
|
|
break;
|
|
case 'd':
|
|
g_debug = true;
|
|
break;
|
|
case '?':
|
|
usage();
|
|
break;
|
|
case 'i':
|
|
interactive = true;
|
|
break;
|
|
case 'f':
|
|
do_files[nr_do_files++] = optarg;
|
|
break;
|
|
case 'z':
|
|
{
|
|
struct crypto_key_t g_zero_key;
|
|
sb_get_zero_key(&g_zero_key);
|
|
add_keys(&g_zero_key, 1);
|
|
break;
|
|
}
|
|
case 'x':
|
|
{
|
|
struct crypto_key_t key;
|
|
sb1_get_default_key(&key);
|
|
add_keys(&key, 1);
|
|
break;
|
|
}
|
|
case 'a':
|
|
{
|
|
struct crypto_key_t key;
|
|
char *s = optarg;
|
|
if(!parse_key(&s, &key))
|
|
bug("Invalid key specified as argument\n");
|
|
if(*s != 0)
|
|
bug("Trailing characters after key specified as argument\n");
|
|
add_keys(&key, 1);
|
|
break;
|
|
}
|
|
default:
|
|
printf("Internal error: unknown option '%c'\n", c);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(!my_lua_create_arg(g_lua, argc - optind, argv + optind))
|
|
return 1;
|
|
|
|
for(int i = 0; i < nr_do_files; i++)
|
|
{
|
|
if(luaL_dofile(g_lua, do_files[i]))
|
|
{
|
|
printf("error in %s: %s\n", do_files[i], lua_tostring(g_lua, -1));
|
|
return 1;
|
|
}
|
|
lua_pop(g_lua, lua_gettop(g_lua));
|
|
}
|
|
|
|
if(nr_do_files == 0 && optind < argc)
|
|
printf("Warning: extra unused arguments on command lines\n");
|
|
|
|
if(interactive)
|
|
{
|
|
printf("Entering interactive mode. You can use 'quit()' or 'exit()' to quit.\n");
|
|
rl_bind_key('\t', rl_complete);
|
|
while(!g_exit)
|
|
{
|
|
char *input = readline("> ");
|
|
if(!input)
|
|
break;
|
|
add_history(input);
|
|
// evaluate string
|
|
if(luaL_dostring(g_lua, input))
|
|
printf("error: %s\n", lua_tostring(g_lua, -1));
|
|
// pop everything to start from a clean stack
|
|
lua_pop(g_lua, lua_gettop(g_lua));
|
|
free(input);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|