mirror of
https://github.com/radareorg/radare2
synced 2026-08-22 20:23:32 -04:00
Add the new jni analysis plugin ##analysis
This commit is contained in:
parent
e9dc573b3f
commit
961e10bc39
8 changed files with 538 additions and 0 deletions
1
dist/plugins-cfg/plugins.def.cfg
vendored
1
dist/plugins-cfg/plugins.def.cfg
vendored
|
|
@ -14,6 +14,7 @@ anal.callargs
|
|||
anal.tcc
|
||||
anal.blaze
|
||||
anal.gopclntab
|
||||
anal.jni
|
||||
anal.path
|
||||
anal.a2f
|
||||
anal.six
|
||||
|
|
|
|||
1
dist/plugins-cfg/plugins.sdk.cfg
vendored
1
dist/plugins-cfg/plugins.sdk.cfg
vendored
|
|
@ -13,6 +13,7 @@ anal.autoname
|
|||
anal.callargs
|
||||
anal.tcc
|
||||
anal.a2f
|
||||
anal.jni
|
||||
anal.sbpf
|
||||
anal.tp
|
||||
anal.thumb
|
||||
|
|
|
|||
1
dist/plugins-cfg/plugins.static.cfg
vendored
1
dist/plugins-cfg/plugins.static.cfg
vendored
|
|
@ -14,6 +14,7 @@ anal.null
|
|||
anal.autoname
|
||||
anal.drcov
|
||||
anal.gopclntab
|
||||
anal.jni
|
||||
arch.arc
|
||||
arch.arm_cs
|
||||
arch.arm_gnu
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ r_anal_sources = [
|
|||
'p/anal_a2f.c',
|
||||
'p/anal_blaze.c',
|
||||
'p/anal_gopclntab.c',
|
||||
'p/anal_jni.c',
|
||||
'p/anal_path.c',
|
||||
'p/anal_drcov.c',
|
||||
'p/anal_six.c',
|
||||
|
|
|
|||
349
libr/anal/p/anal_jni.c
Normal file
349
libr/anal/p/anal_jni.c
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
/* radare - LGPL - Copyright 2026 - pancake */
|
||||
|
||||
#include <r_anal.h>
|
||||
|
||||
#define JNI_MIN_TABLE_METHODS 2
|
||||
#define JNI_MAX_DATA_SECTION (64 * 1024 * 1024)
|
||||
#define JNI_MAX_METHOD_NAME 256
|
||||
#define JNI_MAX_DESCRIPTOR 2048
|
||||
|
||||
typedef struct {
|
||||
ut64 record_addr;
|
||||
ut64 name_addr;
|
||||
ut64 descriptor_addr;
|
||||
ut64 function_addr;
|
||||
char *name;
|
||||
char *descriptor;
|
||||
RBinJavaMember *member;
|
||||
} JniMethod;
|
||||
|
||||
typedef struct {
|
||||
ut64 addr;
|
||||
RList /*<JniMethod *>*/ *methods;
|
||||
} JniTable;
|
||||
|
||||
typedef struct {
|
||||
RAnal *anal;
|
||||
RBinObject *bo;
|
||||
Sdb *db;
|
||||
int pointer_size;
|
||||
bool big_endian;
|
||||
int tables_count;
|
||||
} JniScanContext;
|
||||
|
||||
static void jni_method_free(void *ptr) {
|
||||
JniMethod *method = ptr;
|
||||
if (method) {
|
||||
free (method->name);
|
||||
free (method->descriptor);
|
||||
r_bin_java_member_free (method->member);
|
||||
free (method);
|
||||
}
|
||||
}
|
||||
|
||||
static void jni_table_free(JniTable *table) {
|
||||
if (table) {
|
||||
r_list_free (table->methods);
|
||||
free (table);
|
||||
}
|
||||
}
|
||||
|
||||
static bool jni_add_shift(ut64 addr, st64 shift, ut64 *result) {
|
||||
if (shift >= 0) {
|
||||
ut64 delta = (ut64)shift;
|
||||
if (addr > UT64_MAX - delta) {
|
||||
return false;
|
||||
}
|
||||
*result = addr + delta;
|
||||
return true;
|
||||
}
|
||||
ut64 delta = (ut64)(-(shift + 1)) + 1;
|
||||
if (addr < delta) {
|
||||
return false;
|
||||
}
|
||||
*result = addr - delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool jni_addr_has_perm(JniScanContext *ctx, ut64 addr, int perm) {
|
||||
RIORegion region;
|
||||
return ctx->anal->iob.get_region_at (ctx->anal->iob.io, ®ion, addr)
|
||||
&& (region.perm & perm) == perm;
|
||||
}
|
||||
|
||||
static bool jni_resolve_addr(JniScanContext *ctx, ut64 addr, int perm, ut64 *result) {
|
||||
if (!addr) {
|
||||
return false;
|
||||
}
|
||||
ut64 shifted;
|
||||
bool has_shifted = ctx->bo->baddr_shift
|
||||
&& jni_add_shift (addr, ctx->bo->baddr_shift, &shifted);
|
||||
if (!ctx->bo->is_reloc_patched && has_shifted
|
||||
&& jni_addr_has_perm (ctx, shifted, perm)) {
|
||||
*result = shifted;
|
||||
return true;
|
||||
}
|
||||
if (jni_addr_has_perm (ctx, addr, perm)) {
|
||||
*result = addr;
|
||||
return true;
|
||||
}
|
||||
if (has_shifted && jni_addr_has_perm (ctx, shifted, perm)) {
|
||||
*result = shifted;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *jni_read_string(JniScanContext *ctx, ut64 addr, size_t maxlen) {
|
||||
RIORegion region;
|
||||
if (!ctx->anal->iob.get_region_at (ctx->anal->iob.io, ®ion, addr)
|
||||
|| !(region.perm & R_PERM_R)) {
|
||||
return NULL;
|
||||
}
|
||||
ut64 end = r_itv_end (region.itv);
|
||||
if (end <= addr) {
|
||||
return NULL;
|
||||
}
|
||||
size_t available = R_MIN ((ut64)maxlen, end - addr);
|
||||
char *str = R_NEWS (char, available + 1);
|
||||
if (!ctx->anal->iob.read_at (ctx->anal->iob.io, addr, (ut8 *)str, available)) {
|
||||
free (str);
|
||||
return NULL;
|
||||
}
|
||||
str[available] = 0;
|
||||
char *nul = memchr (str, 0, available);
|
||||
if (!nul) {
|
||||
free (str);
|
||||
return NULL;
|
||||
}
|
||||
*nul = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool jni_method_name_is_valid(const char *name) {
|
||||
if (R_STR_ISEMPTY (name)) {
|
||||
return false;
|
||||
}
|
||||
const ut8 first = (ut8)*name;
|
||||
if (first < 0x80 && !isalpha (first) && first != '_' && first != '$') {
|
||||
return false;
|
||||
}
|
||||
const ut8 *p = (const ut8 *)name + 1;
|
||||
for (; *p; p++) {
|
||||
if (*p < 0x80 && !isalnum (*p) && *p != '_' && *p != '$') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ut64 jni_read_pointer(JniScanContext *ctx, const ut8 *buf) {
|
||||
return ctx->pointer_size == 8
|
||||
? r_read_ble64 (buf, ctx->big_endian)
|
||||
: r_read_ble32 (buf, ctx->big_endian);
|
||||
}
|
||||
|
||||
static bool jni_resolve_function(JniScanContext *ctx, ut64 addr, ut64 *result) {
|
||||
const char *arch = ctx->anal->config->arch;
|
||||
if ((addr & 1) && arch && r_str_startswith (arch, "arm")) {
|
||||
if (jni_resolve_addr (ctx, addr & ~1ULL, R_PERM_X, result)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return jni_resolve_addr (ctx, addr, R_PERM_X, result);
|
||||
}
|
||||
|
||||
static JniMethod *jni_parse_method(JniScanContext *ctx, const ut8 *buf, ut64 record_addr) {
|
||||
ut64 name_addr;
|
||||
ut64 descriptor_addr;
|
||||
ut64 function_addr;
|
||||
const int pointer_size = ctx->pointer_size;
|
||||
if (!jni_resolve_function (ctx, jni_read_pointer (ctx, buf + pointer_size * 2),
|
||||
&function_addr)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!jni_resolve_addr (ctx, jni_read_pointer (ctx, buf), R_PERM_R, &name_addr)
|
||||
|| !jni_resolve_addr (ctx, jni_read_pointer (ctx, buf + pointer_size),
|
||||
R_PERM_R, &descriptor_addr)) {
|
||||
return NULL;
|
||||
}
|
||||
char *name = jni_read_string (ctx, name_addr, JNI_MAX_METHOD_NAME);
|
||||
if (!jni_method_name_is_valid (name)) {
|
||||
free (name);
|
||||
return NULL;
|
||||
}
|
||||
char *descriptor = jni_read_string (ctx, descriptor_addr, JNI_MAX_DESCRIPTOR);
|
||||
if (!descriptor || *descriptor != '(') {
|
||||
free (name);
|
||||
free (descriptor);
|
||||
return NULL;
|
||||
}
|
||||
RBinJavaMember *member = r_bin_java_member_parse (NULL, name, descriptor,
|
||||
R_BIN_JAVA_MEMBER_METHOD, 0);
|
||||
if (!member) {
|
||||
free (name);
|
||||
free (descriptor);
|
||||
return NULL;
|
||||
}
|
||||
JniMethod *method = R_NEW (JniMethod);
|
||||
method->record_addr = record_addr;
|
||||
method->name_addr = name_addr;
|
||||
method->descriptor_addr = descriptor_addr;
|
||||
method->function_addr = function_addr;
|
||||
method->name = name;
|
||||
method->descriptor = descriptor;
|
||||
method->member = member;
|
||||
return method;
|
||||
}
|
||||
|
||||
static JniTable *jni_parse_table(JniScanContext *ctx, const ut8 *buf, size_t size, size_t offset, ut64 section_addr) {
|
||||
const size_t record_size = ctx->pointer_size * 3;
|
||||
JniTable *table = R_NEW (JniTable);
|
||||
table->addr = section_addr + offset;
|
||||
table->methods = r_list_newf (jni_method_free);
|
||||
while (offset <= size - record_size) {
|
||||
JniMethod *method = jni_parse_method (ctx, buf + offset,
|
||||
section_addr + offset);
|
||||
if (!method) {
|
||||
break;
|
||||
}
|
||||
r_list_append (table->methods, method);
|
||||
offset += record_size;
|
||||
}
|
||||
if (r_list_length (table->methods) < JNI_MIN_TABLE_METHODS) {
|
||||
jni_table_free (table);
|
||||
return NULL;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
static void jni_store_table(JniScanContext *ctx, JniTable *table) {
|
||||
const int table_index = ctx->tables_count++;
|
||||
sdb_num_setf (ctx->db, table->addr, 0, "table.%d.addr", table_index);
|
||||
sdb_num_setf (ctx->db, r_list_length (table->methods), 0,
|
||||
"table.%d.count", table_index);
|
||||
RListIter *iter;
|
||||
JniMethod *method;
|
||||
int method_index = 0;
|
||||
r_list_foreach (table->methods, iter, method) {
|
||||
sdb_num_setf (ctx->db, method->record_addr, 0,
|
||||
"table.%d.method.%d.record", table_index, method_index);
|
||||
sdb_num_setf (ctx->db, method->name_addr, 0,
|
||||
"table.%d.method.%d.name_addr", table_index, method_index);
|
||||
sdb_num_setf (ctx->db, method->descriptor_addr, 0,
|
||||
"table.%d.method.%d.descriptor_addr", table_index, method_index);
|
||||
sdb_num_setf (ctx->db, method->function_addr, 0,
|
||||
"table.%d.method.%d.function", table_index, method_index);
|
||||
sdb_setf (ctx->db, method->name, 0,
|
||||
"table.%d.method.%d.name", table_index, method_index);
|
||||
sdb_setf (ctx->db, method->descriptor, 0,
|
||||
"table.%d.method.%d.descriptor", table_index, method_index);
|
||||
sdb_setf (ctx->db, method->member->definition, 0,
|
||||
"table.%d.method.%d.definition", table_index, method_index);
|
||||
sdb_setf (ctx->db, method->member->jni_definition, 0,
|
||||
"table.%d.method.%d.jni_definition", table_index, method_index);
|
||||
method_index++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool jni_section_is_scannable(const RBinSection *section, size_t min_size) {
|
||||
return section && !section->is_segment
|
||||
&& (section->perm & R_PERM_R) && !(section->perm & R_PERM_X)
|
||||
&& section->size >= min_size && section->size <= JNI_MAX_DATA_SECTION
|
||||
&& section->vaddr != UT64_MAX && section->name
|
||||
&& strstr (section->name, "data");
|
||||
}
|
||||
|
||||
static void jni_scan_section(JniScanContext *ctx, const RBinSection *section) {
|
||||
const size_t record_size = ctx->pointer_size * 3;
|
||||
const size_t min_size = record_size * JNI_MIN_TABLE_METHODS;
|
||||
if (!jni_section_is_scannable (section, min_size)) {
|
||||
return;
|
||||
}
|
||||
ut64 section_addr;
|
||||
if (!jni_resolve_addr (ctx, section->vaddr, R_PERM_R, §ion_addr)) {
|
||||
return;
|
||||
}
|
||||
size_t size = (size_t)section->size;
|
||||
if (section_addr > UT64_MAX - size) {
|
||||
return;
|
||||
}
|
||||
ut8 *buf = R_NEWS (ut8, size);
|
||||
if (!ctx->anal->iob.read_at (ctx->anal->iob.io, section_addr, buf, (int)size)) {
|
||||
free (buf);
|
||||
return;
|
||||
}
|
||||
size_t offset = (ctx->pointer_size - (section_addr % ctx->pointer_size))
|
||||
% ctx->pointer_size;
|
||||
while (offset <= size - min_size) {
|
||||
JniTable *table = jni_parse_table (ctx, buf, size, offset, section_addr);
|
||||
if (!table) {
|
||||
offset += ctx->pointer_size;
|
||||
continue;
|
||||
}
|
||||
jni_store_table (ctx, table);
|
||||
offset += r_list_length (table->methods) * record_size;
|
||||
jni_table_free (table);
|
||||
}
|
||||
free (buf);
|
||||
}
|
||||
|
||||
static int jni_scan(RAnal *anal) {
|
||||
if (!anal->iob.read_at || !anal->iob.get_region_at
|
||||
|| !anal->binb.bin || !anal->binb.bin->cur
|
||||
|| !anal->binb.bin->cur->bo || !anal->binb.get_sections_vec) {
|
||||
return 0;
|
||||
}
|
||||
const int pointer_size = anal->config->bits / 8;
|
||||
if (pointer_size != 4 && pointer_size != 8) {
|
||||
return 0;
|
||||
}
|
||||
Sdb *db = sdb_ns (anal->sdb, "jni", 1);
|
||||
sdb_reset (db);
|
||||
JniScanContext ctx = {
|
||||
.anal = anal,
|
||||
.bo = anal->binb.bin->cur->bo,
|
||||
.db = db,
|
||||
.pointer_size = pointer_size,
|
||||
.big_endian = R_ARCH_CONFIG_IS_BIG_ENDIAN (anal->config),
|
||||
};
|
||||
RVecRBinSection *sections = anal->binb.get_sections_vec (anal->binb.bin);
|
||||
RBinSection *section;
|
||||
R_VEC_FOREACH (sections, section) {
|
||||
jni_scan_section (&ctx, section);
|
||||
}
|
||||
sdb_num_set (db, "tables", ctx.tables_count, 0);
|
||||
return ctx.tables_count;
|
||||
}
|
||||
|
||||
static bool jni_pre_analysis(RAnal *anal) {
|
||||
return jni_scan (anal) > 0;
|
||||
}
|
||||
|
||||
static int jni_eligible(RAnal *anal) {
|
||||
if (!anal || !anal->binb.bin || !anal->binb.bin->cur
|
||||
|| !anal->binb.bin->cur->bo) {
|
||||
return -1;
|
||||
}
|
||||
return R_VPACK_HAS (anal->binb.bin->cur->bo->langs, R_BIN_LANG_JNI)? 0: -1;
|
||||
}
|
||||
|
||||
RAnalPlugin r_anal_plugin_jni = {
|
||||
.meta = {
|
||||
.name = "jni",
|
||||
.desc = "JNI native method table discovery",
|
||||
.author = "pancake",
|
||||
.license = "LGPL3",
|
||||
},
|
||||
.eligible = jni_eligible,
|
||||
.pre_analysis = jni_pre_analysis,
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
R_API RLibStruct radare_plugin = {
|
||||
.type = R_LIB_TYPE_ANAL,
|
||||
.data = &r_anal_plugin_jni,
|
||||
.version = R2_VERSION,
|
||||
.abiversion = R2_ABIVERSION
|
||||
};
|
||||
#endif
|
||||
11
libr/anal/p/jni.mk
Normal file
11
libr/anal/p/jni.mk
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
OBJ_JNI=anal_jni.o
|
||||
|
||||
STATIC_OBJ+=${OBJ_JNI}
|
||||
TARGET_JNI=anal_jni.${EXT_SO}
|
||||
|
||||
ALL_TARGETS+=${TARGET_JNI}
|
||||
|
||||
${TARGET_JNI}: ${OBJ_JNI}
|
||||
${CC} $(call libname,anal_jni) ${LDFLAGS} \
|
||||
${CFLAGS} -o anal_jni.${EXT_SO} ${OBJ_JNI} \
|
||||
-L../../bin -lr_bin
|
||||
|
|
@ -1854,6 +1854,7 @@ extern RAnalPlugin r_anal_plugin_path;
|
|||
extern RAnalPlugin r_anal_plugin_sbpf;
|
||||
extern RAnalPlugin r_anal_plugin_tcc;
|
||||
extern RAnalPlugin r_anal_plugin_gopclntab;
|
||||
extern RAnalPlugin r_anal_plugin_jni;
|
||||
extern RAnalPlugin r_anal_plugin_six;
|
||||
extern RAnalPlugin r_anal_plugin_thumb;
|
||||
extern RAnalPlugin r_anal_plugin_tp;
|
||||
|
|
|
|||
173
test/unit/test_anal_jni.c
Normal file
173
test/unit/test_anal_jni.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#include <r_core.h>
|
||||
#include "minunit.h"
|
||||
|
||||
#define DATA_ADDR 0x1000
|
||||
#define STRINGS_ADDR 0x2000
|
||||
#define CODE_ADDR 0x3000
|
||||
#define RECORD_SIZE64 24
|
||||
#define REBASE 0x10000
|
||||
|
||||
static void write_record(ut8 *buf, int index, int ps, bool be, ut64 name, ut64 desc, ut64 fcn) {
|
||||
ut8 *record = buf + index * ps * 3;
|
||||
if (ps == 8) {
|
||||
r_write_ble64 (record, name, be);
|
||||
r_write_ble64 (record + 8, desc, be);
|
||||
r_write_ble64 (record + 16, fcn, be);
|
||||
} else {
|
||||
r_write_ble32 (record, name, be);
|
||||
r_write_ble32 (record + 4, desc, be);
|
||||
r_write_ble32 (record + 8, fcn, be);
|
||||
}
|
||||
}
|
||||
|
||||
static RCore *jni_test_core_new(int bits, bool big_endian, ut64 shift) {
|
||||
RCore *core = r_core_new ();
|
||||
core->io->va = true;
|
||||
r_io_open_at (core->io, "malloc://128", R_PERM_RW, 0644, DATA_ADDR + shift);
|
||||
r_io_open_at (core->io, "malloc://256", R_PERM_RW, 0644, STRINGS_ADDR + shift);
|
||||
r_io_open_at (core->io, "malloc://128", R_PERM_RWX, 0644, CODE_ADDR + shift);
|
||||
r_config_set (core->config, "asm.arch", "x86");
|
||||
r_config_set_i (core->config, "asm.bits", bits);
|
||||
core->anal->config->endian = big_endian? R_SYS_ENDIAN_BIG: R_SYS_ENDIAN_LITTLE;
|
||||
|
||||
RBinFile *bf = R_NEW0 (RBinFile);
|
||||
RBinObject *bo = R_NEW0 (RBinObject);
|
||||
bf->bo = bo;
|
||||
bf->rbin = core->bin;
|
||||
core->bin->cur = bf;
|
||||
RVecRBinSection_init (&bo->sections_vec);
|
||||
RBinSection *section = RVecRBinSection_emplace_back (&bo->sections_vec);
|
||||
section->name = strdup (".data");
|
||||
section->vaddr = DATA_ADDR;
|
||||
section->paddr = DATA_ADDR;
|
||||
section->size = (bits / 8) * 3 * 3;
|
||||
section->vsize = section->size;
|
||||
section->perm = R_PERM_RW;
|
||||
bo->baddr_shift = shift;
|
||||
bo->langs = r_vpack_add (bo->langs, R_BIN_LANG_JNI);
|
||||
return core;
|
||||
}
|
||||
|
||||
static void jni_test_core_free(RCore *core) {
|
||||
RBinFile *bf = core->bin->cur;
|
||||
core->bin->cur = NULL;
|
||||
RVecRBinSection_fini (&bf->bo->sections_vec);
|
||||
free (bf->bo);
|
||||
free (bf);
|
||||
r_core_free (core);
|
||||
}
|
||||
|
||||
static RAnalPlugin *jni_test_plugin(RAnal *anal) {
|
||||
RListIter *iter;
|
||||
RAnalPlugin *plugin;
|
||||
r_list_foreach (anal->libstore->plugins, iter, plugin) {
|
||||
if (!strcmp (plugin->meta.name, "jni")) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool test_jni_native_method_table_scan(void) {
|
||||
RCore *core = jni_test_core_new (64, false, 0);
|
||||
mu_assert_notnull (core, "create test core");
|
||||
ut8 strings[0x90] = { 0 };
|
||||
memcpy (strings, "first", sizeof ("first"));
|
||||
memcpy (strings + 0x10, "(I)V", sizeof ("(I)V"));
|
||||
memcpy (strings + 0x20, "second", sizeof ("second"));
|
||||
memcpy (strings + 0x30, "()I", sizeof ("()I"));
|
||||
memcpy (strings + 0x40, "onlyOne", sizeof ("onlyOne"));
|
||||
memcpy (strings + 0x50, "not-a-descriptor", sizeof ("not-a-descriptor"));
|
||||
memcpy (strings + 0x70, "looksValid", sizeof ("looksValid"));
|
||||
memcpy (strings + 0x80, "(V)V", sizeof ("(V)V"));
|
||||
mu_assert_true (r_io_write_at (core->io, STRINGS_ADDR, strings, sizeof (strings)),
|
||||
"write JNI strings");
|
||||
|
||||
ut8 data[RECORD_SIZE64 * 3] = { 0 };
|
||||
write_record (data, 0, 8, false, STRINGS_ADDR, STRINGS_ADDR + 0x10, CODE_ADDR);
|
||||
write_record (data, 1, 8, false, STRINGS_ADDR + 0x20, STRINGS_ADDR + 0x30,
|
||||
CODE_ADDR + 0x10);
|
||||
write_record (data, 2, 8, false, STRINGS_ADDR + 0x40, STRINGS_ADDR + 0x50,
|
||||
CODE_ADDR + 0x20);
|
||||
mu_assert_true (r_io_write_at (core->io, DATA_ADDR, data, sizeof (data)),
|
||||
"write valid JNI table");
|
||||
RAnalPlugin *plugin = jni_test_plugin (core->anal);
|
||||
mu_assert_notnull (plugin, "find JNI analysis plugin");
|
||||
mu_assert_eq (plugin->eligible (core->anal), 0, "JNI plugin eligibility");
|
||||
mu_assert_true (plugin->pre_analysis (core->anal), "discover JNI table");
|
||||
Sdb *db = sdb_ns (core->anal->sdb, "jni", 0);
|
||||
mu_assert_notnull (db, "JNI analysis database");
|
||||
mu_assert_eq (sdb_num_get (db, "tables", 0), 1, "one JNI table");
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.addr", 0), DATA_ADDR, "table address");
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.count", 0), 2, "method count");
|
||||
mu_assert_streq (sdb_const_get (db, "table.0.method.0.name", 0), "first",
|
||||
"first method name");
|
||||
mu_assert_streq (sdb_const_get (db, "table.0.method.0.descriptor", 0), "(I)V",
|
||||
"first method descriptor");
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.method.1.function", 0), CODE_ADDR + 0x10,
|
||||
"second method function");
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
write_record (data, 0, 8, false, STRINGS_ADDR, STRINGS_ADDR + 0x10, CODE_ADDR);
|
||||
mu_assert_true (r_io_write_at (core->io, DATA_ADDR, data, sizeof (data)),
|
||||
"write one-record JNI candidate");
|
||||
mu_assert_false (plugin->pre_analysis (core->anal),
|
||||
"reject a single JNI record");
|
||||
mu_assert_eq (sdb_num_get (db, "tables", 0), 0, "single record is not a table");
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
write_record (data, 0, 8, false, STRINGS_ADDR + 0x40, STRINGS_ADDR + 0x50,
|
||||
CODE_ADDR);
|
||||
write_record (data, 1, 8, false, STRINGS_ADDR + 0x70, STRINGS_ADDR + 0x80,
|
||||
CODE_ADDR + 0x10);
|
||||
mu_assert_true (r_io_write_at (core->io, DATA_ADDR, data, sizeof (data)),
|
||||
"write non-JNI pointer triples");
|
||||
mu_assert_false (plugin->pre_analysis (core->anal),
|
||||
"reject malformed JNI descriptors");
|
||||
mu_assert_eq (sdb_num_get (db, "tables", 0), 0, "invalid triples are not a table");
|
||||
|
||||
jni_test_core_free (core);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_jni_big_endian_32bit_table(void) {
|
||||
RCore *core = jni_test_core_new (32, true, REBASE);
|
||||
mu_assert_notnull (core, "create 32-bit test core");
|
||||
ut8 strings[0x40] = { 0 };
|
||||
memcpy (strings, "first", sizeof ("first"));
|
||||
memcpy (strings + 0x10, "(I)V", sizeof ("(I)V"));
|
||||
memcpy (strings + 0x20, "second", sizeof ("second"));
|
||||
memcpy (strings + 0x30, "()I", sizeof ("()I"));
|
||||
mu_assert_true (r_io_write_at (core->io, STRINGS_ADDR + REBASE, strings, sizeof (strings)),
|
||||
"write 32-bit JNI strings");
|
||||
ut8 data[36] = { 0 };
|
||||
write_record (data, 0, 4, true, STRINGS_ADDR, STRINGS_ADDR + 0x10, CODE_ADDR);
|
||||
write_record (data, 1, 4, true, STRINGS_ADDR + 0x20, STRINGS_ADDR + 0x30,
|
||||
CODE_ADDR + 0x10);
|
||||
mu_assert_true (r_io_write_at (core->io, DATA_ADDR + REBASE, data, sizeof (data)),
|
||||
"write big-endian JNI table");
|
||||
RAnalPlugin *plugin = jni_test_plugin (core->anal);
|
||||
mu_assert_notnull (plugin, "find JNI analysis plugin");
|
||||
mu_assert_true (plugin->pre_analysis (core->anal), "discover big-endian table");
|
||||
Sdb *db = sdb_ns (core->anal->sdb, "jni", 0);
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.addr", 0), DATA_ADDR + REBASE,
|
||||
"rebased table address");
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.count", 0), 2, "32-bit method count");
|
||||
mu_assert_eq (sdb_num_get (db, "table.0.method.1.function", 0),
|
||||
CODE_ADDR + REBASE + 0x10,
|
||||
"32-bit function pointer");
|
||||
jni_test_core_free (core);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool all_tests(void) {
|
||||
mu_run_test (test_jni_native_method_table_scan);
|
||||
mu_run_test (test_jni_big_endian_32bit_table);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return all_tests ();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue