rizin/librz/core/csearch.c

541 lines
18 KiB
C

// SPDX-FileCopyrightText: 2024-2025 deroad <deroad@kumo.xn--q9jyb4c>
// SPDX-FileCopyrightText: 2024-2025 Rot127 <rot127@posteo.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_bin.h>
#include <rz_config.h>
#include <rz_util/rz_str.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_regex.h>
#include <rz_util/rz_file.h>
#include <rz_core.h>
#include <rz_search.h>
#include <rz_util/rz_assert.h>
#include <rz_util/rz_str_search.h>
/**
* \brief Sets up the search find options according to the core config.
*
* \param core The core to get the config from.
*
* \return The find options to use. Or NULL in case of failure.
*/
RZ_API RZ_OWN RzSearchFindOpt *rz_core_setup_default_search_find_opts(RzCore *core) {
rz_return_val_if_fail(core, NULL);
RzSearchFindOpt *fopts = rz_search_find_opt_new();
if (!fopts) {
RZ_LOG_ERROR("Failed allocating find options.\n");
return NULL;
}
if (!(rz_search_find_opt_set_overlap_match(fopts, rz_config_get_b(core->config, "search.overlap")) &&
rz_search_find_opt_set_alignment(fopts, rz_config_get_i(core->config, "search.align")))) {
RZ_LOG_ERROR("Failed set find options.\n");
rz_search_find_opt_free(fopts);
return NULL;
}
return fopts;
}
/**
* \brief Sets up the search parameters according to the core IO layer and config.
*
* \param core The core to get the IO maps, settings and other relevant information from.
* \param search_opts Search options to set up. Only fields to search behavior will be set (max_threads, max hits). Can be NULL.
*
* \return The boundaries to search in. Or NULL in case of failure.
*/
RZ_API RZ_OWN RzList /*<RzIOMap *>*/ *rz_core_setup_io_search_parameters(RzCore *core, RZ_NULLABLE RZ_OUT RzSearchOpt *search_opts) {
rz_return_val_if_fail(core && core->io && core->config, NULL);
RzList *boundaries = NULL;
if (!core->io) {
RZ_LOG_ERROR("core: RzIO is not available.\n");
return NULL;
}
boundaries = rz_core_get_boundaries_select(core, "search.from", "search.to", "search.in");
if (!boundaries || rz_list_empty(boundaries)) {
ut64 from = rz_config_get_i(core->config, "search.from");
ut64 to = rz_config_get_i(core->config, "search.to");
if (!boundaries) {
RZ_LOG_ERROR("Failed to initialize boundaries within [0x%" PFMT64x ", 0x%" PFMT64x "].\n", from, to);
} else {
RZ_LOG_ERROR("The range [0x%" PFMT64x ", 0x%" PFMT64x "] doesn't overlap with a mapped memory region or\n"
"the region is not included in search.in.\n",
from, to);
}
goto fail;
}
if (search_opts) {
// Set search options known by core.
ut32 max_threads = rz_th_max_threads(rz_config_get_i(core->config, "search.max_threads"));
ut32 max_hits = rz_config_get_i(core->config, "search.maxhits");
const char *show_progress = rz_config_get(core->config, "search.show_progress");
if (!(rz_search_opt_set_max_threads(search_opts, max_threads) &&
rz_search_opt_set_max_hits(search_opts, max_hits) &&
rz_search_opt_set_show_progress_from_str(search_opts, show_progress))) {
RZ_LOG_ERROR("core: Failed to setup search options.\n");
goto fail;
}
RzSearchFindOpt *fopts = rz_core_setup_default_search_find_opts(core);
if (!fopts) {
RZ_LOG_ERROR("Failed setup find options.\n");
goto fail;
}
rz_search_opt_set_find_options(search_opts, fopts);
}
return boundaries;
fail:
rz_list_free(boundaries);
return NULL;
}
static bool default_search_no_cancel(void *user, size_t n_hits, RzSearchCancelReason invoke_reason) {
return rz_cons_is_breaked();
}
static RzList /*<RzSearchHit *>*/ *perform_search_on_core_io(RzCore *core, RZ_BORROW RzSearchOpt *search_opts, RZ_BORROW RzList /*<RzIOMap *>*/ *boundaries, RZ_BORROW RzSearchCollection *collection) {
RzList *hits = NULL;
hits = rz_search_on_io(search_opts, collection, core->io, boundaries);
if (!hits) {
ut64 from = rz_config_get_i(core->config, "search.from");
ut64 to = rz_config_get_i(core->config, "search.to");
RZ_LOG_ERROR("core: Failed to search within [0x%" PFMT64x ", 0x%" PFMT64x "].\n", from, to);
}
return hits;
}
static RzSearchOpt *default_search_options() {
RzSearchOpt *def_options = rz_search_opt_new();
if (!def_options) {
RZ_LOG_ERROR("search: Failed to allocate search options.\n");
return NULL;
} else if (!rz_search_opt_set_cancel_cb(def_options, default_search_no_cancel, NULL)) {
RZ_LOG_ERROR("search: Failed to setup callback for search options.\n");
rz_search_opt_free(def_options);
return NULL;
}
return def_options;
}
/**
* \brief Finds a byte array in the IO layer of the given core and core configuration.
*
* \param core The RzCore core.
* \param opt The search options to apply. If it is NULL a default set of options is used.
* \param pattern The bytes pattern to search.
*
* \return On success returns a valid pointer, otherwise NULL
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_bytes(RZ_NONNULL RzCore *core, RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts, RZ_NONNULL RZ_OWN RzSearchBytesPattern *pattern) {
rz_return_val_if_fail(core && core->config && pattern, NULL);
if (rz_search_bytes_pattern_len(pattern) == 0) {
RZ_LOG_ERROR("core: Cannot search for byte pattern if 'length' == 0.\n");
rz_search_bytes_pattern_free(pattern);
return NULL;
}
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = rz_search_collection_bytes();
if (!collection ||
!rz_search_collection_bytes_add_pattern(collection, pattern)) {
RZ_LOG_ERROR("core: Failed to initialize search collection.\n");
rz_search_bytes_pattern_free(pattern);
goto quit;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
if (!rz_search_opt_set_chunk_size(user_opts, rz_search_bytes_pattern_len(pattern))) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Finds a value ranges in the IO layer of the given core and core configuration.
*
* \param core The RzCore core.
* \param opt The search options to apply. If it is NULL a default set of options is used.
* \param vranges The value ranges to search.
*
* \return On success returns a valid pointer, otherwise NULL
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_values(RZ_NONNULL RzCore *core, RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts, RZ_NONNULL RZ_OWN RzVector /*<RzSearchValueRange>*/ *vranges) {
rz_return_val_if_fail(core && core->config && vranges, NULL);
if (rz_vector_empty(vranges)) {
RZ_LOG_ERROR("core: No value ranges to search.\n");
rz_vector_free(vranges);
return NULL;
}
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = rz_search_collection_values();
if (!collection ||
!rz_search_collection_values_add(collection, vranges)) {
RZ_LOG_ERROR("core: Failed to initialize search collection.\n");
goto quit;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
if (!rz_search_opt_set_chunk_size(user_opts, RZ_SEARCH_VALUE_SEARCH_MAX_WIDTH)) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Finds a string within the `search.in` boundaries.
*
* \param core The RzCore core.
* \param opt The search options to apply. If NULL, a default set of options is used.
* \param[in] re_pattern The regex pattern to search.
* \param[in] re_pattern_len The length of \p re_pattern if it is a literal, 0 otherwise.
* \param[in] flags The regex flags to the \p re_pattern.
* \param[in] expected The expected encoding.
*
* \return On success returns a valid pointer to a list of search hits, otherwise NULL.
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_string(RZ_NONNULL RzCore *core, RZ_BORROW RZ_NONNULL RzSearchOpt *user_opts, RZ_NONNULL const char *re_pattern, size_t re_pattern_len, RzRegexFlags cflags, RzStrEnc encoding) {
rz_return_val_if_fail(core && user_opts && re_pattern, NULL);
if (RZ_STR_ISEMPTY(re_pattern)) {
RZ_LOG_ERROR("core: invalid string: empty string.\n");
return NULL;
}
if (strlen(re_pattern) >= core->bin->str_search_cfg.max_length) {
RZ_LOG_ERROR("core: String to search is larger then search.str.max_length.\n");
return NULL;
}
// Copy RzUtilStrScanOptions from RzBin
RzUtilStrScanOptions scan_opt = {
// buf_size is effectively the maximum string length.
// Gets renamed with the refactor.
.max_str_length = core->bin->str_search_cfg.max_length,
.min_str_length = RZ_MAX(re_pattern_len, core->bin->str_search_cfg.min_length),
.prefer_big_endian = core->analysis->big_endian,
.check_ascii_freq = core->bin->str_search_cfg.check_ascii_freq,
};
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = NULL;
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
if (!rz_search_opt_set_chunk_size(user_opts, scan_opt.max_str_length)) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
size_t match_alignment = rz_search_find_opt_get_alignment(rz_search_opt_get_find_options(user_opts));
collection = rz_search_collection_strings(&scan_opt, rz_search_opt_get_max_threads(user_opts));
if (!collection ||
!rz_search_collection_string_add(collection, re_pattern, cflags, match_alignment, encoding)) {
rz_search_collection_free(collection);
rz_list_free(boundaries);
RZ_LOG_ERROR("core: Failed to initialize search collection.\n");
return NULL;
}
rz_search_collection_strings_check_config_improvements(
collection, boundaries, user_opts, &scan_opt, true);
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Finds a cryptographic material in the IO layer of the given core and core configuration.
*
* \param core The RzCore core.
* \param opt The search options to apply. If it is NULL a default set of options is used.
* \param type The cryptographic material type (if invalid, the search will be performed on all materials).
*
* \return On success returns a pointer to the search hits, otherwise NULL
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_cryptographic_material(
RZ_NONNULL RzCore *core,
RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts,
RzSearchCollectionCryptographicType type) {
rz_return_val_if_fail(core && core->config, NULL);
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = rz_search_collection_cryptographic();
if (!collection) {
return NULL;
}
if (!rz_search_collection_cryptographic_add(collection, type)) {
goto quit;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
// minimal element size is always 2 (since 1 would be unaligned)
if (!rz_search_opt_set_chunk_size(user_opts, 2)) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Finds a hash material in the IO layer of the given core and core configuration.
*
* \param core The RzCore core.
* \param opt The search options to apply. If it is NULL a default set of options is used.
* \param data Optional additional search data. Some crytpographic searches require additional data (e.g. entropy search).
*
* \return On success returns a pointer to the search hits, otherwise NULL
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_hash(
RZ_NONNULL RzCore *core,
RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts,
RZ_NONNULL const char *algo_name,
RZ_NONNULL const char *expected_digits,
ut64 block_size) {
rz_return_val_if_fail(core && core->config && core->hash, NULL);
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = rz_search_collection_hash();
if (!collection) {
return NULL;
}
if (!rz_search_collection_hash_add(collection, core->hash, algo_name, expected_digits, block_size)) {
goto quit;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
if (!rz_search_opt_set_chunk_size(user_opts, rz_search_hash_get_element_size(collection))) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Finds a hash material in the IO layer of the given core and core configuration.
*
* \param core The RzCore core.
* \param opt The search options to apply. If it is NULL a default set of options is used.
* \param data Optional additional search data. Some crytpographic searches require additional data (e.g. entropy search).
*
* \return On success returns a pointer to the search hits, otherwise NULL
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_entropy(
RZ_NONNULL RzCore *core,
RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts,
bool fractional,
double min_inclusive_limit,
double max_inclusive_limit,
ut64 block_size) {
rz_return_val_if_fail(core && core->config && core->hash, NULL);
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
RzSearchCollection *collection = rz_search_collection_entropy(core->hash);
if (!collection) {
return NULL;
}
if (!rz_search_collection_entropy_add(collection, fractional, min_inclusive_limit, max_inclusive_limit, block_size)) {
goto quit;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
if (!rz_search_opt_set_chunk_size(user_opts, block_size)) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}
/**
* \brief Searches magics defined in \p magic_dir.
*
* \param core The core to use.
* \param user_opts User defined search options. If NULL defaults are used.
* \param magic_dir The directory or file with magics to search for. If NULL, the directory from 'dir.magic' is used.
*
* \return A list of search hits or NULL in case of failure.
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_magic(RZ_NONNULL RzCore *core, RZ_BORROW RZ_NULLABLE RzSearchOpt *user_opts, RZ_NULLABLE const char *magic_dir) {
rz_return_val_if_fail(core && core->config, NULL);
RzList *hits = NULL;
RzList *boundaries = NULL;
RzSearchOpt *search_opts = NULL;
if (!magic_dir) {
magic_dir = rz_config_get(core->config, "dir.magic");
}
RzSearchCollection *collection = rz_search_collection_magic(magic_dir);
if (!collection) {
return NULL;
}
if (!user_opts) {
// override user_opts with default one
user_opts = search_opts = default_search_options();
if (!search_opts) {
goto quit;
}
}
if (!rz_search_opt_set_chunk_size(user_opts, RZ_MAGIC_BUF_SIZE)) {
RZ_LOG_ERROR("search: Failed to update chunk size in the search options.\n");
goto quit;
}
boundaries = rz_core_setup_io_search_parameters(core, user_opts);
if (!boundaries) {
RZ_LOG_ERROR("core: Setting up search from core failed.\n");
goto quit;
}
hits = perform_search_on_core_io(core, user_opts, boundaries, collection);
quit:
rz_list_free(boundaries);
rz_search_opt_free(search_opts);
rz_search_collection_free(collection);
return hits;
}