mirror of
https://github.com/radareorg/radare2.git
synced 2024-11-26 22:50:48 +00:00
Autodetect libc version and support manual specification ##debug
* Improve dmh usability * Add unit tests for regexpes used
This commit is contained in:
parent
4fc2daf5e3
commit
b6c28b3830
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 ();
|
||||
|
@ -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 ?
|
||||
|
@ -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
|
||||
|
@ -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@
|
||||
|
49
test/unit/test_get_glibc_version.c
Normal file
49
test/unit/test_get_glibc_version.c
Normal 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 ();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user