Autodetect libc version and support manual specification ##debug

* Improve dmh usability
* Add unit tests for regexpes used
This commit is contained in:
Armin Weihbold 2024-02-14 14:38:40 +01:00 committed by GitHub
parent 4fc2daf5e3
commit b6c28b3830
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 275 additions and 19 deletions

View File

@ -3563,6 +3563,7 @@ R_API int r_core_config_init(RCore *core) {
SETDESC (n, "choose malloc structure parser");
SETOPTIONS (n, "glibc", "jemalloc", NULL);
SETPREF ("dbg.glibc.path", "", "if not empty, use the given path to resolve the libc");
SETPREF ("dbg.glibc.version", "", "if not empty, assume the given libc version");
#if __GLIBC_MINOR__ > 25
SETBPREF ("dbg.glibc.tcache", "true", "parse the tcache (glibc.minor > 2.25.x)");
#else

View File

@ -100,29 +100,197 @@ static GHT GH(get_main_arena_with_symbol)(RCore *core, RDebugMap *map) {
return main_arena;
}
static GH(section_content) GH(get_section_content)(RCore *core, const char *path, const char *section_name) {
RBin *bin = core->bin;
RBinFile *bf = r_bin_cur (bin);
bool found_section = false;
GHT paddr;
GH(section_content) content = {.size = GHT_MAX, .buf = NULL};
RBinFileOptions opt;
r_bin_file_options_init (&opt, -1, 0, 0, false);
if (!r_bin_open (bin, path, &opt)) {
R_LOG_ERROR ("section_content: r_bin_open failed on path %s", path);
return content;
}
RBinFile *libc_bf = r_bin_cur (bin);
RList *sections = r_bin_get_sections (bin);
RBinSection *section;
RListIter *iter;
r_list_foreach (sections, iter, section) {
if (!strcmp (section->name, section_name)) {
found_section = true;
paddr = section->paddr;
content.size = section->size;
break;
}
}
if (!found_section) {
R_LOG_WARN ("section_content: section %s not found", section_name);
goto cleanup_exit;
}
// eprintf ("get_section_bytes: section found: %s content.size: %#08x paddr: %#08x\n", section_name, content.size, paddr);
content.buf = calloc (content.size, 1);
if (!content.buf) {
R_LOG_ERROR ("section_content: calloc failed");
goto cleanup_exit;
}
st64 read_size = r_buf_read_at (libc_bf->buf, paddr, content.buf, content.size);
if (read_size != content.size) {
R_LOG_ERROR ("section_content: section read unexpected content.size: %#08x (section->size: %d)", read_size, content.size);
free (content.buf);
content.buf = NULL;
}
cleanup_exit:
r_bin_file_delete (bin, libc_bf->id);
r_bin_file_set_cur_binfile (bin, bf);
return content;
}
R_API double GH(get_glibc_version)(RCore *core, const char *libc_path) {
double version = 0.0;
// First see if there is a "__libc_version" symbol
// If yes read version from there
GHT version_symbol = GH (get_va_symbol) (core, libc_path, "__libc_version");
if (version_symbol != GHT_MAX) {
FILE *libc_file = fopen (libc_path, "rb");
if (libc_file == NULL) {
R_LOG_WARN ("resolve_glibc_version: Failed to open %s", libc_path);
return false;
}
// TODO: futureproof this
char version_buffer[5] = {0};
fseek (libc_file, version_symbol, SEEK_SET);
if (fread (version_buffer, 1, 4, libc_file) != 4) {
R_LOG_WARN ("resolve_glibc_version: Failed to read 4 bytes of version symbol");
return false;
};
fclose (libc_file);
if (!r_regex_match ("\\d.\\d\\d", "e", version_buffer)) {
R_LOG_WARN ("resolve_glibc_version: Unexpected version format: %s", version_buffer);
return false;
}
version = strtod (version_buffer, NULL);
R_LOG_INFO ("libc version %.2f identified from symbol", version);
return version;
}
// Next up we try to read version from banner in .rodata section
// also inspired by pwndbg
GH(section_content) rodata = GH (get_section_content) (core, libc_path, ".rodata");
const ut8 *banner_start = NULL;
if (rodata.buf != NULL) {
banner_start = r_mem_mem (rodata.buf, rodata.size, (const ut8 *)"GNU C Library", strlen ("GNU C Library"));
}
if (banner_start != NULL) {
RRegex *rx = r_regex_new ("release version (\\d.\\d\\d)", "en");
RList *matches = r_regex_match_list (rx, (const char *)banner_start);
// We only care about the first match
const char *first_match = r_list_first (matches);
if (first_match) {
const char *version_start = first_match + strlen ("release version ");
version = strtod (version_start, NULL);
}
r_list_free (matches);
r_regex_free (rx);
}
free (rodata.buf);
if (version != 0) {
R_LOG_INFO ("libc version %.2f identified from .rodata banner", version);
return version;
}
R_LOG_WARN ("get_glibc_version failed");
return version;
}
static bool GH(resolve_glibc_version)(RCore *core) {
r_return_val_if_fail (core && core->dbg && core->dbg->maps, false);
double version = 0;
RDebugMap *map;
RListIter *iter;
bool found_glibc_map = false;
if (core->dbg->glibc_version_resolved) {
return true;
}
const char *dbg_glibc_version = r_config_get (core->config, "dbg.glibc.version");
if (R_STR_ISEMPTY (dbg_glibc_version)) {
dbg_glibc_version = NULL;
}
if (dbg_glibc_version) {
// TODO: use ^ and $ which appear to be broken
if (!r_regex_match ("\\d.\\d\\d", "e", dbg_glibc_version)) {
R_LOG_WARN ("resolve_glibc_version: Unexpected version format in dbg.glibc.version: %s"
" (expected format \"\\d.\\d\\d\")", dbg_glibc_version);
} else {
version = strtod (dbg_glibc_version, NULL);
core->dbg->glibc_version = (int) round ((version * 100));
core->dbg->glibc_version_d = version;
core->dbg->glibc_version_resolved = true;
R_LOG_INFO ("libc version %.2f set from dbg.glibc.version", core->dbg->glibc_version_d);
return true;
}
}
r_return_val_if_fail (core && core->dbg && core->dbg->maps, false);
r_debug_map_sync (core->dbg);
// Search for binary in memory maps named *libc-* or *libc.*
// TODO: This is very brittle, other bin names or LD_PRELOAD could be a problem
r_list_foreach (core->dbg->maps, iter, map) {
if (r_str_startswith (core->bin->file, map->name)) {
continue;
}
if (r_regex_match (".*libc[.-]", "e", map->name)) {
found_glibc_map = true;
break;
}
}
// TODO: handle static binaries
if (!found_glibc_map) {
R_LOG_WARN ("resolve_glibc_version: no libc found in memory maps (cannot handle static binaries)");
return false;
}
// At this point we found a map in memory that _should_ be libc
version = GH (get_glibc_version) (core, map->file);
if (version != 0) {
core->dbg->glibc_version = (int) round ((version * 100));
core->dbg->glibc_version_d = version;
core->dbg->glibc_version_resolved = true;
char version_buffer[315] = {0};
// TODO: better way snprintf to 4 chars warns
// note: snprintf output between 4 and 314 bytes into a destination of size 4
snprintf (version_buffer, sizeof (version_buffer)-1, "%.2f", version);
r_config_set (core->config, "dbg.glibc.version", version_buffer);
return true;
}
return false;
}
static bool GH(is_tcache)(RCore *core) {
double v = 0;
if (!r_config_get_b (core->config, "cfg.debug")) {
return r_config_get_b (core->config, "dbg.glibc.tcache");
}
RDebugMap *map;
RListIter *iter;
r_debug_map_sync (core->dbg);
r_list_foreach (core->dbg->maps, iter, map) {
// In case the binary is named *libc-* or *libc.*
if (strncmp (map->name, core->bin->file, strlen (map->name)) != 0) {
char *fp = strstr (map->name, "libc-");
if (fp) {
break;
}
fp = strstr (map->name, "libc.");
if (fp) {
v = r_num_get_double (core->num, fp + 5);
core->dbg->glibc_version = (int) round((v * 100));
return (v > 2.25);
}
}
if (core->dbg->glibc_version_resolved || GH (resolve_glibc_version) (core)) {
return core->dbg->glibc_version_d > 2.25;
}
R_LOG_WARN ("is_tcache: glibc_version could not be resolved");
return false;
}
@ -390,6 +558,13 @@ static bool GH(r_resolve_main_arena)(RCore *core, GHT *m_arena) {
return true;
}
// R_LOG_INFO ("Resolving libc version");
if (!GH (resolve_glibc_version) (core)) {
R_LOG_WARN ("Could not determine libc version");
// TODO: maybe add config setting to hardcode libc version?
return false;
}
GHT brk_start = GHT_MAX, brk_end = GHT_MAX;
GHT libc_addr_sta = GHT_MAX, libc_addr_end = 0;
GHT main_arena_sym = GHT_MAX;

View File

@ -394,7 +394,9 @@ R_API RDebug *r_debug_new(int hard) {
dbg->q_regs = NULL;
dbg->call_frames = NULL;
dbg->main_arena_resolved = false;
dbg->glibc_version_resolved = false;
dbg->glibc_version = 231; /* default version ubuntu 20 */
dbg->glibc_version_d = 0; /* no default glibc version */
r_debug_signal_init (dbg);
if (hard) {
dbg->bp = r_bp_new ();

View File

@ -433,7 +433,9 @@ typedef struct r_debug_t {
bool verbose;
size_t maxsnapsize;
bool main_arena_resolved; /* is the main_arena resolved already? */
bool glibc_version_resolved; /* is the libc version resolved already? */
int glibc_version;
double glibc_version_d; // TODO: move over to this only
} RDebug;
// TODO: rename to r_debug_process_t ? maybe a thread too ?

View File

@ -269,6 +269,16 @@ typedef struct r_heap_info_64 {
/* char pad[NPAD * SZ & MALLOC_ALIGN_MASK]; */
} RHeapInfo_64;
typedef struct _section_content_32 {
ut32 size;
ut8 *buf;
} section_content_32;
typedef struct _section_content_64 {
ut64 size;
ut8 *buf;
} section_content_64;
#ifdef __cplusplus
}
#endif

View File

@ -7,6 +7,6 @@ Name: r_core
Description: radare foundation libraries
Version: @VERSION@
Requires: r_config r_cons r_io r_util r_flag r_asm r_debug r_bin r_lang r_io r_anal r_bp r_egg r_reg r_search r_syscall r_socket r_fs r_magic r_crypto r_arch r_esil
Libs: -L${libdir} -lr_core @SSL_LDFLAGS@ @CAPSTONE_LDFLAGS@
Libs: -L${libdir} -lr_core -lm @SSL_LDFLAGS@ @CAPSTONE_LDFLAGS@
Libs.private: -L${libdir} ${libdir}/libr.a
Cflags: -I${includedir}/libr @SSL_CFLAGS@ @CAPSTONE_CFLAGS@

View File

@ -0,0 +1,49 @@
#include <r_bin.h>
#include <r_core.h>
#include <math.h>
#include "../../libr/include/r_heap_glibc.h"
#define R_INCLUDE_BEGIN 1
#include "../../libr/core/dmh_glibc.inc.c"
#undef R_INCLUDE_BEGIN
#include "minunit.h"
// Adapted https://github.com/radareorg/radare2/pull/22516/commits/d59c813cc4fc574c85aa210aed4aa0636fac3184 by MewtR
bool test_get_glibc_version (void) {
RCore *core = r_core_new ();
double version = 0.0f;
int glibc_version = 0;
// 2.27
version = GH (get_glibc_version) (core, "bins/elf/libc-2.27.so");
glibc_version = (int)round ((version * 100));
mu_assert_eq (glibc_version, 227, "Incorrect libc version, expected 2.27");
// 2.28
version = GH (get_glibc_version) (core, "bins/elf/libc.so.6");
glibc_version = (int)round ((version * 100));
mu_assert_eq (glibc_version, 228, "Incorrect libc version, expected 2.28");
// 2.31
version = GH (get_glibc_version) (core, "bins/elf/libc-2.31.so");
glibc_version = (int)round ((version * 100));
mu_assert_eq (glibc_version, 231, "Incorrect libc version, expected 2.31");
// 2.32
version = GH (get_glibc_version) (core, "bins/elf/libc-2.32.so");
glibc_version = (int)round ((version * 100));
mu_assert_eq (glibc_version, 232, "Incorrect libc version, expected 2.32");
r_core_free (core);
mu_end;
}
bool all_tests () {
mu_run_test (test_get_glibc_version);
return tests_passed != tests_run;
}
int main (int argc, char **argv) {
return all_tests ();
}

View File

@ -105,9 +105,26 @@ static int test_begin(void) {
return 0;
}
static int test_libc_match(void) {
const char *needle_libname = ".*libc[.-]";
const char *needle_version = "\\d.\\d\\d";
const char *haystack_libname = "/usr/lib/libc.so.6";
const char *haystack_version = "2.38";
int res;
res = r_regex_match (needle_libname, "e", haystack_libname);
mu_assert_eq (res, 1, "regex lib name failed");
res = r_regex_match (needle_version, "e", haystack_version);
mu_assert_eq (res, 1, "regex lib version failed");
mu_end;
return 0;
}
int main(int argc, char **argv) {
mu_run_test (test_regex);
mu_run_test (test_or);
mu_run_test (test_begin);
mu_run_test (test_libc_match);
return 0;
}