radare2/libr/core/cmd_search_rop.c
2017-03-16 22:29:49 +01:00

739 lines
20 KiB
C

/* radare - LGPL - Copyright 2009-2016 - Alexandru Caciulescu */
#include <stddef.h>
#include "r_core.h"
#include "r_io.h"
#include "r_list.h"
#include "r_types_base.h"
static RList* parse_list (const char *str) {
RList *list;
char *line, *data, *p, *str_n;
if (!str) {
return NULL;
}
str_n = strdup (str);
list = r_list_newf (free);
if (!list) {
free (str_n);
return NULL;
}
line = strtok (str_n, "\n");
data = strchr (line, '=');
p = strtok (data + 1, ",");
while (p) {
r_list_append (list, (void*)strdup (p));
p = strtok (NULL, ",");
}
free (str_n);
return list;
}
static RList* get_constants(const char *str) {
RList *list;
char *p, *data;
if (!str) {
return NULL;
}
data = strdup (str);
list = r_list_newf (free);
p = strtok (data, ",");
while (p) {
if (strtol (p, NULL, 0)) {
r_list_append (list, (void*)strdup (p));
}
p = strtok (NULL, ",");
}
return list;
}
static bool isFlag (RRegItem *reg) {
const char *type = r_reg_get_type (reg->type);
if (!strcmp (type, "flg"))
return true;
return false;
}
// binary op
static bool simulate_op (const char *op, ut64 src1, ut64 src2, ut64 old_src1, ut64 old_src2, ut64 *result, int size) {
ut64 limit;
if (size == 64) {
limit = UT64_MAX;
} else {
limit = 1ULL << size;
}
if (!strcmp (op, "^")) {
*result = src1 ^ src2;
return true;
}
if (!strcmp (op, "+")) {
*result = src1 + src2;
return true;
}
if (!strcmp (op, "-")) {
if (src2 > src1) {
*result = limit + (src1 - src2);
} else {
*result = src1 - src2;
}
return true;
}
if (!strcmp (op, "*")) {
*result = src1 * src2;
return true;
}
if (!strcmp (op, "|")) {
*result = src1 | src2;
return true;
}
if (!strcmp (op, "/")) {
*result = src1 / src2;
return true;
}
if (!strcmp (op, "%")) {
*result = src1 % src2;
return true;
}
if (!strcmp (op, "<<")) {
*result = src1 << src2;
return true;
}
if (!strcmp (op, ">>")) {
*result = src1 >> src2;
return true;
}
if (!strcmp (op, "&")) {
*result = src1 & src2;
return true;
}
if (!strcmp (op, "+=")) {
*result = old_src1 + src2;
return true;
}
if (!strcmp (op, "-=")) {
if (src2 > old_src1) {
*result = limit + (old_src1 - src2);
} else {
*result = old_src1 - src2;
}
return true;
}
if (!strcmp (op, "*=")) {
*result = old_src1 * src2;
return true;
}
if (!strcmp (op, "/=")) {
*result = old_src1 / src2;
return true;
}
if (!strcmp (op, "%=")) {
*result = old_src1 % src2;
return true;
}
if (!strcmp (op, "<<")) {
*result = src1 << src2;
return true;
}
if (!strcmp (op, ">>")) {
*result = src1 >> src2;
return true;
}
if (!strcmp (op, "&=")) {
*result = src1 & src2;
return true;
}
if (!strcmp (op, "^=")) {
*result = src1 ^ src2;
return true;
}
if (!strcmp (op, "|=")) {
*result = src1 | src2;
return true;
}
return false;
}
// fill REGs with known values
static void fillRegisterValues (RCore *core) {
RListIter *iter_reg;
RList *regs;
RRegItem *reg_item;
int nr = 10;
regs = r_reg_get_list (core->dbg->reg, 0);
if (!regs) {
return;
}
r_list_foreach (regs, iter_reg, reg_item) {
r_reg_arena_pop (core->dbg->reg);
r_reg_set_value (core->dbg->reg, reg_item, nr);
r_reg_arena_push (core->dbg->reg);
nr += 3;
}
}
// split esil string in flags part and main instruction
// hacky, only tested for x86, TODO: portable version
// NOTE: esil_main and esil_flg are heap allocated and must be freed by the caller
static void esil_split_flg (char *esil_str, char **esil_main, char **esil_flg) {
char *split = strstr (esil_str, "f,=");
const int kCommaHits = 2;
int hits = 0;
if (split) {
while (hits != kCommaHits) {
--split;
if (*split == ',') {
hits++;
}
}
*esil_flg = strdup (++split);
*esil_main = r_str_ndup (esil_str, strlen (esil_str) - strlen (*esil_flg) - 1);
}
}
#define FREE_ROP { \
R_FREE (out); \
R_FREE (esil_flg); \
R_FREE (esil_main); \
r_list_free (ops_list); \
ops_list = NULL; \
r_list_free (flg_read); \
flg_read = NULL; \
r_list_free (flg_write); \
flg_write = NULL; \
r_list_free (reg_read); \
reg_read = NULL; \
r_list_free (reg_write); \
reg_write = NULL; \
r_list_free (mem_read); \
mem_read = NULL; \
r_list_free (mem_write); \
mem_write = NULL; \
}
static char* rop_classify_constant(RCore *core, RList *ropList) {
char *esil_str, *constant;
char *ct = NULL, *esil_main = NULL, *esil_flg = NULL, *out = NULL;
RListIter *iter_r, *iter_dst, *iter_const;
RRegItem *item_dst;
RList *head, *constants;
RList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL,
*reg_read = NULL, *reg_write = NULL, *mem_read = NULL,
*mem_write = NULL;
const bool romem = r_config_get_i (core->config, "esil.romem");
const bool stats = r_config_get_i (core->config, "esil.stats");
if (!romem || !stats) {
// eprintf ("Error: esil.romem and esil.stats must be set TRUE");
return NULL;
}
r_list_foreach (ropList, iter_r, esil_str) {
constants = get_constants (esil_str);
// if there are no constants in the instruction continue
if (r_list_empty (constants)) {
continue;
}
// init regs with known values
fillRegisterValues (core);
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
ct = NULL;
goto continue_error;
}
esil_split_flg (esil_str, &esil_main, &esil_flg);
cmd_anal_esil (core, esil_main? esil_main: esil_str);
out = sdb_querys (core->anal->esil->stats, NULL, 0, "*");
if (!out) {
goto continue_error;
}
ops_list = parse_list (strstr (out, "ops.list"));
flg_read = parse_list (strstr (out, "flg.read"));
flg_write = parse_list (strstr (out, "flg.write"));
reg_read = parse_list (strstr (out, "reg.read"));
reg_write = parse_list (strstr (out, "reg.write"));
mem_read = parse_list (strstr (out, "mem.read"));
mem_write = parse_list (strstr (out, "mem.write"));
if (!r_list_find (ops_list, "=", (RListComparator)strcmp)) {
goto continue_error;
}
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
goto out_error;
}
r_list_foreach (head, iter_dst, item_dst) {
ut64 diff_dst, value_dst;
if (!r_list_find (reg_write, item_dst->name,
(RListComparator)strcmp)) {
continue;
}
value_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
diff_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
//restore initial value
r_reg_set_value (core->dbg->reg, item_dst, diff_dst);
if (value_dst != diff_dst) {
r_list_foreach (constants, iter_const, constant) {
if (value_dst == r_num_get (NULL, constant)) {
ct = r_str_appendf (ct, "%s <-- 0x%"PFMT64x";", item_dst->name, value_dst);
}
}
}
}
continue_error:
// coverity may complain here but as long as the pointer is set back to
// NULL is safe that is why is used R_FREE
FREE_ROP;
r_list_free (constants);
}
return ct;
out_error:
FREE_ROP;
r_list_free (constants);
return NULL;
}
static char* rop_classify_mov(RCore *core, RList *ropList) {
char *esil_str;
char *mov = NULL, *esil_main = NULL, *esil_flg = NULL, *out = NULL;
RListIter *iter_src, *iter_r, *iter_dst;
RRegItem *item_src, *item_dst;
RList *head;
RList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL,
*reg_read = NULL, *reg_write = NULL, *mem_read = NULL,
*mem_write = NULL;
const bool romem = r_config_get_i (core->config, "esil.romem");
const bool stats = r_config_get_i (core->config, "esil.stats");
if (!romem || !stats) {
// eprintf ("Error: esil.romem and esil.stats must be set TRUE");
return NULL;
}
r_list_foreach (ropList, iter_r, esil_str) {
// init regs with known values
fillRegisterValues (core);
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
goto out_error;
}
esil_split_flg (esil_str, &esil_main, &esil_flg);
cmd_anal_esil (core, esil_main? esil_main: esil_str);
out = sdb_querys (core->anal->esil->stats, NULL, 0, "*");
if (out) {
ops_list = parse_list (strstr (out, "ops.list"));
flg_read = parse_list (strstr (out, "flg.read"));
flg_write = parse_list (strstr (out, "flg.write"));
reg_read = parse_list (strstr (out, "reg.read"));
reg_write = parse_list (strstr (out, "reg.write"));
mem_read = parse_list (strstr (out, "mem.read"));
mem_write = parse_list (strstr (out, "mem.write"));
} else {
goto continue_error;
}
if (!r_list_find (ops_list, "=", (RListComparator)strcmp)) {
goto continue_error;
}
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
goto out_error;
}
r_list_foreach (head, iter_dst, item_dst) {
ut64 diff_dst, value_dst;
if (!r_list_find (reg_write, item_dst->name,
(RListComparator)strcmp)) {
continue;
}
// you never mov into flags
if (isFlag (item_dst)) {
continue;
}
value_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
diff_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
r_list_foreach (head, iter_src, item_src) {
ut64 diff_src, value_src;
if (!r_list_find (reg_read, item_src->name,
(RListComparator)strcmp)) {
continue;
}
// you never mov from flags
if (item_src == item_dst || isFlag (item_src)) {
continue;
}
value_src = r_reg_get_value (core->dbg->reg, item_src);
r_reg_arena_swap (core->dbg->reg, false);
diff_src = r_reg_get_value (core->dbg->reg, item_src);
r_reg_arena_swap (core->dbg->reg, false);
//restore initial value
r_reg_set_value (core->dbg->reg, item_src, diff_src);
if (value_dst == value_src && value_dst != diff_dst) {
mov = r_str_appendf (mov, "%s <-- %s;",
item_dst->name, item_src->name);
}
}
}
continue_error:
FREE_ROP;
}
return mov;
out_error:
FREE_ROP;
return NULL;
}
static char* rop_classify_arithmetic(RCore *core, RList *ropList) {
char *esil_str, *op;
char *arithmetic = NULL, *esil_flg = NULL, *esil_main = NULL,
*out = NULL;
RListIter *iter_src1, *iter_src2, *iter_r, *iter_dst, *iter_ops;
RRegItem *item_src1, *item_src2, *item_dst;
RList *head;
RList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL,
*reg_read = NULL, *reg_write = NULL, *mem_read = NULL,
*mem_write = NULL;
const bool romem = r_config_get_i (core->config, "esil.romem");
const bool stats = r_config_get_i (core->config, "esil.stats");
ut64 *op_result = R_NEW0 (ut64);
ut64 *op_result_r = R_NEW0 (ut64);
if (!romem || !stats) {
// eprintf ("Error: esil.romem and esil.stats must be set TRUE");
free (op_result);
free (op_result_r);
return NULL;
}
r_list_foreach (ropList, iter_r, esil_str) {
// init regs with known values
fillRegisterValues (core);
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
goto out_error;
}
esil_split_flg (esil_str, &esil_main, &esil_flg);
if (esil_main) {
cmd_anal_esil (core, esil_main);
} else {
cmd_anal_esil (core, esil_str);
}
out = sdb_querys (core->anal->esil->stats, NULL, 0, "*");
// r_cons_println (out);
if (!out) {
goto continue_error;
}
ops_list = parse_list (strstr (out, "ops.list"));
flg_read = parse_list (strstr (out, "flg.read"));
flg_write = parse_list (strstr (out, "flg.write"));
reg_read = parse_list (strstr (out, "reg.read"));
reg_write = parse_list (strstr (out, "reg.write"));
mem_read = parse_list (strstr (out, "mem.read"));
mem_write = parse_list (strstr (out, "mem.write"));
r_list_foreach (ops_list, iter_ops, op) {
r_list_foreach (head, iter_src1, item_src1) {
ut64 value_src1, diff_src1;
value_src1 = r_reg_get_value (core->dbg->reg, item_src1);
r_reg_arena_swap (core->dbg->reg, false);
diff_src1 = r_reg_get_value (core->dbg->reg, item_src1);
r_reg_arena_swap (core->dbg->reg, false);
if (!r_list_find (reg_read, item_src1->name,
(RListComparator)strcmp)) {
continue;
}
r_list_foreach (head, iter_src2, item_src2) {
ut64 value_src2, diff_src2;
value_src2 = r_reg_get_value (core->dbg->reg, item_src2);
r_reg_arena_swap (core->dbg->reg, false);
diff_src2 = r_reg_get_value (core->dbg->reg, item_src2);
if (!r_list_find (reg_read, item_src2->name,
(RListComparator)strcmp)) {
continue;
}
// TODO check condition
if (iter_src1 == iter_src2) {
continue;
}
r_list_foreach (head, iter_dst, item_dst) {
ut64 value_dst;
bool redundant = false, simulate, simulate_r;
value_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
if (!r_list_find (reg_write, item_dst->name,
(RListComparator)strcmp)) {
continue;
}
// dont check flags for arithmetic
if (isFlag (item_dst)) {
continue;
}
simulate = simulate_op (op, value_src1, value_src2, diff_src1, diff_src2, op_result, item_dst->size);
simulate_r = simulate_op (op, value_src2, value_src1, diff_src2, diff_src1, op_result_r, item_dst->size);
if (/*value_src1 != 0 && value_src2 != 0 && */simulate && value_dst == *op_result) {
// r_cons_println ("Debug: FOUND ONE !");
char *tmp = r_str_newf ("%s <-- %s %s %s;", item_dst->name, item_src1->name, op, item_src2->name);
if (arithmetic && !strstr (arithmetic, tmp)) {
arithmetic = r_str_append (arithmetic, tmp);
} else if (!arithmetic) {
arithmetic = r_str_append (arithmetic, tmp);
}
free (tmp);
redundant = true;
} else if (!redundant /*&& value_src1 != 0 && value_src2 != 0*/ && simulate_r && value_dst == *op_result_r) {
// r_cons_println ("Debug: FOUND ONE reversed!");
char *tmp = r_str_newf ("%s <-- %s %s %s;", item_dst->name, item_src2->name, op, item_src1->name);
if (arithmetic && !strstr (arithmetic, tmp)) {
arithmetic = r_str_append (arithmetic, tmp);
} else if (!arithmetic) {
arithmetic = r_str_append (arithmetic, tmp);
}
free (tmp);
}
}
}
}
}
continue_error:
FREE_ROP;
}
free (op_result);
free (op_result_r);
return arithmetic;
out_error:
FREE_ROP;
free (op_result);
free (op_result_r);
return NULL;
}
static char* rop_classify_arithmetic_const(RCore *core, RList *ropList) {
char *esil_str, *op, *constant;
char *arithmetic = NULL, *esil_flg = NULL, *esil_main = NULL;
RListIter *iter_src1, *iter_r, *iter_dst, *iter_ops, *iter_const;
RRegItem *item_src1, *item_dst;
RList *head, *constants;
RList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL, *reg_read = NULL,
*reg_write = NULL, *mem_read = NULL, *mem_write = NULL;
const bool romem = r_config_get_i (core->config, "esil.romem");
const bool stats = r_config_get_i (core->config, "esil.stats");
ut64 *op_result = R_NEW0 (ut64);
ut64 *op_result_r = R_NEW0 (ut64);
if (!romem || !stats) {
// eprintf ("Error: esil.romem and esil.stats must be set TRUE");
free (op_result);
free (op_result_r);
return NULL;
}
r_list_foreach (ropList, iter_r, esil_str) {
constants = get_constants (esil_str);
// if there are no constants in the instruction continue
if (r_list_empty (constants)) {
continue;
}
// init regs with known values
fillRegisterValues (core);
head = r_reg_get_list (core->dbg->reg, 0);
if (!head) {
arithmetic = NULL;
continue;
}
esil_split_flg (esil_str, &esil_main, &esil_flg);
if (esil_main) {
cmd_anal_esil (core, esil_main);
} else {
cmd_anal_esil (core, esil_str);
}
char *out = sdb_querys (core->anal->esil->stats, NULL, 0, "*");
// r_cons_println (out);
if (out) {
ops_list = parse_list (strstr (out, "ops.list"));
flg_read = parse_list (strstr (out, "flg.read"));
flg_write = parse_list (strstr (out, "flg.write"));
reg_read = parse_list (strstr (out, "reg.read"));
reg_write = parse_list (strstr (out, "reg.write"));
mem_read = parse_list (strstr (out, "mem.read"));
mem_write = parse_list (strstr (out, "mem.write"));
} else {
free (op_result);
free (op_result_r);
goto continue_error;
}
r_list_foreach (ops_list, iter_ops, op) {
r_list_foreach (head, iter_src1, item_src1) {
ut64 value_src1, diff_src1;
value_src1 = r_reg_get_value (core->dbg->reg, item_src1);
r_reg_arena_swap (core->dbg->reg, false);
diff_src1 = r_reg_get_value (core->dbg->reg, item_src1);
r_reg_arena_swap (core->dbg->reg, false);
if (!r_list_find (reg_read, item_src1->name,
(RListComparator)strcmp)) {
continue;
}
r_list_foreach (head, iter_dst, item_dst) {
ut64 value_dst, diff_dst;
bool redundant = false, simulate, simulate_r;
value_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
diff_dst = r_reg_get_value (core->dbg->reg, item_dst);
r_reg_arena_swap (core->dbg->reg, false);
if (!r_list_find (reg_write, item_dst->name,
(RListComparator)strcmp)) {
continue;
}
// dont check flags for arithmetic
if (isFlag (item_dst)) {
continue;
}
if (value_dst != diff_dst) {
r_list_foreach (constants, iter_const, constant) {
ut64 value_ct = r_num_get (NULL, constant);
simulate = simulate_op (op, value_src1, value_ct,
diff_src1, value_ct, op_result,
item_dst->size);
simulate_r = simulate_op (op, value_ct, value_src1,
value_ct, diff_src1, op_result_r,
item_dst->size);
if (simulate && value_dst == *op_result) {
char *tmp = r_str_newf ("%s <-- %s %s %s;", item_dst->name, item_src1->name, op, constant);
if (arithmetic && !strstr (arithmetic, tmp)) {
arithmetic = r_str_append (arithmetic, tmp);
} else if (!arithmetic) {
arithmetic = r_str_append (arithmetic, tmp);
}
free (tmp);
redundant = true;
} else if (!redundant && simulate_r && value_dst == *op_result_r) {
char *tmp = r_str_newf ("%s <-- %s %s %s;", item_dst->name, constant, op, item_src1->name);
if (arithmetic && !strstr (arithmetic, tmp)) {
arithmetic = r_str_append (arithmetic, tmp);
} else if (!arithmetic) {
arithmetic = r_str_append (arithmetic, tmp);
}
free (tmp);
}
}
}
}
}
}
continue_error:
FREE_ROP;
r_list_free (constants);
}
free (op_result);
free (op_result_r);
return arithmetic;
}
static int rop_classify_nops(RCore *core, RList *ropList) {
char *esil_str;
int changes = 1;
RListIter *iter_r;
const bool romem = r_config_get_i (core->config, "esil.romem");
const bool stats = r_config_get_i (core->config, "esil.stats");
if (!romem || !stats) {
// eprintf ("Error: esil.romem and esil.stats must be set TRUE");
return -2;
}
r_list_foreach (ropList, iter_r, esil_str) {
fillRegisterValues (core);
// r_cons_printf ("Emulating nop:%s\n", esil_str);
cmd_anal_esil (core, esil_str);
char *out = sdb_querys (core->anal->esil->stats, NULL, 0, "*");
// r_cons_println (out);
if (out) {
free (out);
return 0;
}
// directly say NOP
continue;
}
return changes;
}
static void rop_classify (RCore *core, Sdb *db, RList *ropList, const char *key, unsigned int size) {
Sdb *db_nop = NULL, *db_mov = NULL, *db_ct = NULL, *db_aritm = NULL, *db_aritm_ct = NULL;
int nop = 0; rop_classify_nops (core, ropList);
char *mov, *ct, *arithm, *arithm_ct, *str;
db_nop = sdb_ns (db, "nop", true);
db_mov = sdb_ns (db, "mov", true);
db_ct = sdb_ns (db, "const", true);
db_aritm = sdb_ns (db, "arithm", true);
db_aritm_ct = sdb_ns (db, "arithm_ct", true);
if (!db_nop || !db_mov || !db_ct || !db_aritm || !db_aritm_ct) {
eprintf ("Error: Could not create SDB 'rop' sub-namespaces\n");
return;
}
nop = rop_classify_nops (core, ropList);
mov = rop_classify_mov (core, ropList);
ct = rop_classify_constant (core, ropList);
arithm = rop_classify_arithmetic (core, ropList);
arithm_ct = rop_classify_arithmetic_const (core, ropList);
str = r_str_newf ("0x%"PFMT64x, size);
if (nop == 1) {
char *str_nop = r_str_newf ("%s NOP", str);
sdb_set (db_nop, key, str_nop, 0);
free (str_nop);
} else {
if (mov) {
char *str_mov = r_str_newf ("%s MOV { %s }", str, mov);
sdb_set (db_mov, key, str_mov, 0);
free (str_mov);
free (mov);
}
if (ct) {
char *str_ct = r_str_newf ("%s LOAD_CONST { %s }", str, ct);
sdb_set (db_ct, key, str_ct, 0);
free (str_ct);
free (ct);
}
if (arithm) {
char *str_arithm = r_str_newf ("%s ARITHMETIC { %s }", str, arithm);
sdb_set (db_aritm, key, str_arithm, 0);
free (str_arithm);
free (arithm);
}
if (arithm_ct) {
char *str_arithm_ct = r_str_newf ("%s ARITHMETIC_CONST { %s }", str, arithm_ct);
sdb_set (db_aritm_ct, key, str_arithm_ct, 0);
free (str_arithm_ct);
free (arithm_ct);
}
}
free (str);
}