blob: 7153a220c9bb8cd11bab7daaf219bbf8d5a321d4 [file] [log] [blame]
/***************************************************************************
* __________ __ ___.
* 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;
}