rizin/librz/analysis/p/analysis_bf.c
2022-02-01 17:25:54 +08:00

281 lines
7.6 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);
if (c) {
putchar(c);
fflush(stdout);
}
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;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
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;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
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;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = bf_right_arrow();
}
break;
case '<':
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = bf_left_arrow();
}
break;
case '+':
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = bf_inc();
}
break;
case '-':
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = bf_dec();
}
break;
case '.':
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
op->il_op = bf_out();
}
op->type = RZ_ANALYSIS_OP_TYPE_STORE;
break;
case ',':
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
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;
if (mask & RZ_ANALYSIS_OP_MASK_IL) {
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