rizin/librz/bin/format/le/le.c

1879 lines
55 KiB
C

// SPDX-FileCopyrightText: 2019 GustavoLCR <gugulcr@gmail.com>
// SPDX-FileCopyrightText: 2023 svr <svr.work@protonmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file le.c
* \brief LE/LX/LC binary format plugin.
*
* The LE and LX are two very similar binary formats. Both acronyms stand for "linear executable".
* The bulk of information about formats comes in the form of the LX spec by IBM. It's incomplete
* and vague in places, so few open source projects have been used to fill in the blanks. The LC
* is a variety of LX and is handled in the same way here.
*
* The LE format is commonly used for:
* - For DOS protected mode software using an extender such as DOS/4GW (most common).
* - VxD device drivers by a number of MS and Novel OSes.
* - In OS/2 occasionally.
*
* The LX/LC format is used as a main binary format in OS/2.
*
* The following sources have been used:
*
* [1] IBM OS/2 16/32-BIT OBJECT MODULE FORMAT (OMF) AND LINEAR EXECUTABLE MODULE FORMAT (LX) rev10:
* http://www.edm2.com/index.php/IBM_OS/2_16/32-bit_Object_Module_Format_%28OMF%29_and_Linear_eXecutable_Module_Format_%28LX%29
*
* [2] lxLite LX executable packer:
* https://github.com/bitwiseworks/lxlite/blob/master/src/os2exe.pas
*
* [3] DOS/32 Advanced DOS Extender unbind utility:
* https://github.com/abbec/dos32a/blob/master/src/sb/sbind.asm
**/
#include "le.h"
#include <rz_bin.h>
#include <rz_types.h>
#include <sdbht.h>
#define CHECK(expr) \
if (!(expr)) { \
goto fail_cleanup; \
}
#define CHECK_READ(X, tmp, out) \
CHECK(rz_buf_read##X##_offset(buf, offset, &tmp) && *offset <= offset_end) \
out = tmp;
#define CHECK_READ8(out) CHECK_READ(8, tmp8, out)
#define CHECK_READ16(out) CHECK_READ(_le16, tmp16, out)
#define CHECK_READ32(out) CHECK_READ(_le32, tmp32, out)
/// --- Auxilliary functions ----------------------------------------------------------------------
static const char *le_get_module_type(rz_bin_le_obj_t *bin) {
switch (bin->header->mflags & M_TYPE_MASK) {
case M_TYPE_EXE: return "Program module (EXE)";
case M_TYPE_DLL: return "Library module (DLL)";
case M_TYPE_PDD: return "Physical Device Driver";
case M_TYPE_VDD: return "Virtual Device Driver";
default: return "Unknown";
}
}
static const char *le_get_os_type(rz_bin_le_obj_t *bin) {
switch (bin->header->os) {
case 1: return "OS/2";
case 2: return "Windows";
case 3: return "DOS 4.x";
case 4: return "Windows 386";
case 5: return "IBM Microkernel Personality Neutral";
default: return "Unknown";
}
}
static const char *le_get_cpu_type(rz_bin_le_obj_t *bin) {
switch (bin->header->cpu) {
case 1: return "80286";
case 2: return "80386";
case 3: return "80486";
case 0x20: return "N10";
case 0x21: return "N11";
case 0x40: return "r3000";
case 0x41: return "r6000";
case 0x42: return "r4000";
default: return "Unknown";
}
}
static const char *le_get_arch(rz_bin_le_obj_t *bin) {
switch (bin->header->cpu) {
case 1:
case 2:
case 3:
return "x86";
case 0x20:
case 0x21:
return "i860";
case 0x40:
case 0x41:
case 0x42:
return "mips";
default:
return "Unknown";
}
}
static bool le_read_len_str_offset(RzBuffer *buf, ut64 *offset, char **out) {
*out = NULL;
ut8 len;
if (!rz_buf_read8_offset(buf, offset, &len)) {
return false;
}
if (!len) {
return true; // success yet *out == NULL, this is why the return value is bool
}
ut8 *str = calloc((size_t)len + 1, sizeof(char));
if (!str) {
return false;
}
if (!rz_buf_read_offset(buf, offset, str, len)) {
free(str);
return false;
}
for (ut8 *s = str; s != str + len; s++) {
// non-ascii characters should not appear here
if (*s == 0 || *s > 127) {
free(str);
return false;
}
}
*out = (char *)str;
return true;
}
static ut32 le_reloc_target_offset(ut32 i) {
// TODO supposedly i860 / mips binaries exist, will 4 byte alignment suffice?
return i * 4;
}
static ut32 le_reloc_target_vaddr(rz_bin_le_obj_t *bin, ut32 i) {
return bin->reloc_target_map_base + le_reloc_target_offset(i);
}
static ut32 le_reloc_targets_vfile_size(rz_bin_le_obj_t *bin) {
return le_reloc_target_offset(bin->reloc_targets_count);
}
static ut32 le_obj_perm(LE_object *obj) {
ut32 perm = 0;
perm |= obj->flags & O_READABLE ? RZ_PERM_R : 0;
perm |= obj->flags & O_WRITABLE ? RZ_PERM_W : 0;
perm |= obj->flags & O_EXECUTABLE ? RZ_PERM_X : 0;
return perm;
}
static ut64 le_vaddr_to_paddr(rz_bin_le_obj_t *bin, ut32 vaddr) {
LE_map *m;
rz_vector_foreach (bin->le_maps, m) {
if (m->vaddr <= vaddr && vaddr <= m->vaddr + m->vsize) {
if (vaddr > m->vaddr + m->size) {
return 0;
} else {
return m->paddr + (vaddr - m->vaddr);
}
}
}
return 0;
}
static void le_import_free(LE_import *imp) {
if (!imp) {
return;
}
free(imp->proc_name);
free(imp);
}
static ut32 le_import_hash(LE_import *imp) {
ut32 ord_mix = (((ut32)imp->mod_ord + 1) << 16) ^ (imp->proc_ord + 1);
return sdb_hash(imp->proc_name) ^ ((ord_mix + 1013904223) * 1664525);
}
static int le_import_cmp(LE_import *a, LE_import *b) {
if (a->mod_ord != b->mod_ord) {
return a->mod_ord < b->mod_ord ? -1 : 1;
}
if (a->proc_ord != b->proc_ord) {
return a->proc_ord < b->proc_ord ? -1 : 1;
}
return rz_str_cmp(a->proc_name, b->proc_name, -1);
}
static void le_fini_import_kv(HtPPKv *kv) {
le_import_free(kv->key);
}
static RZ_BORROW RzBinImport *le_add_bin_import(rz_bin_le_obj_t *bin, const LE_import *le_imp) {
if (!le_imp) {
return NULL;
}
RzBinImport *import = RZ_NEW0(RzBinImport);
if (!import) {
fail_cleanup:
rz_bin_import_free(import);
return NULL;
}
const char *libname = "";
if (le_imp->mod_ord - 1 < rz_pvector_len(bin->imp_mod_names)) {
libname = rz_pvector_at(bin->imp_mod_names, le_imp->mod_ord - 1);
}
if (le_imp->proc_name) {
CHECK(import->name = rz_str_newf("%s_%s", libname, le_imp->proc_name));
} else {
CHECK(import->name = rz_str_newf("%s_%u", libname, le_imp->proc_ord));
}
import->bind = RZ_BIN_BIND_GLOBAL_STR;
import->type = RZ_BIN_TYPE_UNKNOWN_STR;
CHECK(rz_pvector_push(bin->imports, import));
import->ordinal = ++bin->reloc_targets_count;
return import;
}
static RZ_BORROW RzBinSymbol *le_add_symbol(rz_bin_le_obj_t *bin, ut32 ordinal, ut32 vaddr) {
RzBinSymbol *sym = RZ_NEW0(RzBinSymbol);
if (!sym) {
return NULL;
}
if (!ordinal) {
if (rz_list_empty(bin->symbols)) {
ordinal = 1;
} else {
ordinal = ((RzBinSymbol *)rz_list_last_val(bin->symbols))->ordinal + 1;
}
}
if (!rz_list_append(bin->symbols, sym)) {
rz_bin_symbol_free(sym);
return NULL;
}
sym->ordinal = ordinal;
sym->vaddr = vaddr;
sym->paddr = le_vaddr_to_paddr(bin, vaddr);
sym->bind = RZ_BIN_BIND_GLOBAL_STR;
sym->type = RZ_BIN_TYPE_UNKNOWN_STR;
return sym;
}
static RZ_BORROW LE_import *le_add_import(rz_bin_le_obj_t *bin,
ut16 mod_ord, bool proc_by_ord, ut32 proc, ut32 sym_ord) {
RzBinImport *bin_imp = NULL;
LE_import *le_imp = NULL;
char *proc_name = NULL;
if (false) {
fail_cleanup:
le_import_free(le_imp);
rz_bin_import_free(bin_imp);
free(proc_name);
return NULL;
}
if (!bin->le_import_ht) {
HtPPOptions opt = {
.finiKV = (HtPPFiniKv)le_fini_import_kv,
.cmp = (HtPPComparator)le_import_cmp,
.hashfn = (HtPPHashFunction)le_import_hash,
};
CHECK(bin->le_import_ht = ht_pp_new_opt(&opt));
}
ut32 proc_ord = 0;
if (proc_by_ord) {
proc_ord = proc;
} else {
// The "overload bit" described in "[1] 3.15 Import Procedure Name Table" makes no
// sense, so using the same le_read_len_str_offset() that is used elsewhere
ut64 off = bin->le_off + bin->header->impproc + proc;
CHECK(le_read_len_str_offset(bin->buf, &off, &proc_name));
}
LE_import key = { .mod_ord = mod_ord, .proc_name = proc_name, .proc_ord = proc_ord };
HtPPKv *kv = ht_pp_find_kv(bin->le_import_ht, &key, NULL);
if (kv) {
free(proc_name);
return kv->key;
}
// import does not exists yet, insert a new one
CHECK(le_imp = RZ_NEW0(LE_import));
*le_imp = key;
proc_name = NULL;
CHECK(le_imp->import = le_add_bin_import(bin, le_imp));
ut32 sym_vaddr = le_reloc_target_vaddr(bin, le_imp->import->ordinal - 1);
RzBinSymbol *sym = le_add_symbol(bin, sym_ord, sym_vaddr);
CHECK(le_imp->symbol = sym);
sym->is_imported = true;
CHECK(sym->name = rz_str_dup(le_imp->import->name));
CHECK(ht_pp_insert(bin->le_import_ht, le_imp, NULL));
return le_imp;
}
/**
* \brief Read/write bytes from/to virtual memory range possibly crossing upper page boundary.
* \param bin rz_bin_le_obj_t, LE binary
* \param page LE_page*, the page where the first written byte lies
* \param data_vaddr ut32, virtual address where data is read from / written to
* \param data ut*, a buffer for reading / a data for writing
* \param data_len ut32, buffer length
* \param read bool, read if true, otherwise write
* \return success bool, false if any read/write errors occurred, true otherwise
*
* LX fixups (aka relocations) are a bit tricky. This function is needed to support:
* - parsing fixup chains (read mode, read=true)
* - applying fixups (write mode, read=false)
*
* In LE/LX format such reads / writes can happen on a page boundary, or on a partial page,
* where two parts of a page belong to different vfiles. Consider two adjacent pages,
* \p io_page, and its next page in virtual space:
*
* io_page next page
* ..][...............oooooooooooooooooo][...............ooooooooooooooooo][..
* \physical part/\zeroed virt part/ \physical part/\zeroed virt part/
* (1) (2) (3) (4)
*
* Intervals (1), (2), (3), (4) can each correspond to its own map+vfile pair. Consider few
* possible I/O scenarios:
*
* - inside (1)
* - on the boundary (1)-(2)
* - inside (2)
* - on the boundary (2)-(3)
* - touching (1)-(2)-(3) -- possible when (2) is very small and is covered by I/O
*
* Note that \p data_vaddr is required to be inside \p io_page, so it's impossible
* for I/O to cross the lower boundary of (1) or happen wholly to the right of (2).
*
* The function works by iterating maps left to right for as long as there's hope of finding
* intersections with the I/O interval. When patching fixups \p data_len never exceeds
* 6 bytes (for 16:32 fixups), but the algorithm should work for any \p data_len.
**/
static bool page_io(rz_bin_le_obj_t *bin, LE_page *io_page,
ut32 data_vaddr, ut8 *data, ut32 data_len, bool read) {
for (ut32 mi = io_page->le_map_num - 1; mi < rz_vector_len(bin->le_maps); mi++) {
LE_map *m = rz_vector_index_ptr(bin->le_maps, mi);
if (m->obj_num != io_page->obj_num) {
return true; // the map belonging to another object reached, stop
}
ut32 vfile_beg = m->vaddr;
ut32 vfile_end = m->vaddr + m->size;
ut32 vdata_beg = data_vaddr;
ut32 vdata_end = data_vaddr + data_len;
if (vdata_end <= vfile_beg) {
return true; // no further intersections possible, stop
}
if (vfile_end <= vdata_beg) {
continue; // no intersection yet, try next map
}
ut32 vbeg = RZ_MAX(vfile_beg, vdata_beg);
ut32 vend = RZ_MIN(vfile_end, vdata_end);
ut32 len = vend - vbeg;
RzBuffer *vfile_buf = m->is_physical ? bin->buf_patched : m->vfile_buf;
ut64 paddr = m->paddr + vbeg - vfile_beg;
ut8 *buf = data + vbeg - vdata_beg;
if (!vfile_buf) {
// likely vfiles haven't been created correctly
RZ_LOG_ERROR("LE: attempted %s %d byte(s) at 0x%" PFMT64x " of map %s "
"with no buffer.\n",
read ? "reading" : "writing", len, paddr,
m->vfile_name ? m->vfile_name : "NULL");
rz_return_val_if_reached(false);
}
bool good;
if (read) {
good = rz_buf_read_at(vfile_buf, paddr, buf, len) == len;
} else {
good = rz_buf_write_at(vfile_buf, paddr, buf, len) == len;
}
if (!good) {
// likely data_vaddr is outside page_io, misused this function
RZ_LOG_ERROR("LE: error %s vfile, %d byte(s) at 0x%" PFMT64x ".\n",
read ? "reading" : "writing", len, paddr);
rz_return_val_if_reached(false);
}
}
return true;
}
static bool page_read(rz_bin_le_obj_t *bin, LE_page *io_page, ut32 vaddr, ut8 *buf, ut32 len) {
return page_io(bin, io_page, vaddr, buf, len, true);
}
static bool page_write(rz_bin_le_obj_t *bin, LE_page *io_page, ut32 vaddr, ut8 *buf, ut32 len) {
return page_io(bin, io_page, vaddr, buf, len, false);
}
static ut32 le_reloc_vaddr(rz_bin_le_obj_t *bin, LE_reloc *reloc) {
return bin->le_pages[reloc->src_page].vaddr + reloc->src_off;
}
static ut32 le_reloc_size(LE_reloc *reloc) {
static const ut8 szmap[] = {
[FIXUP_BYTE] = 1,
[FIXUP_SEL16] = 2,
[FIXUP_OFF16] = 2,
[FIXUP_OFF32] = 4,
[FIXUP_SEL16_OFF16] = 4,
[FIXUP_REL32] = 4,
[FIXUP_SEL16_OFF32] = 6,
};
return reloc->type < sizeof(szmap) ? szmap[reloc->type] : 0;
}
/// --- Loading all things LE from the binary -----------------------------------------------------
/**
* \brief Find offsets of an LE header or an MZ/LE header pair.
*
* Supported cases:
* - A standalone LE binary (just an LE header).
* - An MZ stub followed by an LE executbale (MZ-LE).
* - A DOS extender bound executable typical for DOS protected mode software (MZ-BW-MZ-LE).
*
* Header search is implemented after the code in [3]
**/
static bool le_get_header_offset(RzBuffer *b, ut64 *mz_off, ut64 *le_off) {
for (ut64 pos = 0, mz = 0;;) {
ut8 magic[2];
if (rz_buf_read_at(b, pos, magic, 2) != 2) {
break;
}
if (!memcmp(magic, "LE", 2) || !memcmp(magic, "LX", 2) || !memcmp(magic, "LC", 2)) {
if (mz_off) {
*mz_off = mz;
}
if (le_off) {
*le_off = pos;
}
return true;
}
bool is_mz = !memcmp(magic, "MZ", 2);
if (is_mz) {
ut32 bound_le_off;
if (!rz_buf_read_le32_at(b, pos + 0x3c, &bound_le_off)) {
break;
}
if (bound_le_off & 0xFFFF) {
mz = pos;
pos += bound_le_off;
continue;
}
}
// BW is a DOS extender related header similar to MZ
if (is_mz || !memcmp(magic, "BW", 2)) {
ut16 page_count;
ut16 last_page_bytes;
if (!rz_buf_read_le16_at(b, pos + 2, &last_page_bytes) ||
!rz_buf_read_le16_at(b, pos + 4, &page_count)) {
break;
}
pos += ((ut64)page_count - is_mz) * 512 + last_page_bytes;
continue;
}
break;
}
return false;
}
// See [1] 3.2 LX Header
static bool le_load_header(rz_bin_le_obj_t *bin) {
if (!le_get_header_offset(bin->buf, &bin->mz_off, &bin->le_off)) {
return false;
}
ut64 off = bin->le_off;
bin->header = RZ_NEW0(LE_header);
if (!bin->header ||
!rz_buf_read_offset(bin->buf, &off, bin->header->magic, 2) ||
!rz_buf_read8_offset(bin->buf, &off, &bin->header->border) ||
!rz_buf_read8_offset(bin->buf, &off, &bin->header->worder) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->level) ||
!rz_buf_read_le16_offset(bin->buf, &off, &bin->header->cpu) ||
!rz_buf_read_le16_offset(bin->buf, &off, &bin->header->os) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->ver) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->mflags) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->mpages) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->startobj) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->eip) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->stackobj) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->esp) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->pagesize) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->pageshift) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->fixupsize) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->fixupsum) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->ldrsize) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->ldrsum) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->objtab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->objcnt) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->objmap) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->itermap) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->rsrctab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->rsrccnt) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->restab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->enttab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->dirtab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->dircnt) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->fpagetab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->frectab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->impmod) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->impmodcnt) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->impproc) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->pagesum) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->datapage) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->preload) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->nrestab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->cbnrestab) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->nressum) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->autodata) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->debuginfo) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->debuglen) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->instpreload) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->instdemand) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->heapsize) ||
!rz_buf_read_le32_offset(bin->buf, &off, &bin->header->stacksize)) {
return false;
}
if (bin->header->border || bin->header->worder) {
// shouldn't be hard to support, but I couldn't find any such binaries to test on
RZ_LOG_ERROR("LE: only little-endian byte and word order is supported, "
"got (%d, %d), expected (0, 0).\n",
bin->header->border, bin->header->worder);
return false;
}
bin->is_le = !memcmp("LE", bin->header->magic, 2);
return true;
}
// Loading objects, see [1] 3.4 Object Table
static bool le_load_objects(rz_bin_le_obj_t *bin) {
LE_header *h = bin->header;
if (!h->objcnt) {
return true; // no objects, binary is a forwarders-only library
}
ut64 offset = bin->le_off + h->objtab;
if (rz_buf_size(bin->buf) < offset + sizeof(LE_object) * h->objcnt) {
return false;
}
bin->objects = calloc(h->objcnt, sizeof(LE_object));
if (!bin->objects) {
return false;
}
for (LE_object *obj = bin->objects; obj != bin->objects + h->objcnt; obj++) {
if (!rz_buf_read_le32_offset(bin->buf, &offset, &obj->virtual_size) ||
!rz_buf_read_le32_offset(bin->buf, &offset, &obj->reloc_base_addr) ||
!rz_buf_read_le32_offset(bin->buf, &offset, &obj->flags) ||
!rz_buf_read_le32_offset(bin->buf, &offset, &obj->page_tbl_idx) ||
!rz_buf_read_le32_offset(bin->buf, &offset, &obj->page_tbl_entries) ||
!rz_buf_read_le32_offset(bin->buf, &offset, &obj->reserved)) {
return false;
}
}
return true;
}
// Loading a single non-empty entry, see [1] 3.8.1-4
static bool le_load_entry_record(rz_bin_le_obj_t *bin, ut64 *offset, ut8 type, ut32 obj_num,
RzVector /*<LE_entry>*/ *entries) {
if (false) {
fail_cleanup:
return false;
}
LE_entry e = { .is_empty = false };
ut32 sym_ord = rz_vector_len(entries) + 1;
ut8 entry_flags;
ut32 entry_off;
switch (type) {
case ENTRY_16: {
ut16 offset16;
CHECK(rz_buf_read8_offset(bin->buf, offset, &entry_flags));
CHECK(rz_buf_read_le16_offset(bin->buf, offset, &offset16));
entry_off = offset16;
break;
}
case ENTRY_CALLGATE: {
ut16 offset16;
ut16 callgate; // unused
CHECK(rz_buf_read8_offset(bin->buf, offset, &entry_flags));
CHECK(rz_buf_read_le16_offset(bin->buf, offset, &offset16));
CHECK(rz_buf_read_le16_offset(bin->buf, offset, &callgate));
entry_off = offset16;
break;
}
case ENTRY_32: {
CHECK(rz_buf_read8_offset(bin->buf, offset, &entry_flags));
CHECK(rz_buf_read_le32_offset(bin->buf, offset, &entry_off));
break;
}
case ENTRY_FORWARDER: {
ut16 imp_mod_ord;
ut32 imp_proc;
CHECK(rz_buf_read8_offset(bin->buf, offset, &entry_flags));
CHECK(rz_buf_read_le16_offset(bin->buf, offset, &imp_mod_ord));
CHECK(rz_buf_read_le32_offset(bin->buf, offset, &imp_proc));
e.is_forwarder = true;
e.is_exported = true;
bool proc_by_ord = entry_flags & E_IMPORT_BY_ORD;
e.is_forwarder_import_by_ord = proc_by_ord;
LE_import *le_imp;
CHECK(le_imp = le_add_import(bin, imp_mod_ord, proc_by_ord, imp_proc, sym_ord));
e.symbol = le_imp->symbol;
break;
}
}
if (type == ENTRY_16 || type == ENTRY_CALLGATE || type == ENTRY_32) {
e.is_exported = entry_flags & E_EXPORTED;
e.is_shared = entry_flags & E_SHARED;
e.is_param_dword = type == ENTRY_32;
e.param_count = entry_flags >> E_PARAM_COUNT_SHIFT;
e.obj_num = obj_num;
if (obj_num - 1 < bin->header->objcnt) {
ut32 entry_vaddr = bin->objects[obj_num - 1].reloc_base_addr + entry_off;
CHECK(e.symbol = le_add_symbol(bin, sym_ord, entry_vaddr));
} else {
// rare, 16 bit only, TODO what is it?
RZ_LOG_WARN("LE: invalid object #%u specified for symbol %u\n",
obj_num, sym_ord);
}
}
CHECK(rz_vector_push(entries, &e));
return true;
}
// Loading symbols, see [1] 3.8 Entry Table
static RZ_OWN RzVector /*<LE_entry>*/ *le_load_entries(rz_bin_le_obj_t *bin) {
char *name = NULL;
RzVector *entries = rz_vector_new(sizeof(LE_entry), NULL, NULL);
if (!entries) {
fail_cleanup:
rz_vector_free(entries);
free(name);
return NULL;
}
ut64 offset = bin->le_off + bin->header->enttab;
while (true) {
ut8 entry_count;
CHECK(rz_buf_read8_offset(bin->buf, &offset, &entry_count));
if (!entry_count) {
break;
}
ut8 entry_type;
CHECK(rz_buf_read8_offset(bin->buf, &offset, &entry_type));
ut8 type = entry_type & ~E_PARAM_TYPING_PRESENT;
rz_vector_reserve(entries, rz_vector_len(entries) + entry_count);
switch (type) {
case ENTRY_EMPTY: {
LE_entry e = { .is_empty = true };
while (entry_count--) {
rz_vector_push(entries, &e);
}
continue;
}
case ENTRY_16:
case ENTRY_CALLGATE:
case ENTRY_32:
case ENTRY_FORWARDER: {
ut16 obj_num = 0;
CHECK(rz_buf_read_le16_offset(bin->buf, &offset, &obj_num));
for (int i = 0; i < entry_count; i++) {
CHECK(le_load_entry_record(bin, &offset, type, obj_num, entries));
}
continue;
}
}
RZ_LOG_WARN("LE: unsupported entry bundle type %d, skipping the remainder "
"of the table after having read %" PFMTSZu " entries.\n",
type, rz_vector_len(entries));
break;
}
// Load symbol names, see [1] 3.7 Resident or Non-resident Name Table Entry
LE_header *h = bin->header;
ut64 offset_beg[2] = { bin->le_off + h->restab, bin->mz_off + h->nrestab };
ut64 offset_end[2] = { bin->le_off + h->enttab, bin->mz_off + h->nrestab + h->cbnrestab };
for (ut32 i = 0; i < 2; i++) {
ut64 off = offset_beg[i], end = offset_end[i];
while (off + 1 <= end) {
if (!le_read_len_str_offset(bin->buf, &off, &name)) {
break;
}
if (off + 2 > end) {
RZ_FREE(name);
break;
}
ut16 entry_ord;
CHECK(rz_buf_read_le16_offset(bin->buf, &off, &entry_ord));
if (name && entry_ord - 1 < rz_vector_len(entries)) {
LE_entry *e = rz_vector_index_ptr(entries, entry_ord - 1);
if (e->symbol && !e->symbol->name) {
e->symbol->name = name;
name = NULL;
continue;
}
}
RZ_FREE(name);
}
}
// try naming entries accessible only by ordinal
LE_entry *e;
int ei = 0;
rz_vector_foreach (entries, e) {
ei++;
if (!e->is_empty && !e->is_forwarder && e->symbol && !e->symbol->name) {
e->symbol->name = rz_str_newf("%u", ei);
}
}
return entries;
}
// Loading page map, see [1] 3.5 Object Page Table
static RZ_OWN LE_page *le_load_pages(rz_bin_le_obj_t *bin) {
LE_header *h = bin->header;
ut64 offset = bin->le_off + h->objmap;
LE_page *le_pages = NULL;
if (rz_buf_size(bin->buf) < offset + (ut64)h->mpages * (bin->is_le ? 4 : 8)) {
fail_cleanup:
free(le_pages);
return NULL;
}
CHECK(le_pages = calloc(h->mpages, sizeof(LE_header)));
LE_page *page = le_pages;
for (ut32 page_i = 0; page_i < h->mpages; page_i++, page++) {
if (bin->is_le) {
// 4 byte record: 3 byte big endian page number, 1 byte flags
ut32 record;
CHECK(rz_buf_read_be32_offset(bin->buf, &offset, &record));
ut32 page_num = record >> 8;
ut8 page_flags = record & 0xFF;
if (page_flags != 0) {
RZ_LOG_WARN("LE: unsupported LE page flags 0x%02x for page #%d.\n",
page_flags, page_i + 1);
}
if (!page_num) {
// This is likely the result of file damage or tempering, guessing
// number being just an 1-based index would work for typical LE.
page_num = page_i + 1;
RZ_LOG_WARN("LE: page #%u invalid page number corrected.\n", page_num);
}
page->type = PAGE_LEGAL;
page->paddr = bin->mz_off + h->datapage + (ut64)(page_num - 1) * h->pagesize;
page->psize = page_i != h->mpages - 1 ? h->pagesize : h->le_last_page_size;
} else {
// 8 byte record: 4 offset, 2 size, 2 flags
ut32 page_offset;
ut16 page_size;
ut16 page_flags;
CHECK(rz_buf_read_le32_offset(bin->buf, &offset, &page_offset));
CHECK(rz_buf_read_le16_offset(bin->buf, &offset, &page_size));
CHECK(rz_buf_read_le16_offset(bin->buf, &offset, &page_flags));
ut32 off = page_offset << h->pageshift;
switch (page_flags) {
case PAGE_LEGAL:
page->paddr = bin->mz_off + h->datapage + off;
break;
case PAGE_ITERATED:
case PAGE_COMPRESSED:
page->paddr = bin->mz_off + h->itermap + off;
break;
case PAGE_INVALID:
case PAGE_RANGE:
default:
page_flags = PAGE_ZEROED;
RZ_LOG_WARN("LE: unsupported LX page flags 0x%04x for page #%d.\n",
page_flags, page_i + 1);
case PAGE_ZEROED:
break;
}
page->type = (LE_page_type)page_flags;
page->psize = page_size;
}
}
// assign object number to pages, calculate vaddr
for (ut32 oi = 0; oi < h->objcnt; oi++) {
LE_object *obj = &bin->objects[oi];
ut32 voff = 0;
LE_page *page = &le_pages[obj->page_tbl_idx - 1];
for (ut32 i = 0; i < obj->page_tbl_entries; i++, page++) {
unsigned int pi = (obj->page_tbl_idx - 1) + i;
if (pi >= h->mpages) {
RZ_LOG_ERROR("LE: object #%u page table entry index %u is out "
"of range.\n",
oi + 1, pi + 1);
goto fail_cleanup;
}
page->obj_num = oi + 1;
page->vaddr = obj->reloc_base_addr + voff;
page->vsize = h->pagesize;
voff += h->pagesize;
}
if (voff > obj->virtual_size) {
ut32 extra = voff - obj->virtual_size;
if (extra < h->pagesize) {
page->vsize -= extra;
} else {
RZ_LOG_WARN("LE: object #%u vsize is smaller than the sum of its "
"pages 0x%x < 0x%x, object has been extended.\n",
oi + 1, obj->virtual_size, voff);
obj->virtual_size = voff;
}
}
}
// assign fixup page map boundaries
ut32 fixup_page_base_paddr = bin->le_off + h->frectab;
ut64 fixup_map_paddr = bin->le_off + h->fpagetab;
for (ut32 pi = 0; pi <= h->mpages; pi++) {
ut32 start;
if (!rz_buf_read_le32_offset(bin->buf, &fixup_map_paddr, &start)) {
goto fail_cleanup;
}
start += fixup_page_base_paddr;
if (pi < h->mpages) {
le_pages[pi].fixup_page_start = start;
}
if (pi > 0) {
le_pages[pi - 1].fixup_page_end = start;
}
}
return le_pages;
}
// See [2] UnpackMethod1 for the algortihm.
static void le_unpack_iterated(rz_bin_le_obj_t *bin, ut8 *out, ut32 out_size, LE_page *page) {
if (false) {
fail_cleanup:
RZ_LOG_WARN("LE: unpacking type 1 (iterated) page at 0x%" PFMT64x " failed.\n",
page->paddr);
return;
}
ut64 off = page->paddr;
ut64 end = off + page->psize;
while (off < end) {
ut16 reps, len;
CHECK(off + 2 <= end);
CHECK(rz_buf_read_le16_offset(bin->buf, &off, &reps));
if (reps == 0) {
break;
}
CHECK(off + 2 <= end);
CHECK(rz_buf_read_le16_offset(bin->buf, &off, &len));
CHECK(len <= out_size && off + len <= end);
CHECK(rz_buf_read_offset(bin->buf, &off, out, len));
ut8 *pattern = out;
out += len;
out_size -= len;
for (ut32 i = 1; i < reps; i++) {
memcpy(out, pattern, RZ_MIN(out_size, len));
if (out_size < len) {
break;
}
out += len;
out_size -= len;
}
break;
}
}
// See [2] UnpackMethod2 for the algortihm.
static void le_unpack_compressed(rz_bin_le_obj_t *bin, ut8 *out, ut32 out_size, LE_page *page) {
ut8 tmp8;
ut16 tmp16;
RzBuffer *buf = bin->buf;
ut64 off = page->paddr, *offset = &off;
ut64 offset_end = *offset + page->psize;
ut32 out_size_total = out_size;
if (false) {
fail_cleanup:
RZ_LOG_WARN("LE: unpacking type 5 (compressed) page at 0x%" PFMT64x " failed.\n",
page->paddr);
return;
}
// copy N bytes from source
#define COPY(N) \
CHECK(*offset + (N) <= offset_end && (N) <= out_size); \
CHECK(rz_buf_read_offset(buf, offset, out, (N))); \
out += (N); \
out_size -= (N);
// appends N bytes at *(out - backstep) to *out, ranges can overlap!
#define DUP(backstep, N) \
CHECK(out_size_total - out_size >= backstep); \
for (ut32 i = (N); i; i--, out++, out_size--) { \
*out = *(out - backstep); \
}
while (*offset < offset_end) {
ut8 b1;
CHECK_READ8(b1);
ut8 type = b1 & 3;
if (type == 0) {
if (b1 == 0) {
ut8 b2, b3;
CHECK_READ8(b2);
if (b2 == 0) {
break;
}
CHECK_READ8(b3);
CHECK(b2 <= out_size);
memset(out, b3, b2);
out += b2;
out_size -= b2;
} else {
COPY(b1 >> 2);
}
} else if (type == 1) {
ut16 bof;
ut8 b2;
*offset -= 1;
CHECK_READ16(bof);
bof >>= 7;
b2 = ((b1 >> 4) & 7) + 3;
b1 = ((b1 >> 2) & 3);
COPY(b1);
DUP(bof, b2);
} else if (type == 2) {
ut16 bof;
*offset -= 1;
CHECK_READ16(bof);
bof >>= 4;
b1 = ((b1 >> 2) & 3) + 3;
DUP(bof, b1);
} else if (type == 3) {
ut8 b2;
ut16 word1, word2, bof;
*offset -= 1;
CHECK_READ16(word1);
*offset -= 1;
CHECK_READ16(word2);
b1 = (word1 >> 2) & 0xf;
b2 = (word1 >> 6) & 0x3f;
bof = word2 >> 4;
COPY(b1);
DUP(bof, b2);
}
}
#undef DUP
#undef COPY
}
static void le_map_fini(void *map, void *unused) {
LE_map *m = map;
rz_buf_free(m->vfile_buf);
free(m->vfile_buf_data);
free(m->vfile_name);
}
static bool le_add_map(RzVector /*<LE_map>*/ *le_maps, LE_map *map, LE_page *page) {
if (map->vsize == 0 || (map->size == 0 && map->is_physical)) {
return true; // adding empty maps makes no sense, might happen in a crafted binary
}
LE_map *prev = rz_vector_empty(le_maps) ? NULL : rz_vector_tail(le_maps);
bool prev_ok = prev && prev->obj_num == map->obj_num && prev->is_physical == map->is_physical;
bool merge_virtual = prev_ok && !map->is_physical;
bool merge_physical = prev_ok && map->is_physical && prev->paddr + prev->size == map->paddr;
if (merge_virtual || merge_physical) {
prev->size += map->size;
prev->vsize += map->vsize;
} else {
if (!rz_vector_push(le_maps, map)) {
return false;
}
}
if (!page->le_map_num) {
page->le_map_num = rz_vector_len(le_maps);
}
return true;
}
static RzVector /*<LE_map>*/ *le_create_maps(rz_bin_le_obj_t *bin) {
RzVector *le_maps = rz_vector_new(sizeof(LE_map), le_map_fini, NULL);
ut8 *tmp_buf = NULL;
if (!le_maps) {
fail_cleanup:
rz_vector_free(le_maps);
free(tmp_buf);
return NULL;
}
LE_header *h = bin->header;
for (ut32 oi = 0; oi != h->objcnt; oi++) {
LE_object *obj = &bin->objects[oi];
LE_map m = { .obj_num = oi + 1 };
size_t len_before = rz_vector_len(le_maps);
ut32 beg = obj->page_tbl_idx - 1, end = beg + obj->page_tbl_entries;
for (ut32 pi = beg; pi != end; pi++) {
if (pi >= h->mpages) {
RZ_LOG_ERROR("LE: object #%u page table entry index %u is out "
"of range.\n",
oi + 1, pi + 1);
goto fail_cleanup;
}
LE_page *page = &bin->le_pages[pi];
m.first_page_num = pi + 1;
if (page->type == PAGE_LEGAL) {
// physical part of a normal page
m.size = page->psize;
m.vsize = page->psize;
m.paddr = page->paddr;
m.vaddr = page->vaddr;
m.is_physical = true;
CHECK(le_add_map(le_maps, &m, page));
if (page->psize < page->vsize) {
// zero padded virtual remainder of a normal page
m.size = 0;
m.vsize = h->pagesize - page->psize;
m.paddr = 0;
m.vaddr = page->vaddr + page->psize;
m.is_physical = false;
CHECK(le_add_map(le_maps, &m, page));
}
} else {
// virtual unpacked or zero filled page
m.size = page->vsize;
m.vsize = page->vsize;
m.paddr = 0;
m.vaddr = page->vaddr;
m.is_physical = false;
CHECK(le_add_map(le_maps, &m, page));
}
}
// if the last map is zero-filled virtual, merge it with the preceding map
if (rz_vector_len(le_maps) >= len_before + 2) {
LE_map *last = rz_vector_tail(le_maps), *prev = last - 1;
if (last->size == 0) {
prev->vsize += last->vsize;
rz_vector_pop(le_maps, NULL);
}
}
// if an object has no pages, create a single map for the whole object
if (rz_vector_len(le_maps) == len_before) {
m.size = 0;
m.vsize = obj->virtual_size;
m.paddr = 0;
m.vaddr = obj->reloc_base_addr;
m.is_physical = false;
CHECK(rz_vector_push(le_maps, &m));
} else {
// otherwise extend last map to match object vsize
LE_map *last = rz_vector_tail(le_maps);
ut32 obj_vend = obj->reloc_base_addr + obj->virtual_size;
if (obj_vend > last->vaddr + last->vsize) {
last->vsize = obj_vend - last->vaddr;
}
}
((LE_map *)rz_vector_tail(le_maps))->is_obj_last = true;
}
// name maps
LE_map *m;
ut32 num = 1;
rz_vector_foreach (le_maps, m) {
const char *map_kind = m->is_physical ? "physical" : "virtual";
CHECK(m->vfile_name = rz_str_newf("obj%d-%s%u", m->obj_num, map_kind, num++));
}
// allocate buffers, fill zero pages, unpack compressed pages
rz_vector_foreach (le_maps, m) {
if (m->is_physical) {
continue;
}
if (m->size == 0 && m->is_obj_last) {
continue; // fully virtual and last map in object, no need for vfile
}
ut32 buf_size = m->size ? m->size : m->vsize;
CHECK(tmp_buf = RZ_NEWS0(ut8, buf_size));
ut32 offset = 0;
for (ut32 pi = m->first_page_num - 1; pi < bin->header->mpages; pi++) {
LE_page *page = &bin->le_pages[pi];
if (page->le_map_num - 1 != m - (LE_map *)rz_vector_head(le_maps)) {
break;
}
ut32 size = page->vsize;
if (page->type == PAGE_LEGAL) {
// !is_physical && type == PAGE_LEGAL means zero padded remainder
// of a normal page, thus its size is physical size inverted
size = h->pagesize - page->psize;
}
CHECK(offset + size <= buf_size);
switch (page->type) {
case PAGE_ITERATED:
le_unpack_iterated(bin, tmp_buf + offset, size, page);
break;
case PAGE_COMPRESSED:
le_unpack_compressed(bin, tmp_buf + offset, size, page);
break;
default:
break;
}
offset += size;
}
CHECK(m->vfile_buf = rz_buf_new_with_pointers(tmp_buf, m->size, false));
m->vfile_buf_data = tmp_buf;
tmp_buf = NULL;
}
// calculate reloc_target_map_base
ut32 max_vaddr = 0;
rz_vector_foreach (le_maps, m) {
max_vaddr = RZ_MAX(max_vaddr, m->vaddr + m->vsize);
}
CHECK(h->pagesize);
bin->reloc_target_map_base = max_vaddr - (max_vaddr % h->pagesize) + (h->pagesize * 2);
return le_maps;
}
static RZ_OWN RzPVector /*<char *>*/ *le_load_import_mod_names(rz_bin_le_obj_t *bin) {
RzPVector *names = rz_pvector_new(free);
char *modname = NULL;
if (!names) {
fail_cleanup:
rz_pvector_free(names);
free(modname);
return NULL;
}
ut64 off = bin->le_off + bin->header->impmod;
for (ut32 i = 0; i < bin->header->impmodcnt; i++) {
if (!le_read_len_str_offset(bin->buf, &off, &modname)) {
break;
}
CHECK(rz_pvector_push(names, modname));
modname = NULL;
}
return names;
}
static bool le_patch_relocs(rz_bin_le_obj_t *bin) {
// possibly resize vfiles
ut32 *max_vaddr = NULL;
LE_map **last_map = NULL;
if (false) {
fail_cleanup:
free(max_vaddr);
free(last_map);
return false;
}
// mark last map for each object
CHECK(last_map = RZ_NEWS0(LE_map *, bin->header->objcnt));
LE_map *m;
rz_vector_foreach (bin->le_maps, m) {
last_map[m->obj_num - 1] = m;
}
// search maximum vaddr patched by relocs for each object
CHECK(max_vaddr = RZ_NEWS0(ut32, bin->header->objcnt));
for (ut32 oi = 0; oi < bin->header->objcnt; oi++) {
max_vaddr[oi] = bin->objects[oi].reloc_base_addr; // set minimum
}
RzListIter *iter;
LE_reloc *reloc;
rz_list_foreach (bin->le_relocs, iter, reloc) {
LE_page *page = &bin->le_pages[reloc->src_page];
if (page->obj_num) {
ut32 *max = &max_vaddr[page->obj_num - 1];
*max = RZ_MAX(*max, le_reloc_vaddr(bin, reloc) + le_reloc_size(reloc));
}
}
// extending allocated parts of objects to allow patching virtual region
for (ut32 oi = 0; oi < bin->header->objcnt; oi++) {
m = last_map[oi];
if (max_vaddr[oi] > m->vaddr + m->size) {
ut32 sz = max_vaddr[oi] - m->vaddr;
// without tmp realloc failure would result in a leak
ut8 *tmp = realloc(m->vfile_buf_data, sz);
CHECK(m->vfile_buf_data = tmp);
rz_buf_free(m->vfile_buf);
CHECK(m->vfile_buf = rz_buf_new_with_pointers(tmp, sz, false));
m->size = sz;
}
}
RZ_FREE(max_vaddr);
RZ_FREE(last_map);
rz_list_foreach (bin->le_relocs, iter, reloc) {
LE_page *page = &bin->le_pages[reloc->src_page];
if (page->obj_num == 0) {
continue;
}
LE_fixup_type t = reloc->type;
ut32 vaddr = le_reloc_vaddr(bin, reloc);
if (t == FIXUP_BYTE) {
// TODO FIXUP_BYTE is unsupported, occurances are extremely rare though.
continue;
}
// write 32 bit offset
if (t == FIXUP_REL32 || t == FIXUP_SEL16_OFF32 || t == FIXUP_OFF32) {
ut8 tmp[4];
ut32 target = reloc->target_vaddr + reloc->addend;
if (t == FIXUP_REL32) {
target -= vaddr + 4; // relative offset, for call/jump
}
rz_write_le32(tmp, target);
CHECK(page_write(bin, page, vaddr, tmp, sizeof(tmp)));
}
// write 16 bit offset
if (t == FIXUP_SEL16_OFF16 || t == FIXUP_OFF16) {
ut8 tmp[2];
ut32 obj = reloc->trg_obj_num;
if (obj) {
ut32 obj_base = obj <= bin->header->objcnt
? bin->objects[obj - 1].reloc_base_addr
: bin->reloc_target_map_base;
ut32 target = reloc->target_vaddr + reloc->addend - obj_base;
if (target <= UT16_MAX) {
rz_write_le16(tmp, target);
CHECK(page_write(bin, page, vaddr, tmp, 2));
} else {
RZ_LOG_WARN("LE: failed to apply fixup at vaddr=0x%x, "
"16 bit target offset=0x%x is too big.\n",
vaddr, target);
}
} else {
RZ_LOG_WARN("LE: failed to apply fixup at vaddr=0x%x, "
"no target object specified.\n",
vaddr);
}
}
// write selector
if (t == FIXUP_SEL16_OFF32 || t == FIXUP_SEL16_OFF16 || t == FIXUP_SEL16) {
ut8 tmp[2];
rz_write_le16(tmp, reloc->trg_obj_num);
vaddr += le_reloc_size(reloc) - 2; // selector is patched at tailing 2 bytes
CHECK(page_write(bin, page, vaddr, tmp, 2));
}
}
if (bin->buf_patched) {
rz_buf_sparse_set_write_mode(bin->buf_patched, RZ_BUF_SPARSE_WRITE_MODE_THROUGH);
}
return true;
}
static bool le_append_fixup(rz_bin_le_obj_t *bin, LE_reloc *reloc, RzList /*<RzBinReloc *>*/ *out,
bool skip_fixup) {
if (skip_fixup) {
return true; // Fixup has been parsed but contains invalid data, ignore and proceed
}
if (reloc->src_off < 0) {
// Skipping fixups with negative source offset, since they are already accounted for
// on the preceding page as per [1] 3.13 Fixup Record Table:
//
// Note: For fixups that cross page boundaries, a separate fixup record is
// specified for each page. An offset is still used for the 2nd page, but it
// now becomes a negative offset since the fixup originated on the preceding
// page. (For example if only the last one byte of a 32-bit address is on
// the page to be fixed up, then the offset would have a value of -3)"
//
return true; // Not an error
}
LE_reloc *tmp = RZ_NEWCOPY(LE_reloc, reloc);
if (!tmp || !rz_list_append(out, tmp)) {
free(tmp);
return false;
}
rz_list_append(bin->le_fixups, tmp);
return true;
}
static bool le_load_fixup_record(rz_bin_le_obj_t *bin, RzList /*<RzBinReloc *>*/ *relocs_out,
ut32 page_i, ut64 *offset, ut64 offset_end) {
LE_header *h = bin->header;
ut64 start_offset = *offset;
if (false) {
fail_cleanup:
RZ_LOG_WARN("LE: unsupported or malformed fixup record at 0x%" PFMT64x
", skipping the rest of the page.\n",
start_offset);
return false;
}
// variables used in CHECK_READ*
RzBuffer *buf = bin->buf;
ut32 tmp32 = 0;
ut16 tmp16 = 0;
ut8 tmp8 = 0;
LE_page *cur_page = &bin->le_pages[page_i];
LE_reloc rel = { .src_page = page_i };
ut8 src_flags;
CHECK_READ8(src_flags);
rel.type = src_flags & F_SOURCE_TYPE_MASK;
// TODO what does src_flags & F_SOURCE_ALIAS do? only valid when segment selector is present
ut8 trg_flags;
CHECK_READ8(trg_flags);
ut8 trg_type = trg_flags & F_TARGET_TYPE_MASK;
ut8 src_list_count = 0;
st16 src_off = 0; // signed integer, can be negative!
if (src_flags & F_SOURCE_LIST) {
CHECK_READ8(src_list_count);
} else {
CHECK_READ16(src_off);
}
ut16 ordinal;
if (trg_flags & F_TARGET_ORD16) {
CHECK_READ16(ordinal);
} else {
CHECK_READ8(ordinal);
}
bool skip_fixup = false;
ut16 imp_mod_ord = 0;
ut32 imp_proc_ord = 0;
ut32 imp_proc_name_off = 0;
ut32 target_base_vaddr = 0;
switch (trg_type) {
case TARGET_INTERNAL:
if (ordinal - 1 < h->objcnt) {
rel.trg_obj_num = ordinal;
target_base_vaddr = bin->objects[ordinal - 1].reloc_base_addr;
} else {
skip_fixup = true;
}
if (rel.type != FIXUP_SEL16) {
ut32 target_offset;
if (trg_flags & F_TARGET_OFF32) {
CHECK_READ32(target_offset);
} else {
CHECK_READ16(target_offset);
}
rel.target_vaddr = target_base_vaddr + target_offset;
} else {
// FIXUP_SEL16 doesn't truly have a target_vaddr, it targets a segment as a
// whole and writes a "segment selector". Here target_vaddr is set to its
// target segment's start. It's purely for informational purposes, otherwise
// there would be no way to understand its target by ir command output.
rel.target_vaddr = target_base_vaddr;
}
break;
case TARGET_IMPORT_ORDINAL:
imp_mod_ord = ordinal;
if (trg_flags & F_TARGET_ORD8) {
CHECK_READ8(imp_proc_ord);
} else if (trg_flags & F_TARGET_OFF32) {
CHECK_READ32(imp_proc_ord);
} else {
CHECK_READ16(imp_proc_ord);
}
break;
case TARGET_IMPORT_NAME:
imp_mod_ord = ordinal;
if (trg_flags & F_TARGET_OFF32) {
CHECK_READ32(imp_proc_name_off);
} else {
CHECK_READ16(imp_proc_name_off);
}
break;
case TARGET_INTERNAL_ENTRY: {
LE_entry *e = NULL;
if (ordinal - 1 < rz_vector_len(bin->le_entries)) {
e = rz_vector_index_ptr(bin->le_entries, ordinal - 1);
}
if (!e || e->is_empty || e->is_forwarder || !e->symbol) {
RZ_LOG_WARN("LE: relocation references invalid entry #%d.\n", ordinal);
skip_fixup = true;
break;
}
rel.symbol = e->symbol;
rel.target_vaddr = e->symbol->vaddr;
rel.trg_obj_num = e->obj_num;
if (e->obj_num - 1 < h->objcnt) {
target_base_vaddr = bin->objects[e->obj_num - 1].reloc_base_addr;
}
break;
}
default:
RZ_LOG_WARN("LE: unsupported fixup target type %u.\n", trg_type);
goto fail_cleanup;
}
if (imp_mod_ord) {
rel.trg_obj_num = h->objcnt + 1; // pseudo object for imports
bool proc_by_ord = imp_proc_ord > 0;
ut32 proc = proc_by_ord ? imp_proc_ord : imp_proc_name_off;
LE_import *le_imp;
CHECK(le_imp = le_add_import(bin, imp_mod_ord, proc_by_ord, proc, 0));
rel.import = le_imp->import;
rel.target_vaddr = le_imp->symbol->vaddr;
}
if (trg_flags & F_TARGET_ADDITIVE) {
if (trg_flags & F_TARGET_ADD32) {
CHECK_READ32(rel.addend);
} else {
CHECK_READ16(rel.addend);
}
}
// handle fixup chain, sources list, or just a single fixup
RzListIter *prev_tail = rz_list_tail(relocs_out);
if (!src_list_count) {
rel.src_off = src_off; // in non-list cases src_off has already been read by now
// [1] 3.13.5 Internal Chaining Fixups
bool is_chain = trg_flags & F_TARGET_CHAIN;
if (is_chain) {
ut64 start_paddr = cur_page->paddr + src_off;
for (ut32 cnt = 0; src_off != 0xFFF; cnt++) {
if (src_off < 0 || src_off + 4 > h->pagesize || cnt > h->pagesize) {
RZ_LOG_WARN("LE: malformed or circular fixup chain at 0x%" PFMT64x ".\n",
start_paddr);
while (relocs_out->tail != prev_tail) {
rz_bin_reloc_free(rz_list_pop(relocs_out));
}
break;
}
union {
ut32 fixupinfo;
ut8 buf[4];
} u = { .fixupinfo = 0 };
CHECK(page_read(bin, cur_page, cur_page->vaddr + src_off, u.buf, 4));
rel.target_vaddr = target_base_vaddr + (u.fixupinfo & 0xFFFFF);
rel.src_off = src_off;
CHECK(le_append_fixup(bin, &rel, relocs_out, skip_fixup));
src_off = u.fixupinfo >> 20;
}
} else if (!is_chain) {
CHECK(le_append_fixup(bin, &rel, relocs_out, skip_fixup));
}
} else {
while (src_list_count--) {
CHECK_READ16(rel.src_off);
CHECK(le_append_fixup(bin, &rel, relocs_out, skip_fixup));
}
}
return true;
}
static RZ_OWN RzList /*<LE_reloc *>*/ *le_load_relocs(rz_bin_le_obj_t *bin) {
RzList *relocs = rz_list_newf(NULL);
if (!relocs) {
return NULL;
}
for (ut32 pi = 0; pi < bin->header->mpages; pi++) {
LE_page *page = &bin->le_pages[pi];
ut64 off = page->fixup_page_start, end = page->fixup_page_end;
while (off < end) {
if (!le_load_fixup_record(bin, relocs, pi, &off, end)) {
break; // malformed page, continue reading from the next page
}
}
}
return relocs;
}
/// --- Plugin callbacks --------------------------------------------------------------------------
static void rz_bin_le_free(rz_bin_le_obj_t *bin) {
if (!bin) {
return;
}
free(bin->header);
free(bin->modname);
rz_buf_free(bin->buf_patched);
free(bin->objects);
free(bin->le_pages);
rz_vector_free(bin->le_maps);
rz_pvector_free(bin->imp_mod_names);
rz_list_free(bin->symbols);
rz_vector_free(bin->le_entries);
rz_pvector_free(bin->imports);
ht_pp_free(bin->le_import_ht);
rz_list_free(bin->le_relocs);
rz_list_free(bin->le_fixups);
free(bin);
}
void rz_bin_le_destroy(RzBinFile *bf) {
rz_bin_le_free(bf->o->bin_obj);
}
bool rz_bin_le_load_buffer(RzBinFile *bf, RzBinObject *obj, RzBuffer *buf, Sdb *sdb) {
rz_return_val_if_fail(bf && obj && buf, false);
rz_bin_le_obj_t *bin = RZ_NEW0(rz_bin_le_obj_t);
char const *err_ctx = ", unable to load file header.";
if (!bin) {
fail_cleanup:
RZ_LOG_ERROR("LE: loading binary failed%s\n", err_ctx);
rz_bin_le_free(bin);
return false;
}
bin->buf = buf;
CHECK(le_load_header(bin));
if (bin->header->objcnt) {
CHECK(bin->buf_patched = rz_buf_new_sparse_overlay(buf, RZ_BUF_SPARSE_WRITE_MODE_SPARSE));
} else {
RZ_LOG_WARN("LE: binary has no code, probably a forwarder-only library.\n");
}
bin->type = le_get_module_type(bin);
bin->cpu = le_get_cpu_type(bin);
bin->os = le_get_os_type(bin);
bin->arch = le_get_arch(bin);
ut64 off = bin->le_off + bin->header->restab;
le_read_len_str_offset(bin->buf, &off, &bin->modname);
err_ctx = ", unable to load objects.";
CHECK(le_load_objects(bin));
err_ctx = ", unable to load data pages.";
CHECK(bin->le_pages = le_load_pages(bin));
err_ctx = ", unable to build maps.";
CHECK(bin->le_maps = le_create_maps(bin));
err_ctx = ", unable to load imports.";
CHECK(bin->imports = rz_pvector_new((RzListFree)rz_bin_import_free));
CHECK(bin->symbols = rz_list_newf((RzListFree)rz_bin_symbol_free));
CHECK(bin->imp_mod_names = le_load_import_mod_names(bin));
CHECK(bin->le_entries = le_load_entries(bin));
err_ctx = ", unable to load and apply relocations.";
CHECK(bin->le_fixups = rz_list_newf(free));
CHECK(bin->le_relocs = le_load_relocs(bin));
CHECK(le_patch_relocs(bin));
obj->bin_obj = bin;
return true;
}
bool rz_bin_le_check_buffer(RzBuffer *b) {
return le_get_header_offset(b, NULL, NULL);
}
static void no_free(void *unused) {}
RZ_OWN RzPVector /*<RzBinImport *>*/ *rz_bin_le_get_imports(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
if (rz_pvector_empty(bin->imports)) {
return NULL;
}
RzPVector *l = rz_pvector_clone(bin->imports);
l->v.free_user = no_free; // silence assertion, there's no need to delete imports
return l;
}
RZ_OWN RzPVector /*<RzBinSymbol *>*/ *rz_bin_le_get_symbols(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
if (rz_list_empty(bin->symbols)) {
return NULL;
}
RzListIter *iter;
RzBinSymbol *sym;
RzPVector *vec = rz_pvector_new(NULL);
rz_list_foreach (bin->symbols, iter, sym) {
rz_pvector_push(vec, sym);
}
return vec;
}
RZ_OWN RzPVector /*<RzBinSection *>*/ *rz_bin_le_get_sections(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
RzPVector *sections = rz_pvector_new((RzPVectorFree)rz_bin_section_free);
RzBinSection *sec = NULL;
if (!sections) {
fail_cleanup:
rz_pvector_free(sections);
rz_bin_section_free(sec);
return NULL;
}
ut32 obj_num = 0, sec_num = 0;
LE_map *le_map;
rz_vector_foreach (bin->le_maps, le_map) {
CHECK(sec = RZ_NEW0(RzBinSection));
if (obj_num == le_map->obj_num) {
sec_num++;
} else {
obj_num = le_map->obj_num;
sec_num = 1;
}
CHECK(sec->name = rz_str_newf("obj%u_%u", obj_num, sec_num));
sec->size = le_map->size;
sec->vsize = le_map->vsize;
sec->vaddr = le_map->vaddr;
sec->paddr = le_map->paddr;
LE_object *obj = &bin->objects[obj_num - 1];
sec->perm = le_obj_perm(obj);
sec->bits = obj->flags & O_BIG_BIT ? RZ_SYS_BITS_32 : RZ_SYS_BITS_16;
sec->is_data = obj->flags & O_RESOURCE || !(sec->perm & RZ_PERM_X);
CHECK(rz_pvector_push(sections, sec));
sec = NULL;
}
return sections;
}
RZ_OWN RzPVector /*<RzBinAddr *>*/ *rz_bin_le_get_entry_points(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
LE_header *h = bin->header;
RzBinAddr *addr = NULL;
RzPVector *entries = rz_pvector_new((RzPVectorFree)free);
if (!entries) {
fail_cleanup:
rz_pvector_free(entries);
free(addr);
return NULL;
}
// EXE entry point or DLL initialization routine, h->startobj can be 0 (invalid) for DLL,
// which means this particular DLL does not need initialization.
if (h->startobj - 1 < h->objcnt) {
CHECK(addr = RZ_NEW0(RzBinAddr));
addr->vaddr = bin->objects[h->startobj - 1].reloc_base_addr + h->eip;
addr->paddr = le_vaddr_to_paddr(bin, addr->vaddr);
CHECK(rz_pvector_push(entries, addr));
addr = NULL;
}
// Exported functions, only DLLs have these.
LE_entry *e;
rz_vector_foreach (bin->le_entries, e) {
if (!e->is_empty && !e->is_forwarder && e->is_exported && e->symbol) {
CHECK(addr = RZ_NEW0(RzBinAddr));
addr->vaddr = e->symbol->vaddr;
addr->paddr = le_vaddr_to_paddr(bin, addr->vaddr);
CHECK(rz_pvector_push(entries, addr));
addr = NULL;
}
}
return entries;
}
static void str_copy(void *dst, void *src) {
char **_dst = (char **)dst;
char **_src = (char **)src;
*_dst = rz_str_dup(*_src);
}
RZ_OWN RzPVector /*<char *>*/ *rz_bin_le_get_libs(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
if (rz_pvector_empty(bin->imp_mod_names)) {
return NULL;
}
RzPVector *ret = rz_pvector_clonef(bin->imp_mod_names, str_copy);
if (ret) {
ret->v.free = bin->imp_mod_names->v.free;
ret->v.free_user = bin->imp_mod_names->v.free_user;
}
return ret;
}
#define VFILE_NAME_PATCHED "patched"
#define VFILE_NAME_RELOC_TARGETS "reloc-targets"
RZ_OWN RzPVector /*<RzBinVirtualFile *>*/ *rz_bin_le_get_virtual_files(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
RzBinVirtualFile *vf = NULL;
RzBuffer *buf = NULL;
RzPVector *vfiles = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free);
if (!vfiles) {
fail_cleanup:
rz_bin_virtual_file_free(vf);
rz_pvector_free(vfiles);
rz_buf_free(buf);
return NULL;
}
if (bin->buf_patched) {
// patched vfile over main buffer
CHECK(vf = RZ_NEW0(RzBinVirtualFile));
CHECK(vf->name = rz_str_dup(VFILE_NAME_PATCHED));
vf->buf = bin->buf_patched;
vf->buf_owned = false;
CHECK(rz_pvector_push(vfiles, vf));
vf = NULL;
}
// virtual file per memory range not backed by physical pages (unpacked & zero-filled pages)
LE_map *le_map;
rz_vector_foreach (bin->le_maps, le_map) {
if (le_map->is_physical) {
continue;
}
CHECK(vf = RZ_NEW0(RzBinVirtualFile));
CHECK(vf->name = rz_str_dup(le_map->vfile_name));
vf->buf = le_map->vfile_buf;
vf->buf_owned = false;
CHECK(rz_pvector_push(vfiles, vf));
vf = NULL;
}
// virtual file for reloc targets
ut64 rtmsz = le_reloc_targets_vfile_size(bin);
if (rtmsz) {
CHECK(vf = RZ_NEW0(RzBinVirtualFile));
CHECK(vf->name = rz_str_dup(VFILE_NAME_RELOC_TARGETS))
CHECK(vf->buf = rz_buf_new_empty(rtmsz));
vf->buf_owned = true;
CHECK(rz_pvector_push(vfiles, vf));
vf = NULL;
}
return vfiles;
}
RZ_OWN RzPVector /*<RzBinReloc *>*/ *rz_bin_le_get_relocs(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
RzList /*<LE_reloc *>*/ *le_relocs = bin->le_relocs;
RzPVector /*<RzBinReloc *>*/ *relocs = rz_pvector_new(free);
RzBinReloc *reloc = NULL;
if (!relocs) {
fail_cleanup:
rz_pvector_free(relocs);
rz_bin_reloc_free(reloc);
return NULL;
}
RzListIter *it;
LE_reloc *le_reloc;
rz_list_foreach (le_relocs, it, le_reloc) {
CHECK(reloc = RZ_NEW0(RzBinReloc));
CHECK(rz_pvector_push(relocs, reloc));
reloc->symbol = le_reloc->symbol;
reloc->import = le_reloc->import;
reloc->addend = le_reloc->addend;
reloc->vaddr = le_reloc_vaddr(bin, le_reloc);
reloc->paddr = le_vaddr_to_paddr(bin, reloc->vaddr);
reloc->target_vaddr = le_reloc->target_vaddr;
switch (le_reloc->type) {
case FIXUP_BYTE:
reloc->type = RZ_BIN_RELOC_8;
break;
case FIXUP_SEL16:
case FIXUP_OFF16:
reloc->type = RZ_BIN_RELOC_16;
break;
case FIXUP_OFF32:
case FIXUP_SEL16_OFF16:
case FIXUP_REL32:
reloc->type = RZ_BIN_RELOC_32;
break;
case FIXUP_SEL16_OFF32:
reloc->type = RZ_BIN_RELOC_32;
reloc = NULL;
// adding additional 2-byte relocation right after the 4-byte one
// to represent a 48 bit 16:32 relocation
CHECK(reloc = RZ_NEW0(RzBinReloc));
*reloc = *(RzBinReloc *)rz_pvector_tail(relocs);
reloc->vaddr += 4;
reloc->paddr += 4;
reloc->type = RZ_BIN_RELOC_16;
CHECK(rz_pvector_push(relocs, reloc));
reloc = NULL;
break;
default:
break;
}
}
return relocs;
}
RZ_OWN RzPVector /*<RzBinMap *>*/ *rz_bin_le_get_maps(RzBinFile *bf) {
rz_bin_le_obj_t *bin = bf->o->bin_obj;
RzPVector *maps = rz_pvector_new((RzPVectorFree)rz_bin_map_free);
RzBinMap *map = NULL;
if (!maps) {
fail_cleanup:
rz_pvector_free(maps);
rz_bin_map_free(map);
return NULL;
}
LE_map *le_map;
ut32 map_num = 0;
ut32 obj_num = 0;
rz_vector_foreach (bin->le_maps, le_map) {
LE_object *obj = &bin->objects[le_map->obj_num - 1];
if (le_map->obj_num != obj_num) {
obj_num = le_map->obj_num;
map_num = 1;
} else {
map_num++;
}
CHECK(map = RZ_NEW0(RzBinMap));
map->perm = le_obj_perm(obj);
map->vaddr = le_map->vaddr;
map->psize = le_map->size;
map->vsize = le_map->vsize;
CHECK(map->name = rz_str_newf("obj%u_map%u", obj_num, map_num));
if (le_map->is_physical) {
map->paddr = le_map->paddr;
CHECK(map->vfile_name = rz_str_dup(VFILE_NAME_PATCHED));
} else {
map->paddr = 0;
if (map->psize != 0) {
CHECK(map->vfile_name = rz_str_dup(le_map->vfile_name));
}
}
CHECK(rz_pvector_push(maps, map));
map = NULL;
}
CHECK(map = RZ_NEW0(RzBinMap));
ut64 rtmsz = le_reloc_targets_vfile_size(bin);
map->perm = RZ_PERM_R | RZ_PERM_X;
map->vaddr = bin->reloc_target_map_base;
map->psize = rtmsz;
map->vsize = rtmsz;
CHECK(map->name = rz_str_dup(VFILE_NAME_RELOC_TARGETS));
CHECK(map->vfile_name = rz_str_dup(VFILE_NAME_RELOC_TARGETS));
CHECK(rz_pvector_push(maps, map));
return maps;
}