static int wasm_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) { WasmOp wop = {0}; memset (op, '\0', sizeof (RAnalOp)); int ret = wasm_dis (&wop, data, len); op->size = ret; op->addr = addr; op->type = R_ANAL_OP_TYPE_UNK; op->id = wop.op; switch (wop.op) { /* Calls here are using index instead of address */ case WASM_OP_BLOCK: cf_stack_ptr++; break; case WASM_OP_CALL: case WASM_OP_CALLINDIRECT: op->type = R_ANAL_OP_TYPE_CALL; op->jump = get_cf_offset (anal, data); break; case WASM_OP_BR: op->type = R_ANAL_OP_TYPE_JMP; op->jump = get_cf_offset (anal, data); break; case WASM_OP_BRIF: op->type = R_ANAL_OP_TYPE_CJMP; op->jump = get_cf_offset (anal, data); break; default: break; } return op->size; }
static bool advance_till_scope_end(RAnal* anal, RAnalOp *op, ut64 address, ut32 expected_type, ut32 depth, bool use_else) { ut8 buffer[16]; ut8 *ptr = buffer; ut8 *end = ptr + sizeof (buffer); WasmOp wop = {{0}}; int size = 0; while (anal->iob.read_at (anal->iob.io, address, buffer, sizeof (buffer))) { size = wasm_dis (&wop, ptr, end - ptr); if (!wop.txt || (wop.type == WASM_TYPE_OP_CORE && wop.op.core == WASM_OP_TRAP)) { // if invalid stop here. break; } if (wop.type == WASM_TYPE_OP_CORE) { WasmOpCodes wopop = wop.op.core; if (wopop == WASM_OP_LOOP || wopop == WASM_OP_BLOCK || wopop == WASM_OP_IF) { depth++; } if (use_else && wopop == WASM_OP_ELSE && !depth) { op->type = expected_type; op->jump = address + 1; // else size == 1 return true; } else if (wopop == WASM_OP_END && depth > 0) { // let's wait till i get the final depth depth--; } else if (wopop == WASM_OP_END && !depth) { op->type = expected_type; op->jump = address; return true; } } address += size; } return false; }
static int disassemble(RAsm *a, RAsmOp *op, const ut8 *buf, int len) { WasmOp wop = {0}; int ret = wasm_dis (&wop, buf, len); strncpy (op->buf_asm, wop.txt, sizeof (op->buf_asm)); op->buf_asm[sizeof (op->buf_asm) - 1] = 0; op->size = ret; return op->size; }
/* * Searches where the opcode ends. * A wasm scope is formed by: * block/loop -> end * if -> end * if -> else -> end * Also a function call has an end. * call X -> end */ static ut64 find_scope(ut64 addr, const ut8 *data, int len, bool is_loop) { WasmOp wop = {0}; st32 count = 0; ut32 offset = addr; while (len > 0) { int ret = wasm_dis (&wop, data, len); switch (wop.op) { /* Calls here are using index instead of address */ case WASM_OP_BLOCK: count++; break; case WASM_OP_LOOP: count++; break; case WASM_OP_IF: count++; break; case WASM_OP_ELSE: if (!count && !is_loop) { free (wop.txt); return offset + ret; } break; case WASM_OP_END: if (!count) { free (wop.txt); return offset; } else { count--; } break; default: break; } offset += ret; data += ret; len -= ret; free (wop.txt); } return UT64_MAX; }
// analyzes the wasm opcode. static int wasm_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) { WasmOp wop = {{0}}; RAnalHint *hint = NULL; memset (op, '\0', sizeof (RAnalOp)); int ret = wasm_dis (&wop, data, len); op->jump = UT64_MAX; op->fail = UT64_MAX; op->ptr = op->val = UT64_MAX; op->size = ret; op->addr = addr; op->sign = true; op->type = R_ANAL_OP_TYPE_UNK; switch (wop.type) { case WASM_TYPE_OP_CORE: op->id = wop.op.core; break; case WASM_TYPE_OP_ATOMIC: op->id = (0xfe << 8) | wop.op.atomic; break; } if (!wop.txt || !strncmp (wop.txt, "invalid", 7)) { op->type = R_ANAL_OP_TYPE_ILL; free (wop.txt); return -1; } if (addr_old == addr && (wop.type != WASM_TYPE_OP_CORE || wop.op.core != WASM_OP_END)) { goto anal_end; } switch (wop.type) { case WASM_TYPE_OP_CORE: switch (wop.op.core) { /* Calls here are using index instead of address */ case WASM_OP_LOOP: op->type = R_ANAL_OP_TYPE_NOP; if (!(hint = r_anal_hint_get (anal, addr))) { scope_hint--; r_anal_hint_set_opcode (anal, scope_hint, "loop"); r_anal_hint_set_jump (anal, scope_hint, addr); } break; case WASM_OP_BLOCK: op->type = R_ANAL_OP_TYPE_NOP; if (!(hint = r_anal_hint_get (anal, addr))) { scope_hint--; r_anal_hint_set_opcode (anal, scope_hint, "block"); r_anal_hint_set_jump (anal, scope_hint, addr); } break; case WASM_OP_IF: if (!(hint = r_anal_hint_get (anal, addr))) { scope_hint--; r_anal_hint_set_opcode (anal, scope_hint, "if"); r_anal_hint_set_jump (anal, scope_hint, addr); if (advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_CJMP, 0, true)) { op->fail = addr + op->size; } } else { op->type = R_ANAL_OP_TYPE_CJMP; op->jump = hint->jump; op->fail = addr + op->size; } break; case WASM_OP_ELSE: // get if and set hint. if (!(hint = r_anal_hint_get (anal, addr))) { advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_JMP, 0, true); } else { op->type = R_ANAL_OP_TYPE_JMP; op->jump = hint->jump; } break; case WASM_OP_BR: { RAnalHint *hint2 = NULL; ut32 val; read_u32_leb128 (data + 1, data + len, &val); if ((hint2 = r_anal_hint_get (anal, addr)) && hint2->jump != UT64_MAX) { op->type = R_ANAL_OP_TYPE_JMP; op->jump = hint2->jump; } else if ((hint = r_anal_hint_get (anal, scope_hint))) { if (hint->opcode && !strncmp ("loop", hint->opcode, 4)) { op->type = R_ANAL_OP_TYPE_JMP; op->jump = hint->jump; r_anal_hint_set_jump (anal, addr, op->jump); } else { if (advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_JMP, val, false)) { r_anal_hint_set_jump (anal, addr, op->jump); } } } else { if (advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_JMP, val, false)) { eprintf ("[wasm] cannot find jump type for br (using block type)\n"); r_anal_hint_set_jump (anal, addr, op->jump); } else { eprintf ("[wasm] cannot find jump for br\n"); } } r_anal_hint_free (hint2); } break; case WASM_OP_BRIF: { RAnalHint *hint2 = NULL; ut32 val; read_u32_leb128 (data + 1, data + len, &val); if ((hint2 = r_anal_hint_get (anal, addr)) && hint2->jump != UT64_MAX) { op->type = R_ANAL_OP_TYPE_CJMP; op->jump = hint2->jump; op->fail = addr + op->size; } else if ((hint = r_anal_hint_get (anal, scope_hint))) { if (hint->opcode && !strncmp ("loop", hint->opcode, 4)) { op->fail = addr + op->size; op->jump = hint->jump; r_anal_hint_set_jump (anal, addr, op->jump); } else { if (advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_CJMP, val, false)) { op->fail = addr + op->size; r_anal_hint_set_jump (anal, addr, op->jump); } } } else { if (advance_till_scope_end (anal, op, addr + op->size, R_ANAL_OP_TYPE_CJMP, val, false)) { eprintf ("[wasm] cannot find jump type for br_if (using block type)\n"); op->fail = addr + op->size; r_anal_hint_set_jump (anal, addr, op->jump); } else { eprintf ("[wasm] cannot find jump for br_if\n"); } } r_anal_hint_free (hint2); } break; case WASM_OP_END: { op->type = R_ANAL_OP_TYPE_NOP; if (scope_hint < UT64_MAX) { hint = r_anal_hint_get (anal, scope_hint); if (hint && !strncmp ("loop", hint->opcode, 4)) { r_anal_hint_set_jump (anal, addr, op->jump); r_anal_hint_set_jump (anal, op->jump, addr); } else if (hint && !strncmp ("block", hint->opcode, 5)) { // if/else/block r_anal_hint_set_jump (anal, hint->jump, addr); r_anal_hint_set_jump (anal, addr, UT64_MAX); } if (hint) { r_anal_hint_set_opcode (anal, scope_hint, "invalid"); r_anal_hint_set_jump (anal, scope_hint, UT64_MAX); r_anal_hint_del (anal, scope_hint, 1); scope_hint++; } else { // all wasm routines ends with an end. op->eob = true; op->type = R_ANAL_OP_TYPE_RET; scope_hint = UT64_MAX; } } else { if (!(hint = r_anal_hint_get (anal, addr))) { // all wasm routines ends with an end. op->eob = true; op->type = R_ANAL_OP_TYPE_RET; } } } break; case WASM_OP_I32REMS: case WASM_OP_I32REMU: op->type = R_ANAL_OP_TYPE_MOD; break; case WASM_OP_GETLOCAL: case WASM_OP_I32LOAD: case WASM_OP_I64LOAD: case WASM_OP_F32LOAD: case WASM_OP_F64LOAD: case WASM_OP_I32LOAD8S: case WASM_OP_I32LOAD8U: case WASM_OP_I32LOAD16S: case WASM_OP_I32LOAD16U: case WASM_OP_I64LOAD8S: case WASM_OP_I64LOAD8U: case WASM_OP_I64LOAD16S: case WASM_OP_I64LOAD16U: case WASM_OP_I64LOAD32S: case WASM_OP_I64LOAD32U: op->type = R_ANAL_OP_TYPE_LOAD; break; case WASM_OP_SETLOCAL: case WASM_OP_TEELOCAL: op->type = R_ANAL_OP_TYPE_STORE; break; case WASM_OP_I32EQZ: case WASM_OP_I32EQ: case WASM_OP_I32NE: case WASM_OP_I32LTS: case WASM_OP_I32LTU: case WASM_OP_I32GTS: case WASM_OP_I32GTU: case WASM_OP_I32LES: case WASM_OP_I32LEU: case WASM_OP_I32GES: case WASM_OP_I32GEU: case WASM_OP_I64EQZ: case WASM_OP_I64EQ: case WASM_OP_I64NE: case WASM_OP_I64LTS: case WASM_OP_I64LTU: case WASM_OP_I64GTS: case WASM_OP_I64GTU: case WASM_OP_I64LES: case WASM_OP_I64LEU: case WASM_OP_I64GES: case WASM_OP_I64GEU: case WASM_OP_F32EQ: case WASM_OP_F32NE: case WASM_OP_F32LT: case WASM_OP_F32GT: case WASM_OP_F32LE: case WASM_OP_F32GE: case WASM_OP_F64EQ: case WASM_OP_F64NE: case WASM_OP_F64LT: case WASM_OP_F64GT: case WASM_OP_F64LE: case WASM_OP_F64GE: op->type = R_ANAL_OP_TYPE_CMP; break; case WASM_OP_I64OR: case WASM_OP_I32OR: op->type = R_ANAL_OP_TYPE_OR; break; case WASM_OP_I64XOR: case WASM_OP_I32XOR: op->type = R_ANAL_OP_TYPE_XOR; break; case WASM_OP_I32CONST: case WASM_OP_I64CONST: case WASM_OP_F32CONST: case WASM_OP_F64CONST: op->type = R_ANAL_OP_TYPE_MOV; { ut8 arg = data[1]; r_strbuf_setf (&op->esil, "4,sp,-=,%d,sp,=[4]", arg); } break; case WASM_OP_I64ADD: case WASM_OP_I32ADD: case WASM_OP_F32ADD: case WASM_OP_F64ADD: op->type = R_ANAL_OP_TYPE_ADD; break; case WASM_OP_I64SUB: case WASM_OP_I32SUB: case WASM_OP_F32SUB: case WASM_OP_F64SUB: op->type = R_ANAL_OP_TYPE_SUB; break; case WASM_OP_NOP: op->type = R_ANAL_OP_TYPE_NOP; r_strbuf_setf (&op->esil, ""); break; case WASM_OP_CALL: case WASM_OP_CALLINDIRECT: op->type = R_ANAL_OP_TYPE_CALL; op->jump = get_cf_offset (anal, data, len); op->fail = addr + op->size; if (op->jump != UT64_MAX) { op->ptr = op->jump; } r_strbuf_setf (&op->esil, "4,sp,-=,0x%"PFMT64x",sp,=[4],0x%"PFMT64x",pc,=", op->fail, op->jump); break; case WASM_OP_RETURN: // should be ret, but if there the analisys is stopped. op->type = R_ANAL_OP_TYPE_CRET; default: break; } break; case WASM_TYPE_OP_ATOMIC: switch (wop.op.atomic) { case WASM_OP_I32ATOMICLOAD: case WASM_OP_I64ATOMICLOAD: case WASM_OP_I32ATOMICLOAD8U: case WASM_OP_I32ATOMICLOAD16U: case WASM_OP_I64ATOMICLOAD8U: case WASM_OP_I64ATOMICLOAD16U: case WASM_OP_I64ATOMICLOAD32U: op->type = R_ANAL_OP_TYPE_LOAD; break; case WASM_OP_I32ATOMICSTORE: case WASM_OP_I64ATOMICSTORE: case WASM_OP_I32ATOMICSTORE8: case WASM_OP_I32ATOMICSTORE16: case WASM_OP_I64ATOMICSTORE8: case WASM_OP_I64ATOMICSTORE16: case WASM_OP_I64ATOMICSTORE32: op->type = R_ANAL_OP_TYPE_STORE; break; case WASM_OP_I32ATOMICRMWADD: case WASM_OP_I64ATOMICRMWADD: case WASM_OP_I32ATOMICRMW8UADD: case WASM_OP_I32ATOMICRMW16UADD: case WASM_OP_I64ATOMICRMW8UADD: case WASM_OP_I64ATOMICRMW16UADD: case WASM_OP_I64ATOMICRMW32UADD: op->type = R_ANAL_OP_TYPE_ADD; break; case WASM_OP_I32ATOMICRMW8USUB: case WASM_OP_I32ATOMICRMW16USUB: case WASM_OP_I32ATOMICRMWSUB: case WASM_OP_I64ATOMICRMW8USUB: case WASM_OP_I64ATOMICRMW16USUB: case WASM_OP_I64ATOMICRMW32USUB: case WASM_OP_I64ATOMICRMWSUB: op->type = R_ANAL_OP_TYPE_SUB; break; case WASM_OP_I32ATOMICRMWAND: case WASM_OP_I64ATOMICRMWAND: case WASM_OP_I32ATOMICRMW8UAND: case WASM_OP_I32ATOMICRMW16UAND: case WASM_OP_I64ATOMICRMW8UAND: case WASM_OP_I64ATOMICRMW16UAND: case WASM_OP_I64ATOMICRMW32UAND: op->type = R_ANAL_OP_TYPE_AND; break; case WASM_OP_I32ATOMICRMWOR: case WASM_OP_I64ATOMICRMWOR: case WASM_OP_I32ATOMICRMW8UOR: case WASM_OP_I32ATOMICRMW16UOR: case WASM_OP_I64ATOMICRMW8UOR: case WASM_OP_I64ATOMICRMW16UOR: case WASM_OP_I64ATOMICRMW32UOR: op->type = R_ANAL_OP_TYPE_OR; break; case WASM_OP_I32ATOMICRMWXOR: case WASM_OP_I64ATOMICRMWXOR: case WASM_OP_I32ATOMICRMW8UXOR: case WASM_OP_I32ATOMICRMW16UXOR: case WASM_OP_I64ATOMICRMW8UXOR: case WASM_OP_I64ATOMICRMW16UXOR: case WASM_OP_I64ATOMICRMW32UXOR: op->type = R_ANAL_OP_TYPE_XOR; break; case WASM_OP_I32ATOMICRMWXCHG: case WASM_OP_I64ATOMICRMWXCHG: case WASM_OP_I32ATOMICRMW8UXCHG: case WASM_OP_I32ATOMICRMW16UXCHG: case WASM_OP_I64ATOMICRMW8UXCHG: case WASM_OP_I64ATOMICRMW16UXCHG: case WASM_OP_I64ATOMICRMW32UXCHG: op->type = R_ANAL_OP_TYPE_XCHG; break; default: break; } default: break; } anal_end: addr_old = addr; free (wop.txt); r_anal_hint_free (hint); return op->size; }
// analyzes the wasm opcode. static int wasm_op(RAnal *anal, RAnalOp *op, ut64 addr, const ut8 *data, int len) { ut64 addr2 = UT64_MAX; int i; WasmOp wop = {0}; memset (op, '\0', sizeof (RAnalOp)); int ret = wasm_dis (&wop, data, len); op->jump = UT64_MAX; op->fail = UT64_MAX; op->ptr = op->val = UT64_MAX; op->size = ret; op->addr = addr; op->sign = true; op->type = R_ANAL_OP_TYPE_UNK; op->id = wop.op; if (!wop.txt || !strncmp (wop.txt, "invalid", 7)) { op->type = R_ANAL_OP_TYPE_ILL; wasm_stack_ptr = 0; free (wop.txt); return -1; } if (wasm_stack_ptr >= WASM_STACK_SIZE) { wasm_stack_ptr = 0; op->type = R_ANAL_OP_TYPE_NULL; free (wop.txt); return -1; } switch (wop.op) { /* Calls here are using index instead of address */ case WASM_OP_LOOP: addr2 = find_scope (addr + op->size, data + op->size, len - op->size, true); op->type = R_ANAL_OP_TYPE_NOP; if (addr2 != UT64_MAX && addr_old != addr) { //eprintf("0x%016x > stack %u (loop)\n", addr, wasm_stack_ptr); wasm_stack[wasm_stack_ptr].loop = addr; wasm_stack[wasm_stack_ptr].end = addr2; wasm_stack[wasm_stack_ptr].size = wop.len; wasm_stack_ptr++; } //op->fail = addr + op->size; break; case WASM_OP_BLOCK: op->type = R_ANAL_OP_TYPE_NOP; addr2 = find_scope (addr + op->size, data + op->size, len - op->size, true); if (addr2 != UT64_MAX && addr_old != addr) { //eprintf("0x%016x > stack %u (block)\n", addr, wasm_stack_ptr); wasm_stack[wasm_stack_ptr].loop = UT64_MAX; wasm_stack[wasm_stack_ptr].end = addr2; wasm_stack[wasm_stack_ptr].size = wop.len; wasm_stack_ptr++; } break; case WASM_OP_IF: op->type = R_ANAL_OP_TYPE_CJMP; op->jump = find_scope (addr + op->size, data + op->size, len - op->size, false); op->fail = addr + op->size; if (op->jump != UT64_MAX && addr_old != addr) { //eprintf("0x%016x > stack %u (if)\n", addr, wasm_stack_ptr); wasm_stack[wasm_stack_ptr].loop = UT64_MAX; wasm_stack[wasm_stack_ptr].end = op->fail; wasm_stack[wasm_stack_ptr].size = wop.len; wasm_stack_ptr++; } break; case WASM_OP_ELSE: op->type = R_ANAL_OP_TYPE_JMP; op->jump = find_scope (addr + op->size, data + op->size, len - op->size, false); break; case WASM_OP_I32REMS: case WASM_OP_I32REMU: op->type = R_ANAL_OP_TYPE_MOD; break; case WASM_OP_END: //eprintf("0x%016x < stack %u (end)\n", addr, wasm_stack_ptr); if (wasm_stack_ptr > 0) { op->type = R_ANAL_OP_TYPE_NOP; if (addr != UT64_MAX) { for (i = wasm_stack_ptr - 1; i > 0; i--) { if (wasm_stack[i].end == addr && wasm_stack[i].loop != UT64_MAX) { op->type = R_ANAL_OP_TYPE_CJMP; op->jump = wasm_stack[i].loop; op->fail = addr + op->size; break; } } } wasm_stack_ptr--; } else { // all wasm routines ends with an end. op->eob = true; op->type = R_ANAL_OP_TYPE_RET; } break; case WASM_OP_GETLOCAL: case WASM_OP_I32LOAD: case WASM_OP_I64LOAD: case WASM_OP_F32LOAD: case WASM_OP_F64LOAD: case WASM_OP_I32LOAD8S: case WASM_OP_I32LOAD8U: case WASM_OP_I32LOAD16S: case WASM_OP_I32LOAD16U: case WASM_OP_I64LOAD8S: case WASM_OP_I64LOAD8U: case WASM_OP_I64LOAD16S: case WASM_OP_I64LOAD16U: case WASM_OP_I64LOAD32S: case WASM_OP_I64LOAD32U: op->type = R_ANAL_OP_TYPE_LOAD; break; case WASM_OP_SETLOCAL: case WASM_OP_TEELOCAL: op->type = R_ANAL_OP_TYPE_STORE; break; case WASM_OP_I32EQZ: case WASM_OP_I32EQ: case WASM_OP_I32NE: case WASM_OP_I32LTS: case WASM_OP_I32LTU: case WASM_OP_I32GTS: case WASM_OP_I32GTU: case WASM_OP_I32LES: case WASM_OP_I32LEU: case WASM_OP_I32GES: case WASM_OP_I32GEU: case WASM_OP_I64EQZ: case WASM_OP_I64EQ: case WASM_OP_I64NE: case WASM_OP_I64LTS: case WASM_OP_I64LTU: case WASM_OP_I64GTS: case WASM_OP_I64GTU: case WASM_OP_I64LES: case WASM_OP_I64LEU: case WASM_OP_I64GES: case WASM_OP_I64GEU: case WASM_OP_F32EQ: case WASM_OP_F32NE: case WASM_OP_F32LT: case WASM_OP_F32GT: case WASM_OP_F32LE: case WASM_OP_F32GE: case WASM_OP_F64EQ: case WASM_OP_F64NE: case WASM_OP_F64LT: case WASM_OP_F64GT: case WASM_OP_F64LE: case WASM_OP_F64GE: op->type = R_ANAL_OP_TYPE_CMP; break; case WASM_OP_I64OR: case WASM_OP_I32OR: op->type = R_ANAL_OP_TYPE_OR; break; case WASM_OP_I64XOR: case WASM_OP_I32XOR: op->type = R_ANAL_OP_TYPE_XOR; break; case WASM_OP_I32CONST: case WASM_OP_I64CONST: case WASM_OP_F32CONST: case WASM_OP_F64CONST: op->type = R_ANAL_OP_TYPE_MOV; { ut8 arg = data[1]; r_strbuf_setf (&op->esil, "4,sp,-=,%d,sp,=[4]", arg); } break; case WASM_OP_I64ADD: case WASM_OP_I32ADD: case WASM_OP_F32ADD: case WASM_OP_F64ADD: op->type = R_ANAL_OP_TYPE_ADD; break; case WASM_OP_I64SUB: case WASM_OP_I32SUB: case WASM_OP_F32SUB: case WASM_OP_F64SUB: op->type = R_ANAL_OP_TYPE_SUB; break; case WASM_OP_NOP: op->type = R_ANAL_OP_TYPE_NOP; r_strbuf_setf (&op->esil, ""); break; case WASM_OP_CALL: case WASM_OP_CALLINDIRECT: op->type = R_ANAL_OP_TYPE_CALL; op->jump = get_cf_offset (anal, data); op->fail = addr + op->size; if (op->jump != UT64_MAX) { op->ptr = op->jump; } r_strbuf_setf (&op->esil, "4,sp,-=,0x%"PFMT64x",sp,=[4],0x%"PFMT64x",pc,=", op->fail, op->jump); break; case WASM_OP_BR: op->type = R_ANAL_OP_TYPE_JMP; set_br_jump(op, data, len - op->size); break; case WASM_OP_BRIF: op->fail = addr + op->size; op->type = R_ANAL_OP_TYPE_CJMP; set_br_jump(op, data, len - op->size); break; case WASM_OP_RETURN: // should be ret, but if there the analisys is stopped. op->type = R_ANAL_OP_TYPE_CRET; default: break; } op_old = wop.op; addr_old = addr; free (wop.txt); return op->size; }