From ca0fee009977a8449ba55126de496846adee4957 Mon Sep 17 00:00:00 2001 From: Nikesh Chavhan Date: Thu, 23 Oct 2025 08:45:10 +0000 Subject: [PATCH 1/4] Add string type filtering for iz/izz commands (issue #18821) - Added support for filtering strings by type: iza (ascii), izu (utf8), izw (wide/utf16), izW (wide32) - Implemented colon syntax for charset names: iz:ascii, iz:utf8, iz:wide, iz:wide32, iz:base64 - Added str_type field to RCoreBinFilter struct for passing filter through call chain - Modified _print_strings to filter based on RBinString type field - Updated help messages to document new filtering options - All filters work with both iz (data sections) and izz (whole binary) commands - Supports combination with other modifiers (json, quiet, radare mode) Resolves #18821 --- libr/core/cbin.c | 19 +++++---- libr/core/cmd_info.inc.c | 84 +++++++++++++++++++++++++++++++++++++--- libr/include/r_core.h | 1 + 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/libr/core/cbin.c b/libr/core/cbin.c index 4a13dc9df66c7..b6096a4d6c560 100644 --- a/libr/core/cbin.c +++ b/libr/core/cbin.c @@ -342,7 +342,7 @@ R_API bool r_core_bin_set_cur(RCore *core, RBinFile *binfile) { return true; } -static void _print_strings(RCore *core, RList *list, PJ *pj, int mode, int va) { +static void _print_strings(RCore *core, RList *list, PJ *pj, int mode, int va, int str_type_filter) { RTable *table = r_core_table_new (core, "strings"); if (!table) { return; @@ -374,6 +374,10 @@ static void _print_strings(RCore *core, RList *list, PJ *pj, int mode, int va) { const char *section_name, *type_string; ut64 paddr = string->paddr; ut64 vaddr = rva (core->bin, paddr, string->vaddr, va); + // Apply string type filter if specified + if (str_type_filter != 0 && string->type != str_type_filter) { + continue; + } if (!r_bin_string_filter (bin, string->string, vaddr)) { continue; } @@ -572,7 +576,7 @@ static void _print_strings(RCore *core, RList *list, PJ *pj, int mode, int va) { R_CRITICAL_LEAVE (core); } -static bool bin_raw_strings(RCore *core, PJ *pj, int mode, int va) { +static bool bin_raw_strings(RCore *core, PJ *pj, int mode, int va, int str_type_filter) { RBinFile *bf = r_bin_cur (core->bin); bool new_bf = false; if (bf && strstr (bf->file, "malloc://")) { @@ -613,7 +617,7 @@ static bool bin_raw_strings(RCore *core, PJ *pj, int mode, int va) { va = false; } RList *l = r_bin_raw_strings (bf, 0); - _print_strings (core, l, pj, mode, va); + _print_strings (core, l, pj, mode, va, str_type_filter); r_list_free (l); if (new_bf) { r_buf_free (bf->buf); @@ -624,7 +628,7 @@ static bool bin_raw_strings(RCore *core, PJ *pj, int mode, int va) { return true; } -static bool bin_strings(RCore *core, PJ *pj, int mode, int va) { +static bool bin_strings(RCore *core, PJ *pj, int mode, int va, int str_type_filter) { RBinFile *binfile = r_bin_cur (core->bin); RBinPlugin *plugin = r_bin_file_cur_plugin (binfile); int rawstr = r_config_get_i (core->config, "bin.str.raw"); @@ -646,7 +650,7 @@ static bool bin_strings(RCore *core, PJ *pj, int mode, int va) { } RList *list = r_bin_get_strings (core->bin); if (list) { - _print_strings (core, list, pj, mode, va); + _print_strings (core, list, pj, mode, va, str_type_filter); return true; } return false; @@ -5045,6 +5049,7 @@ static bool bin_signature(RCore *core, PJ *pj, int mode) { R_API bool r_core_bin_info(RCore *core, int action, PJ *pj, int mode, int va, RCoreBinFilter *filter, const char *chksum) { R_RETURN_VAL_IF_FAIL (core, false); const char *name = (filter && filter->name)? filter->name : NULL; + int str_type_filter = (filter && filter->str_type)? filter->str_type : 0; bool ret = true; ut64 at = UT64_MAX, loadaddr = r_bin_get_laddr (core->bin); if (filter && filter->addr) { @@ -5053,9 +5058,9 @@ R_API bool r_core_bin_info(RCore *core, int action, PJ *pj, int mode, int va, RC // use our internal values for va va = va ? VA_TRUE : VA_FALSE; if ((action & R_CORE_BIN_ACC_RAW_STRINGS)) { - ret &= bin_raw_strings (core, pj, mode, va); + ret &= bin_raw_strings (core, pj, mode, va, str_type_filter); } else if ((action & R_CORE_BIN_ACC_STRINGS)) { - ret &= bin_strings (core, pj, mode, va); + ret &= bin_strings (core, pj, mode, va, str_type_filter); } if ((action & R_CORE_BIN_ACC_INFO)) { ret &= bin_info (core, pj, mode, loadaddr); diff --git a/libr/core/cmd_info.inc.c b/libr/core/cmd_info.inc.c index 27bc6575050e2..bc1087ea6675a 100644 --- a/libr/core/cmd_info.inc.c +++ b/libr/core/cmd_info.inc.c @@ -50,12 +50,17 @@ static RCoreHelpMessage help_msg_ic = { }; static RCoreHelpMessage help_msg_iz = { - "Usage: iz", "[][jq*]", "List strings", + "Usage: iz", "[auwW:charset][zjq*]", "List strings with optional type/charset filter", "iz", "", "strings in data sections (in JSON/Base64)", + "iza", "", "show only ascii strings", + "izu", "", "show only utf8/unicode strings", + "izw", "", "show only wide (utf16) strings", + "izW", "", "show only wide32 (utf32) strings", + "iz:", "charset", "show only strings with specific charset (ascii, utf8, wide, wide32, base64)", "iz,", "[:help]", "perform a table query on strings listing", "iz-", " [addr]", "purge string via bin.str.purge", "iz*", "", "print flags and comments r2 commands for all the strings", - "izz", "", "search for Strings in the whole binary", + "izz", "[auwW:charset]", "search for Strings in the whole binary (with optional type filter)", "izz*", "", "same as iz* but exposing the strings of the whole binary", "izzz", "", "dump Strings from whole binary to r2 shell (for huge files)", NULL @@ -1386,8 +1391,42 @@ static void cmd_ic(RCore *core, const char *input, PJ *pj, bool is_array, bool v } } +// Helper to parse string type filter from command argument +static int parse_str_type_filter(const char ch, const char *name) { + if (ch) { + switch (ch) { + case 'a': return R_STRING_TYPE_ASCII; + case 'u': return R_STRING_TYPE_UTF8; + case 'w': return R_STRING_TYPE_WIDE; + case 'W': return R_STRING_TYPE_WIDE32; + case 'b': return R_STRING_TYPE_BASE64; + } + } + if (name) { + if (!strcmp (name, "ascii")) { + return R_STRING_TYPE_ASCII; + } + if (!strcmp (name, "utf8") || !strcmp (name, "unicode")) { + return R_STRING_TYPE_UTF8; + } + if (!strcmp (name, "wide") || !strcmp (name, "utf16")) { + return R_STRING_TYPE_WIDE; + } + if (!strcmp (name, "wide32") || !strcmp (name, "utf32")) { + return R_STRING_TYPE_WIDE32; + } + if (!strcmp (name, "base64")) { + return R_STRING_TYPE_BASE64; + } + } + return 0; // no filter +} + static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const char *input) { bool rdump = false; + int str_type_filter = 0; + RCoreBinFilter filter = {0}; + if (input[1] == '-') { // "iz-" char *strpurge = core->bin->strpurge; ut64 addr = core->addr; @@ -1408,6 +1447,20 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c addr); core->tmpseek = old_tmpseek; } else if (input[1] == 'z') { // "izz" + // Check for type filter after izz + if (input[2] == 'a' || input[2] == 'u' || input[2] == 'w' || input[2] == 'W' || input[2] == 'b') { + str_type_filter = parse_str_type_filter (input[2], NULL); + input++; + } else if (input[2] == ':') { // "izz:charset" + const char *charset = input + 3; + str_type_filter = parse_str_type_filter (0, charset); + // Skip to end or next modifier + while (*input && *input != '*' && *input != 'j' && *input != 'q') { + input++; + } + input--; // Will be incremented in switch + } + switch (input[2]) { case 'z':// "izzz" rdump = true; @@ -1440,11 +1493,27 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c r_list_free (res); } } else { - RBININFO ("strings", R_CORE_BIN_ACC_RAW_STRINGS, NULL, 0); + filter.str_type = str_type_filter; + r_core_bin_info (core, R_CORE_BIN_ACC_RAW_STRINGS, pj, mode, va, &filter, NULL); } } else { // "iz" bool validcmd = true; + + // Check for type filter after iz + if (input[1] == 'a' || input[1] == 'u' || input[1] == 'w' || input[1] == 'W' || input[1] == 'b') { + str_type_filter = parse_str_type_filter (input[1], NULL); + input++; + } else if (input[1] == ':') { // "iz:charset" + const char *charset = input + 2; + str_type_filter = parse_str_type_filter (0, charset); + // Skip to end or next modifier + while (*input && *input != ',' && *input != '*' && *input != 'j' && *input != 'q' && *input != ' ') { + input++; + } + input--; // Will be incremented below + } + switch (input[1]) { case ',': // "iz," R_FREE (core->table_query); @@ -1466,7 +1535,10 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c input++; break; default: - // invalid subcommand handler? + // Could be end of filter, check if valid + if (str_type_filter) { + validcmd = true; + } break; } if (validcmd) { @@ -1474,11 +1546,11 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c RListIter *iter; RBinFile *bf; RBinFile *cur = core->bin->cur; + filter.str_type = str_type_filter; r_list_foreach (bfiles, iter, bf) { core->bin->cur = bf; RBinObject *bo = r_bin_cur_object (core->bin); - RBININFO ("strings", R_CORE_BIN_ACC_STRINGS, NULL, - (bo && bo->strings)? r_list_length (bo->strings): 0); + r_core_bin_info (core, R_CORE_BIN_ACC_STRINGS, pj, mode, va, &filter, NULL); } core->bin->cur = cur; r_list_free (bfiles); diff --git a/libr/include/r_core.h b/libr/include/r_core.h index 11b7cf3f0ebe9..2820423ebb957 100644 --- a/libr/include/r_core.h +++ b/libr/include/r_core.h @@ -864,6 +864,7 @@ R_API void r_core_recover_vars(RCore *core, RAnalFunction *fcn, bool argonly); typedef struct r_core_bin_filter_t { ut64 addr; const char *name; + int str_type; // filter strings by type (0 = no filter, 'a' = ascii, 'u' = utf8, 'w' = wide, 'W' = wide32, 'b' = base64) } RCoreBinFilter; R_API bool r_core_bin_info(RCore *core, int action, PJ *pj, int mode, int va, RCoreBinFilter *filter, const char *chksum); From 0ac3499ab76356906ea7073c47c4faadb47f5914 Mon Sep 17 00:00:00 2001 From: Nikesh Chavhan Date: Thu, 23 Oct 2025 08:50:38 +0000 Subject: [PATCH 2/4] gitignore: Add personal work files and temporary files - Ignore .personal_files/ directory for work-in-progress files - Ignore TESTING_*.md files - Ignore Windows Zone.Identifier files - Ignore test backup files --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 3b51f9dd4ccee..96564cc9df50d 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,9 @@ crash-* corpus* libr/anal/d/types-windows.sdb.txt libr/bin/d/dll/*.c + +# Personal work files +.personal_files/ +TESTING_*.md +*:Zone.Identifier +test/db/cmd/*.backup From a786a70aaabeaed540e519ce06641586563b3599 Mon Sep 17 00:00:00 2001 From: Nikesh Chavhan Date: Mon, 27 Oct 2025 06:25:20 +0000 Subject: [PATCH 3/4] Fix unused variable 'bo' warning in cmd_iz function --- libr/core/cmd_info.inc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libr/core/cmd_info.inc.c b/libr/core/cmd_info.inc.c index bc1087ea6675a..8aa4b8075bf82 100644 --- a/libr/core/cmd_info.inc.c +++ b/libr/core/cmd_info.inc.c @@ -1549,7 +1549,6 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c filter.str_type = str_type_filter; r_list_foreach (bfiles, iter, bf) { core->bin->cur = bf; - RBinObject *bo = r_bin_cur_object (core->bin); r_core_bin_info (core, R_CORE_BIN_ACC_STRINGS, pj, mode, va, &filter, NULL); } core->bin->cur = cur; From 3e5e38293f4cfdf9956237a00dbfb20bf24aa99d Mon Sep 17 00:00:00 2001 From: Nikesh Chavhan Date: Mon, 3 Nov 2025 13:46:45 +0000 Subject: [PATCH 4/4] Fix lint errors: Remove tabs from empty lines in cmd_info.inc.c --- libr/core/cmd_info.inc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libr/core/cmd_info.inc.c b/libr/core/cmd_info.inc.c index 8aa4b8075bf82..236967bb15ed9 100644 --- a/libr/core/cmd_info.inc.c +++ b/libr/core/cmd_info.inc.c @@ -1426,7 +1426,7 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c bool rdump = false; int str_type_filter = 0; RCoreBinFilter filter = {0}; - + if (input[1] == '-') { // "iz-" char *strpurge = core->bin->strpurge; ut64 addr = core->addr; @@ -1460,7 +1460,7 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c } input--; // Will be incremented in switch } - + switch (input[2]) { case 'z':// "izzz" rdump = true; @@ -1499,7 +1499,7 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c } else { // "iz" bool validcmd = true; - + // Check for type filter after iz if (input[1] == 'a' || input[1] == 'u' || input[1] == 'w' || input[1] == 'W' || input[1] == 'b') { str_type_filter = parse_str_type_filter (input[1], NULL); @@ -1513,7 +1513,7 @@ static void cmd_iz(RCore *core, PJ *pj, int mode, int is_array, bool va, const c } input--; // Will be incremented below } - + switch (input[1]) { case ',': // "iz," R_FREE (core->table_query);