/*************************************************************************** * __________ __ ___. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ""; } } 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 Do lua file\n"); printf(" -k Add key file\n"); printf(" -z Add zero key\n"); printf(" --add-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; }