1
0
Fork 0
forked from len0rd/rockbox
foxbox/utils/hwpatcher/hwpatcher.c
Amaury Pouly c9a028cc18 Introduce hwpatcher, a tool to patch binaries
This tool is a scriptable (lua) tool to patch binaries, it supports:
- raw binary
- ELF
- SB(v1/v2)
It also contains some basic routines to parse and generate useful arm/thumb code
like jump or register load/store. This is very useful to take a firmware and
patch an interrupt vector or some code to jump to an extra payload added to
the binary. Examples are provided for several STMP based target which the payload
is expected to be hwstub, and also for the Sansa View. A typical patcher usually
requires three elements:
- the lua patcher itself
- the payload (hwstub for example)
- (optional) a small stub either to jump properly to the payload or determine
  under which circumstance to do the jump (hold a key for example)

Change-Id: I6d36020a3bc9e636615ac8221b7591ade5f251e3
2014-06-24 18:07:56 +02:00

1123 lines
32 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"
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
};
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;
}
/* 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");
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;
}