498 lines
14 KiB
C
498 lines
14 KiB
C
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
|
|
// SPDX-FileCopyrightText: 2021 Oleg Bushin
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/**
|
|
* \file Calculates a list of possible base addresses candidates using the strings position
|
|
* Original code from 2013 Michael Coppola
|
|
* https://github.com/mncoppola/ws30/blob/master/basefind.py
|
|
*/
|
|
|
|
#include <rz_basefind.h>
|
|
#include <rz_th.h>
|
|
|
|
typedef struct basefind_addresses_t {
|
|
ut64 *ptr;
|
|
ut32 size;
|
|
} BaseFindArray;
|
|
|
|
typedef struct basefind_data_t {
|
|
ut32 score;
|
|
ut64 start;
|
|
ut64 end;
|
|
BaseFindArray *array;
|
|
RzAtomicBool *loop;
|
|
} BaseFindData;
|
|
|
|
typedef struct basefind_thread_data_t {
|
|
ut32 id;
|
|
ut64 current;
|
|
ut64 base_start;
|
|
ut64 base_end;
|
|
ut64 alignment;
|
|
ut64 io_size;
|
|
ut32 score_min;
|
|
RzThreadLock *lock;
|
|
RzList /*<RzBaseFindScore *>*/ *scores;
|
|
HtUU *pointers;
|
|
BaseFindArray *array;
|
|
RzAtomicBool *loop;
|
|
} BaseFindThreadData;
|
|
|
|
typedef struct basefind_ui_info_t {
|
|
RzAtomicBool *loop;
|
|
RzThreadPool *pool;
|
|
void *user;
|
|
RzBaseFindThreadInfoCb callback;
|
|
} BaseFindUIInfo;
|
|
|
|
static void basefind_stop_all_search_threads(RzThreadPool *pool) {
|
|
size_t pool_size = rz_th_pool_size(pool);
|
|
for (ut32 i = 0; i < pool_size; ++i) {
|
|
RzThread *th = rz_th_pool_get_thread(pool, i);
|
|
if (!th) {
|
|
continue;
|
|
}
|
|
BaseFindThreadData *bftd = rz_th_get_user(th);
|
|
rz_atomic_bool_set(bftd->loop, false);
|
|
}
|
|
}
|
|
|
|
static RzBinFile *basefind_new_bin_file(RzCore *core) {
|
|
// Copied from cbin.c -> rz_core_bin_whole_strings_print
|
|
// TODO: manually creating an RzBinFile like this is a hack and abuse of RzBin API
|
|
// If we don't want to use an RzBinFile for searching strings, the raw strings search
|
|
// should be refactored out of bin.
|
|
RzIODesc *desc = rz_io_desc_get(core->io, core->file->fd);
|
|
if (!desc) {
|
|
RZ_LOG_ERROR("basefind: cannot get RzIODesc from core.\n");
|
|
return NULL;
|
|
}
|
|
|
|
RzBinFile *bf = RZ_NEW0(RzBinFile);
|
|
if (!bf) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate RzBinFile structure.\n");
|
|
return NULL;
|
|
}
|
|
|
|
bf->file = rz_str_dup(desc->name);
|
|
bf->size = rz_io_desc_size(desc);
|
|
if (bf->size == UT64_MAX) {
|
|
RZ_LOG_ERROR("basefind: filesize exceeds memory size (UT64_MAX).\n");
|
|
free(bf->file);
|
|
free(bf);
|
|
return NULL;
|
|
}
|
|
|
|
bf->buf = rz_buf_new_with_io_fd(&core->bin->iob, core->file->fd);
|
|
bf->rbin = core->bin;
|
|
return bf;
|
|
}
|
|
|
|
static void basefind_array_free(BaseFindArray *array) {
|
|
if (!array) {
|
|
return;
|
|
}
|
|
free(array->ptr);
|
|
free(array);
|
|
}
|
|
|
|
static bool basefind_array_has(const BaseFindArray *array, ut64 value) {
|
|
// half-interval search should be better here.
|
|
for (ut32 i = 0; i < array->size; ++i) {
|
|
if (array->ptr[i] == value) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static BaseFindArray *basefind_create_array_of_addresses(RzCore *core, RzBinStringSearchOpt *opt) {
|
|
RzPVector *strings = NULL;
|
|
BaseFindArray *array = NULL;
|
|
RzBinFile *alloc = NULL;
|
|
RzBinFile *current = rz_bin_cur(core->bin);
|
|
if (!current) {
|
|
current = alloc = basefind_new_bin_file(core);
|
|
if (!current) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// if this list is sorted we can improve speed via half-interval search
|
|
strings = rz_bin_file_strings(current, opt);
|
|
if (!strings || rz_pvector_empty(strings)) {
|
|
RZ_LOG_ERROR("basefind: cannot find strings in binary with a minimum size of %" PFMTSZu ".\n", opt->min_length);
|
|
rz_pvector_free(strings);
|
|
return NULL;
|
|
}
|
|
|
|
array = RZ_NEW0(BaseFindArray);
|
|
if (!array) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate BaseFindArray.\n");
|
|
goto error;
|
|
}
|
|
|
|
array->size = rz_pvector_len(strings);
|
|
array->ptr = RZ_NEWS0(ut64, array->size);
|
|
if (!array->ptr) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate array of addresses.\n");
|
|
basefind_array_free(array);
|
|
array = NULL;
|
|
goto error;
|
|
}
|
|
|
|
ut32 idx;
|
|
void **iter;
|
|
RzBinString *string;
|
|
rz_pvector_enumerate (strings, iter, idx) {
|
|
string = *iter;
|
|
RZ_LOG_VERBOSE("basefind: 0x%016" PFMT64x " '%s'\n", string->paddr, string->string);
|
|
array->ptr[idx] = string->paddr;
|
|
}
|
|
RZ_LOG_INFO("basefind: located %u strings\n", array->size);
|
|
|
|
error:
|
|
rz_pvector_free(strings);
|
|
if (alloc) {
|
|
rz_buf_free(alloc->buf);
|
|
free(alloc->file);
|
|
free(alloc);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
static HtUU *basefind_create_pointer_map(RzCore *core, ut32 pointer_size) {
|
|
rz_return_val_if_fail(pointer_size == sizeof(ut32) || pointer_size == sizeof(ut64), NULL);
|
|
|
|
HtUU *map = ht_uu_new();
|
|
if (!map) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate hashmap for pointer.\n");
|
|
return NULL;
|
|
}
|
|
|
|
ut8 buffer[sizeof(ut64)];
|
|
ut64 io_size = rz_io_size(core->io);
|
|
ut64 address = 0;
|
|
bool big_endian = rz_config_get_b(core->config, "cfg.bigendian");
|
|
|
|
for (ut64 pos = 0; pos < io_size; pos += pointer_size) {
|
|
rz_io_pread_at(core->io, pos, buffer, pointer_size);
|
|
address = pointer_size == sizeof(ut64) ? rz_read_ble64(buffer, big_endian) : rz_read_ble32(buffer, big_endian);
|
|
ut64 value = ht_uu_find(map, address, NULL) + 1;
|
|
ht_uu_insert(map, address, value);
|
|
}
|
|
RZ_LOG_INFO("basefind: located %u pointers\n", ht_uu_size(map));
|
|
|
|
return map;
|
|
}
|
|
|
|
static bool basefind_pointer_map_iter(BaseFindData *bfd, const ut64 address, const ut64 hits) {
|
|
if (!rz_atomic_bool_get(bfd->loop)) {
|
|
return false;
|
|
}
|
|
if (address < bfd->start || address >= bfd->end) {
|
|
return true;
|
|
}
|
|
ut64 offset = address - bfd->start;
|
|
if (basefind_array_has(bfd->array, offset)) {
|
|
bfd->score += hits;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int basefind_score_compare(const RzBaseFindScore *a, const RzBaseFindScore *b, void *user) {
|
|
if (b->score == a->score) {
|
|
if (b->candidate == a->candidate) {
|
|
return 0;
|
|
} else if (b->candidate < a->candidate) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
} else if (b->score < a->score) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void *basefind_thread_runner(BaseFindThreadData *bftd) {
|
|
RzAtomicBool *loop = bftd->loop;
|
|
RzBaseFindScore *pair = NULL;
|
|
BaseFindData bfd;
|
|
ut64 base;
|
|
|
|
bfd.array = bftd->array;
|
|
bfd.loop = loop;
|
|
for (base = bftd->base_start; base < bftd->base_end; base += bftd->alignment) {
|
|
if (!rz_atomic_bool_get(loop)) {
|
|
break;
|
|
}
|
|
bftd->current = base;
|
|
bfd.score = 0;
|
|
bfd.start = base;
|
|
bfd.end = base + bftd->io_size;
|
|
ht_uu_foreach(bftd->pointers, (HtUUForeachCallback)basefind_pointer_map_iter, &bfd);
|
|
|
|
if (bfd.score < bftd->score_min) {
|
|
// ignore any score below than score_min
|
|
continue;
|
|
}
|
|
|
|
pair = RZ_NEW0(RzBaseFindScore);
|
|
if (!pair) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate RzBaseFindScore.\n");
|
|
break;
|
|
}
|
|
pair->score = bfd.score;
|
|
pair->candidate = base;
|
|
|
|
rz_th_lock_enter(bftd->lock);
|
|
if (!rz_list_append(bftd->scores, pair)) {
|
|
rz_th_lock_leave(bftd->lock);
|
|
free(pair);
|
|
RZ_LOG_ERROR("basefind: cannot append new score to the scores list.\n");
|
|
break;
|
|
}
|
|
RZ_LOG_DEBUG("basefind: possible candidate at 0x%016" PFMT64x " with score of %u\n", base, bfd.score);
|
|
rz_th_lock_leave(bftd->lock);
|
|
}
|
|
bftd->current = base;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void basefind_set_thread_info(BaseFindThreadData *bftd, RzBaseFindThreadInfo *th_info, ut32 thread_idx) {
|
|
ut32 percentage = ((bftd->current - bftd->base_start) * 100) / (bftd->base_end - bftd->base_start);
|
|
if (percentage > 100) {
|
|
percentage = 100;
|
|
}
|
|
|
|
th_info->thread_idx = thread_idx;
|
|
th_info->begin_address = bftd->base_start;
|
|
th_info->current_address = bftd->current;
|
|
th_info->end_address = bftd->base_end;
|
|
th_info->percentage = percentage;
|
|
}
|
|
|
|
// this thread does not care about thread-safety since it only prints
|
|
// data that will always be available during its lifetime.
|
|
static void *basefind_thread_ui(BaseFindUIInfo *ui_info) {
|
|
RzThreadPool *pool = ui_info->pool;
|
|
RzAtomicBool *loop = ui_info->loop;
|
|
ut32 pool_size = rz_th_pool_size(pool);
|
|
RzBaseFindThreadInfoCb callback = ui_info->callback;
|
|
void *user = ui_info->user;
|
|
RzBaseFindThreadInfo th_info;
|
|
th_info.n_threads = pool_size;
|
|
|
|
do {
|
|
for (ut32 i = 0; i < pool_size; ++i) {
|
|
RzThread *th = rz_th_pool_get_thread(pool, i);
|
|
if (!th) {
|
|
continue;
|
|
}
|
|
BaseFindThreadData *bftd = rz_th_get_user(th);
|
|
basefind_set_thread_info(bftd, &th_info, i);
|
|
if (!callback(&th_info, user)) {
|
|
basefind_stop_all_search_threads(pool);
|
|
goto end;
|
|
}
|
|
}
|
|
rz_sys_usleep(100000);
|
|
} while (rz_atomic_bool_get(loop));
|
|
end:
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool create_thread_interval(RzThreadPool *pool, BaseFindThreadData *bfd) {
|
|
RzThread *thread = rz_th_new((RzThreadFunction)basefind_thread_runner, bfd);
|
|
if (!thread) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate RzThread\n");
|
|
return false;
|
|
} else if (!rz_th_pool_add_thread(pool, thread)) {
|
|
RZ_LOG_ERROR("basefind: cannot add thread to pool\n");
|
|
rz_th_free(thread);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculates a list of possible base addresses candidates using the strings position
|
|
*
|
|
* The code finds all the strings in memory with a minimum acceptable size (via opt.min_string_len)
|
|
* and calculates all possible words 32 or 64 bit large sizes (endianness via cfg.bigendian) in the
|
|
* given binary.
|
|
* These addresses are then compared with the strings and a variable base address which is increased
|
|
* over time by opt.alignment.
|
|
*
|
|
* The scores are added to the result list with the associated base address if their score are higher
|
|
* than opt.min_score, otherwise they are ignored.
|
|
*
|
|
* It is possible via opt.callback to set a callback function that can stop the search (when returning
|
|
* false) or display the thread statuses (the callback will be called N-times for N spawned threads.
|
|
*
|
|
* \param core RzCore struct to use.
|
|
* \param options Pointer to the RzBaseFindOpt structure.
|
|
*/
|
|
RZ_API RZ_OWN RzList /*<RzBaseFindScore *>*/ *rz_basefind(RZ_NONNULL RzCore *core, RZ_NONNULL RzBaseFindOpt *options) {
|
|
rz_return_val_if_fail(core && options, NULL);
|
|
RzList *scores = NULL;
|
|
BaseFindArray *array = NULL;
|
|
HtUU *pointers = NULL;
|
|
size_t pool_size = 1;
|
|
RzThreadPool *pool = NULL;
|
|
RzThreadLock *lock = NULL;
|
|
RzThread *user_thread = NULL;
|
|
BaseFindUIInfo ui_info = { 0 };
|
|
|
|
ut64 base_start = options->start_address;
|
|
ut64 base_end = options->end_address;
|
|
ut64 alignment = options->alignment;
|
|
|
|
if (options->pointer_size != 32 && options->pointer_size != 64) {
|
|
RZ_LOG_ERROR("basefind: supported pointer sizes are 32 and 64 bits.\n");
|
|
return NULL;
|
|
} else if (!core->file) {
|
|
RZ_LOG_ERROR("basefind: the file was not opened via RzCore.\n");
|
|
return NULL;
|
|
} else if (base_start >= base_end) {
|
|
RZ_LOG_ERROR("basefind: start address is greater or equal to end address.\n");
|
|
return NULL;
|
|
} else if (alignment < 1) {
|
|
RZ_LOG_ERROR("basefind: the alignment is set to zero bytes.\n");
|
|
return NULL;
|
|
} else if (options->min_score < 1) {
|
|
RZ_LOG_ERROR("basefind: the minimum score is set to zero.\n");
|
|
return NULL;
|
|
} else if (options->min_string_len < 1) {
|
|
RZ_LOG_ERROR("basefind: the minimum string length is set to zero.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (alignment < RZ_BASEFIND_BASE_ALIGNMENT) {
|
|
RZ_LOG_WARN("basefind: the alignment is less than 0x%x bytes, "
|
|
"which may result in a very slow search.\n",
|
|
RZ_BASEFIND_BASE_ALIGNMENT);
|
|
}
|
|
|
|
// Copy RzBin string search configuration.
|
|
RzBinStringSearchOpt opt = core->bin->str_search_cfg;
|
|
|
|
// Enforce raw binary mode, thread count & min string length.
|
|
opt.mode = RZ_BIN_STRING_SEARCH_MODE_RAW_BINARY;
|
|
opt.max_threads = options->max_threads;
|
|
opt.min_length = options->min_string_len;
|
|
|
|
array = basefind_create_array_of_addresses(core, &opt);
|
|
if (!array) {
|
|
goto rz_basefind_end;
|
|
}
|
|
|
|
pointers = basefind_create_pointer_map(core, options->pointer_size / 8);
|
|
if (!pointers) {
|
|
goto rz_basefind_end;
|
|
}
|
|
|
|
scores = rz_list_newf((RzListFree)free);
|
|
if (!scores) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate new scores list.\n");
|
|
goto rz_basefind_end;
|
|
}
|
|
|
|
pool = rz_th_pool_new(options->max_threads);
|
|
if (!pool) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate thread pool.\n");
|
|
goto rz_basefind_end;
|
|
}
|
|
pool_size = rz_th_pool_size(pool);
|
|
|
|
lock = rz_th_lock_new(false);
|
|
if (!lock) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate thread lock.\n");
|
|
goto rz_basefind_end;
|
|
}
|
|
|
|
RZ_LOG_VERBOSE("basefind: using %u threads\n", (ut32)pool_size);
|
|
|
|
ut64 io_size = rz_io_size(core->io);
|
|
ut64 sector_size = (((base_end - base_start) + pool_size - 1) / pool_size);
|
|
for (size_t i = 0; i < pool_size; ++i) {
|
|
BaseFindThreadData *bftd = RZ_NEW(BaseFindThreadData);
|
|
if (!bftd) {
|
|
RZ_LOG_ERROR("basefind: cannot allocate BaseFindThreadData.\n");
|
|
basefind_stop_all_search_threads(pool);
|
|
goto rz_basefind_end;
|
|
}
|
|
bftd->alignment = alignment;
|
|
bftd->base_start = base_start + (sector_size * i);
|
|
bftd->current = bftd->base_start;
|
|
bftd->base_end = bftd->base_start + sector_size;
|
|
bftd->score_min = options->min_score;
|
|
bftd->io_size = io_size;
|
|
bftd->lock = lock;
|
|
bftd->scores = scores;
|
|
bftd->pointers = pointers;
|
|
bftd->array = array;
|
|
bftd->loop = rz_atomic_bool_new(true);
|
|
if (!create_thread_interval(pool, bftd)) {
|
|
free(bftd);
|
|
basefind_stop_all_search_threads(pool);
|
|
goto rz_basefind_end;
|
|
}
|
|
}
|
|
|
|
if (options->callback) {
|
|
ui_info.pool = pool;
|
|
ui_info.user = options->user;
|
|
ui_info.callback = options->callback;
|
|
ui_info.loop = rz_atomic_bool_new(true);
|
|
user_thread = rz_th_new((RzThreadFunction)basefind_thread_ui, &ui_info);
|
|
if (!user_thread) {
|
|
basefind_stop_all_search_threads(pool);
|
|
goto rz_basefind_end;
|
|
}
|
|
}
|
|
|
|
// wait the pool to finish
|
|
rz_th_pool_wait(pool);
|
|
|
|
if (options->callback) {
|
|
rz_atomic_bool_set(ui_info.loop, false);
|
|
rz_th_wait(user_thread);
|
|
rz_th_free(user_thread);
|
|
rz_atomic_bool_free(ui_info.loop);
|
|
|
|
RzBaseFindThreadInfo th_info;
|
|
th_info.n_threads = pool_size;
|
|
for (ut32 i = 0; i < pool_size; ++i) {
|
|
RzThread *th = rz_th_pool_get_thread(pool, i);
|
|
if (!th) {
|
|
continue;
|
|
}
|
|
BaseFindThreadData *bftd = rz_th_get_user(th);
|
|
basefind_set_thread_info(bftd, &th_info, i);
|
|
options->callback(&th_info, options->user);
|
|
}
|
|
}
|
|
|
|
rz_list_sort(scores, (RzListComparator)basefind_score_compare, NULL);
|
|
|
|
rz_basefind_end:
|
|
if (pool) {
|
|
for (ut32 i = 0; i < pool_size; ++i) {
|
|
RzThread *th = rz_th_pool_get_thread(pool, i);
|
|
if (!th) {
|
|
continue;
|
|
}
|
|
BaseFindThreadData *bftd = rz_th_get_user(th);
|
|
rz_atomic_bool_free(bftd->loop);
|
|
free(bftd);
|
|
}
|
|
rz_th_pool_free(pool);
|
|
}
|
|
rz_th_lock_free(lock);
|
|
basefind_array_free(array);
|
|
ht_uu_free(pointers);
|
|
return scores;
|
|
}
|