rizin/librz/analysis/p/analysis_bf.c
Florian Märkl 6a09385746 [RzIL] Remove vector-oplists in favor of seq
The root type of a lifted op is now simply a single RzILOpEffect, which
can be for example a chain of seq ops. This is in line with what BAP
uses.
For convenient creation of sequences, `rz_il_op_new_seqn(ut32 n, ...)`
is a drop-in replacement for `rz_il_make_oplist(ut32 n, ...)`.
2021-12-28 13:37:54 +01:00

451 lines
11 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
struct bf_stack_t {
ut64 stack[BF_ID_STACK];
int sp;
};
typedef struct bf_stack_t BfStack;
typedef struct bf_context_t {
BfStack *stack;
HtUP *label_names;
ut64 op_count;
} BfContext;
#define bf_il_ptr() rz_il_op_new_var("ptr")
#define bf_il_set_ptr(x) rz_il_op_new_set("ptr", 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_hash_find_val_by_name(vm, "ptr");
rz_il_vm_mem_store(vm, 0, ptr_val->data.bv, bv);
rz_bv_free(bv);
}
static void bf_syscall_write(RzILVM *vm, RzILOpEffect *op) {
RzILVal *ptr_val = rz_il_hash_find_val_by_name(vm, "ptr");
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);
}
ut64 pop_astack(BfStack *stack) {
if (stack->sp <= 0) {
RZ_LOG_ERROR("RzIL: brainfuck: the stack is empty\n");
return -1;
}
stack->sp -= 1;
return stack->stack[stack->sp];
}
void push_astack(BfStack *stack, ut64 id) {
if (stack->sp >= BF_ID_STACK - 1) {
RZ_LOG_ERROR("RzIL: brainfuck: the stack is full\n");
return;
}
stack->stack[stack->sp] = id;
stack->sp += 1;
}
ut64 parse_label_id(char *lbl_name) {
char *addr_str = strchr(lbl_name, ']') + 1;
ut64 addr = rz_num_math(NULL, addr_str);
return addr;
}
RzILOpEffect *bf_right_arrow(RzILVM *vm, ut64 id) {
// (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(RzILVM *vm, ut64 id) {
// (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(RzILVM *vm, ut64 id) {
// (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(RzILVM *vm, ut64 id) {
// (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(RzILVM *vm, ut64 id) {
// (goto write)
return rz_il_op_new_goto("write");
}
RzILOpEffect *bf_in(RzILVM *vm, ut64 id) {
// (goto hook_read)
return rz_il_op_new_goto("read");
}
RzILOpEffect *bf_llimit(RzILVM *vm, BfContext *ctx, ut64 id, ut64 addr) {
// (perform (branch (load mem (var ptr))
// (do nothing)
// (goto ]))
char *cur_lbl_name = NULL, *to_free = NULL, *dst_lbl_name = NULL;
RzILEffectLabel *cur_label, *dst_label;
RzBitVector *cur_addr;
cur_lbl_name = ht_up_find(ctx->label_names, addr, NULL);
if (!cur_lbl_name) {
// no label name bind to current address
cur_lbl_name = to_free = rz_str_newf("[%lld", addr);
ht_up_insert(ctx->label_names, addr, cur_lbl_name);
push_astack(ctx->stack, addr);
// create a label in VM
cur_label = rz_il_vm_find_label_by_name(vm, cur_lbl_name);
if (!cur_label) {
// should always reach here if enter "!cur_lbl_name" branch
cur_addr = rz_bv_new_from_ut64(vm->addr_size, addr);
rz_il_vm_create_label(vm, cur_lbl_name, cur_addr);
rz_bv_free(cur_addr);
}
}
dst_lbl_name = rz_str_newf("]%lld", addr);
dst_label = rz_il_vm_find_label_by_name(vm, dst_lbl_name);
if (!dst_label) {
dst_label = rz_il_vm_create_label_lazy(vm, dst_lbl_name);
}
free(dst_lbl_name);
free(to_free);
RzILOpBitVector *var = rz_il_op_new_var("ptr");
RzILOpBool *cond = rz_il_op_new_non_zero(rz_il_op_new_load(0, var));
// goto ]
RzILOpEffect *goto_ = rz_il_op_new_goto(dst_label->label_id);
// branch if (load mem (var ptr)) is false then goto ]
return rz_il_op_new_branch(cond, NULL, goto_);
}
RzILOpEffect *bf_rlimit(RzILVM *vm, BfContext *ctx, ut64 id, ut64 addr) {
// (perform (branch (load mem (var ptr))
// (goto [)
// (do nothing))
char *cur_lbl_name = NULL, *to_free = NULL, *dst_lbl_name = NULL;
RzILEffectLabel *dst_label;
ut64 dst_addr;
cur_lbl_name = ht_up_find(ctx->label_names, addr, NULL);
if (!cur_lbl_name) {
dst_addr = pop_astack(ctx->stack);
to_free = cur_lbl_name = rz_str_newf("]%lld", dst_addr);
ht_up_insert(ctx->label_names, addr, cur_lbl_name);
}
if (!rz_il_hash_find_addr_by_lblname(vm, cur_lbl_name)) {
RzBitVector *cur_bv_addr = rz_bv_new_from_ut64(vm->addr_size, addr);
rz_il_vm_update_label(vm, cur_lbl_name, cur_bv_addr);
rz_bv_free(cur_bv_addr);
}
// Get label of '['
dst_addr = parse_label_id(cur_lbl_name);
dst_lbl_name = ht_up_find(ctx->label_names, dst_addr, NULL);
rz_return_val_if_fail(dst_lbl_name, NULL);
dst_label = rz_il_vm_find_label_by_name(vm, dst_lbl_name);
RzILOpBitVector *var = rz_il_op_new_var("ptr");
RzILOpBool *cond = rz_il_op_new_non_zero(rz_il_op_new_load(0, var));
// goto [
RzILOpEffect *goto_ = rz_il_op_new_goto(dst_label->label_id);
// branch if (load mem (var ptr)) is true then goto ]
RzILOpEffect *branch = rz_il_op_new_branch(cond, goto_, NULL);
free(to_free);
return branch;
}
static bool bf_specific_init(RzAnalysisRzil *rzil) {
RzILVM *vm = rzil->vm;
// load reg
rz_il_vm_add_reg(vm, "ptr", BF_ADDR_SIZE);
// set ptr to BF_ADDR_MEM
RzILVal *ptr = rz_il_hash_find_val_by_name(vm, "ptr");
rz_bv_set_from_ut64(ptr->data.bv, BF_ADDR_MEM);
RzILEffectLabel *read_label = rz_il_vm_create_label_lazy(vm, "read");
RzILEffectLabel *write_label = rz_il_vm_create_label_lazy(vm, "write");
read_label->addr = (void *)bf_syscall_read;
write_label->addr = (void *)bf_syscall_write;
read_label->type = EFFECT_LABEL_SYSCALL;
write_label->type = EFFECT_LABEL_HOOK;
rzil->inited = true;
return true;
}
static void bf_free_kv(HtUPKv *kv) {
free(kv->value);
}
static BfContext *bf_context_new() {
BfContext *ctx = RZ_NEW0(BfContext);
if (!ctx) {
return NULL;
}
ctx->stack = RZ_NEW0(BfStack);
ctx->label_names = ht_up_new((HtUPDupValue)strdup, bf_free_kv, NULL);
return ctx;
}
static void bf_context_free(BfContext *ctx) {
if (!ctx) {
return;
}
ht_up_free(ctx->label_names);
free(ctx->stack);
free(ctx);
}
static bool bf_fini_rzil(RzAnalysis *analysis) {
rz_return_val_if_fail(analysis && analysis->rzil, false);
RzAnalysisRzil *rzil = analysis->rzil;
bf_context_free(rzil->user);
if (rzil->vm) {
rz_il_vm_fini(rzil->vm);
}
rzil->user = NULL;
rzil->inited = false;
return true;
}
static bool bf_init_rzil(RzAnalysis *analysis) {
rz_return_val_if_fail(analysis && analysis->rzil, false);
RzAnalysisRzil *rzil = analysis->rzil;
if (rzil->inited) {
RZ_LOG_ERROR("RzIL: brainfuck: already initialized\n");
return true;
}
// TODO : get some arguments from rizin, predefined some for now.
ut32 addrsize = BF_ADDR_SIZE;
ut64 start_addr = 0;
// create core theory VM
if (!rz_il_vm_init(rzil->vm, start_addr, addrsize, false)) {
RZ_LOG_ERROR("RzIL: brainfuck: failed to initialize VM\n");
return false;
}
RzBuffer *buf = rz_buf_new_sparse_overlay(rzil->io_buf, RZ_BUF_SPARSE_WRITE_MODE_SPARSE);
if (!buf) {
rz_il_vm_fini(rzil->vm);
return false;
}
RzILMem *mem = rz_il_mem_new(buf, 64);
if (!mem) {
rz_buf_free(buf);
rz_il_vm_fini(rzil->vm);
return false;
}
rz_il_vm_add_mem(rzil->vm, 0, mem);
// init bf RZIL user-defined context
rzil->user = bf_context_new();
// bf specific init things
return bf_specific_init(rzil);
}
#define BUFSIZE_INC 32
static int bf_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) {
if (!op) {
return -1;
} else if (!analysis->rzil) {
RZ_LOG_ERROR("RzIL: brainfuck: the VM hasn't been initialized\n");
return -1;
}
op->size = 1;
op->id = getid(buf[0]);
op->addr = addr;
BfContext *ctx = analysis->rzil->user;
RzILVM *vm = analysis->rzil->vm;
RzILOpEffect *ilop = NULL;
op->rzil_op = RZ_NEW0(RzAnalysisRzilOp);
if (!op->rzil_op) {
RZ_LOG_ERROR("Fail to init rzil op\n");
return -1;
}
ut64 dst = 0LL;
switch (buf[0]) {
case '[':
ilop = bf_llimit(vm, ctx, op->id, addr);
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->fail = addr + 1;
buf = rz_mem_dup((void *)buf, len);
if (!buf) {
break;
}
{
const ut8 *p = buf + 1;
int lev = 0, i = 1;
len--;
while (i < len && *p) {
if (*p == '[') {
lev++;
}
if (*p == ']') {
lev--;
if (lev == -1) {
dst = addr + (size_t)(p - buf);
dst++;
op->jump = dst;
goto beach;
}
}
if (*p == 0x00 || *p == 0xff) {
op->type = RZ_ANALYSIS_OP_TYPE_ILL;
goto beach;
}
if (i == len - 1 && analysis->read_at) {
int new_buf_len = len + 1 + BUFSIZE_INC;
ut8 *new_buf = calloc(new_buf_len, 1);
if (new_buf) {
free((ut8 *)buf);
(void)analysis->read_at(analysis, addr, new_buf, new_buf_len);
buf = new_buf;
p = buf + i;
len += BUFSIZE_INC;
}
}
p++;
i++;
}
}
beach:
free((ut8 *)buf);
break;
case ']':
ilop = bf_rlimit(vm, ctx, op->id, addr);
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
break;
case '>':
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
ilop = bf_right_arrow(vm, op->id);
break;
case '<':
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
ilop = bf_left_arrow(vm, op->id);
break;
case '+':
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
ilop = bf_inc(vm, op->id);
break;
case '-':
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
ilop = bf_dec(vm, op->id);
break;
case '.':
ilop = bf_out(vm, op->id);
op->type = RZ_ANALYSIS_OP_TYPE_STORE;
break;
case ',':
ilop = bf_in(vm, op->id);
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;
ilop = rz_il_op_new_nop();
break;
}
if (ilop) {
op->rzil_op->op = ilop;
}
ctx->op_count++;
return op->size;
}
static char *get_reg_profile(RzAnalysis *analysis) {
return strdup(
"=PC pc\n"
"=BP brk\n"
"=SP ptr\n"
"=A0 rax\n"
"=A1 rbx\n"
"=A2 rcx\n"
"=A3 rdx\n"
"gpr ptr .32 0 0\n" // data pointer
"gpr pc .32 4 0\n" // program counter
"gpr brk .32 8 0\n" // brackets
"gpr scr .32 12 0\n" // screen
"gpr kbd .32 16 0\n" // keyboard
);
}
RzAnalysisPlugin rz_analysis_plugin_bf = {
.name = "bf",
.desc = "brainfuck code analysis plugin",
.license = "LGPL3",
.arch = "bf",
.bits = 8,
.op = &bf_op,
.get_reg_profile = get_reg_profile,
.rzil_init = bf_init_rzil,
.rzil_fini = bf_fini_rzil
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_ANALYSIS,
.data = &rz_analysis_plugin_bf,
.version = RZ_VERSION
};
#endif