rizin/librz/bin/p/bin_dex.c
2020-10-05 21:39:56 +08:00

2214 lines
60 KiB
C

/* radare - LGPL - Copyright 2011-2020 - pancake, h4ng3r */
#include <rz_cons.h>
#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
#include <rz_bin.h>
#include "../i/private.h"
#include "dex/dex.h"
#define rz_hash_adler32 __adler32
#include "../../hash/adler32.c"
// globals to kill
extern struct rz_bin_dbginfo_t rz_bin_dbginfo_dex;
static bool dexdump = false;
static Sdb *mdb = NULL;
static const char *dexSubsystem = NULL;
static bool simplifiedDemangling = false; // depends on asm.pseudo
static ut64 get_method_flags(ut64 MA) {
ut64 flags = 0;
if (MA & RZ_DEX_METH_PUBLIC) {
flags |= RZ_BIN_METH_PUBLIC;
}
if (MA & RZ_DEX_METH_PRIVATE) {
flags |= RZ_BIN_METH_PRIVATE;
}
if (MA & RZ_DEX_METH_PROTECTED) {
flags |= RZ_BIN_METH_PROTECTED;
}
if (MA & RZ_DEX_METH_STATIC) {
flags |= RZ_BIN_METH_STATIC;
}
if (MA & RZ_DEX_METH_FINAL) {
flags |= RZ_BIN_METH_FINAL;
}
if (MA & RZ_DEX_METH_SYNCHRONIZED) {
flags |= RZ_BIN_METH_SYNCHRONIZED;
}
if (MA & RZ_DEX_METH_BRIDGE) {
flags |= RZ_BIN_METH_BRIDGE;
}
if (MA & RZ_DEX_METH_VARARGS) {
flags |= RZ_BIN_METH_VARARGS;
}
if (MA & RZ_DEX_METH_NATIVE) {
flags |= RZ_BIN_METH_NATIVE;
}
if (MA & RZ_DEX_METH_ABSTRACT) {
flags |= RZ_BIN_METH_ABSTRACT;
}
if (MA & RZ_DEX_METH_STRICT) {
flags |= RZ_BIN_METH_STRICT;
}
if (MA & RZ_DEX_METH_SYNTHETIC) {
flags |= RZ_BIN_METH_SYNTHETIC;
}
if (MA & RZ_DEX_METH_MIRANDA) {
flags |= RZ_BIN_METH_MIRANDA;
}
if (MA & RZ_DEX_METH_CONSTRUCTOR) {
flags |= RZ_BIN_METH_CONSTRUCTOR;
}
if (MA & RZ_DEX_METH_DECLARED_SYNCHRONIZED) {
flags |= RZ_BIN_METH_DECLARED_SYNCHRONIZED;
}
return flags;
}
static ut64 offset_of_method_idx(RBinFile *bf, struct rz_bin_dex_obj_t *dex, int idx) {
// ut64 off = dex->header.method_offset + idx;
return sdb_num_get (mdb, sdb_fmt ("method.%d", idx), 0);
}
static ut64 dex_field_offset(RBinDexObj *bin, int fid) {
return bin->header.fields_offset + (fid * 8); // (sizeof (DexField) * fid);
}
static const char *getstr(RBinDexObj *dex, int idx) {
ut8 buf[LEB_MAX_SIZE];
if (idx < 0 || idx >= dex->header.strings_size || !dex->strings) {
return NULL;
}
if (dex->cal_strings) {
const char *p = dex->cal_strings[idx];
if (!RZ_STR_ISEMPTY (p)) {
return p;
}
} else {
dex->cal_strings = RZ_NEWS0 (char *, dex->header.strings_size);
}
const ut32 string_index = dex->strings[idx];
if (string_index >= dex->size) {
return NULL;
}
if (rz_buf_read_at (dex->b, string_index, buf, sizeof (buf)) != sizeof (buf)) {
return NULL;
}
ut64 len;
int uleblen = rz_uleb128 (buf, sizeof (buf), &len, NULL) - buf;
if (!uleblen || uleblen >= dex->size || uleblen >= dex->header.strings_size) {
return NULL;
}
if (!len || len >= dex->size) {
return NULL;
}
ut8 *ptr = malloc (len + 1);
if (ptr) {
rz_buf_read_at (dex->b, string_index + uleblen, ptr, len);
ptr[len] = 0;
dex->cal_strings[idx] = (char *)ptr;
return (const char *)ptr;
}
return NULL;
}
// TODO move to util
static int countOnes(ut32 val) {
if (!val) {
return 0;
}
/* visual studio doesnt supports __buitin_clz */
#if defined(_MSC_VER) || defined(__TINYC__)
int count = 0;
val = val - ((val >> 1) & 0x55555555);
val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
count = (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
return count;
#else
return __builtin_clz (val);
#endif
}
typedef enum {
kAccessForClass = 0,
kAccessForMethod = 1,
kAccessForField = 2,
kAccessForMAX
} AccessFor;
static char *createAccessFlagStr(ut32 flags, AccessFor forWhat) {
#define NUM_FLAGS 18
static const char *kAccessStrings[kAccessForMAX][NUM_FLAGS] = {
{
/* class, inner class */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"?", /* 0x0020 */
"?", /* 0x0040 */
"?", /* 0x0080 */
"?", /* 0x0100 */
"INTERFACE", /* 0x0200 */
"ABSTRACT", /* 0x0400 */
"?", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"ANNOTATION", /* 0x2000 */
"ENUM", /* 0x4000 */
"?", /* 0x8000 */
"VERIFIED", /* 0x10000 */
"OPTIMIZED", /* 0x20000 */
},
{
/* method */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"SYNCHRONIZED", /* 0x0020 */
"BRIDGE", /* 0x0040 */
"VARARGS", /* 0x0080 */
"NATIVE", /* 0x0100 */
"?", /* 0x0200 */
"ABSTRACT", /* 0x0400 */
"STRICT", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"?", /* 0x2000 */
"?", /* 0x4000 */
"MIRANDA", /* 0x8000 */
"CONSTRUCTOR", /* 0x10000 */
"DECLARED_SYNCHRONIZED", /* 0x20000 */
},
{
/* field */
"PUBLIC", /* 0x0001 */
"PRIVATE", /* 0x0002 */
"PROTECTED", /* 0x0004 */
"STATIC", /* 0x0008 */
"FINAL", /* 0x0010 */
"?", /* 0x0020 */
"VOLATILE", /* 0x0040 */
"TRANSIENT", /* 0x0080 */
"?", /* 0x0100 */
"?", /* 0x0200 */
"?", /* 0x0400 */
"?", /* 0x0800 */
"SYNTHETIC", /* 0x1000 */
"?", /* 0x2000 */
"ENUM", /* 0x4000 */
"?", /* 0x8000 */
"?", /* 0x10000 */
"?", /* 0x20000 */
},
};
size_t i, count = countOnes (flags);
const int kLongest = 21;
const int maxSize = (count + 1) * (kLongest + 1);
char* str, *cp;
// produces a huge number????
if (count < 1 || (count * (kLongest+1)) < 1) {
return NULL;
}
cp = str = (char*) calloc (count + 1, (kLongest + 1));
if (!str) {
return NULL;
}
for (i = 0; i < NUM_FLAGS; i++) {
if (flags & 0x01) {
const char *accessStr = kAccessStrings[forWhat][i];
int len = strlen (accessStr);
if (cp != str) {
*cp++ = ' ';
}
if (((cp - str) + len) >= maxSize) {
free (str);
return NULL;
}
memcpy (cp, accessStr, len);
cp += len;
}
flags >>= 1;
}
*cp = '\0';
return str;
}
static const char *dex_type_descriptor(RBinDexObj *bin, int type_idx) {
if (type_idx < 0 || type_idx >= bin->header.types_size) {
return NULL;
}
return getstr (bin, bin->types[type_idx].descriptor_id);
}
static ut16 type_desc(RBinDexObj *bin, ut16 type_idx) {
if (type_idx >= bin->header.types_size || type_idx >= bin->size) {
return UT16_MAX;
}
return bin->types[type_idx].descriptor_id;
}
static char *dex_get_proto(RBinDexObj *bin, int proto_id) {
if (proto_id >= bin->header.prototypes_size) {
return NULL;
}
ut32 params_off = bin->protos[proto_id].parameters_off;
if (params_off >= bin->size) {
return NULL;
}
ut32 type_id = bin->protos[proto_id].return_type_id;
if (type_id >= bin->header.types_size ) {
return NULL;
}
const char *return_type = getstr (bin, bin->types[type_id].descriptor_id);
if (!return_type) {
return NULL;
}
if (!params_off) {
return rz_str_newf ("()%s", return_type);;
}
ut8 params_buf[sizeof (ut32)];
if (!rz_buf_read_at (bin->b, params_off, params_buf, sizeof (params_buf))) {
return NULL;
}
// size of the list, in 16 bit entries
ut32 list_size = rz_read_le32 (params_buf);
if (list_size >= ST32_MAX) {
eprintf ("Warning: function prototype contains too many parameters (> 2 million).\n");
list_size = ST32_MAX;
}
size_t typeidx_bufsize = (list_size * sizeof (ut16));
if (params_off + typeidx_bufsize > bin->size) {
typeidx_bufsize = bin->size - params_off;
eprintf ("Warning: truncated typeidx buffer\n");
}
RStrBuf *sig = rz_strbuf_new ("(");
if (typeidx_bufsize > 0) {
ut8 *typeidx_buf = malloc (typeidx_bufsize);
if (!typeidx_buf || !rz_buf_read_at (bin->b, params_off + 4, typeidx_buf, typeidx_bufsize)) {
rz_strbuf_free (sig);
return NULL;
}
size_t off;
for (off = 0; off + 1 < typeidx_bufsize; off += 2) {
ut16 type_idx = rz_read_le16 (typeidx_buf + off);
ut16 type_desc_id = type_desc (bin, type_idx);
if (type_desc_id == UT16_MAX) {
rz_strbuf_append (sig, "?;");
} else {
const char *buff = getstr (bin, type_desc_id);
rz_strbuf_append (sig, buff? buff: "?;");
}
}
free (typeidx_buf);
}
rz_strbuf_appendf (sig, ")%s", return_type);
return rz_strbuf_drain (sig);
}
static char *dex_method_signature(RBinDexObj *bin, int method_idx) {
if (method_idx < 0 || method_idx >= bin->header.method_size) {
return NULL;
}
return dex_get_proto (bin, bin->methods[method_idx].proto_id);
}
static ut32 read32(RBuffer* b, ut64 addr) {
ut32 n = 0;
rz_buf_read_at (b, addr, (ut8*)&n, sizeof (n));
return rz_read_le32 (&n);
}
static ut16 read16(RBuffer* b, ut64 addr) {
ut16 n = 0;
rz_buf_read_at (b, addr, (ut8*)&n, sizeof (n));
return rz_read_le16 (&n);
}
static RzList *dex_method_signature2(RBinDexObj *bin, int method_idx) {
ut32 proto_id, params_off, list_size;
ut16 type_idx;
size_t i;
RzList *params = rz_list_new ();
if (!params) {
return NULL;
}
if (method_idx < 0 || method_idx >= bin->header.method_size) {
goto out_error;
}
proto_id = bin->methods[method_idx].proto_id;
if (proto_id >= bin->header.prototypes_size) {
goto out_error;
}
params_off = bin->protos[proto_id].parameters_off;
if (params_off >= bin->size) {
goto out_error;
}
if (!params_off) {
return params;
}
list_size = read32 (bin->b, params_off);
for (i = 0; i < list_size; i++) {
ut64 of = params_off + 4 + (i * 2);
if (of >= bin->size || of < params_off) {
break;
}
type_idx = read16 (bin->b, of);
if (type_idx >= bin->header.types_size || type_idx > bin->size) {
break;
}
const char *buff = getstr (bin, bin->types[type_idx].descriptor_id);
if (!buff) {
break;
}
rz_list_append (params, (void *)buff);
}
return params;
out_error:
rz_list_free (params);
return NULL;
}
// TODO: fix this, now has more registers that it should
// XXX. this is using binfile->buf directly :(
// https://github.com/android/platform_dalvik/blob/0641c2b4836fae3ee8daf6c0af45c316c84d5aeb/libdex/DexDebugInfo.cpp#L312
// https://github.com/android/platform_dalvik/blob/0641c2b4836fae3ee8daf6c0af45c316c84d5aeb/libdex/DexDebugInfo.cpp#L141
static void dex_parse_debug_item(RBinFile *bf, RBinDexClass *c, int MI, int MA, int paddr, int ins_size, int insns_size, char *class_name, int regsz, int debug_info_off) {
RBin *rbin = bf->rbin;
RBinDexObj *dex = bf->o->bin_obj; // bin .. unnecessary arg
// runtime error: pointer index expression with base 0x000000004402 overflowed to 0xffffffffff0043fc
if (debug_info_off >= rz_buf_size (bf->buf)) {
return;
}
ut64 line_start;
ut64 parameters_size;
ut64 param_type_idx;
ut16 argReg = regsz - ins_size;
ut64 source_file_idx = c->source_file;
RzList *params, *debug_positions, *emitted_debug_locals = NULL;
bool keep = true;
if (argReg > regsz) {
return; // this return breaks tests
}
rz_buf_seek (bf->buf, debug_info_off, RZ_BUF_SET);
ut64 res;
rz_buf_uleb128 (bf->buf, &res);
line_start = res;
rz_buf_uleb128 (bf->buf, &res);
parameters_size = res;
// TODO: check when we should use source_file
// The state machine consists of five registers
ut32 address = 0;
ut32 line = line_start;
if (!(debug_positions = rz_list_newf ((RzListFree)free))) {
return;
}
if (!(emitted_debug_locals = rz_list_newf ((RzListFree)free))) {
free (debug_positions);
return;
}
struct dex_debug_local_t *debug_locals = calloc (sizeof (struct dex_debug_local_t), regsz + 1);
if (!(MA & 0x0008)) {
debug_locals[argReg].name = "this";
debug_locals[argReg].descriptor = rz_str_newf ("%s;", class_name);
debug_locals[argReg].startAddress = 0;
debug_locals[argReg].signature = NULL;
debug_locals[argReg].live = true;
argReg++;
}
if (!(params = dex_method_signature2 (dex, MI))) {
goto beach;
}
RzListIter *iter;
const char *name;
char *type;
int reg;
rz_list_foreach (params, iter, type) {
if ((argReg >= regsz) || !type || parameters_size <= 0) {
goto beach;
}
(void)rz_buf_uleb128 (bf->buf, &res);
param_type_idx = res - 1;
name = getstr (dex, param_type_idx);
reg = argReg;
switch (type[0]) {
case 'D':
case 'J':
argReg += 2;
break;
default:
argReg += 1;
break;
}
if (!name || !*name) {
debug_locals[reg].name = name;
debug_locals[reg].descriptor = type;
debug_locals[reg].signature = NULL;
debug_locals[reg].startAddress = address;
debug_locals[reg].live = true;
}
parameters_size--;
}
ut8 opcode = 0;
if (rz_buf_read (bf->buf, &opcode, 1) != 1) {
goto beach;
}
while (keep) {
switch (opcode) {
case 0x0: // DBG_END_SEQUENCE
keep = false;
break;
case 0x1: // DBG_ADVANCE_PC
{
ut64 addr_diff;
rz_buf_uleb128 (bf->buf, &addr_diff);
address += addr_diff;
}
break;
case 0x2: // DBG_ADVANCE_LINE
{
st64 line_diff;
rz_buf_sleb128 (bf->buf, &line_diff);
line += line_diff;
}
break;
case 0x3: // DBG_START_LOCAL
{
ut64 register_num, name_idx, type_idx;
rz_buf_uleb128 (bf->buf, &register_num);
rz_buf_uleb128 (bf->buf, &name_idx);
rz_buf_uleb128 (bf->buf, &type_idx);
name_idx--;
type_idx--;
if (register_num >= regsz) {
goto beach;
}
// Emit what was previously there, if anything
// emitLocalCbIfLive
if (debug_locals[register_num].live) {
struct dex_debug_local_t *local = malloc (
sizeof (struct dex_debug_local_t));
if (!local) {
keep = false;
break;
}
local->name = debug_locals[register_num].name;
local->descriptor = debug_locals[register_num].descriptor;
local->startAddress = debug_locals[register_num].startAddress;
local->signature = debug_locals[register_num].signature;
local->live = true;
local->reg = register_num;
local->endAddress = address;
rz_list_append (emitted_debug_locals, local);
}
debug_locals[register_num].name = getstr (dex, name_idx);
debug_locals[register_num].descriptor = dex_type_descriptor (dex, type_idx);
debug_locals[register_num].startAddress = address;
debug_locals[register_num].signature = NULL;
debug_locals[register_num].live = true;
//eprintf("DBG_START_LOCAL %x %x %x\n", register_num, name_idx, type_idx);
}
break;
case 0x4: //DBG_START_LOCAL_EXTENDED
{
ut64 register_num, name_idx, type_idx, sig_idx;
rz_buf_uleb128 (bf->buf, &register_num);
rz_buf_uleb128 (bf->buf, &name_idx);
rz_buf_uleb128 (bf->buf, &type_idx);
rz_buf_uleb128 (bf->buf, &sig_idx);
sig_idx--;
type_idx--;
name_idx--;
if (register_num >= regsz) {
goto beach;
}
// Emit what was previously there, if anything
// emitLocalCbIfLive
if (debug_locals[register_num].live) {
struct dex_debug_local_t *local = malloc (
sizeof (struct dex_debug_local_t));
if (!local) {
keep = false;
break;
}
local->name = debug_locals[register_num].name;
local->descriptor = debug_locals[register_num].descriptor;
local->startAddress = debug_locals[register_num].startAddress;
local->signature = debug_locals[register_num].signature;
local->live = true;
local->reg = register_num;
local->endAddress = address;
rz_list_append (emitted_debug_locals, local);
}
debug_locals[register_num].name = getstr (dex, name_idx);
debug_locals[register_num].descriptor = dex_type_descriptor (dex, type_idx);
debug_locals[register_num].startAddress = address;
debug_locals[register_num].signature = getstr (dex, sig_idx);
debug_locals[register_num].live = true;
}
break;
case 0x5: // DBG_END_LOCAL
{
ut64 register_num;
rz_buf_uleb128 (bf->buf, &register_num);
// emitLocalCbIfLive
if (register_num >= regsz) {
goto beach;
}
if (debug_locals[register_num].live) {
struct dex_debug_local_t *local = malloc (
sizeof (struct dex_debug_local_t));
if (!local) {
keep = false;
break;
}
local->name = debug_locals[register_num].name;
local->descriptor = debug_locals[register_num].descriptor;
local->startAddress = debug_locals[register_num].startAddress;
local->signature = debug_locals[register_num].signature;
local->live = true;
local->reg = register_num;
local->endAddress = address;
rz_list_append (emitted_debug_locals, local);
}
debug_locals[register_num].live = false;
}
break;
case 0x6: // DBG_RESTART_LOCAL
{
ut64 register_num;
rz_buf_uleb128 (bf->buf, &register_num);
if (register_num >= regsz) {
goto beach;
}
if (!debug_locals[register_num].live) {
debug_locals[register_num].startAddress = address;
debug_locals[register_num].live = true;
}
}
break;
case 0x7: //DBG_SET_PROLOGUE_END
break;
case 0x8: //DBG_SET_PROLOGUE_BEGIN
break;
case 0x9:
{
ut64 res;
rz_buf_uleb128 (bf->buf, &res);
source_file_idx = res - 1;
}
break;
default:
{
int adjusted_opcode = opcode - 10;
address += (adjusted_opcode / 15);
line += -4 + (adjusted_opcode % 15);
struct dex_debug_position_t *position =
RZ_NEW0 (struct dex_debug_position_t);
if (!position) {
keep = false;
break;
}
position->source_file_idx = source_file_idx;
position->address = address;
position->line = line;
rz_list_append (debug_positions, position);
}
break;
}
if (rz_buf_read (bf->buf, &opcode, 1) != 1) {
break;
}
}
if (!bf->sdb_addrinfo) {
bf->sdb_addrinfo = sdb_new0 ();
}
RzListIter *iter1;
struct dex_debug_position_t *pos;
// Loading the debug info takes too much time and nobody uses this afaik
// 0.5s of 5s is spent in this loop
rz_list_foreach (debug_positions, iter1, pos) {
const char *line = getstr (dex, pos->source_file_idx);
char offset[64] = {0};
if (!line || !*line) {
continue;
}
char *fileline = rz_str_newf ("%s|%"PFMT64d, line, pos->line);
char *offset_ptr = sdb_itoa (pos->address + paddr, offset, 16);
sdb_set (bf->sdb_addrinfo, offset_ptr, fileline, 0);
sdb_set (bf->sdb_addrinfo, fileline, offset_ptr, 0);
free (fileline);
RBinDwarfRow *rbindwardrow = RZ_NEW0 (RBinDwarfRow);
if (!rbindwardrow) {
dexdump = false;
break;
}
if (line) {
rbindwardrow->file = strdup (line);
rbindwardrow->address = pos->address;
rbindwardrow->line = pos->line;
rz_list_append (dex->lines_list, rbindwardrow);
} else {
free (rbindwardrow);
}
}
if (!dexdump) {
goto beach;
}
RzListIter *iter2;
struct dex_debug_position_t *position;
rbin->cb_printf (" positions :\n");
rz_list_foreach (debug_positions, iter2, position) {
rbin->cb_printf (" 0x%04"PFMT64x" line=%llu\n",
position->address, position->line);
}
rbin->cb_printf (" locals :\n");
RzListIter *iter3;
struct dex_debug_local_t *local;
rz_list_foreach (emitted_debug_locals, iter3, local) {
if (local->signature) {
rbin->cb_printf (
" 0x%04x - 0x%04x reg=%d %s %s %s\n",
local->startAddress, local->endAddress,
local->reg, local->name, local->descriptor,
local->signature);
} else {
rbin->cb_printf (
" 0x%04x - 0x%04x reg=%d %s %s\n",
local->startAddress, local->endAddress,
local->reg, local->name, local->descriptor);
}
}
for (reg = 0; reg < regsz; reg++) {
if (!debug_locals[reg].name) {
continue;
}
if (debug_locals[reg].live) {
if (debug_locals[reg].signature) {
rbin->cb_printf (
" 0x%04x - 0x%04x reg=%d %s %s %s\n",
debug_locals[reg].startAddress,
insns_size, reg, debug_locals[reg].name,
debug_locals[reg].descriptor,
debug_locals[reg].signature);
} else {
rbin->cb_printf (
" 0x%04x - 0x%04x reg=%d %s %s\n",
debug_locals[reg].startAddress,
insns_size, reg, debug_locals[reg].name,
debug_locals[reg].descriptor);
}
}
}
beach:
rz_list_free (debug_positions);
rz_list_free (emitted_debug_locals);
rz_list_free (params);
free (debug_locals);
}
static Sdb *get_sdb(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o, NULL);
RBinObject *o = bf->o;
rz_return_val_if_fail (o && o->bin_obj, NULL);
struct rz_bin_dex_obj_t *bin = (struct rz_bin_dex_obj_t *) o->bin_obj;
return bin->kv;
}
static bool load_buffer(RBinFile *bf, void **bin_obj, RBuffer *buf, ut64 loadaddr, Sdb *sdb) {
*bin_obj = rz_bin_dex_new_buf (buf);
return *bin_obj != NULL;
}
static ut64 baddr(RBinFile *bf) {
return 0;
}
static bool check_buffer(RBuffer *buf) {
ut8 tmp[8];
int r = rz_buf_read_at (buf, 0, tmp, sizeof (tmp));
if (r < sizeof (tmp)) {
return false;
}
// Non-extended opcode dex file
if (!memcmp (tmp, "dex\n035\0", 8)) {
return true;
}
// Extended (jumnbo) opcode dex file, ICS+ only (sdk level 14+)
if (!memcmp (tmp, "dex\n036\0", 8)) {
return true;
}
// Two new opcodes: invoke-polymorphic and invoke-custom (sdk level 26+)
if (!memcmp (tmp, "dex\n038\0", 8)) {
return true;
}
// M3 (Nov-Dec 07)
if (!memcmp (tmp, "dex\n009\0", 8)) {
return true;
}
// M5 (Feb-Mar 08)
if (!memcmp (tmp, "dex\n009\0", 8)) {
return true;
}
// Default fall through, should still be a dex file
if (!memcmp (tmp, "dex\n", 4)) {
return true;
}
return false;
}
static RBinInfo *info(RBinFile *bf) {
RBinHash *h;
RBinInfo *ret = RZ_NEW0 (RBinInfo);
if (!ret) {
return NULL;
}
ret->file = bf->file? strdup (bf->file): NULL;
ret->type = strdup ("DEX CLASS");
ret->has_va = true;
ret->has_lit = true;
ret->bclass = rz_bin_dex_get_version (bf->o->bin_obj);
ret->rclass = strdup ("class");
ret->os = strdup ("linux");
ret->subsystem = strdup (dexSubsystem? dexSubsystem: "java");
ret->machine = strdup ("Dalvik VM");
h = &ret->sum[0];
h->type = "sha1";
h->len = 20;
h->addr = 12;
h->from = 12;
h->to = rz_buf_size (bf->buf) - 32;
rz_buf_read_at (bf->buf, 12, h->buf, 20);
h = &ret->sum[1];
h->type = "adler32";
h->len = 4;
h->addr = 8;
h->from = 12;
h->to = rz_buf_size (bf->buf) - h->from;
rz_buf_read_at (bf->buf, 8, h->buf, 12);
h = &ret->sum[2];
h->type = 0;
rz_buf_read_at (bf->buf, 8, h->buf, 4);
// this is slow but computed once, so we can use rz_buf_data or just do rz_buf_read()
// not sure if we want to expose the computed checksum everytime we open the file
// also the checksum is computed by other methods in RBin, so maybe good to generalize
{
ut32 fc = rz_buf_read_le32_at (bf->buf, 8);
ut64 tmpsz;
const ut8 *tmp = rz_buf_data (bf->buf, &tmpsz);
ut32 cc = __adler32 (tmp + 12, tmpsz - 12);
if (fc != cc) {
eprintf ("# adler32 checksum doesn't match. Type this to fix it:\n");
eprintf ("wx `ph sha1 $s-32 @32` @12 ; wx `ph adler32 $s-12 @12` @8\n");
}
}
ret->arch = strdup ("dalvik");
ret->lang = "dalvik";
ret->bits = 32;
ret->big_endian = 0;
ret->dbg_info = 0; //1 | 4 | 8; /* Stripped | LineNums | Syms */
return ret;
}
static RzList *strings(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o, NULL);
RBinString *ptr = NULL;
RzList *ret = NULL;
int i;
ut64 len;
ut8 buf[LEB_MAX_SIZE];
ut64 off;
struct rz_bin_dex_obj_t *bin = (struct rz_bin_dex_obj_t *)bf->o->bin_obj;
if (!bin || !bin->strings) {
return NULL;
}
if (bin->header.strings_size > bin->size) {
bin->strings = NULL;
return NULL;
}
if (!(ret = rz_list_newf (free))) {
return NULL;
}
for (i = 0; i < bin->header.strings_size; i++) {
if (!(ptr = RZ_NEW0 (RBinString))) {
break;
}
if (bin->strings[i] > bin->size || bin->strings[i] + 6 > bin->size) {
goto out_error;
}
rz_buf_read_at (bin->b, bin->strings[i], buf, sizeof (buf));
rz_uleb128 (buf, sizeof (buf), &len, NULL);
if (len > 5 && len < RZ_BIN_SIZEOF_STRINGS) {
ptr->string = malloc (len + 1);
if (!ptr->string) {
goto out_error;
}
off = bin->strings[i] + rz_uleb128_len (buf, sizeof (buf));
if (off + len >= bin->size || off + len < len) {
free (ptr->string);
goto out_error;
}
rz_buf_read_at (bin->b, off, (ut8*)ptr->string, len);
ptr->string[len] = 0;
if ((ptr->string[0] == 'L' && strchr (ptr->string, '/')) || !strncmp (ptr->string, "[L", 2)) {
free (ptr->string);
free (ptr);
continue;
}
ptr->vaddr = ptr->paddr = bin->strings[i];
ptr->size = len;
ptr->length = len;
ptr->ordinal = i+1;
rz_list_append (ret, ptr);
} else {
free (ptr);
}
}
return ret;
out_error:
rz_list_free (ret);
free (ptr);
return NULL;
}
static const char *dex_method_name(RBinDexObj *bin, int idx) {
if (idx < 0 || idx >= bin->header.method_size) {
return NULL;
}
ut16 cid = bin->methods[idx].class_id;
if (cid >= bin->header.strings_size) {
return NULL;
}
int tid = bin->methods[idx].name_id;
if (tid < 0 || tid >= bin->header.strings_size) {
return NULL;
}
return getstr (bin, tid);
}
static char *simplify(char *s) {
char *p = (char *)rz_str_rchr (s, NULL, '/');
if (p) {
rz_str_cpy (s, p + 1);
}
rz_str_replace_char (s, '/', '.');
return s;
}
static char *dex_class_name_byid(RBinDexObj *bin, int cid) {
rz_return_val_if_fail (bin && bin->types, NULL);
if (cid < 0 || cid >= bin->header.types_size) {
return NULL;
}
int tid = bin->types[cid].descriptor_id;
const char *s = getstr (bin, tid);
if (s) {
char *r = strdup (s);
if (simplifiedDemangling) {
simplify (r);
}
return r;
}
return NULL;
}
static char *dex_class_name(RBinDexObj *bin, RBinDexClass *c) {
char *s = dex_class_name_byid (bin, c->class_id);
if (simplifiedDemangling) {
simplify (s);
if (*s == 'L') {
rz_str_cpy (s, s + 1);
}
}
return s;
}
static char *dex_field_name(RBinDexObj *bin, int fid) {
int tid;
ut16 cid, type_id;
rz_return_val_if_fail (bin && bin->fields, NULL);
if (fid < 0 || fid >= bin->header.fields_size) {
return NULL;
}
cid = bin->fields[fid].class_id;
if (cid >= bin->header.types_size) {
return NULL;
}
type_id = bin->fields[fid].type_id;
if (type_id >= bin->header.types_size) {
return NULL;
}
tid = bin->fields[fid].name_id;
const char *a = getstr (bin, bin->types[cid].descriptor_id);
const char *b = getstr (bin, tid);
const char *c = getstr (bin, bin->types[type_id].descriptor_id);
if (simplifiedDemangling) {
if (a && b && c) {
char *_a = simplify (strdup (a));
char *_b = simplify (strdup (b));
char *_c = simplify (strdup (c));
char *str = rz_str_newf ("(%s) %s.%s", _c, _a, _b);
free (_a);
free (_b);
free (_c);
return str;
}
return rz_str_newf ("(%d) %d.%d",
bin->types[type_id].descriptor_id,
tid,
bin->types[cid].descriptor_id);
}
return (a && b && c)
? rz_str_newf ("%s->%s %s", a, b, c)
: rz_str_newf ("%d->%d %d", bin->types[cid].descriptor_id, tid, bin->types[type_id].descriptor_id);
}
static char *dex_method_fullname(RBinDexObj *bin, int method_idx) {
rz_return_val_if_fail (bin && bin->types, NULL);
if (method_idx < 0 || method_idx >= bin->header.method_size) {
return NULL;
}
ut16 cid = bin->methods[method_idx].class_id;
if (cid >= bin->header.types_size) {
return NULL;
}
const char *name = dex_method_name (bin, method_idx);
if (!name) {
return NULL;
}
char *flagname = NULL;
char *class_name = dex_class_name_byid (bin, cid);
if (!class_name) {
class_name = strdup ("???");
}
rz_str_replace_char (class_name, ';', 0);
char *signature = dex_method_signature (bin, method_idx);
if (signature) {
flagname = rz_str_newf ("%s.%s%s", class_name, name, signature);
free (signature);
} else {
flagname = rz_str_newf ("%s.%s%s", class_name, name, "???");
}
free (class_name);
if (flagname && simplifiedDemangling) {
char *p = strchr (flagname, '(');
if (p) {
*p = 0;
char *q = strchr (p + 1, ')');
if (q) {
simplify (q + 1);
rz_str_cpy (p, q + 1);
}
simplify (flagname);
}
}
return flagname;
}
static ut64 dex_get_type_offset(RBinFile *bf, int type_idx) {
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
if (!bin || !bin->types) {
return 0;
}
if (type_idx < 0 || type_idx >= bin->header.types_size) {
return 0;
}
return bin->header.types_offset + type_idx * 0x04; //&bin->types[type_idx];
}
static const char *dex_class_super_name(RBinDexObj *bin, RBinDexClass *c) {
rz_return_val_if_fail (bin && bin->types && c, NULL);
int cid = c->super_class;
if (cid < 0 || cid >= bin->header.types_size) {
return NULL;
}
int tid = bin->types[cid].descriptor_id;
return getstr (bin, tid);
}
static ut64 peek_uleb(RBuffer *b, bool *err, size_t *nn) {
ut64 n = UT64_MAX;
int len = rz_buf_uleb128 (b, &n);
if (len < 1) {
if (err) {
*err |= true;
}
} else {
*nn += len;
}
return n;
}
static void parse_dex_class_fields(RBinFile *bf, RBinDexClass *c, RBinClass *cls,
int *sym_count, ut64 fields_count, bool is_sfield) {
RBinDexObj *dex = bf->o->bin_obj;
RBin *bin = bf->rbin;
ut64 lastIndex = 0;
ut8 ff[sizeof (DexField)] = {0};
int total, tid;
DexField field;
size_t i, skip = 0;
for (i = 0; i < fields_count; i++) {
bool err = false;
ut64 fieldIndex = peek_uleb (bf->buf, &err, &skip);
ut64 accessFlags = peek_uleb (bf->buf, &err, &skip);
if (err) {
break;
}
fieldIndex += lastIndex;
total = dex->header.fields_offset + (sizeof (DexField) * fieldIndex);
if (total >= dex->size || total < dex->header.fields_offset) {
break;
}
if (rz_buf_read_at (bf->buf, total, ff, sizeof (DexField)) != sizeof (DexField)) {
break;
}
field.class_id = rz_read_le16 (ff);
field.type_id = rz_read_le16 (ff + 2);
field.name_id = rz_read_le32 (ff + 4);
const char *fieldName = getstr (dex, field.name_id);
if (field.type_id >= dex->header.types_size) {
break;
}
tid = dex->types[field.type_id].descriptor_id;
const char *type_str = getstr (dex, tid);
RBinSymbol *sym = RZ_NEW0 (RBinSymbol);
if (!sym) {
break;
}
if (is_sfield) {
sym->name = rz_str_newf ("%s.sfield_%s:%s", cls->name, fieldName, type_str);
sym->type = "STATIC";
} else {
sym->name = rz_str_newf ("%s.ifield_%s:%s", cls->name, fieldName, type_str);
sym->type = "FIELD";
}
sym->name = rz_str_replace (sym->name, "method.", "", 0);
rz_str_replace_char (sym->name, ';', 0);
sym->paddr = sym->vaddr = total;
sym->ordinal = (*sym_count)++;
if (dexdump) {
const char *accessStr = createAccessFlagStr (
accessFlags, kAccessForField);
bin->cb_printf (" #%d : (in %s;)\n", i,
cls->name);
bin->cb_printf (" name : '%s'\n", fieldName);
bin->cb_printf (" type : '%s'\n", type_str);
bin->cb_printf (" access : 0x%04x (%s)\n",
(ut32)accessFlags, accessStr? accessStr: "");
}
rz_list_append (dex->methods_list, sym);
RBinField *field = RZ_NEW0 (RBinField);
if (field) {
field->vaddr = field->paddr = sym->paddr;
field->name = strdup (sym->name);
field->flags = get_method_flags (accessFlags);
rz_list_append (cls->fields, field);
}
lastIndex = fieldIndex;
}
}
// TODO: refactor this method
// XXX it needs a lot of love!!!
static void parse_dex_class_method(RBinFile *bf, RBinDexClass *c, RBinClass *cls,
int *sym_count, ut64 DM, int *methods, bool is_direct) {
PrintfCallback cb_printf = bf->rbin->cb_printf;
RBinDexObj *dex = bf->o->bin_obj;
bool bin_dbginfo = bf->rbin->want_dbginfo;
int i;
ut64 omi = 0;
bool catchAll;
ut16 regsz = 0, ins_size = 0, outs_size = 0, tries_size = 0;
ut16 start_addr, insn_count = 0;
ut32 debug_info_off = 0, insns_size = 0;
if (!dex->trycatch_list) {
dex->trycatch_list = rz_list_newf ((RzListFree)rz_bin_trycatch_free);
}
size_t skip = 0;
ut64 bufsz = rz_buf_size (bf->buf);
ut64 encoded_method_addr;
bool err = false;
ut64 MI, MA, MC;
for (i = 0; i < DM; i++) {
err = false;
skip = 0;
// Needed because theres another rbufseek call inside this loop. must be fixed
encoded_method_addr = rz_buf_tell (bf->buf);
MI = peek_uleb (bf->buf, &err, &skip);
if (err) {
eprintf ("Error\n");
break;
}
MI += omi;
omi = MI;
MA = peek_uleb (bf->buf, &err, &skip);
if (err) {
eprintf ("Error\n");
break;
}
MC = peek_uleb (bf->buf, &err, &skip);
if (err) {
eprintf ("Error\n");
break;
}
// TODO: MOVE CHECKS OUTSIDE!
if (MI < dex->header.method_size) {
if (methods) {
methods[MI] = 1;
}
}
const char *method_name = dex_method_name (dex, MI);
char *signature = dex_method_signature (dex, MI);
if (!method_name) {
method_name = strdup ("unknown");
}
char *flag_name = rz_str_newf ("%s.method.%s%s", cls->name, method_name, signature);
if (!flag_name || !*flag_name) {
RZ_FREE (flag_name);
RZ_FREE (signature);
continue;
}
// TODO: check size
// ut64 prolog_size = 2 + 2 + 2 + 2 + 4 + 4;
ut64 v2, handler_type, handler_addr;
int t = 0;
if (MC > 0) {
// TODO: parse debug info
// XXX why bf->buf->base???
if (MC + 16 >= dex->size || MC + 16 < MC) {
RZ_FREE (flag_name);
RZ_FREE (signature);
continue;
}
if (bufsz < MC || bufsz < MC + 16) {
RZ_FREE (flag_name);
RZ_FREE (signature);
continue;
}
regsz = rz_buf_read_le16_at (bf->buf, MC);
if (regsz == UT16_MAX) {
RZ_FREE (flag_name);
RZ_FREE (signature);
break;
}
ins_size = rz_buf_read_le16_at (bf->buf, MC + 2);
if (ins_size == UT16_MAX) {
RZ_FREE (flag_name);
RZ_FREE (signature);
break;
}
outs_size = rz_buf_read_le16_at (bf->buf, MC + 4);
tries_size = rz_buf_read_le16_at (bf->buf, MC + 6);
if (tries_size == UT16_MAX) {
RZ_FREE (flag_name);
RZ_FREE (signature);
break;
}
debug_info_off = rz_buf_read_le32_at (bf->buf, MC + 8);
insns_size = rz_buf_read_le32_at (bf->buf, MC + 12);
int padd = 0;
if (tries_size > 0 && insns_size % 2) {
padd = 2;
}
t = 16 + 2 * insns_size + padd;
}
if (dexdump) {
const char* accessStr = createAccessFlagStr (MA, kAccessForMethod);
cb_printf (" #%d : (in %s;)\n", i, cls->name);
cb_printf (" name : '%s'\n", method_name);
cb_printf (" type : '%s'\n", signature);
cb_printf (" access : 0x%04x (%s)\n", (ut32)MA, accessStr);
}
if (MC > 0) {
if (dexdump) {
cb_printf (" code -\n");
cb_printf (" registers : %d\n", regsz);
cb_printf (" ins : %d\n", ins_size);
cb_printf (" outs : %d\n", outs_size);
cb_printf (
" insns size : %d 16-bit code "
"units\n",
insns_size);
}
if (tries_size > 0) {
if (dexdump) {
cb_printf (" catches : %d\n", tries_size);
}
int j, m = 0;
//XXX bucle controlled by tainted variable it could produces huge loop
ut64 offorig = rz_buf_tell (bf->buf);
for (j = 0; j < tries_size; j++) {
ut64 offset = MC + t + j * 8;
if (offset >= dex->size || offset < MC) {
RZ_FREE (signature);
break;
}
if (bufsz < offset || bufsz < offset + 8) {
RZ_FREE (signature);
break;
}
// start address of the block of code covered by this entry.
// The address is a count of 16-bit code units to the start of the first covered instruction.
start_addr = rz_buf_read_le32_at (bf->buf, offset);
// number of 16-bit code units covered by this entry.
// The last code unit covered (inclusive) is start_addr + insn_count - 1.
insn_count = rz_buf_read_le16_at (bf->buf, offset + 4);
// offset in bytes from the start of the associated encoded_catch_hander_list
// to the encoded_catch_handler for this entry.
// This must be an offset to the start of an encoded_catch_handler.
ut64 handler_off = rz_buf_read_le16_at (bf->buf, offset + 6);
ut64 method_offset = MC + 16;
ut64 try_from = (start_addr * 2) + method_offset;
ut64 try_to = (start_addr * 2) + (insn_count * 2) + method_offset + 2;
ut64 try_catch = try_to + handler_off - 1;
if (dexdump) {
cb_printf (" 0x%04x - 0x%04x\n", start_addr, (start_addr + insn_count));
}
RBinTrycatch *tc = rz_bin_trycatch_new (method_offset, try_from, try_to, try_catch, 0);
rz_list_append (dex->trycatch_list, tc);
//XXX tries_size is tainted and oob here
int off = MC + t + tries_size * 8 + handler_off;
if (off >= dex->size || off < tries_size) {
RZ_FREE (signature);
break;
}
// TODO: catch left instead of null
st64 size;
if (rz_buf_seek (bf->buf, off, RZ_BUF_SET) == -1) {
break;
}
int r = rz_buf_sleb128 (bf->buf, &size);
if (r <= 0) {
break;
}
if (size <= 0) {
catchAll = true;
size = -size;
// XXX this is probably wrong
} else {
catchAll = false;
}
for (m = 0; m < size; m++) {
r = rz_buf_uleb128 (bf->buf, &handler_type);
if (r <= 0) {
break;
}
r = rz_buf_uleb128 (bf->buf, &handler_addr);
if (r <= 0) {
break;
}
if (handler_type > 0 && handler_type < dex->header.types_size) {
const char *s = getstr (dex, dex->types[handler_type].descriptor_id);
if (dexdump) {
cb_printf (
" %s "
"-> 0x%04"PFMT64x"\n",
s,
handler_addr);
}
} else {
if (dexdump) {
cb_printf (" (error) -> 0x%04"PFMT64x"\n", handler_addr);
}
}
}
if (catchAll) {
r = rz_buf_uleb128 (bf->buf, &v2);
if (r <= 0) {
break;
}
if (dexdump) {
cb_printf (" <any> -> 0x%04"PFMT64x"\n", v2);
}
}
}
rz_buf_seek (bf->buf, offorig, RZ_BUF_SET);
} else {
if (dexdump) {
cb_printf (
" catches : "
"(none)\n");
}
}
} else {
if (dexdump) {
cb_printf (" code : (none)\n");
}
}
if (*flag_name) {
RBinSymbol *sym = RZ_NEW0 (RBinSymbol);
if (!sym) {
RZ_FREE (flag_name);
break;
}
sym->name = flag_name;
// is_direct is no longer used
// if method has code *addr points to code
// otherwise it points to the encoded method
if (MC > 0) {
sym->type = RZ_BIN_TYPE_FUNC_STR;
sym->paddr = MC;// + 0x10;
sym->vaddr = MC;// + 0x10;
} else {
sym->type = RZ_BIN_TYPE_METH_STR;
sym->paddr = encoded_method_addr;
sym->vaddr = encoded_method_addr;
}
dex->code_from = RZ_MIN (dex->code_from, sym->paddr);
sym->bind = ((MA & 1) == 1) ? RZ_BIN_BIND_GLOBAL_STR : RZ_BIN_BIND_LOCAL_STR;
sym->method_flags = get_method_flags (MA);
sym->ordinal = (*sym_count)++;
if (MC > 0) {
if (bufsz < MC || bufsz < MC + 16) {
RZ_FREE (sym);
RZ_FREE (signature);
continue;
}
ut16 tries_size = rz_buf_read_le16_at (bf->buf, MC + 6);
ut32 insns_size = rz_buf_read_le32_at (bf->buf, MC + 12);
ut64 prolog_size = 2 + 2 + 2 + 2 + 4 + 4;
if (tries_size > 0) {
//prolog_size += 2 + 8*tries_size; // we need to parse all so the catch info...
}
// TODO: prolog_size
sym->paddr = MC + prolog_size;// + 0x10;
sym->vaddr = MC + prolog_size;// + 0x10;
//if (is_direct) {
sym->size = insns_size * 2;
//}
//eprintf("%s (0x%x-0x%x) size=%d\nregsz=%d\ninsns_size=%d\nouts_size=%d\ntries_size=%d\ninsns_size=%d\n", flag_name, sym->vaddr, sym->vaddr+sym->size, prolog_size, regsz, ins_size, outs_size, tries_size, insns_size);
rz_list_append (dex->methods_list, sym);
rz_list_append (cls->methods, sym);
if (dex->code_from == UT64_MAX || dex->code_from > sym->paddr) {
dex->code_from = sym->paddr;
}
if (dex->code_to < sym->paddr) {
dex->code_to = sym->paddr + sym->size;
}
if (!mdb) {
mdb = sdb_new0 ();
}
sdb_num_set (mdb, sdb_fmt ("method.%"PFMT64d, MI), sym->paddr, 0);
// -----------------
// WORK IN PROGRESS
// -----------------
#if 0
if (0) {
if (MA & 0x10000) { //ACC_CONSTRUCTOR
if (!cdb) {
cdb = sdb_new0 ();
}
sdb_num_set (cdb, sdb_fmt ("%d", c->class_id), sym->paddr, 0);
}
}
#endif
} else {
sym->size = 0;
rz_list_append (dex->methods_list, sym);
rz_list_append (cls->methods, sym);
}
if (MC > 0 && debug_info_off > 0 && dex->header.data_offset < debug_info_off &&
debug_info_off < dex->header.data_offset + dex->header.data_size) {
if (bin_dbginfo) {
ut64 addr = rz_buf_tell (bf->buf);
dex_parse_debug_item (bf, c, MI, MA, sym->paddr, ins_size,
insns_size, cls->name, regsz, debug_info_off);
rz_buf_seek (bf->buf, addr, RZ_BUF_SET);
}
} else if (MC > 0) {
if (dexdump) {
cb_printf (" positions :\n");
cb_printf (" locals :\n");
}
}
} else {
RZ_FREE (flag_name);
}
RZ_FREE (signature);
}
}
static void parse_class(RBinFile *bf, RBinDexClass *c, int class_index, int *methods, int *sym_count) {
rz_return_if_fail (bf && bf->o && c);
RBinDexObj *dex = bf->o->bin_obj;
RBin *rbin = bf->rbin;
int z;
RBinClass *cls = RZ_NEW0 (RBinClass);
if (!cls) {
goto beach;
}
cls->name = dex_class_name (dex, c);
if (!cls->name) {
goto beach;
}
rz_str_replace_char (cls->name, ';', 0);
cls->index = class_index;
cls->addr = dex->header.class_offset + (class_index * DEX_CLASS_SIZE);
cls->methods = rz_list_new ();
const char *super = dex_class_super_name (dex, c);
cls->super = super? strdup (super): NULL;
if (!cls->methods) {
free (cls);
goto beach;
}
cls->fields = rz_list_new ();
if (!cls->fields) {
rz_list_free (cls->methods);
free (cls);
goto beach;
}
const char *str = createAccessFlagStr (c->access_flags, kAccessForClass);
cls->visibility_str = strdup (str? str: "");
rz_list_append (dex->classes_list, cls);
if (dexdump) {
rbin->cb_printf (" Class descriptor : '%s;'\n", cls->name);
rbin->cb_printf (" Access flags : 0x%04x (%s)\n", c->access_flags,
createAccessFlagStr (c->access_flags, kAccessForClass));
rbin->cb_printf (" Superclass : '%s'\n", cls->super);
rbin->cb_printf (" Interfaces -\n");
}
if (c->interfaces_offset > 0 &&
dex->header.data_offset < c->interfaces_offset &&
c->interfaces_offset < dex->header.data_offset + dex->header.data_size) {
int types_list_size = rz_buf_read_le32_at (bf->buf, c->interfaces_offset);
if (types_list_size < 0 || types_list_size >= dex->header.types_size ) {
goto beach;
}
for (z = 0; z < types_list_size; z++) {
ut16 le16;
ut32 off = c->interfaces_offset + 4 + (z * 2);
rz_buf_read_at (bf->buf, off, (ut8*)&le16, sizeof (le16));
int t = rz_read_le16 (&le16);
if (t > 0 && t < dex->header.types_size ) {
int tid = dex->types[t].descriptor_id;
if (dexdump) {
const char *cn = getstr (dex, tid);
rbin->cb_printf (" #%d : '%s'\n", z, cn);
}
}
}
}
// TODO: this is quite ugly
if (!c || !c->class_data_offset) {
if (dexdump) {
rbin->cb_printf (
" Static fields -\n"
" Instance fields -\n"
" Direct methods -\n"
" Virtual methods -\n");
}
} else {
// TODO: move to func, def or inline
// class_data_offset => [class_offset, class_defs_off+class_defs_size*32]
if (dex->header.class_offset > c->class_data_offset ||
c->class_data_offset <
dex->header.class_offset +
dex->header.class_size * DEX_CLASS_SIZE) {
goto beach;
}
RBinDexClassData *dc = RZ_NEW0 (RBinDexClassData);
if (!dc) {
goto beach;
}
bool err = false;
size_t skip = 0;
rz_buf_seek (bf->buf, c->class_data_offset, RZ_BUF_SET);
dc->static_fields_size = peek_uleb (bf->buf, &err, &skip);
dc->instance_fields_size = peek_uleb (bf->buf, &err, &skip);
dc->direct_methods_size = peek_uleb (bf->buf, &err, &skip);
dc->virtual_methods_size = peek_uleb (bf->buf, &err, &skip);
if (err) {
free (dc);
goto beach;
}
c->class_data = dc;
if (dexdump) { rbin->cb_printf (" Static fields -\n"); }
parse_dex_class_fields (bf, c, cls, sym_count, dc->static_fields_size, true);
if (dexdump) { rbin->cb_printf (" Instance fields -\n"); }
parse_dex_class_fields (bf, c, cls, sym_count, dc->instance_fields_size, false);
if (dexdump) { rbin->cb_printf (" Direct methods -\n"); }
parse_dex_class_method (bf, c, cls, sym_count,
c->class_data->direct_methods_size, methods, true);
if (dexdump) { rbin->cb_printf (" Virtual methods -\n"); }
parse_dex_class_method (bf, c, cls, sym_count,
c->class_data->virtual_methods_size, methods, false);
}
if (dexdump) {
const char *source_file = getstr (dex, c->source_file);
if (!source_file) {
rbin->cb_printf (
" source_file_idx : %d (unknown)\n\n",
c->source_file);
} else {
rbin->cb_printf (" source_file_idx : %d (%s)\n\n",
c->source_file, source_file);
}
}
cls = NULL;
beach:
return;
}
static bool is_class_idx_in_code_classes(RBinDexObj *bin, int class_idx) {
int i;
for (i = 0; i < bin->header.class_size; i++) {
if (class_idx == bin->classes[i].class_id) {
return true;
}
}
return false;
}
static bool dex_loadcode(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, false);
PrintfCallback cb_printf = bf->rbin->cb_printf;
RBinDexObj *bin = bf->o->bin_obj;
size_t i;
int *methods = NULL;
int sym_count = 0;
// doublecheck??
if (bin->methods_list) {
return false;
}
bin->version = rz_bin_dex_get_version (bin);
bin->code_from = UT64_MAX;
bin->code_to = 0;
bin->methods_list = rz_list_newf ((RzListFree)free);
if (!bin->methods_list) {
return false;
}
bin->imports_list = rz_list_newf ((RzListFree)free);
if (!bin->imports_list) {
rz_list_free (bin->methods_list);
return false;
}
bin->lines_list = rz_list_newf ((RzListFree)free);
if (!bin->lines_list) {
rz_list_free (bin->methods_list);
rz_list_free (bin->imports_list);
return false;
}
bin->classes_list = rz_list_newf ((RzListFree)rz_bin_class_free);
if (!bin->classes_list) {
rz_list_free (bin->methods_list);
rz_list_free (bin->imports_list);
rz_list_free (bin->lines_list);
return false;
}
if (bin->header.method_size>bin->size) {
bin->header.method_size = 0;
return false;
}
/* WrapDown the header sizes to avoid huge allocations */
bin->header.method_size = RZ_MIN (bin->header.method_size, bin->size);
bin->header.class_size = RZ_MIN (bin->header.class_size, bin->size);
bin->header.strings_size = RZ_MIN (bin->header.strings_size, bin->size);
// TODO: is this posible after RZ_MIN ??
if (bin->header.strings_size > bin->size) {
eprintf ("Invalid strings size\n");
return false;
}
dexSubsystem = NULL;
if (bin->classes) {
ut64 amount = sizeof (int) * bin->header.method_size;
if (amount > UT32_MAX || amount < bin->header.method_size) {
return false;
}
methods = calloc (1, amount + 1);
for (i = 0; i < bin->header.class_size; i++) {
struct dex_class_t *c = &bin->classes[i];
if (dexdump) {
cb_printf ("Class #%d -\n", i);
}
parse_class (bf, c, i, methods, &sym_count);
}
}
if (methods) {
int import_count = 0;
int sym_count = bin->methods_list->length;
for (i = 0; i < bin->header.method_size; i++) {
int len = 0;
if (methods[i]) {
continue;
}
if (bin->methods[i].class_id >= bin->header.types_size) {
continue;
}
if (is_class_idx_in_code_classes (bin, bin->methods[i].class_id)) {
continue;
}
const char *className = getstr (bin, bin->types[bin->methods[i].class_id].descriptor_id);
if (!className) {
continue;
}
char *class_name = strdup (className);
if (!class_name) {
free (class_name);
continue;
}
if (!dexSubsystem) {
if (strstr (class_name, "wearable/view")) {
dexSubsystem = "android-wear";
} else if (strstr (class_name, "android/view/View")) {
dexSubsystem = "android";
}
}
len = strlen (class_name);
if (len < 1) {
free (class_name);
continue;
}
rz_str_replace_char (class_name, ';', 0);
const char *method_name = dex_method_name (bin, i);
char *signature = dex_method_signature (bin, i);
if (!RZ_STR_ISEMPTY (method_name)) {
RBinImport *imp = RZ_NEW0 (RBinImport);
if (!imp) {
free (methods);
free (signature);
free (class_name);
return false;
}
imp->name = rz_str_newf ("%s.method.%s%s", class_name, method_name, signature);
imp->type = "FUNC";
imp->bind = "NONE";
imp->ordinal = import_count++;
rz_list_append (bin->imports_list, imp);
RBinSymbol *sym = RZ_NEW0 (RBinSymbol);
if (!sym) {
free (methods);
free (signature);
free (class_name);
return false;
}
sym->name = strdup (imp->name);
sym->is_imported = true;
sym->type = RZ_BIN_TYPE_FUNC_STR;
sym->bind = "NONE";
//XXX so damn unsafe check buffer boundaries!!!!
//XXX use rz_buf API!!
sym->paddr = sym->vaddr = bin->header.method_offset + (sizeof (struct dex_method_t) * i) ;
sym->ordinal = sym_count++;
rz_list_append (bin->methods_list, sym);
const char *mname = sdb_fmt ("method.%"PFMT64d, (ut64)i);
sdb_num_set (mdb, mname, sym->paddr, 0);
}
free (signature);
free (class_name);
}
free (methods);
}
return true;
}
static RzList* imports(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
if (!bin->imports_list) {
dex_loadcode (bf);
}
return bin->imports_list;
}
static RzList *trycatch(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
if (!bin->trycatch_list) {
dex_loadcode (bf);
}
return bin->trycatch_list;
}
static RzList *methods(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
if (!bin->methods_list) {
dex_loadcode (bf);
}
return bin->methods_list;
}
static RzList *classes(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
if (!bin->classes_list) {
dex_loadcode (bf);
}
return bin->classes_list;
}
static bool already_entry(RzList *entries, ut64 vaddr) {
RBinAddr *e;
RzListIter *iter;
rz_list_foreach (entries, iter, e) {
if (e->vaddr == vaddr) {
return true;
}
}
return false;
}
static RzList *entries(RBinFile *bf) {
RzListIter *iter;
RBinSymbol *m;
RBinAddr *ptr;
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
RBinDexObj *bin = (RBinDexObj*) bf->o->bin_obj;
RzList *ret = rz_list_newf ((RzListFree)free);
if (!bin->methods_list) {
dex_loadcode (bf);
}
// STEP 1. ".onCreate(Landroid/os/Bundle;)V"
rz_list_foreach (bin->methods_list, iter, m) {
if (strlen (m->name) > 30 && m->bind &&
(!strcmp (m->bind, RZ_BIN_BIND_LOCAL_STR) || !strcmp (m->bind, RZ_BIN_BIND_GLOBAL_STR)) &&
!strcmp (m->name + strlen (m->name) - 31,
".onCreate(Landroid/os/Bundle;)V")) {
if (!already_entry (ret, m->paddr)) {
if ((ptr = RZ_NEW0 (RBinAddr))) {
ptr->paddr = ptr->vaddr = m->paddr;
rz_list_append (ret, ptr);
}
}
}
}
// STEP 2. ".main([Ljava/lang/String;)V"
if (rz_list_empty (ret)) {
rz_list_foreach (bin->methods_list, iter, m) {
if (strlen (m->name) > 26 &&
!strcmp (m->name + strlen (m->name) - 27,
".main([Ljava/lang/String;)V")) {
if (!already_entry (ret, m->paddr)) {
if ((ptr = RZ_NEW0 (RBinAddr))) {
ptr->paddr = ptr->vaddr = m->paddr;
rz_list_append (ret, ptr);
}
}
}
}
}
#if 0
// this is now done by rizin in a generic way
// STEP 3. NOTHING FOUND POINT TO CODE_INIT
if (rz_list_empty (ret)) {
if (!already_entry (ret, bin->code_from)) {
ptr = RZ_NEW0 (RBinAddr);
if (ptr) {
ptr->paddr = ptr->vaddr = bin->code_from;
rz_list_append (ret, ptr);
}
}
}
#endif
return ret;
}
static int getoffset(RBinFile *bf, int type, int idx) {
struct rz_bin_dex_obj_t *dex = bf->o->bin_obj;
switch (type) {
case 'm': // methods
// TODO: ADD CHECK
return offset_of_method_idx (bf, dex, idx);
case 'f':
return dex_field_offset (dex, idx);
case 'o': // objects
eprintf ("TODO: getoffset object\n");
return 0; // //chdex_object_offset (dex, idx);
case 's': // strings
if (dex->header.strings_size > idx) {
if (dex->strings) {
return dex->strings[idx];
}
}
break;
case 't': // type
return dex_get_type_offset (bf, idx);
case 'c': // class
return dex_get_type_offset (bf, idx);
}
return -1;
}
static char *getname(RBinFile *bf, int type, int idx, bool sd) {
simplifiedDemangling = sd; // XXX kill globals
struct rz_bin_dex_obj_t *dex = bf->o->bin_obj;
switch (type) {
case 'm': // methods
return dex_method_fullname (dex, idx);
case 'c': // classes
return dex_class_name_byid (dex, idx);
case 'f': // fields
return dex_field_name (dex, idx);
case 'p': // proto
return dex_get_proto (dex, idx);
}
return NULL;
}
typedef struct {
ut64 addr;
ut64 size;
} Section;
static RBinSection *add_section(RzList *ret, const char *name, Section s, int perm, char *format) {
rz_return_val_if_fail (ret && name, NULL);
rz_return_val_if_fail (s.addr < UT32_MAX, NULL);
rz_return_val_if_fail (s.size > 0 && s.size < UT32_MAX, NULL);
RBinSection *ptr = RZ_NEW0 (RBinSection);
if (ptr) {
ptr->name = strdup (name);
ptr->paddr = ptr->vaddr = s.addr;
ptr->size = ptr->vsize = s.size;
ptr->perm = perm;
ptr->add = false;
if (format) {
ptr->format = format;
}
rz_list_append (ret, ptr);
}
return ptr;
}
static void add_segment(RzList *ret, const char *name, Section s, int perm) {
RBinSection *bs = add_section (ret, name, s, perm, NULL);
if (bs) {
bs->is_segment = true;
bs->add = true;
}
}
static bool validate_section(const char *name, Section *pre, Section *cur, Section *nex, Section *all) {
rz_return_val_if_fail (cur && all, false);
if (pre && cur->addr < (pre->addr + pre->size)) {
eprintf ("Warning: %s Section starts before the previous.\n", name);
}
if (cur->addr >= all->size) {
eprintf ("Warning: %s section starts beyond the end of the file.\n", name);
return false;
}
if (cur->addr == UT64_MAX) {
eprintf ("Warning: %s invalid region size.\n", name);
return false;
}
if ((cur->addr + cur->size) > all->size) {
eprintf ("Warning: %s truncated section because of file size.\n", name);
cur->size = all->size - cur->addr;
}
if (nex) {
if (cur->addr >= nex->addr) {
eprintf ("Warning: invalid %s section address.\n", name);
return false;
}
if ((cur->addr + cur->size) > nex->addr) {
eprintf ("Warning: truncated %s with next section size.\n", name);
cur->size = nex->addr - cur->addr;
}
}
return cur->size > 0;
}
static void fast_code_size(RBinFile *bf) {
const size_t bs = rz_buf_size (bf->buf);
ut64 ns;
ut64 fsym = 0LL;
ut64 fsymsz = 0LL;
RzListIter *iter;
RBinSymbol *m;
RzList *ml = methods (bf);
rz_list_foreach (ml, iter, m) {
if (!fsym || m->paddr < fsym) {
fsym = m->paddr;
}
ns = m->paddr + m->size;
if (ns > bs || m->paddr > bs || m->size > bs) {
continue;
}
if (ns > fsymsz) {
fsymsz = ns;
}
}
struct rz_bin_dex_obj_t *bin = bf->o->bin_obj;
bin->code_from = fsym;
bin->code_to = fsymsz;
}
static RzList *sections(RBinFile *bf) {
struct rz_bin_dex_obj_t *bin = bf->o->bin_obj;
RzList *ret = NULL;
/* find the last method */
const size_t bs = rz_buf_size (bf->buf);
if (!bin->code_from || !bin->code_to) {
fast_code_size (bf);
}
if (!(ret = rz_list_newf (free))) {
return NULL;
}
/* initial section boundary assumptions */
Section s_head = { 0, sizeof (struct dex_header_t) };
Section s_pool = { s_head.size, bin->code_from - sizeof (struct dex_header_t)};
Section s_code = { bin->code_from, bin->code_to - bin->code_from };
Section s_data = { bin->code_to, bs - bin->code_to};
Section s_file = { 0, bs };
/* sanity bound checks and section registrations */
if (validate_section ("header", NULL, &s_head, NULL, &s_file)) {
add_section (ret, "header", s_head, RZ_PERM_R, NULL);
}
if (validate_section ("constpool", &s_head, &s_pool, &s_code, &s_file)) {
char *s_pool_format = rz_str_newf ("Cd %d[%"PFMT64d"]", 4, (ut64) s_pool.size / 4);
add_section (ret, "constpool", s_pool, RZ_PERM_R, s_pool_format);
}
if (validate_section ("code", &s_pool, &s_code, &s_data, &s_file)) {
add_section (ret, "code", s_code, RZ_PERM_RX, NULL);
}
if (validate_section ("data", &s_code, &s_data, NULL, &s_file)) {
add_section (ret, "data", s_data, RZ_PERM_RX, NULL);
}
add_section (ret, "file", s_file, RZ_PERM_R, NULL);
/* add segments */
if (s_code.size > 0) {
add_segment (ret, "code", s_code, RZ_PERM_RX);
}
add_segment (ret, "file", s_file, RZ_PERM_R);
return ret;
}
// iH
static void dex_header(RBinFile *bf) {
RBinDexObj *dex = bf->o->bin_obj;
DexHeader *hdr = &dex->header;
PrintfCallback cb_printf = bf->rbin->cb_printf;
cb_printf ("DEX file header:\n");
cb_printf ("magic : 'dex\\n035\\0'\n");
cb_printf ("checksum : %x\n", hdr->checksum);
cb_printf ("signature : %02x%02x...%02x%02x\n",
hdr->signature[0], hdr->signature[1], hdr->signature[18], hdr->signature[19]);
cb_printf ("file_size : %d\n", hdr->size);
cb_printf ("header_size : %d\n", hdr->header_size);
cb_printf ("link_size : %d\n", hdr->linksection_size);
cb_printf ("link_off : %d (0x%06x)\n", hdr->linksection_offset, hdr->linksection_offset);
cb_printf ("string_ids_size : %d\n", hdr->strings_size);
cb_printf ("string_ids_off : %d (0x%06x)\n", hdr->strings_offset, hdr->strings_offset);
cb_printf ("type_ids_size : %d\n", hdr->types_size);
cb_printf ("type_ids_off : %d (0x%06x)\n", hdr->types_offset, hdr->types_offset);
cb_printf ("proto_ids_size : %d\n", hdr->prototypes_size);
cb_printf ("proto_ids_off : %d (0x%06x)\n", hdr->prototypes_offset, hdr->prototypes_offset);
cb_printf ("field_ids_size : %d\n", hdr->fields_size);
cb_printf ("field_ids_off : %d (0x%06x)\n", hdr->fields_offset, hdr->fields_offset);
cb_printf ("method_ids_size : %d\n", hdr->method_size);
cb_printf ("method_ids_off : %d (0x%06x)\n", hdr->method_offset, hdr->method_offset);
cb_printf ("class_defs_size : %d\n", hdr->class_size);
cb_printf ("class_defs_off : %d (0x%06x)\n", hdr->class_offset, hdr->class_offset);
cb_printf ("data_size : %d\n", hdr->data_size);
cb_printf ("data_off : %d (0x%06x)\n\n", hdr->data_offset, hdr->data_offset);
// TODO: print information stored in the RBIN not this ugly fix
dex->methods_list = NULL;
dexdump = true; /// XXX convert this global into an argument or field in RBinFile or so
dex_loadcode (bf);
dexdump = false;
}
static ut64 size(RBinFile *bf) {
ut8 u32s[sizeof (ut32)] = {0};
int ret = rz_buf_read_at (bf->buf, 108, u32s, 4);
if (ret != 4) {
return 0;
}
ut32 off = rz_read_le32 (u32s);
ret = rz_buf_read_at (bf->buf, 104, u32s, 4);
if (ret != 4) {
return 0;
}
return off + rz_read_le32 (u32s);
}
static RZ_BORROW RzList *lines(RBinFile *bf) {
struct rz_bin_dex_obj_t *dex = bf->o->bin_obj;
return dex->lines_list;
}
// iH*
static RzList *dex_fields(RBinFile *bf) {
RzList *ret = rz_list_new ();
if (!ret) {
return NULL;
}
ret->free = free;
ut64 addr = 0;
#define ROW(nam,siz,val,fmt) \
rz_list_append (ret, rz_bin_field_new (addr, addr, siz, nam, sdb_fmt ("0x%08"PFMT64x, (ut64)val), fmt, false)); \
addr += siz;
rz_buf_seek (bf->buf, 0, RZ_BUF_SET);
ut64 magic = rz_buf_read_le64 (bf->buf);
ROW ("dex_magic", 8, magic, "[8]c");
ut32 checksum = rz_buf_read_le32 (bf->buf);
ROW ("dex_checksum", 4, checksum, "x");
ut8 signature[20];
ROW ("dex_signature", 8, signature, "[20]c");
ut32 size = rz_buf_read_le32 (bf->buf);
ROW ("dex_size", 4, size, "x");
ut32 header_size = rz_buf_read_le32 (bf->buf);
ROW ("dex_header_size", 4, header_size, "x");
ut32 endian = rz_buf_read_le32 (bf->buf);
ROW ("dex_endian", 4, endian, "x");
/*
ROW ("hdr.cputype", 4, mh->cputype, "x");
ROW ("hdr.cpusubtype", 4, mh->cpusubtype, "x");
ROW ("hdr.filetype", 4, mh->filetype, "x");
ROW ("hdr.nbcmds", 4, mh->ncmds, "x");
ROW ("hdr.sizeofcmds", 4, mh->sizeofcmds, "x");
*/
return ret;
}
static int cmp_path(const void *a, const void *b) {
if (!a || !b) {
return 0;
}
return strcmp ((const char*)a, (const char*)b);
}
static bool is_classes_dex(const char *filename) {
return rz_str_startswith (filename, "classes") \
&& rz_str_endswith (filename, ".dex");
}
static RzList* libs(RBinFile *bf) {
rz_return_val_if_fail (bf && bf->o && bf->o->bin_obj, NULL);
char *path = rz_file_dirname (bf->file);
if (rz_str_startswith (path, "./")) {
// avoids stuff like .//.//.//.//.//
free (path);
return NULL;
}
RzList *files = rz_sys_dir (path);
if (!files) {
free (path);
return NULL;
}
RzList *ret = rz_list_newf (free);
if (!ret) {
free (path);
rz_list_free (files);
return NULL;
}
/* opening dex files in order. */
rz_list_sort (files, cmp_path);
RzListIter *iter;
char *file;
rz_list_foreach (files, iter, file) {
if (is_classes_dex (file)) {
char *n = rz_str_newf ("%s%s%s", path, RZ_SYS_DIR, file);
if (strcmp (n, bf->file)) {
rz_list_append (ret, n);
}
free (n);
}
}
rz_list_free (files);
free (path);
return ret;
}
static void destroy(RBinFile *bf) {
rz_return_if_fail (bf && bf->o);
RBinDexObj *obj = bf->o->bin_obj;
rz_bin_dex_free (obj);
}
RBinPlugin rz_bin_plugin_dex = {
.name = "dex",
.desc = "dex format bin plugin",
.license = "LGPL3",
.destroy = &destroy,
.get_sdb = &get_sdb,
.load_buffer = &load_buffer,
.check_buffer = check_buffer,
.baddr = baddr,
.entries = entries,
.classes = classes,
.sections = sections,
.symbols = methods,
.trycatch = trycatch,
.imports = imports,
.strings = strings,
.info = &info,
.header = dex_header,
.fields = dex_fields,
.libs = &libs,
.size = &size,
.get_offset = &getoffset,
.get_name = &getname,
.dbginfo = &rz_bin_dbginfo_dex,
.lines = &lines,
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct radare_plugin = {
.type = RZ_LIB_TYPE_BIN,
.data = &rz_bin_plugin_dex,
.version = RZ_VERSION
};
#endif