String search integration tests (#5358)

* Add integration test for a simple string search.
* Clean up rz_str_is_false
* Doxygen
* Add rz_buf_type
* Sort search hits not just by address but also by size.
* Add a paranoid regex Hindi test.
* Add an integration test using RzCore and searching for UTF-16LE.
* Revert "Temporarily remove codecov workflow due to https://github.com/codecov/codecov-action/issues/1877 (#5366)"

This reverts commit dc5d50ce58.
This commit is contained in:
Rot127 2025-09-14 03:44:14 +00:00 committed by GitHub
parent 63fa6938a0
commit a59b090db9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 464 additions and 32 deletions

View file

@ -336,7 +336,7 @@ jobs:
if: matrix.coverage && matrix.enabled
run: |
gcovr --version
gcovr --gcov-ignore-parse-errors=all --txt build/coverage.txt --cobertura build/coverage.xml build
gcovr --gcov-ignore-parse-errors=all --txt build/coverage.txt --cobertura build/coverage.xml --merge-mode-functions=MERGE_MODE build
- name: Upload coverage to Codecov
if: matrix.coverage && matrix.enabled
uses: codecov/codecov-action@v5

27
codecov.yml Normal file
View file

@ -0,0 +1,27 @@
coverage:
status:
project:
default:
branches:
- dev
- 'release-*'
if_not_found: failure
target: auto
threshold: 0.5%
patch:
default:
if_not_found: success
target: auto
threshold: 0.5%
ignore:
- "binrz/rz-test"
- ".github"
- "doc"
- "man"
- "mk"
- "sys"
- "test"
comment:
layout: "header, files, components, footer"
require_changes: true

View file

@ -293,8 +293,8 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_core_search_string(RZ_NONNULL RzCor
goto quit;
}
collection = rz_search_collection_strings(&scan_opt, expected);
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, expected, match_alignment);
if (!collection ||
!rz_search_collection_string_add(collection, re_pattern, cflags, match_alignment)) {
rz_search_collection_free(collection);

View file

@ -264,7 +264,7 @@ RZ_API RZ_OWN RzSearchCollection *rz_search_collection_bytes();
RZ_API bool rz_search_collection_bytes_add(RZ_NONNULL RzSearchCollection *col, RZ_NULLABLE const char *pattern_desc, RZ_NONNULL const ut8 *bytes, RZ_NULLABLE const ut8 *mask, size_t length);
RZ_API bool rz_search_collection_bytes_add_pattern(RZ_NONNULL RzSearchCollection *col, RZ_NONNULL RZ_OWN RzSearchBytesPattern *bytes_pattern);
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtilStrScanOptions *opts, RzStrEnc expected);
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_BORROW RzUtilStrScanOptions *opts, RzStrEnc expected, size_t alignment);
RZ_API bool rz_search_collection_string_add(RZ_NONNULL RzSearchCollection *col, RZ_NONNULL const char *regex_pattern, RzRegexFlags cflags, size_t match_alignment);
RZ_API bool rz_search_collection_strings_check_config_improvements(
RZ_NULLABLE const RzSearchCollection *col,
@ -279,6 +279,7 @@ RZ_API void rz_search_collection_free(RZ_NULLABLE RzSearchCollection *sc);
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_magic(RZ_NONNULL const char *magic_dir);
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(RZ_BORROW RZ_NONNULL RzSearchOpt *opt, RZ_BORROW RZ_NONNULL RzSearchCollection *col, RZ_BORROW RZ_NONNULL RzIO *io, RZ_BORROW RZ_NONNULL RzList /*<RzIOMap *>*/ *search_in);
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_buffer(RZ_BORROW RZ_NONNULL RzSearchOpt *opt, RZ_BORROW RZ_NONNULL RzSearchCollection *col, RZ_BORROW RZ_NONNULL RzBuffer *buffer);
#ifdef __cplusplus
}

View file

@ -158,6 +158,7 @@ RZ_API void rz_buf_set_overflow_byte(RZ_NONNULL RzBuffer *b, ut8 Oxff);
RZ_API bool rz_buf_is_bytes_buf(const RzBuffer *b);
RZ_DEPRECATE RZ_API RZ_BORROW ut8 *rz_buf_data(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *size);
RZ_API RZ_BORROW const ut8 *rz_buf_get_whole_hot_paths(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *sz);
RZ_API RzBufferType rz_buf_type(RZ_NONNULL const RzBuffer *b);
/**
* \brief Callback to be used with rz_buf_fwd_scan().

View file

@ -151,7 +151,7 @@ RZ_API const char *rz_str_sysbits(const int v);
RZ_API char *rz_str_trunc_ellipsis(const char *str, int len);
RZ_API const char *rz_str_bool(int b);
RZ_API bool rz_str_is_true(const char *s);
RZ_API bool rz_str_is_false(const char *s);
RZ_API bool rz_str_is_false(RZ_NULLABLE const char *s);
RZ_API bool rz_str_is_bool(const char *val);
RZ_API const char *rz_str_ansi_chrn(const char *str, size_t n);
RZ_API size_t rz_str_ansi_len(const char *str);

View file

@ -72,6 +72,17 @@ RZ_API bool rz_search_opt_set_max_threads(RZ_NONNULL RzSearchOpt *opt, RzThreadN
return true;
}
/**
* \brief Enables the printing of progress during the search.
*
* \param opt The search options to update.
* \param show_progress A string describing the progress type. Can be:
* - "off", "no", "false", "0": No progress is printed.
* - "interval": The currently searched in interval is printed.
* - else: Print number of current hits during search.
*
* \return True if options were correctly updated, false otherwise.
*/
RZ_API bool rz_search_opt_set_show_progress_from_str(RZ_NONNULL RzSearchOpt *opt, const char *show_progress) {
rz_return_val_if_fail(opt, false);
if (rz_str_is_false(show_progress)) {
@ -108,7 +119,12 @@ RZ_API const RzSearchFindOpt *rz_search_opt_get_find_options(RZ_NONNULL const Rz
}
RZ_API RZ_OWN RzSearchFindOpt *rz_search_find_opt_new() {
return RZ_NEW0(RzSearchFindOpt);
RzSearchFindOpt *fopts = RZ_NEW0(RzSearchFindOpt);
if (!fopts) {
return NULL;
}
fopts->alignment = 1;
return fopts;
}
RZ_API void rz_search_find_opt_free(RZ_NULLABLE RzSearchFindOpt *opt) {

View file

@ -552,6 +552,8 @@ RZ_API void rz_search_kw_reset(RzSearch *s) {
typedef struct search_ctx {
RzIO *io; ///< the RzIO struct to use
RzThreadLock *io_lock;
RzBuffer *buffer; ///< RzBuffer to search in. If this is set, io should be NULL.
RzThreadLock *buffer_lock;
RzSearchCollection *col; ///< collection to use
RzSearchOpt *opt; ///< User options
RzThreadQueue /* RzSearchHits */ *hits; ///< Hits list
@ -607,14 +609,28 @@ static bool search_iterator_io_map_cb(void *element, void *user) {
ut64 at = window->addr;
ut64 size = window->size;
rz_th_lock_enter(ctx->io_lock);
RzBuffer *buffer = rz_io_nread_at_new_buf(ctx->io, at, size);
if (!buffer || rz_buf_size(buffer) != size) {
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
RzBuffer *buffer = NULL;
if (ctx->io) {
rz_th_lock_enter(ctx->io_lock);
buffer = rz_io_nread_at_new_buf(ctx->io, at, size);
if (!buffer || rz_buf_size(buffer) != size) {
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
rz_th_lock_leave(ctx->io_lock);
goto failure;
}
rz_th_lock_leave(ctx->io_lock);
goto failure;
} else {
ut8 *window_data = RZ_NEWS(ut8, size);
rz_th_lock_enter(ctx->buffer_lock);
size = rz_buf_read_at(ctx->buffer, at, window_data, size);
rz_th_lock_leave(ctx->buffer_lock);
buffer = rz_buf_new_with_bytes(window_data, size);
free(window_data);
if (!buffer) {
RZ_LOG_ERROR("search: failed to read at 0x%08" PFMT64x " (0x%08" PFMT64x " bytes)\n", at, size);
goto failure;
}
}
rz_th_lock_leave(ctx->io_lock);
size_t n_hits = 0;
RzSearchFindBytesCallback find = col->find;
@ -666,6 +682,26 @@ static RzList /*<RzInterval *>*/ *assemble_search_window_list(RzList /*<RzIOMap
return list;
}
static bool perform_sanity_checks(
RZ_BORROW RZ_NONNULL RzSearchOpt *opt,
RZ_BORROW RZ_NONNULL RzSearchCollection *col) {
if (!rz_search_collection_on_bytes_space(col)) {
RZ_LOG_ERROR("search: The search collection is not initialized for byte space.\n");
return false;
}
if (opt->chunk_size < RZ_SEARCH_MIN_CHUNK_SIZE) {
RZ_LOG_ERROR("search: cannot search when buffer size is less than %#" PFMT64x " bytes.\n", RZ_SEARCH_MIN_CHUNK_SIZE);
return false;
}
if (rz_search_collection_is_empty(col)) {
RZ_LOG_ERROR("search: cannot perform the search when the search collection is empty.\n");
return false;
}
return true;
}
/**
* \brief Perform a search within the given search maps of a collection
*
@ -689,13 +725,7 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
RzList /* RzInterval */ *windows = NULL;
RzThread *cancel_th = NULL;
if (!rz_search_collection_on_bytes_space(col)) {
RZ_LOG_ERROR("search: The search collection is not initialized for byte space.\n");
return NULL;
}
if (opt->chunk_size < RZ_SEARCH_MIN_CHUNK_SIZE) {
RZ_LOG_ERROR("search: cannot search when buffer size is less than %#" PFMT64x " bytes.\n", RZ_SEARCH_MIN_CHUNK_SIZE);
if (!perform_sanity_checks(opt, col)) {
return NULL;
}
@ -704,11 +734,6 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
return NULL;
}
if (rz_search_collection_is_empty(col)) {
RZ_LOG_ERROR("search: cannot perform the search when the search collection is empty.\n");
return NULL;
}
hits = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_hit_free);
if (!hits) {
RZ_LOG_ERROR("search: cannot allocate RzSearchHit queue.\n");
@ -775,6 +800,104 @@ RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_io(
return results;
}
/**
* \brief Perform a search within the given search maps of a collection
*
* \param opt The RzSearchOpt to use
* \param col The RzSearchCollection to use
* \param buffer The RzBuffer to search in.
*
* \return On success returns all the hits.
*/
RZ_API RZ_OWN RzList /*<RzSearchHit *>*/ *rz_search_on_buffer(
RZ_BORROW RZ_NONNULL RzSearchOpt *opt,
RZ_BORROW RZ_NONNULL RzSearchCollection *col,
RZ_BORROW RZ_NONNULL RzBuffer *buffer) {
rz_return_val_if_fail(opt && col && buffer, NULL);
search_ctx_t ctx = { 0 };
RzList *results = NULL;
RzThreadQueue *hits = NULL;
RzThreadQueue *intervals = NULL;
RzList /* RzInterval */ *windows = NULL;
RzThread *cancel_th = NULL;
if (!perform_sanity_checks(opt, col)) {
return NULL;
}
hits = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_hit_free);
if (!hits) {
RZ_LOG_ERROR("search: cannot allocate RzSearchHit queue.\n");
return NULL;
}
intervals = rz_th_queue_new(RZ_THREAD_QUEUE_UNLIMITED, (RzListFree)rz_search_interval_free);
if (!intervals) {
RZ_LOG_ERROR("search: cannot allocate RzSearchInterval queue.\n");
rz_th_queue_free(hits);
return NULL;
}
RzList *search_in = rz_list_new();
RzIOMap *map = RZ_NEW0(RzIOMap);
map->itv.addr = 0;
map->itv.size = rz_buf_size(buffer);
rz_list_append(search_in, map);
windows = assemble_search_window_list(search_in, opt);
rz_list_free(search_in);
if (!windows) {
RZ_LOG_ERROR("search: Could not prepare search window queue.\n");
rz_th_queue_free(hits);
rz_th_queue_free(intervals);
return NULL;
}
ctx.col = col;
ctx.opt = opt;
ctx.buffer = buffer;
ctx.buffer_lock = rz_th_lock_new(false);
ctx.loop = rz_atomic_bool_new(true);
ctx.hits = hits;
ctx.finished_intervals = intervals;
if (opt->cancel_cb) {
// create cancel thread
cancel_th = rz_th_new(search_cancel_th, &ctx);
if (!cancel_th) {
RZ_LOG_ERROR("search: cannot allocate cancel thread.\n");
rz_th_queue_free(hits);
rz_th_queue_free(intervals);
rz_atomic_bool_free(ctx.loop);
rz_list_free(windows);
return NULL;
}
}
if (!rz_th_iterate_list(windows, search_iterator_io_map_cb, opt->max_threads, &ctx)) {
RZ_LOG_ERROR("search: cannot iterate over list.\n");
} else {
results = rz_th_queue_pop_all(hits);
}
if (cancel_th) {
// stop & free cancel thread.
rz_atomic_bool_set(ctx.loop, false);
rz_th_wait(cancel_th);
rz_th_free(cancel_th);
rz_atomic_bool_free(ctx.loop);
}
rz_th_lock_free(ctx.io_lock);
rz_list_free(windows);
rz_th_queue_free(hits);
rz_th_queue_free(intervals);
rz_list_sort(results, (RzListComparator)rz_search_hit_cmp, NULL);
rz_list_sorted_uniq(results, (RzListComparator)rz_search_hit_cmp, NULL);
return results;
}
RZ_IPI int rz_search_hit_cmp(RZ_NULLABLE RzSearchHit *a, RZ_NULLABLE RzSearchHit *b, void *user) {
if (!a && !b) {
return 0;
@ -783,9 +906,15 @@ RZ_IPI int rz_search_hit_cmp(RZ_NULLABLE RzSearchHit *a, RZ_NULLABLE RzSearchHit
} else if (!b) {
return 1;
}
if (a->address == b->address) {
if (a->address < b->address) {
return -1;
} else if (a->address > b->address) {
return 1;
}
if (a->size == b->size) {
return 0;
} else if (a->address < b->address) {
} else if (a->size < b->size) {
return -1;
}
return 1;

View file

@ -8,6 +8,7 @@
#include <rz_vector.h>
#include <rz_util/ht_uu.h>
#include <rz_util/rz_str_search.h>
#include "rz_util/rz_str.h"
#include "search_internal.h"
typedef struct string_search {
@ -102,7 +103,7 @@ static bool string_find(RzSearchFindOpt *fopt, void *user, ut64 offset, const Rz
}
// Everything below is the slow and resource extensive route to search strings.
// It will scan the whole buffer for strings, decoding each one with the
// It will scan the whole buffer for strings, decode each one with the
// correct encoding and length and match them.
// This costs a lot. So it is only done for strings with:
// A) A funny encodig we can't match directly with RzRegex/PCRE2 (e.g. EBCDIC).
@ -201,13 +202,23 @@ static void string_free(void *user) {
/**
* \brief Allocates and initialize a string RzSearchCollection
*
* \param opts The RzUtilStrScanOptions options to use
* \param[in] expected The expected encoding
* \param opts The RzUtilStrScanOptions options to use.
* It is allowed to be NULL iff the expected encoding is
* Unicode, has the native machines endianness, and
* \p alignment is the same as the encoding code point width.
* \param[in] expected The expected encoding
* \param[in] alignment The alignment of matches.
*
* \return On success returns a valid pointer, otherwise NULL
*/
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtilStrScanOptions *opts, RzStrEnc expected) {
rz_return_val_if_fail(opts, NULL);
RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_BORROW RzUtilStrScanOptions *scan_opts, RzStrEnc expected, size_t alignment) {
if ((!rz_string_enc_is_utf_native_endian(expected) ||
!rz_string_code_points_align(expected, alignment)) &&
!scan_opts) {
RZ_LOG_ERROR("Initalizeing string search collection failed: opts is not"
"allowed to be NULL if the searched encoding has not the same endianness as the machine.\n");
return NULL;
}
StringSearch *ss = RZ_NEW0(StringSearch);
if (!ss) {
@ -222,7 +233,9 @@ RZ_API RZ_OWN RzSearchCollection *rz_search_collection_strings(RZ_NONNULL RzUtil
return NULL;
}
ss->options = *opts; // Copy the values
if (scan_opts) {
ss->options = *scan_opts; // Copy because they are shared between threads.
}
ss->encoding = expected;
return rz_search_collection_new_bytes_space(string_find, string_is_empty, string_free, ss);

View file

@ -1571,3 +1571,8 @@ RZ_API st64 rz_buf_sleb128(RZ_NONNULL RzBuffer *buffer, RZ_NONNULL st64 *value)
*value = sum;
return used;
}
RZ_API RzBufferType rz_buf_type(RZ_NONNULL const RzBuffer *b) {
rz_return_val_if_fail(b, RZ_BUFFER_INVALID);
return b->type;
}

View file

@ -4044,7 +4044,17 @@ RZ_API bool rz_str_is_true(const char *s) {
return !rz_str_casecmp("yes", s) || !rz_str_casecmp("on", s) || !rz_str_casecmp("true", s) || !rz_str_casecmp("1", s);
}
RZ_API bool rz_str_is_false(const char *s) {
/**
* \brief Returns true if string is case insensitive equal to:
* - no
* - off
* - false
* - 0
*/
RZ_API bool rz_str_is_false(RZ_NULLABLE const char *s) {
if (!s) {
return false;
}
return !rz_str_casecmp("no", s) || !rz_str_casecmp("off", s) || !rz_str_casecmp("false", s) || !rz_str_casecmp("0", s) || !*s;
}

View file

@ -406,6 +406,22 @@ EOF
EXPECT_ERR=
RUN
NAME=String Search - Encoding: utf8 - Hindi - Regex
FILE=bins/cmd/search/string_encodings/Hindi-Lipsum.utf8
CMDS=<<EOF
/z "प.+चने वैश्विक एसलिये .+आदी" r utf8
# Requires increasing the block size because -.-
b 0x1000
# Check string is actually at offset of the hit.
ps utf8 unprintable @ hit.string.utf8.0
EOF
EXPECT=<<EOF
0x00000086 97 hit.string.utf8.0
पहोचने वैश्विक एसलिये पुस्तक हुआआदी विश्व उनका खरिदने समस्याओ मार्गदर्शन रखते तरीके यायेका विश्लेषण पुष्टिकर्ता चुनने सकता बढाता वेबजाल गोपनीयता लिए। संपादक करेसाथ रचना नवंबर
EOF
EXPECT_ERR=
RUN
NAME=String Search - Encoding: utf8 - Korean2
FILE=bins/cmd/search/string_encodings/Korean2-Lipsum.utf8
CMDS=<<EOF

View file

@ -28,6 +28,7 @@ if get_option('enable_tests') and cli_enabled
'pdb',
'project_migrate',
'rzpipe',
'str_search',
]
unit_test_env = environment()
@ -45,6 +46,7 @@ if get_option('enable_tests') and cli_enabled
rz_socket_dep,
rz_core_dep,
rz_io_dep,
rz_search_dep,
rz_bin_dep,
rz_flag_dep,
rz_cons_dep,

View file

@ -0,0 +1,212 @@
// SPDX-FileCopyrightText: 2025 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file Implements integration tests for the string search.
* The code here can also serve as example how to use the search API
* with minimal dependencies.
*
* NOTE: These tests must be run from `<repo_root>/test/` and
* `git clone https://github.com/rizinorg/rizin-testbins/ <repo_root>/test/bins`
* must have been executed before.
*
* For a general overview how the search is implemented see
* librz/search/README.md
*/
#include "../unit/minunit.h"
#include <rz_core.h>
#include "rz_list.h"
#include "rz_search.h"
#include "rz_util/rz_buf.h"
#include "rz_util/rz_str.h"
// The files to search in.
static const char *files[] = {
"./bins/cmd/search/string_encodings/Hindi-Lipsum.utf8",
"./bins/elf/analysis/hello-utf-16",
// Big binaries
//
// A very big file (1.1GB) with mixed data and Chinese characters.
// Code points are aligned to code point width (4 bytes).
// File is not in our rizin-testbin repo due to size.
// But useful for performance testing.
//
// sha256: dc365472d8bbfdc3a3d47b5a0d8061c7d18233b131b0ed12fe599b53248629b2
// "test/bins/test/bins/test_strings_zh.utf-32-le",
//
// Big endian strings to search. Search will significantly slower on little endian machines.
// sha256: 315e96099d4c0ad7501e47f28a7781b8ed9fef0902bce5e962276a285362a684
// "test/bins/test/bins/test_strings_zh.utf-16-be",
};
// Patterns/strings to search in the files from above.
static const char *patterns[][3] = {
// Same Hindi strings but one is shorter.
{ "पहोचने वैश्विक एसलिये पुस्तक हुआआदी", "प.+चने वैश्विक एसलिये .+आ", NULL },
{ "heLLo woRlD", NULL, NULL },
// Big binaries' strings
//
// First is an actual sub string in the binary.
// Second pattern as well, but only if interpreted in UTF-8 strings (file was generated as UTF32-le).
// { "些 公司 任何....可", ":.{13,}", NULL },
};
// One element is a single string in this context.
// This one is the element size in bytes.
// It should be at least as large as the maximum string length (in bytes) you expect.
// Mind though, found strings are allowed to be larger than this.
// Always check RzSearchHit->size for the real byte length of a string.
#define ELEMENT_SIZE 50
/**
* \brief Do a simple literal and regex search for strings in Hindi.
*/
int test_rz_str_search_single_simple(void) {
// Open file as RzBuffer
RzBuffer *file_buffer = rz_buf_new_file(files[0], O_RDONLY, 0);
mu_assert_notnull(file_buffer, "Failed to open file");
// Setup search options. These are _not_ specific for the string search.
// They are applicable to the whole search module, independently what
// is searched (bytes, strings, cryptographic material, values...).
// Configuring specific values is optional.
RzSearchOpt *search_opts = rz_search_opt_new();
mu_assert_notnull(search_opts, "NULL check failed");
rz_search_opt_set_max_threads(search_opts, 4);
rz_search_opt_set_max_hits(search_opts, 10);
rz_search_opt_set_show_progress_from_str(search_opts, "no");
rz_search_opt_set_chunk_size(search_opts, ELEMENT_SIZE);
// The find options allow to configure string specific settings.
RzSearchFindOpt *find_opts = rz_search_find_opt_new();
mu_assert_notnull(find_opts, "NULL check failed");
// Set alignment to 1, because we search UTF-8 and its code points are aligned to 1.
size_t match_alignment = 1;
rz_search_find_opt_set_alignment(find_opts, match_alignment);
rz_search_find_opt_set_overlap_match(find_opts, false);
// Assign find options to the search options.
rz_search_opt_set_find_options(search_opts, find_opts);
// Initialize the collection to search for.
// We can pass NULL here to the RzUtilStrScanOptions parameter,
// because UTF-8 is endianness independent and can directly match the buffer with PCRE2.
// No scanning for strings is required. Hence we don't need the options for it.
RzSearchCollection *collection = rz_search_collection_strings(NULL, RZ_STRING_ENC_UTF8, match_alignment);
mu_assert_notnull(collection, "NULL check failed");
// Now add the two patterns we search for
rz_search_collection_string_add(collection, patterns[0][0], RZ_REGEX_LITERAL, match_alignment);
rz_search_collection_string_add(collection, patterns[0][1], RZ_REGEX_EXTENDED, match_alignment);
RzList *hits = rz_search_on_buffer(search_opts, collection, file_buffer);
mu_assert_eq(rz_list_length(hits), 2, "Incorrect number of strings.");
RzListIter *it;
const RzSearchHit *hit;
rz_list_foreach (hits, it, hit) {
mu_assert_true(hit->size == 97 || hit->size == 91, "Incorrect size");
mu_assert_eq(hit->address, 0x00000086, "Incorrect address");
ut8 *hit_str = RZ_NEWS0(ut8, hit->size + 1);
mu_assert_notnull(hit_str, "NULL check failed");
rz_buf_read_at(file_buffer, hit->address, hit_str, hit->size);
printf("Hit 0x%" PFMT64x ", size %" PFMTSZd ": '%s'\n", hit->address, hit->size, hit_str);
free(hit_str);
}
mu_end;
}
/**
* \brief Do a string search in a binary file.
* The file is opened with as an RzIO instance. Not just as simple buffer.
* Useful if the binary has to be analyzed beyond searching strings in it.
*/
int test_rz_str_search_io_simple(void) {
RzCore *core = rz_core_new();
mu_assert_notnull(core, "NULL check failed");
rz_core_init(core);
mu_assert_true(rz_core_file_open_load(core, files[1], 0, RZ_PERM_R, false), "Loading file failed");
// Setup search options. These are _not_ specific for the string search.
// They are applicable to the whole search module, independently what
// is searched (bytes, strings, cryptographic material, values...).
// Configuring specific values is optional.
RzSearchOpt *search_opts = rz_search_opt_new();
mu_assert_notnull(search_opts, "NULL check failed");
rz_search_opt_set_max_threads(search_opts, 4);
rz_search_opt_set_max_hits(search_opts, 10);
rz_search_opt_set_show_progress_from_str(search_opts, "no");
rz_search_opt_set_chunk_size(search_opts, ELEMENT_SIZE);
// The find options allow to configure string specific settings.
RzSearchFindOpt *find_opts = rz_search_find_opt_new();
mu_assert_notnull(find_opts, "NULL check failed");
// Set alignment to 2, because we search UTF-16 and its code points are aligned to 2.
// It is possible to also set it to any other value of course.
// But any value not aligned to the code point width of UTF-16 (anything not a multiple of 2)
// will slow down the search.
// For details see librz/search/README.md
size_t match_alignment = 2;
rz_search_find_opt_set_alignment(find_opts, match_alignment);
rz_search_find_opt_set_overlap_match(find_opts, false);
// Assign find options to the search options.
rz_search_opt_set_find_options(search_opts, find_opts);
// Please refer to librz/search/README.md for an explanation why string scan options
// are needed for an UTF-16 search.
RzUtilStrScanOptions scan_opt = {
.max_str_length = ELEMENT_SIZE,
.min_str_length = 4,
.prefer_big_endian = false,
.check_ascii_freq = false,
};
RzSearchCollection *collection = rz_search_collection_strings(&scan_opt, RZ_STRING_ENC_UTF16LE, match_alignment);
mu_assert_notnull(collection, "NULL check failed");
// Now add the pattern we search for.
rz_search_collection_string_add(collection, patterns[1][0], RZ_REGEX_CASELESS, match_alignment);
// Get the boundaries the strings are searched in.
// The default address ranges are in the main config under `search.from`, `search.to`.
// The maps to search in are in the config under `search.in`.
RzList *boundaries = rz_core_get_boundaries_select(core, "search.from", "search.to", "search.in");
mu_assert_notnull(boundaries, "NULL check failed");
mu_assert_true(rz_list_length(boundaries) != 0, "The search boundaries are emtpy");
RzList *hits = rz_search_on_io(search_opts, collection, core->io, boundaries);
// Print the hits.
// NOTE: The string address is 0x004005ea.
// This is the virtual address where the string starts.
// If you examine the two bytes before the string's address
// you will notice it is preceeded by a BOM:
// ```
// > px 16 @ 0x004005e8
// - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
// 0x004005e8 fffe 4800 6500 6c00 6c00 6f00 2000 5700 ..H.e.l.l.o. .W.
// ```
// So the string search does not count the BOM to the string!
mu_assert_eq(rz_list_length(hits), 1, "Incorrect number of strings.");
const RzSearchHit *hit = rz_list_get_n(hits, 0);
printf("Hit at 0x%" PFMT64x " size: %" PFMTSZd "\n", hit->address, hit->size);
mu_assert_true(hit->size == 22, "Incorrect size");
mu_assert_eq(hit->address, 0x004005ea, "Incorrect address");
mu_end;
}
int all_tests() {
mu_run_test(test_rz_str_search_single_simple);
mu_run_test(test_rz_str_search_io_simple);
return tests_passed != tests_run;
}
mu_main(all_tests)