mirror of
https://github.com/radareorg/radare2.git
synced 2025-02-03 12:12:06 +00:00
483 lines
13 KiB
C
483 lines
13 KiB
C
/* radare - LGPL - Copyright 2007-2013 - pancake */
|
|
|
|
#include <string.h>
|
|
#include <r_types.h>
|
|
#include <r_lib.h>
|
|
#include <r_asm.h>
|
|
#include <r_anal.h>
|
|
|
|
/* DEPRECATE ?? */
|
|
#include "wine-arm.h"
|
|
#include "../asm/arch/arm/asm-arm.h"
|
|
#include "../asm/arch/arm/winedbg/be_arm.h"
|
|
|
|
static unsigned int disarm_branch_offset(unsigned int pc, unsigned int insoff) {
|
|
unsigned int add = insoff << 2;
|
|
/* zero extend if higher is 1 (0x02000000) */
|
|
if ((add & 0x02000000) == 0x02000000) {
|
|
add |= 0xFC000000;
|
|
}
|
|
return add + pc + 8;
|
|
}
|
|
|
|
#define IS_BRANCH(x) ((x & ARM_BRANCH_I_MASK) == ARM_BRANCH_I)
|
|
#define IS_BRANCHL(x) (IS_BRANCH (x) && (x & ARM_BRANCH_LINK) == ARM_BRANCH_LINK)
|
|
#define IS_RETURN(x) ((x & (ARM_DTM_I_MASK | ARM_DTM_LOAD | (1 << 15))) == (ARM_DTM_I | ARM_DTM_LOAD | (1 << 15)))
|
|
// if ( (inst & ( ARM_DTX_I_MASK | ARM_DTX_LOAD | ( ARM_DTX_RD_MASK ) ) ) == ( ARM_DTX_LOAD | ARM_DTX_I | ( ARM_PC << 12 ) ) )
|
|
#define IS_UNKJMP(x) ((((ARM_DTX_RD_MASK))) == (ARM_DTX_LOAD | ARM_DTX_I | (ARM_PC << 12)))
|
|
#define IS_LOAD(x) ((x & ARM_DTX_LOAD) == (ARM_DTX_LOAD))
|
|
#define IS_CONDAL(x) ((x & ARM_COND_MASK) == ARM_COND_AL)
|
|
#define IS_EXITPOINT(x) (IS_BRANCH (x) || IS_RETURN (x) || IS_UNKJMP (x))
|
|
|
|
#define API static
|
|
|
|
static int op_thumb(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) {
|
|
int op_code;
|
|
ut16 *_ins = (ut16 *) data;
|
|
ut16 ins = *_ins;
|
|
ut32 *_ins32 = (ut32 *) data;
|
|
ut32 ins32 = *_ins32;
|
|
|
|
struct winedbg_arm_insn *arminsn = arm_new ();
|
|
arm_set_thumb (arminsn, true);
|
|
arm_set_input_buffer (arminsn, data);
|
|
arm_set_pc (arminsn, addr);
|
|
op->jump = op->fail = -1;
|
|
op->ptr = op->val = -1;
|
|
op->delay = 0;
|
|
op->size = arm_disasm_one_insn (arminsn);
|
|
op->jump = arminsn->jmp;
|
|
op->fail = arminsn->fail;
|
|
arm_free (arminsn);
|
|
|
|
// TODO: handle 32bit instructions (branches are not correctly decoded //
|
|
|
|
/* CMP */
|
|
if (((ins & B4 (B1110, 0, 0, 0)) == B4 (B0010, 0, 0, 0))
|
|
&& (1 == (ins & B4 (1, B1000, 0, 0)) >> 11)) { // dp3
|
|
op->type = R_ANAL_OP_TYPE_CMP;
|
|
return op->size;
|
|
}
|
|
if ((ins & B4 (B1111, B1100, 0, 0)) == B4 (B0100, 0, 0, 0)) {
|
|
op_code = (ins & B4 (0, B0011, B1100, 0)) >> 6;
|
|
if (op_code == 8 || op_code == 10) { // dp5
|
|
op->type = R_ANAL_OP_TYPE_CMP;
|
|
return op->size;
|
|
}
|
|
}
|
|
if ((ins & B4 (B1111, B1100, 0, 0)) == B4 (B0100, B0100, 0, 0)) {
|
|
op_code = (ins & B4 (0, B0011, 0, 0)) >> 8; // dp8
|
|
if (op_code == 1) {
|
|
op->type = R_ANAL_OP_TYPE_CMP;
|
|
return op->size;
|
|
}
|
|
}
|
|
if (ins == 0xbf) {
|
|
// TODO: add support for more NOP instructions
|
|
op->type = R_ANAL_OP_TYPE_NOP;
|
|
} else if (((op_code = ((ins & B4 (B1111, B1000, 0, 0)) >> 11)) >= 12 &&
|
|
op_code <= 17)) {
|
|
if (op_code % 2) {
|
|
op->type = R_ANAL_OP_TYPE_LOAD;
|
|
} else {
|
|
op->type = R_ANAL_OP_TYPE_STORE;
|
|
}
|
|
} else if ((ins & B4 (B1111, 0, 0, 0)) == B4 (B0101, 0, 0, 0)) {
|
|
op_code = (ins & B4 (0, B1110, 0, 0)) >> 9;
|
|
if (op_code % 2) {
|
|
op->type = R_ANAL_OP_TYPE_LOAD;
|
|
} else {
|
|
op->type = R_ANAL_OP_TYPE_STORE;
|
|
}
|
|
} else if ((ins & B4 (B1111, 0, 0, 0)) == B4 (B1101, 0, 0, 0)) {
|
|
// BNE..
|
|
int delta = (ins & B4 (0, 0, B1111, B1111));
|
|
op->type = R_ANAL_OP_TYPE_CJMP;
|
|
op->jump = addr + 4 + (delta << 1);
|
|
op->fail = addr + 4;
|
|
} else if ((ins & B4 (B1111, B1000, 0, 0)) == B4 (B1110, 0, 0, 0)) {
|
|
// B
|
|
int delta = (ins & B4 (0, 0, B1111, B1111));
|
|
op->type = R_ANAL_OP_TYPE_JMP;
|
|
op->jump = addr + 4 + (delta << 1);
|
|
op->fail = addr + 4;
|
|
} else if ((ins & B4 (B1111, B1111, B1000, 0)) ==
|
|
B4 (B0100, B0111, B1000, 0)) {
|
|
// BLX
|
|
op->type = R_ANAL_OP_TYPE_UCALL;
|
|
op->fail = addr + 4;
|
|
} else if ((ins & B4 (B1111, B1111, B1000, 0)) ==
|
|
B4 (B0100, B0111, 0, 0)) {
|
|
// BX
|
|
op->type = R_ANAL_OP_TYPE_UJMP;
|
|
op->fail = addr + 4;
|
|
} else if ((ins & B4 (B1111, B1000, 0, 0)) == B4 (B1111, 0, 0, 0)) {
|
|
// BL The long branch with link, it's in 2 instructions:
|
|
// prefix: 11110[offset]
|
|
// suffix: 11111[offset] (11101[offset] for blx)
|
|
ut16 nextins = (ins32 & 0xFFFF0000) >> 16;
|
|
ut32 high = (ins & B4 (0, B0111, B1111, B1111)) << 12;
|
|
if (ins & B4 (0, B0100, 0, 0)) {
|
|
high |= B4 (B1111, B1000, 0, 0) << 16;
|
|
}
|
|
int delta = high + ((nextins & B4 (0, B0111, B1111, B1111)) * 2);
|
|
op->jump = (int) (addr + 4 + (delta));
|
|
op->type = R_ANAL_OP_TYPE_CALL;
|
|
op->fail = addr + 4;
|
|
} else if ((ins & B4 (B1111, B1111, 0, 0)) == B4 (B1011, B1110, 0, 0)) {
|
|
op->type = R_ANAL_OP_TYPE_TRAP;
|
|
op->val = (ut64) (ins >> 8);
|
|
} else if ((ins & B4 (B1111, B1111, 0, 0)) == B4 (B1101, B1111, 0, 0)) {
|
|
op->type = R_ANAL_OP_TYPE_SWI;
|
|
op->val = (ut64) (ins >> 8);
|
|
}
|
|
return op->size;
|
|
}
|
|
|
|
#if 0
|
|
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
|
|
"hi", "ls", "ge", "lt", "gt", "le", "al", "nv",
|
|
#endif
|
|
static int iconds[] = {
|
|
R_ANAL_COND_EQ,
|
|
R_ANAL_COND_NE,
|
|
0, // cs
|
|
0, // cc
|
|
0, // mi
|
|
0, // pl
|
|
0, // vs
|
|
0, // vc
|
|
|
|
0, // hi
|
|
0, // ls
|
|
R_ANAL_COND_GE,
|
|
R_ANAL_COND_LT,
|
|
R_ANAL_COND_GT,
|
|
R_ANAL_COND_LE,
|
|
R_ANAL_COND_AL,
|
|
R_ANAL_COND_NV,
|
|
};
|
|
|
|
static int op_cond(const ut8 *data) {
|
|
ut8 b = data[3] >> 4;
|
|
if (b == 0xf) {
|
|
return 0;
|
|
}
|
|
return iconds[b];
|
|
}
|
|
|
|
static int arm_op32(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) {
|
|
const ut8 *b = (ut8 *) data;
|
|
ut8 ndata[4];
|
|
ut32 branch_dst_addr, i = 0;
|
|
ut32 *code = (ut32 *) data;
|
|
struct winedbg_arm_insn *arminsn;
|
|
|
|
if (!data) {
|
|
return 0;
|
|
}
|
|
memset (op, '\0', sizeof (RAnalOp));
|
|
arminsn = arm_new ();
|
|
arm_set_thumb (arminsn, false);
|
|
arm_set_input_buffer (arminsn, data);
|
|
arm_set_pc (arminsn, addr);
|
|
op->jump = op->fail = -1;
|
|
op->ptr = op->val = -1;
|
|
op->addr = addr;
|
|
op->type = R_ANAL_OP_TYPE_UNK;
|
|
|
|
if (anal->big_endian) {
|
|
b = data = ndata;
|
|
ndata[0] = data[3];
|
|
ndata[1] = data[2];
|
|
ndata[2] = data[1];
|
|
ndata[3] = data[0];
|
|
}
|
|
if (anal->bits == 16) {
|
|
arm_free (arminsn);
|
|
return op_thumb (anal, op, addr, data, len);
|
|
}
|
|
op->size = 4;
|
|
op->cond = op_cond (data);
|
|
if (b[2] == 0x8f && b[3] == 0xe2) {
|
|
op->type = R_ANAL_OP_TYPE_ADD;
|
|
#define ROR(x, y) ((int) ((x) >> (y)) | (((x) << (32 - (y)))))
|
|
op->ptr = addr + ROR (b[0], (b[1] & 0xf) << 1) + 8;
|
|
} else if (b[2] >= 0x9c && b[2] <= 0x9f) { // load instruction
|
|
char ch = b[3] & 0xf;
|
|
switch (ch) {
|
|
case 5:
|
|
if ((b[3] & 0xf) == 5) {
|
|
op->ptr = 8 + addr + b[0] + ((b[1] & 0xf) << 8);
|
|
// XXX: if set it breaks the visual disasm wtf
|
|
// op->refptr = true;
|
|
}
|
|
case 4:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
case 9: op->type = R_ANAL_OP_TYPE_LOAD; break;
|
|
}
|
|
} else // 0x000037b8 00:0000 0 800000ef svc 0x00000080
|
|
if (b[2] == 0xa0 && b[3] == 0xe1) {
|
|
int n = (b[0] << 16) + b[1];
|
|
op->type = R_ANAL_OP_TYPE_MOV;
|
|
switch (n) {
|
|
case 0:
|
|
case 0x0110: case 0x0220: case 0x0330: case 0x0440:
|
|
case 0x0550: case 0x0660: case 0x0770: case 0x0880:
|
|
case 0x0990: case 0x0aa0: case 0x0bb0: case 0x0cc0:
|
|
op->type = R_ANAL_OP_TYPE_NOP;
|
|
break;
|
|
}
|
|
} else if (b[3] == 0xef) {
|
|
op->type = R_ANAL_OP_TYPE_SWI;
|
|
op->val = (b[0] | (b[1] << 8) | (b[2] << 2));
|
|
} else if ((b[3] & 0xf) == 5) { // [reg,0xa4]
|
|
#if 0
|
|
0x00000000 a4a09fa4 ldrge sl, [pc], 0xa4
|
|
0x00000000 a4a09fa5 ldrge sl, [pc, 0xa4]
|
|
0x00000000 a4a09fa6 ldrge sl, [pc], r4, lsr 1
|
|
0x00000000 a4a09fa7 ldrge sl, [pc, r4, lsr 1]
|
|
0x00000000 a4a09fe8 ldm pc, {
|
|
r2, r5, r7, sp, pc
|
|
}; < UNPREDICT
|
|
#endif
|
|
if ((b[1] & 0xf0) == 0xf0) {
|
|
// ldr pc, [pc, #1] ;
|
|
// op->type = R_ANAL_OP_TYPE_UJMP;
|
|
op->type = R_ANAL_OP_TYPE_RET; // FAKE FOR FUN
|
|
// op->stackop = R_ANAL_STACK_SET;
|
|
op->jump = 1234;
|
|
// op->ptr = 4+addr+b[0]; // sure? :)
|
|
// op->ptrptr = true;
|
|
}
|
|
// eprintf("0x%08x\n", code[i] & ARM_DTX_LOAD);
|
|
// 0x0001B4D8, 1eff2fe1 bx lr
|
|
} else if (b[3] == 0xe2 && b[2] == 0x8d && b[1] == 0xd0) {
|
|
// ADD SP, SP, ...
|
|
op->type = R_ANAL_OP_TYPE_ADD;
|
|
op->stackop = R_ANAL_STACK_INC;
|
|
op->val = -b[0];
|
|
} else if (b[3] == 0xe2 && b[2] == 0x4d && b[1] == 0xd0) {
|
|
// SUB SP, SP, ..
|
|
op->type = R_ANAL_OP_TYPE_SUB;
|
|
op->stackop = R_ANAL_STACK_INC;
|
|
op->val = b[0];
|
|
} else if (b[3] == 0xe2 && b[2] == 0x4c && b[1] == 0xb0) {
|
|
// SUB SP, FP, ..
|
|
op->type = R_ANAL_OP_TYPE_SUB;
|
|
op->stackop = R_ANAL_STACK_INC;
|
|
op->val = -b[0];
|
|
} else if (b[3] == 0xe2 && b[2] == 0x4b && b[1] == 0xd0) {
|
|
// SUB SP, IP, ..
|
|
op->type = R_ANAL_OP_TYPE_SUB;
|
|
op->stackop = R_ANAL_STACK_INC;
|
|
op->val = -b[0];
|
|
} else if ((code[i] == 0x1eff2fe1) ||
|
|
(code[i] == 0xe12fff1e)) { // bx lr
|
|
op->type = R_ANAL_OP_TYPE_RET;
|
|
} else if ((code[i] & ARM_DTX_LOAD)) { // IS_LOAD(code[i])) {
|
|
ut32 ptr = 0;
|
|
op->type = R_ANAL_OP_TYPE_MOV;
|
|
if (b[2] == 0x1b) {
|
|
/* XXX pretty incomplete */
|
|
op->stackop = R_ANAL_STACK_GET;
|
|
op->ptr = b[0];
|
|
// var_add_access(addr, -b[0], 1, 0); // TODO: set/get (the last 0)
|
|
} else {
|
|
// ut32 oaddr = addr+8+b[0];
|
|
// XXX TODO ret = radare_read_at(oaddr, (ut8*)&ptr, 4);
|
|
if (anal->bits == 32) {
|
|
b = (ut8 *) &ptr;
|
|
op->ptr = b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24);
|
|
// XXX data_xrefs_add(oaddr, op->ptr, 1);
|
|
// TODO change data type to pointer
|
|
} else {
|
|
op->ptr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IS_LOAD (code[i])) {
|
|
op->type = R_ANAL_OP_TYPE_LOAD;
|
|
op->refptr = 4;
|
|
}
|
|
if (((((code[i] & 0xff) >= 0x10 && (code[i] & 0xff) < 0x20)) &&
|
|
((code[i] & 0xffffff00) == 0xe12fff00)) ||
|
|
IS_EXITPOINT (code[i])) {
|
|
// if (IS_EXITPOINT (code[i])) {
|
|
b = data;
|
|
branch_dst_addr = disarm_branch_offset (
|
|
addr, b[0] | (b[1] << 8) |
|
|
(b[2] << 16)); // code[i]&0x00FFFFFF);
|
|
op->ptr = 0;
|
|
if ((((code[i] & 0xff) >= 0x10 && (code[i] & 0xff) < 0x20)) &&
|
|
((code[i] & 0xffffff00) == 0xe12fff00)) {
|
|
op->type = R_ANAL_OP_TYPE_UJMP;
|
|
} else if (IS_BRANCHL (code[i])) {
|
|
if (IS_BRANCH (code[i])) {
|
|
op->type = R_ANAL_OP_TYPE_CALL;
|
|
op->jump = branch_dst_addr;
|
|
op->fail = addr + 4;
|
|
} else {
|
|
op->type = R_ANAL_OP_TYPE_RET;
|
|
}
|
|
} else if (IS_BRANCH (code[i])) {
|
|
if (IS_CONDAL (code[i])) {
|
|
op->type = R_ANAL_OP_TYPE_JMP;
|
|
op->jump = branch_dst_addr;
|
|
op->fail = UT64_MAX;
|
|
} else {
|
|
op->type = R_ANAL_OP_TYPE_CJMP;
|
|
op->jump = branch_dst_addr;
|
|
op->fail = addr + 4;
|
|
}
|
|
} else {
|
|
// unknown jump o return
|
|
// op->type = R_ANAL_OP_TYPE_UJMP;
|
|
// op->type = R_ANAL_OP_TYPE_NOP;
|
|
}
|
|
}
|
|
// op->jump = arminsn->jmp;
|
|
// op->fail = arminsn->fail;
|
|
arm_free (arminsn);
|
|
return op->size;
|
|
}
|
|
|
|
|
|
static ut64 getaddr(ut64 addr, const ut8 *d) {
|
|
if (d[2] >> 7) {
|
|
st32 n = (d[0] + (d[1] << 8) + (d[2] << 16) + (0xff << 24));
|
|
n = -n;
|
|
return addr - (n * 4);
|
|
}
|
|
return addr + (4 * (d[0] + (d[1] << 8) + (d[2] << 16)));
|
|
}
|
|
|
|
static int arm_op64(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *d, int len) {
|
|
memset (op, 0, sizeof (RAnalOp));
|
|
if (d[3] == 0) {
|
|
return -1; // invalid
|
|
}
|
|
op->size = 4;
|
|
op->type = R_ANAL_OP_TYPE_NULL;
|
|
if (d[0] == 0xc0 && d[3] == 0xd6) {
|
|
// defaults to x30 reg. but can be different
|
|
op->type = R_ANAL_OP_TYPE_RET;
|
|
}
|
|
switch (d[3]) {
|
|
case 0x71:
|
|
case 0xeb:
|
|
op->type = R_ANAL_OP_TYPE_CMP;
|
|
break;
|
|
case 0xb8:
|
|
case 0xb9:
|
|
case 0xf8:
|
|
case 0xa9: // ldp/stp
|
|
case 0xf9: // ldr/str
|
|
op->type = R_ANAL_OP_TYPE_LOAD;
|
|
break;
|
|
case 0x91: // mov
|
|
case 0x52: // mov
|
|
case 0x94: // bl A
|
|
case 0x97: // bl A
|
|
op->type = R_ANAL_OP_TYPE_CALL;
|
|
op->jump = getaddr (addr, d);
|
|
op->fail = addr + 4;
|
|
break;
|
|
case 0x54: // beq A
|
|
op->type = R_ANAL_OP_TYPE_CJMP;
|
|
op->jump = addr + (4 * ((d[0] >> 4) | (d[1] << 8) | (d[2] << 16)));
|
|
op->fail = addr + 4;
|
|
break;
|
|
case 0x17: // b A
|
|
case 0x14: // b A
|
|
op->type = R_ANAL_OP_TYPE_JMP;
|
|
op->jump = getaddr (addr, d);
|
|
op->fail = addr + 4;
|
|
break;
|
|
}
|
|
return op->size;
|
|
}
|
|
|
|
static int arm_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) {
|
|
if (anal->bits == 64) {
|
|
return arm_op64 (anal, op, addr, data, len);
|
|
}
|
|
return arm_op32 (anal, op, addr, data, len);
|
|
}
|
|
|
|
static int set_reg_profile(RAnal *anal) {
|
|
// TODO: support 64bit profile
|
|
const char *p32 =
|
|
"=PC r15\n"
|
|
"=SP r13\n"
|
|
"=BP r14\n" // XXX
|
|
"=A0 r0\n"
|
|
"=A1 r1\n"
|
|
"=A2 r2\n"
|
|
"=A3 r3\n"
|
|
"gpr lr .32 56 0\n" // r14
|
|
"gpr pc .32 60 0\n" // r15
|
|
|
|
"gpr r0 .32 0 0\n"
|
|
"gpr r1 .32 4 0\n"
|
|
"gpr r2 .32 8 0\n"
|
|
"gpr r3 .32 12 0\n"
|
|
"gpr r4 .32 16 0\n"
|
|
"gpr r5 .32 20 0\n"
|
|
"gpr r6 .32 24 0\n"
|
|
"gpr r7 .32 28 0\n"
|
|
"gpr r8 .32 32 0\n"
|
|
"gpr r9 .32 36 0\n"
|
|
"gpr r10 .32 40 0\n"
|
|
"gpr r11 .32 44 0\n"
|
|
"gpr r12 .32 48 0\n"
|
|
"gpr r13 .32 52 0\n"
|
|
"gpr r14 .32 56 0\n"
|
|
"gpr r15 .32 60 0\n"
|
|
"gpr r16 .32 64 0\n"
|
|
"gpr r17 .32 68 0\n"
|
|
"gpr cpsr .32 72 0\n";
|
|
return r_reg_set_profile_string (anal->reg, p32);
|
|
}
|
|
|
|
static int archinfo(RAnal *anal, int q) {
|
|
if (q == R_ANAL_ARCHINFO_ALIGN) {
|
|
if (anal && anal->bits == 16) {
|
|
return 2;
|
|
}
|
|
return 4;
|
|
}
|
|
if (q == R_ANAL_ARCHINFO_MAX_OP_SIZE) {
|
|
return 4;
|
|
}
|
|
if (q == R_ANAL_ARCHINFO_MIN_OP_SIZE) {
|
|
if (anal && anal->bits == 16) {
|
|
return 2;
|
|
}
|
|
return 4;
|
|
}
|
|
return 4; // XXX
|
|
}
|
|
|
|
RAnalPlugin r_anal_plugin_arm_gnu = {
|
|
.name = "arm.gnu",
|
|
.arch = "arm",
|
|
.license = "LGPL3",
|
|
.bits = 16 | 32 | 64,
|
|
.desc = "ARM code analysis plugin",
|
|
.archinfo = archinfo,
|
|
.op = &arm_op,
|
|
.set_reg_profile = set_reg_profile,
|
|
};
|
|
|
|
#ifndef CORELIB
|
|
RLibStruct radare_plugin = {
|
|
.type = R_LIB_TYPE_ANAL,
|
|
.data = &r_anal_plugin_arm_gnu,
|
|
.version = R2_VERSION
|
|
};
|
|
#endif
|