/*
Copyright (C) 2017 Sergej Schumilo
This file is part of QEMU-PT (kAFL).
QEMU-PT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QEMU-PT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QEMU-PT. If not, see .
*/
#include "qemu/osdep.h"
#include
#include
#include
#include
#include "debug.h"
#include "file_helper.h"
#include "nyx/interface.h"
#include "nyx/memory_access.h"
#include "nyx/redqueen.h"
#include "nyx/state/state.h"
#include "patcher.h"
#include "redqueen_trace.h"
redqueen_workdir_t redqueen_workdir = { 0 };
void setup_redqueen_workdir(char *workdir)
{
assert(asprintf(&redqueen_workdir.redqueen_results, "%s/redqueen_results.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.symbolic_results, "%s/symbolic_results.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.pt_trace_results, "%s/pt_trace_results.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.redqueen_patches, "%s/redqueen_patches.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.breakpoint_white, "%s/breakpoint_white.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.breakpoint_black, "%s/breakpoint_black.txt",
workdir) > 0);
assert(asprintf(&redqueen_workdir.target_code_dump, "%s/target_code_dump.img",
workdir) > 0);
}
redqueen_t *new_rq_state(CPUState *cpu, page_cache_t *page_cache)
{
redqueen_t *res = malloc(sizeof(redqueen_t));
res->cpu = cpu;
res->intercept_mode = false;
res->singlestep_enabled = false;
res->hooks_applied = 0;
res->page_cache = page_cache;
res->lookup = kh_init(RQ);
res->last_rip = 0x0;
res->next_rip = 0x0;
res->num_breakpoint_whitelist = 0;
res->breakpoint_whitelist = NULL;
res->trace_state = redqueen_trace_new();
return res;
}
static bool is_interessting_lea_at(redqueen_t *self, cs_insn *ins)
{
bool res = false;
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op2 = &(x86->operands[1]);
assert(op2->type == X86_OP_MEM);
x86_reg reg = op2->mem.index;
int64_t disp = (int64_t)op2->mem.disp;
res = disp < 0 && (-disp) > 0xff && op2->mem.scale == 1 &&
op2->mem.base == X86_REG_INVALID && reg != X86_REG_INVALID;
if (res) {
x86_reg reg = op2->mem.index;
if (reg == X86_REG_EIP || reg == X86_REG_RIP || reg == X86_REG_EBP ||
reg == X86_REG_RBP)
{
// nyx_debug_p(REDQUEEN_PREFIX, "got boring index\n");
res = false;
} // don't instrument local stack offset computations
}
return res;
}
static bool uses_register(cs_x86_op *op, x86_reg reg)
{
if (op->type == X86_OP_REG && op->reg == reg) {
return true;
}
if (op->type == X86_OP_MEM && op->mem.base == reg) {
return true;
}
return false;
}
static bool uses_stack_access(cs_x86_op *op)
{
if (uses_register(op, X86_REG_RBP) || uses_register(op, X86_REG_EBP)) {
return true;
}
if (uses_register(op, X86_REG_RSP) || uses_register(op, X86_REG_ESP)) {
return true;
}
return false;
}
static bool is_interessting_add_at(redqueen_t *self, cs_insn *ins)
{
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op1 = &(x86->operands[0]);
cs_x86_op *op2 = &(x86->operands[1]);
if (op2->type == X86_OP_IMM && (op1->type == X86_OP_REG || op1->type == X86_OP_MEM))
{
// offsets needs to be negative, < -0xff to ensure we only look at multi byte substractions
if ((op2->imm > 0x7fff && (((op2->imm >> 8) & 0xff) != 0xff))) {
if (!uses_stack_access(op1)) {
return true;
}
}
}
return false;
}
static bool is_interessting_sub_at(redqueen_t *self, cs_insn *ins)
{
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op1 = &(x86->operands[0]);
cs_x86_op *op2 = &(x86->operands[1]);
if (op2->type == X86_OP_IMM && (op1->type == X86_OP_REG || op1->type == X86_OP_MEM))
{
if (op2->imm > 0xFF) {
if (!uses_stack_access(op1)) {
return true;
}
}
}
return false;
}
static bool is_interessting_xor_at(redqueen_t *self, cs_insn *ins)
{
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op1 = &(x86->operands[0]);
cs_x86_op *op2 = &(x86->operands[1]);
if (op1->type == X86_OP_REG && op2->type == X86_OP_REG) {
if (op1->reg != op2->reg) {
return true;
}
}
return false;
}
static void opcode_analyzer(redqueen_t *self, cs_insn *ins)
{
// uint8_t i, j;
// cs_x86 details = ins->detail->x86;
// printf("SELF %p\n", self->redqueen_state);
// printf("INS %lx\n", ins->address);
if (ins->id == X86_INS_CMP) {
set_rq_instruction(self, ins->address);
// nyx_debug_p(REDQUEEN_PREFIX, "hooking cmp %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
}
if (ins->id == X86_INS_LEA && is_interessting_lea_at(self, ins)) {
// nyx_debug_p(REDQUEEN_PREFIX, "hooking lea %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_SUB && is_interessting_sub_at(self, ins)) {
// nyx_debug_p(REDQUEEN_PREFIX, "hooking sub %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_ADD && is_interessting_add_at(self, ins)) {
// nyx_debug_p(REDQUEEN_PREFIX, "hooking add %lx\n", ins->address);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_XOR && is_interessting_xor_at(self, ins)) {
// nyx_debug_p(REDQUEEN_PREFIX, "hooking xor %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
set_rq_instruction(self, ins->address);
}
if (ins->id == X86_INS_CALL || ins->id == X86_INS_LCALL) {
// nyx_debug_p(REDQUEEN_PREFIX, "hooking call %lx %s %s\n", ins->address, ins->mnemonic, ins->op_str);
set_rq_instruction(self, ins->address);
}
}
void redqueen_callback(void *opaque,
disassembler_mode_t mode,
uint64_t start_addr,
uint64_t end_addr)
{
GET_GLOBAL_STATE()->bb_coverage++;
redqueen_t *self = (redqueen_t *)opaque;
if (start_addr != end_addr) {
uint64_t failed_page = 0;
uint64_t code = start_addr;
cs_insn *insn = page_cache_cs_malloc(self->page_cache, mode);
while (page_cache_disassemble_iter(self->page_cache, &code, insn,
&failed_page, mode))
{
if (insn->address > end_addr) {
break;
}
opcode_analyzer(self, insn);
}
cs_free(insn, 1);
}
}
void destroy_rq_state(redqueen_t *self)
{
redqueen_trace_free(self->trace_state);
kh_destroy(RQ, self->lookup);
free(self);
}
static void redqueen_set_addr_flags(redqueen_t *self, uint64_t addr, uint32_t flags)
{
int unused = 0;
khiter_t k = kh_get(RQ, self->lookup, addr);
if (k == kh_end(self->lookup)) {
k = kh_put(RQ, self->lookup, addr, &unused);
kh_value(self->lookup, k) = 0;
}
kh_value(self->lookup, k) |= flags;
}
static bool redqueen_check_addr_flags(redqueen_t *self, uint64_t addr, uint32_t flags)
{
khiter_t k = kh_get(RQ, self->lookup, addr);
if (k != kh_end(self->lookup)) {
return !!(kh_value(self->lookup, k) & flags);
} else {
return false;
}
}
static bool redqueen_check_addr(redqueen_t *self, uint64_t addr)
{
khiter_t k = kh_get(RQ, self->lookup, addr);
if (k != kh_end(self->lookup)) {
return true;
} else {
return false;
}
}
static uint32_t redqueen_update_addr_count(redqueen_t *self, uint64_t addr)
{
int unused __attribute__((unused));
uint32_t value = 0;
khiter_t k = kh_get(RQ, self->lookup, addr);
if (k != kh_end(self->lookup)) {
value = kh_value(self->lookup, k);
} else {
k = kh_put(RQ, self->lookup, addr, &unused);
}
value++;
kh_value(self->lookup, k) = value;
return value & 0xFF000000UL;
}
void set_rq_instruction(redqueen_t *self, uint64_t addr)
{
if (!redqueen_check_addr_flags(self, addr, CMP_BITMAP_BLACKLISTED)) {
redqueen_set_addr_flags(self, addr, CMP_BITMAP_RQ_INSTRUCTION);
}
}
void set_rq_blacklist(redqueen_t *self, uint64_t addr)
{
redqueen_set_addr_flags(self, addr, CMP_BITMAP_BLACKLISTED);
}
static void insert_hooks_whitelist(redqueen_t *self)
{
for (size_t i = 0; i < self->num_breakpoint_whitelist; i++) {
insert_breakpoint(self->cpu, self->breakpoint_whitelist[i], 1);
}
}
static void insert_hooks_bitmap(redqueen_t *self)
{
uint64_t c = 0;
uint64_t addr;
uint32_t value __attribute__((unused));
uint32_t mode = GET_GLOBAL_STATE()->redqueen_instrumentation_mode;
kh_foreach(self->lookup, addr, value, {
if (redqueen_check_addr_flags(self, addr, CMP_BITMAP_BLACKLISTED)) {
continue;
}
bool should_hook_rq =
(mode == REDQUEEN_LIGHT_INSTRUMENTATION) &&
redqueen_check_addr_flags(self, addr, CMP_BITMAP_SHOULD_HOOK_RQ);
if (should_hook_rq) {
insert_breakpoint(self->cpu, addr, 1);
c++;
}
});
}
void redqueen_insert_hooks(redqueen_t *self)
{
nyx_debug_p(REDQUEEN_PREFIX, "insert hooks\n");
assert(!self->hooks_applied);
switch (GET_GLOBAL_STATE()->redqueen_instrumentation_mode) {
case (REDQUEEN_LIGHT_INSTRUMENTATION):
insert_hooks_bitmap(self);
break;
case (REDQUEEN_WHITELIST_INSTRUMENTATION):
insert_hooks_whitelist(self);
break;
case (REDQUEEN_NO_INSTRUMENTATION):
break;
default:
assert(false);
}
self->hooks_applied = 1;
}
void redqueen_remove_hooks(redqueen_t *self)
{
nyx_debug_p(REDQUEEN_PREFIX, "remove hooks\n");
assert(self->hooks_applied);
remove_all_breakpoints(self->cpu);
for (khiter_t i = kh_begin(self->lookup); i != kh_end(self->lookup); ++i) {
if (!kh_exist(self->lookup, i))
continue;
kh_val(self->lookup, i) &= 0xFF000000UL;
}
self->hooks_applied = 0;
return;
}
static uint64_t get_segment_register(x86_reg reg)
{
X86CPU *cpu = X86_CPU(qemu_get_cpu(0));
CPUX86State *env = &cpu->env;
switch (reg) {
case X86_REG_GS:
return env->segs[R_GS].base;
case X86_REG_FS:
return env->segs[R_FS].base;
case X86_REG_CS:
return env->segs[R_CS].base;
case X86_REG_DS:
return env->segs[R_DS].base;
case X86_REG_SS:
return env->segs[R_SS].base;
default:
break;
}
assert(false);
}
static inline uint64_t sign_extend_from_size(uint64_t value, uint8_t size)
{
switch (size) {
case 64:
return value;
case 32:
return ((int32_t)(value) < 0) ? 0xffffffff00000000 | value : value;
case 16:
return ((int16_t)(value) < 0) ? 0xffffffffffff0000 | value : value;
case 8:
return ((int8_t)(value) < 0) ? 0xffffffffffffff00 | value : value;
}
assert(false);
}
static uint64_t eval_reg(x86_reg reg, uint8_t *size)
{
uint64_t value = 0;
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
switch (reg) {
case X86_REG_RAX:
value = env->regs[RAX];
*size = 64;
break;
case X86_REG_RCX:
value = env->regs[RCX];
*size = 64;
break;
case X86_REG_RDX:
value = env->regs[RDX];
*size = 64;
break;
case X86_REG_RBX:
value = env->regs[RBX];
*size = 64;
break;
case X86_REG_RSP:
value = env->regs[RSP];
*size = 64;
break;
case X86_REG_RBP:
value = env->regs[RBP];
*size = 64;
break;
case X86_REG_RSI:
value = env->regs[RSI];
*size = 64;
break;
case X86_REG_RDI:
value = env->regs[RDI];
*size = 64;
break;
case X86_REG_R8:
value = env->regs[R8];
*size = 64;
break;
case X86_REG_R9:
value = env->regs[R9];
*size = 64;
break;
case X86_REG_R10:
value = env->regs[R10];
*size = 64;
break;
case X86_REG_R11:
value = env->regs[R11];
*size = 64;
break;
case X86_REG_R12:
value = env->regs[R12];
*size = 64;
break;
case X86_REG_R13:
value = env->regs[R13];
*size = 64;
break;
case X86_REG_R14:
value = env->regs[R14];
*size = 64;
break;
case X86_REG_R15:
value = env->regs[R15];
*size = 64;
break;
case X86_REG_EAX:
value = env->regs[RAX] & 0xffffffff;
*size = 32;
break;
case X86_REG_ECX:
value = env->regs[RCX] & 0xffffffff;
*size = 32;
break;
case X86_REG_EDX:
value = env->regs[RDX] & 0xffffffff;
*size = 32;
break;
case X86_REG_EBX:
value = env->regs[RBX] & 0xffffffff;
*size = 32;
break;
case X86_REG_ESP:
value = env->regs[RSP] & 0xffffffff;
*size = 32;
break;
case X86_REG_EBP:
value = env->regs[RBP] & 0xffffffff;
*size = 32;
break;
case X86_REG_ESI:
value = env->regs[RSI] & 0xffffffff;
*size = 32;
break;
case X86_REG_EDI:
value = env->regs[RDI] & 0xffffffff;
*size = 32;
break;
case X86_REG_R8D:
value = env->regs[R8] & 0xffffffff;
*size = 32;
break;
case X86_REG_R9D:
value = env->regs[R9] & 0xffffffff;
*size = 32;
break;
case X86_REG_R10D:
value = env->regs[R10] & 0xffffffff;
*size = 32;
break;
case X86_REG_R11D:
value = env->regs[R11] & 0xffffffff;
*size = 32;
break;
case X86_REG_R12D:
value = env->regs[R12] & 0xffffffff;
*size = 32;
break;
case X86_REG_R13D:
value = env->regs[R13] & 0xffffffff;
*size = 32;
break;
case X86_REG_R14D:
value = env->regs[R14] & 0xffffffff;
*size = 32;
break;
case X86_REG_R15D:
value = env->regs[R15] & 0xffffffff;
*size = 32;
break;
case X86_REG_AX:
value = env->regs[RAX] & 0xffff;
*size = 16;
break;
case X86_REG_CX:
value = env->regs[RCX] & 0xffff;
*size = 16;
break;
case X86_REG_DX:
value = env->regs[RDX] & 0xffff;
*size = 16;
break;
case X86_REG_BX:
value = env->regs[RBX] & 0xffff;
*size = 16;
break;
case X86_REG_SP:
value = env->regs[RSP] & 0xffff;
*size = 16;
break;
case X86_REG_BP:
value = env->regs[RBP] & 0xffff;
*size = 16;
break;
case X86_REG_SI:
value = env->regs[RSI] & 0xffff;
*size = 16;
break;
case X86_REG_DI:
value = env->regs[RDI] & 0xffff;
*size = 16;
break;
case X86_REG_R8W:
value = env->regs[R8] & 0xffff;
*size = 16;
break;
case X86_REG_R9W:
value = env->regs[R9] & 0xffff;
*size = 16;
break;
case X86_REG_R10W:
value = env->regs[R10] & 0xffff;
*size = 16;
break;
case X86_REG_R11W:
value = env->regs[R11] & 0xffff;
*size = 16;
break;
case X86_REG_R12W:
value = env->regs[R12] & 0xffff;
*size = 16;
break;
case X86_REG_R13W:
value = env->regs[R13] & 0xffff;
*size = 16;
break;
case X86_REG_R14W:
value = env->regs[R14] & 0xffff;
*size = 16;
break;
case X86_REG_R15W:
value = env->regs[R15] & 0xffff;
*size = 16;
break;
case X86_REG_AL:
value = env->regs[RAX] & 0xff;
*size = 8;
break;
case X86_REG_CL:
value = env->regs[RCX] & 0xff;
*size = 8;
break;
case X86_REG_DL:
value = env->regs[RDX] & 0xff;
*size = 8;
break;
case X86_REG_BL:
value = env->regs[RBX] & 0xff;
*size = 8;
break;
case X86_REG_SPL:
value = env->regs[RSP] & 0xff;
*size = 8;
break;
case X86_REG_BPL:
value = env->regs[RBP] & 0xff;
*size = 8;
break;
case X86_REG_SIL:
value = env->regs[RSI] & 0xff;
*size = 8;
break;
case X86_REG_DIL:
value = env->regs[RDI] & 0xff;
*size = 8;
break;
case X86_REG_R8B:
value = env->regs[R8] & 0xff;
*size = 8;
break;
case X86_REG_R9B:
value = env->regs[R9] & 0xff;
*size = 8;
break;
case X86_REG_R10B:
value = env->regs[R10] & 0xff;
*size = 8;
break;
case X86_REG_R11B:
value = env->regs[R11] & 0xff;
*size = 8;
break;
case X86_REG_R12B:
value = env->regs[R12] & 0xff;
*size = 8;
break;
case X86_REG_R13B:
value = env->regs[R13] & 0xff;
*size = 8;
break;
case X86_REG_R14B:
value = env->regs[R14] & 0xff;
*size = 8;
break;
case X86_REG_R15B:
value = env->regs[R15] & 0xff;
*size = 8;
break;
case X86_REG_AH:
value = (env->regs[RAX] >> 8) & 0xff;
*size = 8;
break;
case X86_REG_CH:
value = (env->regs[RCX] >> 8) & 0xff;
*size = 8;
break;
case X86_REG_DH:
value = (env->regs[RDX] >> 8) & 0xff;
*size = 8;
break;
case X86_REG_BH:
value = (env->regs[RBX] >> 8) & 0xff;
*size = 8;
break;
case X86_REG_RIP:
value = env->eip;
*size = 64;
break;
case X86_REG_EIP:
value = env->eip & 0xffffffff;
*size = 32;
break;
case X86_REG_IP:
value = env->eip & 0xfffff;
*size = 16;
break;
default:
assert(false);
}
return value;
}
static uint64_t eval_addr(cs_x86_op *op)
{
uint8_t size = 0;
uint64_t base = 0;
uint64_t index = 0;
uint64_t segment = 0;
assert(op->type == X86_OP_MEM);
if (op->mem.base != X86_REG_INVALID) {
base = eval_reg(op->mem.base, &size);
}
if (op->mem.index != X86_REG_INVALID) {
index = eval_reg(op->mem.index, &size);
}
if (op->mem.segment != X86_REG_INVALID) {
segment = get_segment_register(op->mem.segment);
}
uint64_t addr = segment + base + index * op->mem.scale + op->mem.disp;
return addr;
}
static uint64_t eval_mem(cs_x86_op *op)
{
uint64_t val = 0;
assert(op->size == 1 || op->size == 2 || op->size == 4 || op->size == 8);
// nyx_debug_p(REDQUEEN_PREFIX, "EVAL MEM FOR OP:\n");
/* TODO @ sergej: replace me later */
read_virtual_memory(eval_addr(op), (uint8_t *)&val, op->size, qemu_get_cpu(0));
return val;
}
static uint64_t eval(cs_x86_op *op, uint8_t *size)
{
switch ((int)op->type) {
case X86_OP_REG:
return eval_reg(op->reg, size);
case X86_OP_IMM:
*size = 0;
return op->imm;
case X86_OP_MEM:
switch (op->size) {
case 1:
*size = 8;
return eval_mem(op) & 0xff;
case 2:
*size = 16;
return eval_mem(op) & 0xffff;
case 4:
*size = 32;
return eval_mem(op) & 0xffffffff;
case 8:
*size = 64;
return eval_mem(op);
}
}
/* unreachable, dude! */
assert(false);
return 0;
}
static void print_comp_result(uint64_t addr,
const char *type,
uint64_t val1,
uint64_t val2,
uint8_t size,
bool is_imm)
{
char result_buf[256];
const char *format = NULL;
uint8_t pos = 0;
pos += snprintf(result_buf + pos, 256 - pos, "%lx\t\t %s", addr, type);
// nyx_debug_p(REDQUEEN_PREFIX, "got size: %ld\n", size);
uint64_t mask = 0;
switch (size) {
case 64:
format = " 64\t%016lX-%016lX";
mask = 0xffffffffffffffff;
break;
case 32:
format = " 32\t%08X-%08X";
mask = 0xffffffff;
break;
case 16:
format = " 16\t%04X-%04X";
mask = 0xffff;
break;
case 8:
format = " 8\t%02X-%02X";
mask = 0xff;
break;
default:
assert(false);
}
pos += snprintf(result_buf + pos, 256 - pos, format, val1 & mask, val2 & mask);
if (is_imm) {
pos += snprintf(result_buf + pos, 256 - pos, " IMM");
}
pos += snprintf(result_buf + pos, 256 - pos, "\n");
write_re_result(result_buf);
}
static void get_cmp_value(cs_insn *ins, const char *type)
{
uint8_t size_1 = 0;
uint8_t size_2 = 0;
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op1 = &(x86->operands[0]);
cs_x86_op *op2 = &(x86->operands[1]);
uint64_t v1 = eval(op1, &size_1);
uint64_t v2 = eval(op2, &size_2);
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
v1 != v2)
{
print_comp_result(ins->address, type, v1, v2, (size_1 ? size_1 : size_2),
op2->type == X86_OP_IMM);
}
}
static void get_cmp_value_add(cs_insn *ins)
{
uint8_t size_1 = 0;
uint8_t size_2 = 0;
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op1 = &(x86->operands[0]);
cs_x86_op *op2 = &(x86->operands[1]);
uint64_t v1 = eval(op1, &size_1);
uint64_t v2 = -sign_extend_from_size(eval(op2, &size_2), size_1);
if (op2->type != X86_OP_IMM) {
return;
}
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
v1 != v2)
{
bool is_imm = true;
print_comp_result(ins->address, "SUB", v1, v2, size_1, is_imm);
}
}
static void get_cmp_value_lea(cs_insn *ins)
{
uint64_t index_val = 0;
assert(ins);
cs_x86 *x86 = &(ins->detail->x86);
assert(x86->op_count == 2);
cs_x86_op *op2 = &(x86->operands[1]);
assert(op2->type == X86_OP_MEM);
uint8_t size = 0;
if (op2->mem.base != X86_REG_INVALID && op2->mem.index != X86_REG_INVALID) {
return;
}
if (op2->mem.base == X86_REG_INVALID && op2->mem.index == X86_REG_INVALID) {
return;
}
if (op2->mem.base != X86_REG_INVALID) {
index_val = eval_reg(op2->mem.base, &size);
}
if (op2->mem.index != X86_REG_INVALID) {
index_val = eval_reg(op2->mem.index, &size);
}
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION ||
index_val != -op2->mem.disp)
{
bool is_imm = false;
print_comp_result(ins->address, "LEA", index_val, -op2->mem.disp,
op2->size * 8, is_imm);
}
}
static uint64_t limit_to_word_width(uint64_t val)
{
switch (GET_GLOBAL_STATE()->disassembler_word_width) {
case 64:
return val;
case 32:
return val & 0xffffffff;
default:
assert(false);
}
}
static uint64_t word_width_to_bytes(void)
{
switch (GET_GLOBAL_STATE()->disassembler_word_width) {
case 64:
return 8;
case 32:
return 4;
default:
assert(false);
}
}
static uint64_t read_stack(uint64_t word_index)
{
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t rsp = env->regs[RSP];
rsp = limit_to_word_width(rsp);
uint64_t res = 0;
uint64_t stack_ptr = rsp + word_index * word_width_to_bytes();
/* TODO @ sergej */
assert(read_virtual_memory(stack_ptr, (uint8_t *)(&res), 8, qemu_get_cpu(0)));
return limit_to_word_width(res);
}
static void format_strcmp(uint8_t *buf1, uint8_t *buf2)
{
char out_buf[REDQUEEN_MAX_STRCMP_LEN * 4 + 2];
char *tmp_hex_buf = &out_buf[0];
for (int i = 0; i < REDQUEEN_MAX_STRCMP_LEN; i++) {
tmp_hex_buf += sprintf(tmp_hex_buf, "%02X", (uint8_t)buf1[i]);
}
*tmp_hex_buf++ = '-';
for (int i = 0; i < REDQUEEN_MAX_STRCMP_LEN; i++) {
tmp_hex_buf += sprintf(tmp_hex_buf, "%02X", (uint8_t)buf2[i]);
}
char *res = 0;
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t rip = env->eip;
assert(asprintf(&res, "%lx\t\tSTR %d\t%s\n", rip, REDQUEEN_MAX_STRCMP_LEN * 8,
out_buf) != -1);
write_re_result(res);
free(res);
}
static bool test_strchr(uint64_t arg1, uint64_t arg2)
{
CPUState *cpu = qemu_get_cpu(0);
/* TODO @ sergej */
if (!is_addr_mapped(arg1, cpu) || arg2 & (~0xff)) {
return false;
}
uint8_t buf1[REDQUEEN_MAX_STRCMP_LEN];
uint8_t buf2[REDQUEEN_MAX_STRCMP_LEN];
/* TODO @ sergej */
assert(read_virtual_memory(arg1, &buf1[0], REDQUEEN_MAX_STRCMP_LEN, cpu));
if (!memchr(buf1, '\0', REDQUEEN_MAX_STRCMP_LEN)) {
return false;
}
memset(buf2, '\0', REDQUEEN_MAX_STRCMP_LEN);
buf2[0] = (uint8_t)(arg2);
format_strcmp(buf1, buf2);
return true;
}
static bool test_strcmp(uint64_t arg1, uint64_t arg2)
{
CPUState *cpu = qemu_get_cpu(0);
if (!is_addr_mapped(arg1, cpu) || !is_addr_mapped(arg2, cpu)) {
return false;
}
// nyx_debug_p(REDQUEEN_PREFIX,"valid ptrs\n");
uint8_t buf1[REDQUEEN_MAX_STRCMP_LEN];
uint8_t buf2[REDQUEEN_MAX_STRCMP_LEN];
/* TODO @ sergej */
assert(read_virtual_memory(arg1, &buf1[0], REDQUEEN_MAX_STRCMP_LEN, cpu));
assert(read_virtual_memory(arg2, &buf2[0], REDQUEEN_MAX_STRCMP_LEN, cpu));
format_strcmp(buf1, buf2);
return true;
}
static bool test_strcmp_cdecl(void)
{
uint64_t arg1 = read_stack(0);
uint64_t arg2 = read_stack(1);
// nyx_debug_p(REDQUEEN_PREFIX, "extract call params cdecl %lx %lx\n", arg1, arg2);
test_strchr(arg1, arg2);
return test_strcmp(arg1, arg2);
}
static bool test_strcmp_fastcall(void)
{
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t arg1 = env->regs[RCX]; // rcx
uint64_t arg2 = env->regs[RDX]; // rdx
// nyx_debug_p(REDQUEEN_PREFIX, "extract call params fastcall %lx %lx\n", arg1, arg2);
test_strchr(arg1, arg2);
return test_strcmp(arg1, arg2);
}
static bool test_strcmp_sys_v(void)
{
if (GET_GLOBAL_STATE()->disassembler_word_width != 64) {
return false;
}
CPUX86State *env = &(X86_CPU(qemu_get_cpu(0)))->env;
uint64_t arg1 = env->regs[RDI]; // rdx
uint64_t arg2 = env->regs[RSI]; // rsi
// nyx_debug_p(REDQUEEN_PREFIX, "extract call params sysv %lx %lx\n", arg1, arg2);
test_strchr(arg1, arg2);
return test_strcmp(arg1, arg2);
}
static void extract_call_params(void)
{
// nyx_debug_p(REDQUEEN_PREFIX, "extract call at %lx\n", ip);
test_strcmp_cdecl();
test_strcmp_fastcall();
test_strcmp_sys_v();
}
static void handle_hook_redqueen_light(redqueen_t *self, uint64_t ip, cs_insn *insn)
{
if (insn->id == X86_INS_CMP || insn->id == X86_INS_XOR)
{ // handle original redqueen case
get_cmp_value(insn, "CMP");
} else if (insn->id == X86_INS_SUB) { // handle original redqueen case
get_cmp_value(insn, "SUB");
} else if (insn->id == X86_INS_LEA) { // handle original redqueen case
get_cmp_value_lea(insn);
} else if (insn->id == X86_INS_ADD) { // handle original redqueen case
get_cmp_value_add(insn);
} else if (insn->id == X86_INS_CALL || insn->id == X86_INS_LCALL) {
extract_call_params();
}
}
static uint8_t handle_hook_breakpoint(redqueen_t *self, bool write_data)
{
X86CPU *cpu = X86_CPU(self->cpu);
CPUX86State *env = &cpu->env;
cs_insn *insn = NULL;
switch (GET_GLOBAL_STATE()->disassembler_word_width) {
case 64:
insn = page_cache_cs_malloc(self->page_cache, mode_64);
break;
case 32:
insn = page_cache_cs_malloc(self->page_cache, mode_32);
break;
default:
abort();
}
uint8_t ins_size = 0;
uint64_t ip = env->eip;
uint64_t code = ip;
uint64_t failed_page = 0;
switch (GET_GLOBAL_STATE()->disassembler_word_width) {
case 64:
assert(page_cache_disassemble_iter(self->page_cache, &code, insn,
&failed_page, mode_64));
break;
case 32:
assert(page_cache_disassemble_iter(self->page_cache, &code, insn,
&failed_page, mode_32));
break;
default:
abort();
}
ins_size = insn->size;
if (write_data) {
// int mode = self->cpu->redqueen_instrumentation_mode;
int mode = GET_GLOBAL_STATE()->redqueen_instrumentation_mode;
if (mode == REDQUEEN_LIGHT_INSTRUMENTATION ||
mode == REDQUEEN_WHITELIST_INSTRUMENTATION ||
mode == REDQUEEN_SE_INSTRUMENTATION)
{
handle_hook_redqueen_light(self, ip, insn);
}
if (mode == REDQUEEN_SE_INSTRUMENTATION) {
assert(false);
}
}
cs_free(insn, 1);
assert(ins_size != 0);
return ins_size;
}
void handle_hook(redqueen_t *self)
{
X86CPU *cpu = X86_CPU(self->cpu);
CPUX86State *env = &cpu->env;
if (self->next_rip) {
remove_breakpoint(self->cpu, self->next_rip, 1);
if (self->last_rip &&
redqueen_update_addr_count(self, self->last_rip) < REDQUEEN_TRAP_LIMIT)
{
insert_breakpoint(self->cpu, self->last_rip, 1);
}
kvm_update_guest_debug(self->cpu, 0);
self->last_rip = 0;
self->next_rip = 0;
}
if (redqueen_check_addr(self, env->eip)) {
self->last_rip = env->eip;
remove_breakpoint(self->cpu, env->eip, 1);
if (self->cpu->pt_enabled && GET_GLOBAL_STATE()->pt_c3_filter == env->cr[3]) {
self->next_rip = handle_hook_breakpoint(self, true);
} else {
self->next_rip = handle_hook_breakpoint(self, true);
}
}
}
static void _redqueen_update_whitelist(redqueen_t *self)
{
if (GET_GLOBAL_STATE()->redqueen_instrumentation_mode ==
REDQUEEN_WHITELIST_INSTRUMENTATION)
{
free(self->breakpoint_whitelist);
parse_address_file(redqueen_workdir.breakpoint_white,
&self->num_breakpoint_whitelist,
&self->breakpoint_whitelist);
}
}
static void _redqueen_update_blacklist(redqueen_t *self)
{
if (GET_GLOBAL_STATE()->redqueen_update_blacklist) {
size_t num_addrs = 0;
uint64_t *addrs;
parse_address_file(redqueen_workdir.breakpoint_black, &num_addrs, &addrs);
for (size_t i = 0; i < num_addrs; i++) {
set_rq_blacklist(self, addrs[i]);
}
free(addrs);
}
}
void enable_rq_intercept_mode(redqueen_t *self)
{
if (!self->intercept_mode) {
delete_redqueen_files();
_redqueen_update_whitelist(self);
_redqueen_update_blacklist(self);
redqueen_insert_hooks(self);
self->intercept_mode = true;
}
}
void disable_rq_intercept_mode(redqueen_t *self)
{
if (self->intercept_mode) {
redqueen_remove_hooks(self);
self->intercept_mode = false;
}
}