RzAnalysisILVM wraps around the low-level RzILVM and enables emulation of real code from disassembly, rather than raw IL. Analysis plugins now don't actively initialize the vm anymore, but return a fully declarative description RzAnalysisILConfig of how to set up the vm and optionally its initial state. This also enables multiple IL vms to exist at the same time as plugins can not mess with the global vm anymore. See the added integration test for an example.
260 lines
7.2 KiB
C
260 lines
7.2 KiB
C
// SPDX-FileCopyrightText: 2011-2016 pancake <pancake@nopcode.org>
|
|
// SPDX-FileCopyrightText: 2021 heersin <teablearcher@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <string.h>
|
|
#include <rz_types.h>
|
|
#include <rz_lib.h>
|
|
#include <rz_asm.h>
|
|
#include <rz_analysis.h>
|
|
|
|
static int getid(char ch) {
|
|
const char *keys = "[]<>+-,.";
|
|
const char *cidx = strchr(keys, ch);
|
|
return cidx ? cidx - keys + 1 : 0;
|
|
}
|
|
|
|
/* New IL uplift bf */
|
|
#define BF_ADDR_MEM 0x10000
|
|
#define BF_ADDR_SIZE 64
|
|
#define BF_BYTE_SIZE 8
|
|
#define BF_ID_STACK 32
|
|
|
|
#define bf_il_ptr() rz_il_op_new_var("ptr", RZ_IL_VAR_KIND_GLOBAL)
|
|
#define bf_il_set_ptr(x) rz_il_op_new_set("ptr", false, x)
|
|
#define bf_il_one(l) rz_il_op_new_bitv_from_ut64(l, 1)
|
|
|
|
static void bf_syscall_read(RzILVM *vm, RzILOpEffect *op) {
|
|
ut8 c = getc(stdin);
|
|
RzBitVector *bv = rz_bv_new_from_ut64(BF_BYTE_SIZE, c);
|
|
RzILVal *ptr_val = rz_il_vm_get_var_value(vm, RZ_IL_VAR_KIND_GLOBAL, "ptr");
|
|
if (ptr_val->type == RZ_IL_TYPE_PURE_BITVECTOR) {
|
|
rz_il_vm_mem_store(vm, 0, ptr_val->data.bv, bv);
|
|
} else {
|
|
rz_warn_if_reached();
|
|
}
|
|
rz_bv_free(bv);
|
|
}
|
|
|
|
static void bf_syscall_write(RzILVM *vm, RzILOpEffect *op) {
|
|
RzILVal *ptr_val = rz_il_vm_get_var_value(vm, RZ_IL_VAR_KIND_GLOBAL, "ptr");
|
|
if (ptr_val->type != RZ_IL_TYPE_PURE_BITVECTOR) {
|
|
rz_warn_if_reached();
|
|
return;
|
|
}
|
|
RzBitVector *bv = rz_il_vm_mem_load(vm, 0, ptr_val->data.bv);
|
|
ut32 c = rz_bv_to_ut32(bv);
|
|
putchar(c);
|
|
rz_bv_free(bv);
|
|
}
|
|
|
|
RzILOpEffect *bf_right_arrow() {
|
|
// (set ptr (+ (val ptr) (int 1)))
|
|
RzILOpBitVector *add = rz_il_op_new_add(bf_il_ptr(), bf_il_one(BF_ADDR_SIZE));
|
|
return bf_il_set_ptr(add);
|
|
}
|
|
|
|
RzILOpEffect *bf_left_arrow() {
|
|
// (set ptr (- (val ptr) (int 1)))
|
|
RzILOpBitVector *sub = rz_il_op_new_sub(bf_il_ptr(), bf_il_one(BF_ADDR_SIZE));
|
|
return bf_il_set_ptr(sub);
|
|
}
|
|
|
|
RzILOpEffect *bf_inc() {
|
|
// (store mem (var ptr) (+ (load (var ptr)) (int 1)))
|
|
// mem == 0 because is the only mem in bf
|
|
RzILOpBitVector *load = rz_il_op_new_load(0, bf_il_ptr());
|
|
RzILOpBitVector *add = rz_il_op_new_add(load, bf_il_one(BF_BYTE_SIZE));
|
|
return rz_il_op_new_store(0, bf_il_ptr(), add);
|
|
}
|
|
|
|
RzILOpEffect *bf_dec() {
|
|
// (store mem (var ptr) (- (load (var ptr)) (int 1)))
|
|
// mem == 0 because is the only mem in bf
|
|
RzILOpBitVector *load = rz_il_op_new_load(0, bf_il_ptr());
|
|
RzILOpBitVector *sub = rz_il_op_new_sub(load, bf_il_one(BF_BYTE_SIZE));
|
|
return rz_il_op_new_store(0, bf_il_ptr(), sub);
|
|
}
|
|
|
|
RzILOpEffect *bf_out() {
|
|
// (goto write)
|
|
return rz_il_op_new_goto("write");
|
|
}
|
|
|
|
RzILOpEffect *bf_in() {
|
|
// (goto hook_read)
|
|
return rz_il_op_new_goto("read");
|
|
}
|
|
|
|
/**
|
|
* Search matching [ or ] starting at addr in direction given by dir (-1 or 1)
|
|
*/
|
|
static ut64 find_matching_bracket(RzAnalysis *analysis, ut64 addr, int dir) {
|
|
if (!analysis->read_at) {
|
|
return UT64_MAX;
|
|
}
|
|
static const ut64 max_dist = 2048; // some upper bound to avoid (almost) infinite loops
|
|
ut64 dist = 0;
|
|
int lev = dir;
|
|
while (dist < max_dist) {
|
|
dist++;
|
|
addr += dir;
|
|
if (addr == UT64_MAX) {
|
|
break;
|
|
}
|
|
ut8 c;
|
|
analysis->read_at(analysis, addr, &c, 1);
|
|
switch (c) {
|
|
case '[':
|
|
lev++;
|
|
break;
|
|
case ']':
|
|
lev--;
|
|
break;
|
|
case 0:
|
|
case 0xff:
|
|
// invalid code
|
|
return UT64_MAX;
|
|
default:
|
|
continue;
|
|
}
|
|
if (lev == 0) {
|
|
return addr;
|
|
}
|
|
}
|
|
return UT64_MAX;
|
|
}
|
|
|
|
RzILOpEffect *bf_llimit(RzAnalysis *analysis, ut64 addr, ut64 target) {
|
|
// (perform (branch (load mem (var ptr))
|
|
// (do nothing)
|
|
// (goto ]))
|
|
RzILOpBitVector *var = rz_il_op_new_var("ptr", RZ_IL_VAR_KIND_GLOBAL);
|
|
RzILOpBool *cond = rz_il_op_new_non_zero(rz_il_op_new_load(0, var));
|
|
// goto ]
|
|
RzILOpEffect *jmp = rz_il_op_new_jmp(rz_il_op_new_bitv_from_ut64(64, target));
|
|
// branch if (load mem (var ptr)) is false then goto ]
|
|
return rz_il_op_new_branch(cond, NULL, jmp);
|
|
}
|
|
|
|
RzILOpEffect *bf_rlimit(RzAnalysis *analysis, ut64 addr, ut64 target) {
|
|
// (perform (branch (load mem (var ptr))
|
|
// (goto [)
|
|
// (do nothing))
|
|
RzILOpBitVector *var = rz_il_op_new_var("ptr", RZ_IL_VAR_KIND_GLOBAL);
|
|
RzILOpBool *cond = rz_il_op_new_non_zero(rz_il_op_new_load(0, var));
|
|
// goto [
|
|
RzILOpEffect *jmp = rz_il_op_new_jmp(rz_il_op_new_bitv_from_ut64(64, target));
|
|
// branch if (load mem (var ptr)) is true then goto ]
|
|
RzILOpEffect *branch = rz_il_op_new_branch(cond, jmp, NULL);
|
|
return branch;
|
|
}
|
|
|
|
static RzAnalysisILConfig *il_config(RzAnalysis *analysis) {
|
|
RzAnalysisILConfig *cfg = rz_analysis_il_config_new(64, false, 64);
|
|
cfg->init_state = rz_analysis_il_init_state_new();
|
|
if (!cfg->init_state) {
|
|
rz_analysis_il_config_free(cfg);
|
|
return NULL;
|
|
}
|
|
rz_analysis_il_init_state_set_var(cfg->init_state, "ptr", rz_il_value_new_bitv(rz_bv_new_from_ut64(64, BF_ADDR_MEM)));
|
|
RzILEffectLabel *read_label = rz_il_effect_label_new("read", EFFECT_LABEL_SYSCALL);
|
|
read_label->hook = bf_syscall_read;
|
|
rz_analysis_il_config_add_label(cfg, read_label);
|
|
RzILEffectLabel *write_label = rz_il_effect_label_new("write", EFFECT_LABEL_HOOK);
|
|
write_label->hook = bf_syscall_write;
|
|
rz_analysis_il_config_add_label(cfg, write_label);
|
|
return cfg;
|
|
}
|
|
|
|
static int bf_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) {
|
|
rz_return_val_if_fail(analysis && op, -1);
|
|
|
|
op->size = 1;
|
|
op->id = getid(buf[0]);
|
|
op->addr = addr;
|
|
|
|
switch (buf[0]) {
|
|
case '[':
|
|
// Find the jump target, +1 because we jump directly after the matching bracket.
|
|
// If not found this returns UT64_MAX, so this overflows to 0, which is considered the "invalid"
|
|
// value for RzAnalysisOp, so it's fine.
|
|
op->jump = find_matching_bracket(analysis, addr, 1) + 1;
|
|
op->il_op = bf_llimit(analysis, addr, op->jump);
|
|
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
|
|
op->fail = addr + 1;
|
|
break;
|
|
case ']':
|
|
// same idea for target as above
|
|
op->jump = find_matching_bracket(analysis, addr, -1) + 1;
|
|
op->il_op = bf_rlimit(analysis, addr, op->jump);
|
|
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
|
|
break;
|
|
case '>':
|
|
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
|
|
op->il_op = bf_right_arrow();
|
|
break;
|
|
case '<':
|
|
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
|
|
op->il_op = bf_left_arrow();
|
|
break;
|
|
case '+':
|
|
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
|
|
op->il_op = bf_inc();
|
|
break;
|
|
case '-':
|
|
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
|
|
op->il_op = bf_dec();
|
|
break;
|
|
case '.':
|
|
op->il_op = bf_out();
|
|
op->type = RZ_ANALYSIS_OP_TYPE_STORE;
|
|
break;
|
|
case ',':
|
|
op->il_op = bf_in();
|
|
op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
|
|
break;
|
|
case 0x00:
|
|
case 0xff:
|
|
op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
|
|
break;
|
|
default:
|
|
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
|
|
op->il_op = rz_il_op_new_nop();
|
|
break;
|
|
}
|
|
return op->size;
|
|
}
|
|
|
|
static char *get_reg_profile(RzAnalysis *analysis) {
|
|
return strdup(
|
|
"=PC pc\n"
|
|
"=BP ptr\n"
|
|
"=SP ptr\n"
|
|
"=A0 ptr\n"
|
|
"=A1 ptr\n"
|
|
"=A2 ptr\n"
|
|
"=A3 ptr\n"
|
|
"gpr ptr .64 0 0\n" // data pointer
|
|
"gpr pc .64 8 0\n" // program counter
|
|
);
|
|
}
|
|
|
|
RzAnalysisPlugin rz_analysis_plugin_bf = {
|
|
.name = "bf",
|
|
.desc = "brainfuck code analysis plugin",
|
|
.license = "LGPL3",
|
|
.arch = "bf",
|
|
.bits = 64, // RzIL emulation of bf and the reg definitions above use 64bit values
|
|
.op = &bf_op,
|
|
.get_reg_profile = get_reg_profile,
|
|
.il_config = il_config
|
|
};
|
|
|
|
#ifndef RZ_PLUGIN_INCORE
|
|
RZ_API RzLibStruct rizin_plugin = {
|
|
.type = RZ_LIB_TYPE_ANALYSIS,
|
|
.data = &rz_analysis_plugin_bf,
|
|
.version = RZ_VERSION
|
|
};
|
|
#endif
|