rizin/librz/bin/p/bin_nso.c

415 lines
10 KiB
C

// SPDX-FileCopyrightText: 2021 Florian Märkl <info@florianmaerkl.de>
// SPDX-FileCopyrightText: 2017-2018 rkx1209 <rkx1209dev@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_bin.h>
#include "nxo/nxo.h"
#include <lz4.h>
#define NSO_OFF(x) rz_offsetof(NSOHeader, x)
#define NSO_OFFSET_MODMEMOFF rz_offsetof(NXOStart, mod_memoffset)
#define VFILE_NAME_DECOMPRESSED "decompressed"
// starting at 0
typedef struct {
ut32 magic; // NSO0
ut32 pad0; // 4
ut32 pad1; // 8
ut32 pad2; // 12
ut32 text_memoffset; // 16
ut32 text_loc; // 20
ut32 text_size; // 24
ut32 pad3; // 28
ut32 ro_memoffset; // 32
ut32 ro_loc; // 36
ut32 ro_size; // 40
ut32 pad4; // 44
ut32 data_memoffset; // 48
ut32 data_loc; // 52
ut32 data_size; // 56
ut32 bss_size; // 60
} NSOHeader;
static ut64 baddr(RzBinFile *bf) {
return 0x8000000;
}
static bool check_buffer(RzBuffer *b) {
if (rz_buf_size(b) >= 0x20) {
ut8 magic[4];
if (rz_buf_read_at(b, 0, magic, sizeof(magic)) != 4) {
return false;
}
return fileType(magic) != NULL;
}
return false;
}
static bool parse_header_aux(RzBuffer *buf, NSOHeader *r) {
return rz_buf_read_le32_at(buf, NSO_OFF(magic), &r->magic) &&
rz_buf_read_le32_at(buf, NSO_OFF(pad0), &r->pad0) &&
rz_buf_read_le32_at(buf, NSO_OFF(pad1), &r->pad1) &&
rz_buf_read_le32_at(buf, NSO_OFF(pad2), &r->pad2) &&
rz_buf_read_le32_at(buf, NSO_OFF(text_memoffset), &r->text_memoffset) &&
rz_buf_read_le32_at(buf, NSO_OFF(text_loc), &r->text_loc) &&
rz_buf_read_le32_at(buf, NSO_OFF(text_size), &r->text_size) &&
rz_buf_read_le32_at(buf, NSO_OFF(pad3), &r->pad3) &&
rz_buf_read_le32_at(buf, NSO_OFF(ro_memoffset), &r->ro_memoffset) &&
rz_buf_read_le32_at(buf, NSO_OFF(ro_loc), &r->ro_loc) &&
rz_buf_read_le32_at(buf, NSO_OFF(ro_size), &r->ro_size) &&
rz_buf_read_le32_at(buf, NSO_OFF(pad4), &r->pad4) &&
rz_buf_read_le32_at(buf, NSO_OFF(data_memoffset), &r->data_memoffset) &&
rz_buf_read_le32_at(buf, NSO_OFF(data_loc), &r->data_loc) &&
rz_buf_read_le32_at(buf, NSO_OFF(data_size), &r->data_size) &&
rz_buf_read_le32_at(buf, NSO_OFF(bss_size), &r->bss_size);
}
static NSOHeader *parse_header(RzBuffer *buf) {
RZ_STATIC_ASSERT(sizeof(NSOHeader) == 64);
if (rz_buf_size(buf) < sizeof(NSOHeader)) {
return NULL;
}
NSOHeader *r = RZ_NEW0(NSOHeader);
if (!r) {
return NULL;
}
if (!parse_header_aux(buf, r)) {
free(r);
return NULL;
}
return r;
}
static RzBinNXOObj *nso_new(RzBuffer *buf) {
RzBinNXOObj *bin = RZ_NEW0(RzBinNXOObj);
if (!bin) {
return NULL;
}
bin->header = parse_header(buf);
if (!bin->header) {
free(bin);
return NULL;
}
return bin;
}
static void nso_free(RzBinNXOObj *bin) {
if (!bin) {
return;
}
rz_buf_free(bin->decompressed);
free(bin->header);
free(bin);
}
static bool decompress(RzBuffer *source_buf, ut64 source_offset, ut64 source_size, ut8 *dst_buf, ut64 decompressed_size) {
if (!source_size || decompressed_size > (ut64)SIZE_MAX || source_size > (ut64)INT_MAX || decompressed_size > (ut64)INT_MAX) {
return false;
}
ut8 *tmp = RZ_NEWS0(ut8, source_size);
if (!tmp) {
return false;
}
if (rz_buf_read_at(source_buf, source_offset, tmp, source_size) != source_size) {
free(tmp);
return false;
}
int r = LZ4_decompress_safe((const char *)tmp, (char *)dst_buf, source_size, decompressed_size);
free(tmp);
return r == decompressed_size;
}
static bool load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *buf, Sdb *sdb) {
rz_return_val_if_fail(bf && buf, false);
RzBinNXOObj *bin = nso_new(buf);
if (!bin) {
return false;
}
ut8 *tmp = NULL;
NSOHeader *hdr = bin->header;
if (hdr->ro_memoffset >= rz_buf_size(buf)) {
RZ_LOG_ERROR("NSO file smaller than ro section offset\n");
goto another_castle;
}
ut64 total_size = hdr->text_size + hdr->ro_size + hdr->data_size;
if (total_size < hdr->text_size) {
// Prevent integer overflow
goto another_castle;
}
tmp = RZ_NEWS0(ut8, total_size);
if (!tmp) {
goto another_castle;
}
ut64 ba = baddr(bf);
/* Decompress each section */
if (!decompress(buf, hdr->text_memoffset, hdr->ro_memoffset - hdr->text_memoffset, tmp, hdr->text_size)) {
RZ_LOG_ERROR("Failed to decompress NSO text section\n");
goto another_castle;
}
if (!decompress(buf, hdr->ro_memoffset, hdr->data_memoffset - hdr->ro_memoffset, tmp + hdr->text_size, hdr->ro_size)) {
RZ_LOG_ERROR("Failed to decompress NSO ro section\n");
goto another_castle;
}
if (!decompress(buf, hdr->data_memoffset, rz_buf_size(buf) - hdr->data_memoffset, tmp + hdr->text_size + hdr->ro_size, hdr->data_size)) {
RZ_LOG_ERROR("Failed to decompress NSO data section\n");
goto another_castle;
}
bin->decompressed = rz_buf_new_with_pointers(tmp, total_size, true);
if (!bin->decompressed) {
goto another_castle;
}
/* Load unpacked binary */
ut32 modoff;
if (!rz_buf_read_le32_at(bin->decompressed, NSO_OFFSET_MODMEMOFF, &modoff)) {
goto another_castle;
}
RZ_LOG_INFO("MOD Offset = 0x%" PFMT64x "\n", (ut64)modoff);
parseMod(bin->decompressed, bin, modoff, ba);
obj->bin_obj = bin;
return true;
another_castle:
nso_free(bin);
free(tmp);
obj->bin_obj = NULL;
return false;
}
static void destroy(RzBinFile *bf) {
if (!bf->o) {
return;
}
nso_free(bf->o->bin_obj);
}
static RzBinAddr *binsym(RzBinFile *bf, RzBinSpecialSymbol type) {
return NULL; // TODO
}
static RzPVector /*<RzBinAddr *>*/ *entries(RzBinFile *bf) {
RzPVector *ret;
RzBuffer *b = bf->buf;
if (!(ret = rz_pvector_new(free))) {
return NULL;
}
RzBinAddr *ptr = RZ_NEW0(RzBinAddr);
if (!ptr) {
rz_pvector_free(ret);
return NULL;
}
ut32 tmp;
if (!rz_buf_read_le32_at(b, NSO_OFF(text_memoffset), &tmp)) {
rz_pvector_free(ret);
free(ptr);
return NULL;
}
ptr->paddr = tmp;
if (!rz_buf_read_le32_at(b, NSO_OFF(text_loc), &tmp)) {
rz_pvector_free(ret);
free(ptr);
return NULL;
}
ptr->vaddr = tmp;
ptr->vaddr += baddr(bf);
rz_pvector_push(ret, ptr);
return ret;
}
static Sdb *get_sdb(RzBinFile *bf) {
Sdb *kv = sdb_new0();
sdb_num_set(kv, "nso_start.offset", 0, 0);
sdb_num_set(kv, "nso_start.size", 16, 0);
sdb_set(kv, "nso_start.format", "xxq unused mod_memoffset padding", 0);
sdb_num_set(kv, "nso_header.offset", 0, 0);
sdb_num_set(kv, "nso_header.size", 0x40, 0);
sdb_set(kv, "nso_header.format", "xxxxxxxxxxxx magic unk size unk2 text_offset text_size ro_offset ro_size data_offset data_size bss_size unk3", 0);
sdb_ns_set(bf->sdb, "info", kv);
return kv;
}
static RzPVector /*<RzBinVirtualFile *>*/ *virtual_files(RzBinFile *bf) {
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free);
if (!ret) {
return NULL;
}
RzBinNXOObj *bin = bf->o->bin_obj;
if (bin->decompressed) {
RzBinVirtualFile *vf = RZ_NEW0(RzBinVirtualFile);
if (!vf) {
return ret;
}
vf->buf = bin->decompressed;
vf->buf_owned = false;
vf->name = strdup(VFILE_NAME_DECOMPRESSED);
rz_pvector_push(ret, vf);
}
return ret;
}
static RzPVector /*<RzBinMap *>*/ *maps(RzBinFile *bf) {
RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_map_free);
if (!ret) {
return NULL;
}
RzBinNXOObj *bin = bf->o->bin_obj;
NSOHeader *hdr = bin->header;
ut64 ba = baddr(bf);
RzBinMap *map = RZ_NEW0(RzBinMap);
if (!map) {
return ret;
}
map->name = strdup("text");
map->paddr = bin->decompressed ? 0 : hdr->text_memoffset;
map->vsize = map->psize = hdr->text_size;
map->vaddr = hdr->text_loc + ba;
map->perm = RZ_PERM_RX;
map->vfile_name = bin->decompressed ? strdup(VFILE_NAME_DECOMPRESSED) : NULL;
rz_pvector_push(ret, map);
// add ro segment
map = RZ_NEW0(RzBinMap);
if (!map) {
return ret;
}
map->name = strdup("ro");
map->paddr = bin->decompressed ? hdr->text_size : hdr->ro_memoffset;
map->vsize = map->psize = hdr->ro_size;
map->vaddr = hdr->ro_loc + ba;
map->perm = RZ_PERM_R;
map->vfile_name = bin->decompressed ? strdup(VFILE_NAME_DECOMPRESSED) : NULL;
rz_pvector_push(ret, map);
// add data segment
map = RZ_NEW0(RzBinMap);
if (!map) {
return ret;
}
map->name = strdup("data");
map->paddr = bin->decompressed ? hdr->text_size + hdr->ro_size : hdr->data_memoffset;
map->vsize = map->psize = hdr->data_size;
map->vaddr = hdr->data_loc + ba;
map->perm = RZ_PERM_RW;
map->vfile_name = bin->decompressed ? strdup(VFILE_NAME_DECOMPRESSED) : NULL;
rz_pvector_push(ret, map);
return ret;
}
static RzPVector /*<RzBinSection *>*/ *sections(RzBinFile *bf) {
RzPVector *ret = NULL;
RzBinSection *ptr = NULL;
RzBuffer *b = bf->buf;
if (!(ret = rz_pvector_new((RzPVectorFree)rz_bin_section_free))) {
return NULL;
}
if (!(ptr = RZ_NEW0(RzBinSection))) {
return ret;
}
ptr->name = strdup("header");
ut32 tmp;
if (!rz_buf_read_le32_at(b, NSO_OFF(text_memoffset), &tmp)) {
rz_pvector_free(ret);
return NULL;
}
ptr->size = tmp;
if (!rz_buf_read_le32_at(b, NSO_OFF(text_memoffset), &tmp)) {
rz_pvector_free(ret);
return NULL;
}
ptr->vsize = tmp;
ptr->paddr = 0;
ptr->vaddr = 0;
ptr->perm = RZ_PERM_R;
rz_pvector_push(ret, ptr);
RzPVector *mappies = maps(bf);
if (mappies) {
RzPVector *msecs = rz_bin_sections_of_maps(mappies);
if (msecs) {
void **iter;
RzBinSection *section;
rz_pvector_foreach (msecs, iter) {
section = *iter;
rz_pvector_push(ret, section);
}
msecs->v.len = 0;
rz_pvector_free(msecs);
}
rz_pvector_free(mappies);
}
return ret;
}
static RzBinInfo *info(RzBinFile *bf) {
RzBinInfo *ret = RZ_NEW0(RzBinInfo);
if (!ret) {
return NULL;
}
ut8 magic[4];
if (rz_buf_read_at(bf->buf, NSO_OFF(magic), magic, sizeof(magic)) != sizeof(magic)) {
free(ret);
return NULL;
}
const char *ft = fileType(magic);
if (!ft) {
ft = "nso";
}
ret->file = strdup(bf->file);
ret->rclass = strdup(ft);
ret->os = strdup("switch");
ret->arch = strdup("arm");
ret->machine = strdup("Nintendo Switch");
ret->subsystem = strdup(ft);
ret->bclass = strdup("program");
ret->type = strdup("EXEC (executable file)");
ret->bits = 64;
ret->has_va = true;
ret->big_endian = false;
ret->dbg_info = 0;
return ret;
}
#if !RZ_BIN_NSO
RzBinPlugin rz_bin_plugin_nso = {
.name = "nso",
.desc = "Nintendo Switch NSO0 binaries",
.license = "MIT",
.load_buffer = &load_buffer,
.destroy = &destroy,
.check_buffer = &check_buffer,
.baddr = &baddr,
.binsym = &binsym,
.entries = &entries,
.virtual_files = &virtual_files,
.maps = &maps,
.sections = &sections,
.get_sdb = &get_sdb,
.info = &info,
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_BIN,
.data = &rz_bin_plugin_nso,
.version = RZ_VERSION
};
#endif
#endif