rizin/librz/bin/bfile_string.c
Khairul Azhar Kasmiran 7b91fb69c2
Revert #4506 (#4519)
2024-05-27 21:50:58 +08:00

539 lines
14 KiB
C

// SPDX-FileCopyrightText: 2022 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_bin.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_str_search.h>
typedef struct search_interval_t {
ut64 paddr;
ut64 psize;
} SearchInterval;
typedef struct shared_data_t {
RzThreadLock *lock;
RzBinFile *bf;
HtUP *strings_db;
RzStrEnc string_encoding;
size_t max_uni_blocks;
size_t buffer_size;
size_t min_str_length;
bool check_ascii_freq;
bool prefer_big_endian;
} SharedData;
typedef struct search_thread_data_t {
RzThreadQueue *intervals;
RzPVector /*<RzBinString *>*/ *results;
SharedData *shared;
RzAtomicBool *loop;
} SearchThreadData;
static st64 shared_data_read_at(SharedData *sd, ut64 addr, ut8 *buf, ut64 size) {
rz_th_lock_enter(sd->lock);
st64 ret = rz_buf_read_at(sd->bf->buf, addr, buf, size);
rz_th_lock_leave(sd->lock);
return ret;
}
static bool shared_ht_up_insert(SharedData *sd, const ut64 key, void *value) {
rz_th_lock_enter(sd->lock);
bool ret = ht_up_insert(sd->strings_db, key, value);
rz_th_lock_leave(sd->lock);
return ret;
}
static bool is_data_section(RzBinFile *a, RzBinSection *s) {
if (s->has_strings || s->is_data) {
return true;
} else if (!s->name) {
return false;
}
// Rust binaries contains the _const section which is a data section.
return strstr(s->name, "_const") != NULL;
}
static RzBinString *to_bin_string(RzDetectedString *src) {
RzBinString *dst = RZ_NEW0(RzBinString);
if (!dst) {
rz_detected_string_free(src);
RZ_LOG_ERROR("bin_file_strings: cannot allocate RzBinString.\n");
return NULL;
}
dst->string = src->string;
dst->size = src->size;
dst->length = src->length;
dst->type = src->type;
dst->paddr = src->addr;
dst->vaddr = src->addr;
// variables has been transfered to RzBinString
free(src);
return dst;
}
static RzList /*<RzDetectedString *>*/ *string_scan_range(SharedData *shared, const ut64 paddr, const ut64 interval_size) {
RzList *found = rz_list_newf((RzListFree)free);
if (!found) {
return NULL;
}
// ensure the scan buffer size is less or equal to the the actual interval size.
size_t buffer_size = RZ_MIN(shared->buffer_size, interval_size);
RzUtilStrScanOptions scan_opt = {
.buf_size = buffer_size,
.max_uni_blocks = shared->max_uni_blocks,
.min_str_length = shared->min_str_length,
.prefer_big_endian = shared->prefer_big_endian,
.check_ascii_freq = shared->check_ascii_freq,
};
ut8 *buf = calloc(interval_size, 1);
if (!buf) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate string seac buffer.\n");
rz_list_free(found);
return NULL;
}
shared_data_read_at(shared, paddr, buf, interval_size);
ut64 end = paddr + interval_size;
int count = rz_scan_strings_raw(buf, found, &scan_opt, paddr, end, shared->string_encoding);
free(buf);
if (count <= 0) {
RZ_FREE_CUSTOM(found, rz_list_free);
}
return found;
}
static void *search_string_thread_runner(SearchThreadData *std) {
SearchInterval *itv = NULL;
RzDetectedString *detected = NULL;
ut64 paddr = 0, psize = 0;
bool loop = true;
SharedData *shared = std->shared;
const RzBinFile *bf = shared->bf; // this data is always RO
do {
itv = rz_th_queue_pop(std->intervals, false);
if (!itv) {
break;
}
paddr = itv->paddr;
psize = itv->psize;
free(itv);
RZ_LOG_DEBUG("[%p] searching between [0x%08" PFMT64x " : 0x%08" PFMT64x "]\n", std, paddr, paddr + psize);
RzList *list = string_scan_range(shared, paddr, psize);
while (list && rz_atomic_bool_get(std->loop)) {
detected = rz_list_pop_head(list);
if (!detected) {
break;
}
RzBinString *bstr = to_bin_string(detected);
if (!bstr || !rz_pvector_push(std->results, bstr)) {
rz_bin_string_free(bstr);
loop = false;
break;
} else if (!bf->o) {
continue;
}
// find virt address.
bstr->paddr += bf->o->boffset;
bstr->vaddr = rz_bin_object_p2v(bf->o, bstr->paddr);
shared_ht_up_insert(shared, bstr->vaddr, bstr);
}
rz_list_free(list);
} while (loop && rz_atomic_bool_get(std->loop));
RZ_LOG_DEBUG("[%p] died\n", std);
return NULL;
}
static void bin_file_string_search_free(SearchThreadData *std) {
if (!std) {
return;
}
rz_pvector_free(std->results);
rz_atomic_bool_free(std->loop);
free(std);
}
static void interrupt_thread(RzThread *thread) {
if (!thread) {
return;
}
SearchThreadData *std = (SearchThreadData *)rz_th_get_user(thread);
rz_atomic_bool_set(std->loop, false);
rz_th_wait(thread);
}
static void interrupt_pool(RzThreadPool *pool) {
size_t pool_size = rz_th_pool_size(pool);
for (size_t i = 0; i < pool_size; ++i) {
RzThread *th = rz_th_pool_get_thread(pool, i);
interrupt_thread(th);
}
}
static bool create_string_search_thread(RzThreadPool *pool, RzThreadQueue *intervals, SharedData *shared) {
SearchThreadData *std = RZ_NEW0(SearchThreadData);
if (!std) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate SearchThreadData.\n");
return false;
}
std->results = rz_pvector_new((RzPVectorFree)rz_bin_string_free);
if (!std->results) {
bin_file_string_search_free(std);
return false;
}
std->shared = shared;
std->intervals = intervals;
std->loop = rz_atomic_bool_new(true);
RzThread *thread = rz_th_new((RzThreadFunction)search_string_thread_runner, std);
if (!thread) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate RzThread\n");
bin_file_string_search_free(std);
return false;
} else if (!rz_th_pool_add_thread(pool, thread)) {
RZ_LOG_ERROR("bin_file_strings: cannot add thread to pool\n");
interrupt_thread(thread);
bin_file_string_search_free(std);
rz_th_free(thread);
return false;
}
return true;
}
static int string_compare_sort(const RzBinString *a, const RzBinString *b, void *user) {
if (b->paddr > a->paddr) {
return -1;
} else if (b->paddr < a->paddr) {
return 1;
} else if (b->vaddr > a->vaddr) {
return -1;
} else if (b->vaddr < a->vaddr) {
return 1;
}
return 0;
}
static void string_scan_range_cfstring(RzBinFile *bf, HtUP *strings_db, RzPVector /*<RzBinString *>*/ *results, const RzBinSection *section) {
// load objc/swift strings from CFstring table section
RzBinObject *o = bf->o;
const int bits = o->info ? o->info->bits : 32;
const int cfstr_size = (bits == 64) ? 32 : 16;
const int cfstr_offs = (bits == 64) ? 16 : 8;
ut8 *sbuf = calloc(section->size, 1);
if (!sbuf) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate RzBinString.\n");
return;
}
rz_buf_read_at(bf->buf, section->paddr + cfstr_offs, sbuf, section->size);
for (ut64 i = 0; i < section->size; i += cfstr_size) {
ut8 *buf = sbuf;
ut8 *p = buf + i;
if ((i + ((bits == 64) ? 8 : 4)) >= section->size) {
break;
}
ut64 cfstr_vaddr = section->vaddr + i;
ut64 cstr_vaddr = (bits == 64) ? rz_read_le64(p) : rz_read_le32(p);
if (!cstr_vaddr || cstr_vaddr == UT64_MAX) {
continue;
}
RzBinString *s = ht_up_find(strings_db, cstr_vaddr, NULL);
if (!s) {
continue;
}
RzBinString *bs = RZ_NEW0(RzBinString);
if (!bs) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate RzBinString\n");
break;
}
bs->type = s->type;
bs->length = s->length;
bs->size = s->size;
bs->ordinal = s->ordinal;
bs->vaddr = cfstr_vaddr;
bs->paddr = rz_bin_object_v2p(o, bs->vaddr);
bs->string = rz_str_newf("cstr.%s", s->string);
rz_pvector_push(results, bs);
ht_up_insert(strings_db, bs->vaddr, bs);
}
free(sbuf);
}
static void scan_cfstring_table(RzBinFile *bf, HtUP *strings_db, RzPVector /*<RzBinString *>*/ *results, ut64 max_region_size) {
void **iter = NULL;
RzBinSection *section = NULL;
RzBinObject *o = bf->o;
if (!o) {
return;
}
rz_pvector_foreach (o->sections, iter) {
section = *iter;
if (!section->name || section->paddr >= bf->size) {
continue;
} else if (max_region_size && section->size > max_region_size) {
RZ_LOG_WARN("bin_file_strings: search interval size (0x%" PFMT64x
") exeeds max region size (0x%" PFMT64x "), skipping it.\n",
section->size, max_region_size);
continue;
}
if (strstr(section->name, "__cfstring")) {
string_scan_range_cfstring(bf, strings_db, results, section);
}
}
}
/**
* \brief Sets the RzBinStringSearchOpt struct to its default options.
*
* \param opt The RzBinStringSearchOpt struct to initialize.
*/
RZ_API void rz_bin_string_search_opt_init(RZ_NONNULL RzBinStringSearchOpt *opt) {
rz_return_if_fail(opt);
opt->max_threads = RZ_THREAD_POOL_ALL_CORES;
opt->min_length = RZ_BIN_STRING_SEARCH_MIN_STRING;
opt->buffer_size = RZ_BIN_STRING_SEARCH_BUFFER_SIZE;
opt->max_uni_blocks = RZ_BIN_STRING_SEARCH_MAX_UNI_BLOCKS;
opt->max_region_size = RZ_BIN_STRING_SEARCH_MAX_REGION_SIZE;
opt->raw_alignment = RZ_BIN_STRING_SEARCH_RAW_FILE_ALIGNMENT;
opt->string_encoding = RZ_STRING_ENC_GUESS;
opt->check_ascii_freq = RZ_BIN_STRING_SEARCH_CHECK_ASCII_FREQ;
opt->mode = RZ_BIN_STRING_SEARCH_MODE_AUTO;
}
/**
* \brief Generates a RzList struct containing RzBinString from a given RzBinFile
*
* \param bf The RzBinFile to use for searching for strings
* \param opt The options regarding the string search.
*
* \return On success returns RzList pointer, otherwise NULL
*/
RZ_API RZ_OWN RzPVector /*<RzBinString *>*/ *rz_bin_file_strings(RZ_NONNULL RzBinFile *bf, RZ_NONNULL const RzBinStringSearchOpt *opt) {
rz_return_val_if_fail(bf && opt, NULL);
HtUP *strings_db = NULL;
RzPVector *results = NULL;
RzThreadQueue *intervals = NULL;
RzThreadPool *pool = NULL;
RzThreadLock *lock = NULL;
size_t pool_size = 1;
bool prefer_big_endian = false;
const size_t raw_alignment = opt->raw_alignment;
RzBinStringSearchMode mode = opt->mode;
pool = rz_th_pool_new(opt->max_threads);
if (!pool) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate thread pool.\n");
goto fail;
}
pool_size = rz_th_pool_size(pool);
lock = rz_th_lock_new(false);
if (!lock) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate thread lock.\n");
goto fail;
}
intervals = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)free);
if (!intervals) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate intervals queue.\n");
goto fail;
}
strings_db = ht_up_new(NULL, NULL);
if (!strings_db) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate string map.\n");
goto fail;
}
bool has_sections = bf->o && !rz_pvector_empty(bf->o->sections);
if (mode == RZ_BIN_STRING_SEARCH_MODE_AUTO && !has_sections) {
mode = RZ_BIN_STRING_SEARCH_MODE_RAW_BINARY;
}
if (mode == RZ_BIN_STRING_SEARCH_MODE_RAW_BINARY) {
// returns all the strings found on the RzBinFile
ut64 section_size = bf->size / pool_size;
if (section_size & (raw_alignment - 1)) {
section_size += raw_alignment;
section_size &= ~(raw_alignment - 1);
}
if (!section_size) {
section_size += raw_alignment;
}
if (opt->max_region_size && section_size > opt->max_region_size) {
RZ_LOG_ERROR("bin_file_strings: search interval size (0x%" PFMT64x
") exeeds max region size (0x%" PFMTSZx ").\n",
section_size, opt->max_region_size);
goto fail;
}
for (ut64 from = 0; from < bf->size; from += section_size) {
SearchInterval *itv = RZ_NEW0(SearchInterval);
if (!itv) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate SearchInterval.\n");
goto fail;
}
itv->paddr = from;
itv->psize = section_size;
if ((itv->paddr + itv->psize) > bf->size) {
itv->psize = bf->size - itv->paddr;
}
if (!rz_th_queue_push(intervals, itv, true)) {
free(itv);
RZ_LOG_ERROR("bin_file_strings: cannot append SearchInterval to list.\n");
goto fail;
}
}
} else if (has_sections) {
// returns only the strings found on the RzBinFile but within the data section
void **iter = NULL;
RzBinSection *section = NULL;
RzBinObject *o = bf->o;
rz_pvector_foreach (o->sections, iter) {
section = *iter;
if (section->paddr >= bf->size) {
continue;
} else if (opt->max_region_size && section->size > opt->max_region_size) {
RZ_LOG_WARN("bin_file_strings: search interval size (0x%" PFMT64x
") exeeds max region size (0x%" PFMTSZx "), skipping it.\n",
section->size, opt->max_region_size);
continue;
}
if (!is_data_section(bf, section)) {
continue;
}
SearchInterval *itv = RZ_NEW0(SearchInterval);
if (!itv) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate SearchInterval.\n");
goto fail;
}
itv->paddr = section->paddr;
itv->psize = section->size;
if ((itv->paddr + itv->psize) > bf->size) {
itv->psize = bf->size - itv->paddr;
}
if (!rz_th_queue_push(intervals, itv, true)) {
free(itv);
RZ_LOG_ERROR("bin_file_strings: cannot append SearchInterval to list.\n");
goto fail;
}
}
}
if (rz_th_queue_is_empty(intervals)) {
// we just fail directly and return an empty vector, since there are no search intervals.
goto fail;
}
if (bf->o) {
const RzBinInfo *binfo = rz_bin_object_get_info(bf->o);
prefer_big_endian = binfo ? binfo->big_endian : false;
}
SharedData shared = {
.lock = lock,
.bf = bf,
.strings_db = strings_db,
.buffer_size = opt->buffer_size,
.string_encoding = opt->string_encoding,
.max_uni_blocks = opt->max_uni_blocks,
.min_str_length = opt->min_length,
.check_ascii_freq = opt->check_ascii_freq,
.prefer_big_endian = prefer_big_endian,
};
if (shared.min_str_length < 1) {
// always ensure string min length is at least 1
shared.min_str_length = RZ_BIN_STRING_SEARCH_MIN_STRING;
}
RZ_LOG_VERBOSE("bin_file_strings: using %u threads\n", (ut32)pool_size);
for (size_t i = 0; i < pool_size; ++i) {
if (!create_string_search_thread(pool, intervals, &shared)) {
interrupt_pool(pool);
goto fail;
}
}
rz_th_pool_wait(pool);
results = rz_pvector_new((RzPVectorFree)rz_bin_string_free);
if (!results) {
RZ_LOG_ERROR("bin_file_strings: cannot allocate results list.\n");
goto fail;
}
for (ut32 i = 0; i < pool_size; ++i) {
RzThread *th = rz_th_pool_get_thread(pool, i);
if (!th) {
continue;
}
SearchThreadData *std = (SearchThreadData *)rz_th_get_user(th);
if (std) {
rz_pvector_join(results, std->results);
}
}
if (opt->mode != RZ_BIN_STRING_SEARCH_MODE_RAW_BINARY) {
scan_cfstring_table(bf, strings_db, results, opt->max_region_size);
}
rz_pvector_sort(results, (RzPVectorComparator)string_compare_sort, NULL);
{
void **it;
RzBinString *bstr;
ut32 ordinal = 0;
rz_pvector_foreach (results, it) {
bstr = *it;
bstr->ordinal = ordinal;
ordinal++;
}
}
fail:
if (pool) {
for (ut32 i = 0; i < pool_size; ++i) {
RzThread *th = rz_th_pool_get_thread(pool, i);
if (!th) {
continue;
}
SearchThreadData *std = (SearchThreadData *)rz_th_get_user(th);
bin_file_string_search_free(std);
}
rz_th_pool_free(pool);
}
ht_up_free(strings_db);
rz_th_lock_free(lock);
rz_th_queue_free(intervals);
return results;
}