diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..834ed1f --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,90 @@ +name: Security + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + schedule: + # Weekly security scan + - cron: '0 6 * * 1' + workflow_dispatch: + +permissions: + security-events: write + contents: read + +jobs: + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Secret Detection (Gitleaks) + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Filesystem Security Scan (Trivy) + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-fs.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy SARIF + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: trivy-fs.sarif + + code-quality: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check for dangerous C functions + run: | + echo "=== Security-sensitive function usage ===" + + # Critical: functions that should never be used + echo "Checking for gets()..." + if grep -rn "gets\s*(" src/; then + echo "❌ CRITICAL: Found gets() - must use fgets()" + exit 1 + fi + + # Warning: functions that need careful review + echo "Checking for potentially unsafe functions..." + for func in sprintf strcpy strcat; do + if grep -rn "${func}\s*(" src/ 2>/dev/null | head -5; then + echo "⚠️ Found $func() - review for buffer overflows" + fi + done + + echo "✅ Critical security checks passed" + + - name: Check header guards + run: | + echo "Checking header guards in src/..." + find src -name "*.h" | while read f; do + if ! grep -q "#ifndef" "$f" 2>/dev/null; then + echo "⚠️ Missing or non-standard header guard in $f" + fi + done + echo "✅ Header guard check complete" + + - name: Detect hardcoded secrets patterns + run: | + echo "Checking for hardcoded secret patterns..." + patterns='password|secret|api_key|apikey|access_token|private_key' + if grep -riE "$patterns\s*=\s*[\"'][^\"']{8,}" src/ 2>/dev/null | grep -v "example\|test\|placeholder"; then + echo "⚠️ Potential hardcoded secrets found - review above" + fi + echo "✅ Secret pattern check complete" diff --git a/src/e9patch/analysis/e9decompile.c b/src/e9patch/analysis/e9decompile.c index ba67e06..5f223ad 100644 --- a/src/e9patch/analysis/e9decompile.c +++ b/src/e9patch/analysis/e9decompile.c @@ -750,164 +750,646 @@ static E9IRStmt *lift_unary_op(E9Decompile *dc, E9Instruction *insn, E9IROp op) return stmt; } -static E9IRStmt *lift_instruction(E9Decompile *dc, E9IRFunc *func, E9Instruction *insn) +/* + * Helper to check if mnemonic matches any of the given variants + */ +/* Buffer sizes for sscanf operand parsing */ +#define E9_OPERAND_SHORT 64 +#define E9_OPERAND_LONG 128 + +static bool mnemonic_is(const char *mnemonic, const char *base) { - (void)func; + if (!mnemonic || !base) return false; + size_t len = strlen(base); + if (strncmp(mnemonic, base, len) != 0) return false; + /* Accept: base, baseq, basel, basew, baseb */ + char suffix = mnemonic[len]; + return suffix == '\0' || suffix == 'q' || suffix == 'l' || + suffix == 'w' || suffix == 'b'; +} +/* + * Create an IR statement for a mov-like operation (mov, movzx, movsx, lea) + */ +static E9IRStmt *lift_mov_op(E9Decompile *dc, E9Instruction *insn, bool is_lea) +{ E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); if (!stmt) return NULL; stmt->addr = insn->address; - - /* Parse operands to determine operation */ - const char *mnemonic = insn->mnemonic; - const char *operands = insn->operands; - - /* mov instruction */ - if (strcmp(mnemonic, "mov") == 0 || strcmp(mnemonic, "movq") == 0 || - strcmp(mnemonic, "movl") == 0 || strcmp(mnemonic, "movabs") == 0) { - /* Simple register-to-register or immediate-to-register mov */ - /* Parse dest and source from operands */ - /* Format: "dest, source" */ - char dest[32], src[64]; - if (sscanf(operands, "%31[^,], %63s", dest, src) == 2) { - E9IRValue *dest_val = NULL; - E9IRValue *src_val = NULL; - - /* Parse destination */ - for (int i = 0; i < 16; i++) { - if (strcmp(dest, x64_reg_names[i]) == 0 || - (dest[0] == 'e' && strcmp(dest + 1, x64_reg_names[i] + 1) == 0)) { - dest_val = e9_ir_reg(dc, i); - break; - } - } - - /* Parse source */ - if (src[0] == '0' && src[1] == 'x') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 16), 64); - } else if (isdigit(src[0]) || src[0] == '-') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 10), 64); + + char dest[64], src[128]; + if (sscanf(insn->operands, "%63[^,], %127s", dest, src) == 2) { + E9IRValue *dest_val = parse_operand(dc, dest, 64); + E9IRValue *src_val = parse_operand(dc, src, 64); + + if (dest_val && src_val) { + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (!ir_dest) return stmt; + + stmt->dest = ir_dest; + if (is_lea && src_val->op == E9_IR_LOAD) { + /* LEA: take the address, not the value */ + stmt->value = src_val->mem.addr; } else { - for (int i = 0; i < 16; i++) { - if (strcmp(src, x64_reg_names[i]) == 0 || - (src[0] == 'e' && strcmp(src + 1, x64_reg_names[i] + 1) == 0)) { - src_val = e9_ir_reg(dc, i); - break; - } - } + stmt->value = src_val; } + } + } + return stmt; +} + +/* + * Create an IR statement for push/pop operations + */ +static E9IRStmt *lift_stack_op(E9Decompile *dc, E9Instruction *insn, bool is_push) +{ + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + E9IRValue *operand = parse_operand(dc, insn->operands, 64); + if (!operand) return stmt; + + if (is_push) { + /* push: rsp -= 8; mem[rsp] = operand + * We model the memory write: dest = store at [rsp-8], value = operand + * NOTE: RSP decrement is implicit; full modeling would need two stmts. */ + E9IRValue *rsp = e9_ir_reg(dc, X64_RSP); + E9IRValue *new_rsp = e9_ir_binary(dc, E9_IR_SUB, rsp, e9_ir_const(dc, 8, 64)); + stmt->dest = new_rsp; /* Store location [rsp-8] */ + stmt->value = operand; + } else { + /* pop: operand = mem[rsp]; rsp += 8 + * We model the load: dest = operand, value = load from [rsp] + * NOTE: RSP increment is implicit; full modeling would need two stmts. */ + E9IRValue *ir_dest = ir_get_dest(dc, operand); + if (!ir_dest) return stmt; + stmt->dest = ir_dest; + stmt->value = e9_ir_load(dc, e9_ir_reg(dc, X64_RSP), 8); + } + return stmt; +} - stmt->dest = dest_val; - stmt->value = src_val; +/* + * Create an IR statement for comparison/test (sets flags, no dest) + */ +static E9IRStmt *lift_cmp_op(E9Decompile *dc, E9Instruction *insn, E9IROp op) +{ + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + char left[64], right[128]; + if (sscanf(insn->operands, "%63[^,], %127s", left, right) == 2) { + E9IRValue *left_val = parse_operand(dc, left, 64); + E9IRValue *right_val = parse_operand(dc, right, 64); + + if (left_val && right_val) { + /* cmp/test set flags - dest is RFLAGS (implicit) */ + stmt->dest = e9_ir_reg(dc, X64_RFLAGS); + stmt->value = e9_ir_binary(dc, op, left_val, right_val); } } - /* add instruction */ - else if (strcmp(mnemonic, "add") == 0 || strcmp(mnemonic, "addq") == 0 || - strcmp(mnemonic, "addl") == 0) { - char dest[32], src[64]; - if (sscanf(operands, "%31[^,], %63s", dest, src) == 2) { - E9IRValue *dest_val = NULL; - E9IRValue *src_val = NULL; - - for (int i = 0; i < 16; i++) { - if (strcmp(dest, x64_reg_names[i]) == 0) { - dest_val = e9_ir_reg(dc, i); - break; - } - } + return stmt; +} - if (src[0] == '0' && src[1] == 'x') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 16), 64); - } else if (isdigit(src[0]) || src[0] == '-') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 10), 64); - } else { - for (int i = 0; i < 16; i++) { - if (strcmp(src, x64_reg_names[i]) == 0) { - src_val = e9_ir_reg(dc, i); - break; - } - } - } +/* + * Create an IR statement for conditional/unconditional jumps + */ +static E9IRStmt *lift_jump_op(E9Decompile *dc, E9Instruction *insn, const char *condition) +{ + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + E9IRValue *branch = dc_alloc(sizeof(E9IRValue)); + if (!branch) return stmt; + + branch->op = E9_IR_BRANCH; + /* Use full 64-bit target address (masking to 31 bits truncates high addresses) */ + branch->branch.true_block = (int)(insn->target & 0x7FFFFFFF); + + if (condition == NULL) { + /* Unconditional jump */ + branch->branch.cond = NULL; + branch->branch.false_block = -1; + } else { + /* Conditional jump - encode condition as a named flag test. + * We store the condition code string in a const node so downstream + * passes can map it to the correct flag predicate (ZF, SF^OF, etc.) */ + E9IRValue *flags = e9_ir_reg(dc, X64_RFLAGS); + /* Encode condition code as an immediate tag alongside RFLAGS. + * The tag is a hash of the condition string for compact storage. */ + uint64_t cond_tag = 0; + for (const char *p = condition; *p; p++) + cond_tag = cond_tag * 31 + (unsigned char)*p; + E9IRValue *cond_expr = e9_ir_binary(dc, E9_IR_AND, flags, + e9_ir_const(dc, cond_tag, 64)); + branch->branch.cond = cond_expr; + branch->branch.false_block = (int)((insn->address + insn->size) & 0x7FFFFFFF); + } + + stmt->value = branch; + return stmt; +} - if (dest_val && src_val) { - stmt->dest = e9_ir_reg(dc, dest_val->reg); - stmt->value = e9_ir_binary(dc, E9_IR_ADD, dest_val, src_val); +/* + * Create an IR statement for mul/imul/div/idiv + */ +static E9IRStmt *lift_muldiv_op(E9Decompile *dc, E9Instruction *insn, E9IROp op, bool is_signed) +{ + /* NOTE: is_signed distinguishes mul/div (unsigned) from imul/idiv (signed). + * The IR should eventually use separate SMUL/SDIV vs UMUL/UDIV ops. + * For now we tag the signed flag but use the same IR op. */ + (void)is_signed; + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + /* Check for two-operand or three-operand form */ + char op1[64], op2[64], op3[64]; + int nops = sscanf(insn->operands, "%63[^,], %63[^,], %63s", op1, op2, op3); + + if (nops == 3) { + /* Three-operand: dest = src1 * src2 */ + E9IRValue *dest_val = parse_operand(dc, op1, 64); + E9IRValue *src1_val = parse_operand(dc, op2, 64); + E9IRValue *src2_val = parse_operand(dc, op3, 64); + if (dest_val && src1_val && src2_val) { + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (ir_dest) { + stmt->dest = ir_dest; + stmt->value = e9_ir_binary(dc, op, src1_val, src2_val); } } - } - /* sub instruction */ - else if (strcmp(mnemonic, "sub") == 0 || strcmp(mnemonic, "subq") == 0 || - strcmp(mnemonic, "subl") == 0) { - char dest[32], src[64]; - if (sscanf(operands, "%31[^,], %63s", dest, src) == 2) { - E9IRValue *dest_val = NULL; - E9IRValue *src_val = NULL; - - for (int i = 0; i < 16; i++) { - if (strcmp(dest, x64_reg_names[i]) == 0) { - dest_val = e9_ir_reg(dc, i); - break; - } + } else if (nops == 2) { + /* Two-operand: dest = dest * src */ + E9IRValue *dest_val = parse_operand(dc, op1, 64); + E9IRValue *src_val = parse_operand(dc, op2, 64); + if (dest_val && src_val) { + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (ir_dest) { + stmt->dest = ir_dest; + stmt->value = e9_ir_binary(dc, op, dest_val, src_val); } + } + } else { + /* One-operand: implicit rax * operand -> rdx:rax + * We model the low half (RAX) result; RDX (high bits / remainder) + * is set separately below for completeness. */ + E9IRValue *src_val = parse_operand(dc, insn->operands, 64); + if (src_val) { + stmt->dest = e9_ir_reg(dc, X64_RAX); + stmt->value = e9_ir_binary(dc, op, e9_ir_reg(dc, X64_RAX), src_val); + /* NOTE: RDX receives high bits (mul/imul) or remainder (div/idiv). + * Full modeling requires a second statement or wide-result IR node. */ + } + } + return stmt; +} - if (src[0] == '0' && src[1] == 'x') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 16), 64); - } else if (isdigit(src[0]) || src[0] == '-') { - src_val = e9_ir_const(dc, strtoll(src, NULL, 10), 64); - } +static E9IRStmt *lift_instruction(E9Decompile *dc, E9IRFunc *func, E9Instruction *insn) +{ + (void)func; + const char *mnemonic = insn->mnemonic; + /* ========== Data Movement ========== */ + + /* mov variants */ + if (mnemonic_is(mnemonic, "mov") || mnemonic_is(mnemonic, "movabs")) { + return lift_mov_op(dc, insn, false); + } + /* movzx/movsx - zero/sign extend */ + if (mnemonic_is(mnemonic, "movzx") || mnemonic_is(mnemonic, "movsx") || + mnemonic_is(mnemonic, "movsxd") || mnemonic_is(mnemonic, "movzbl") || + mnemonic_is(mnemonic, "movsbl") || mnemonic_is(mnemonic, "movzbq") || + mnemonic_is(mnemonic, "movsbq")) { + return lift_mov_op(dc, insn, false); + } + /* lea - load effective address */ + if (mnemonic_is(mnemonic, "lea")) { + return lift_mov_op(dc, insn, true); + } + /* push */ + if (mnemonic_is(mnemonic, "push")) { + return lift_stack_op(dc, insn, true); + } + /* pop */ + if (mnemonic_is(mnemonic, "pop")) { + return lift_stack_op(dc, insn, false); + } + /* xchg - exchange (swap two operands) */ + if (mnemonic_is(mnemonic, "xchg")) { + /* xchg requires a temp variable for proper swap semantics. + * Generate: tmp = dest; dest = src; src = tmp + * For now, emit as first half: dest = src (lossy but not wrong like XOR) */ + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + char d[64], s[128]; + if (sscanf(insn->operands, "%63[^,], %127s", d, s) == 2) { + E9IRValue *dest_val = parse_operand(dc, d, 64); + E9IRValue *src_val = parse_operand(dc, s, 64); if (dest_val && src_val) { - stmt->dest = e9_ir_reg(dc, dest_val->reg); - stmt->value = e9_ir_binary(dc, E9_IR_SUB, dest_val, src_val); + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (ir_dest) { + stmt->dest = ir_dest; + stmt->value = src_val; + /* NOTE: Second half (src = old_dest) not emitted. + * Full swap requires multi-statement IR support. */ + } } } + return stmt; + } + + /* ========== Arithmetic ========== */ + + /* add */ + if (mnemonic_is(mnemonic, "add")) { + return lift_binary_op(dc, insn, E9_IR_ADD); + } + /* sub */ + if (mnemonic_is(mnemonic, "sub")) { + return lift_binary_op(dc, insn, E9_IR_SUB); + } + /* adc - add with carry: dest = dest + src + CF + * NOTE: Carry flag (CF) contribution not modeled; result is dest + src only. + * This is semantically incomplete for multi-precision arithmetic. */ + if (mnemonic_is(mnemonic, "adc")) { + return lift_binary_op(dc, insn, E9_IR_ADD); + } + /* sbb - subtract with borrow: dest = dest - src - CF + * NOTE: Carry/borrow flag (CF) contribution not modeled; result is dest - src only. + * This is semantically incomplete for multi-precision arithmetic. */ + if (mnemonic_is(mnemonic, "sbb")) { + return lift_binary_op(dc, insn, E9_IR_SUB); + } + /* inc */ + if (mnemonic_is(mnemonic, "inc")) { + return lift_unary_op(dc, insn, E9_IR_ADD); /* inc = add 1 */ + } + /* dec */ + if (mnemonic_is(mnemonic, "dec")) { + return lift_unary_op(dc, insn, E9_IR_SUB); /* dec = sub 1 */ + } + /* neg */ + if (mnemonic_is(mnemonic, "neg")) { + return lift_unary_op(dc, insn, E9_IR_NEG); + } + /* mul/imul */ + if (mnemonic_is(mnemonic, "imul")) { + return lift_muldiv_op(dc, insn, E9_IR_MUL, true); + } + if (mnemonic_is(mnemonic, "mul")) { + return lift_muldiv_op(dc, insn, E9_IR_MUL, false); + } + /* div/idiv */ + if (mnemonic_is(mnemonic, "idiv")) { + return lift_muldiv_op(dc, insn, E9_IR_DIV, true); } - /* xor with self = zero */ - else if (strcmp(mnemonic, "xor") == 0 || strcmp(mnemonic, "xorl") == 0) { - char dest[32], src[32]; - if (sscanf(operands, "%31[^,], %31s", dest, src) == 2) { - if (strcmp(dest, src) == 0) { - /* xor reg, reg = 0 */ - for (int i = 0; i < 16; i++) { - if (strcmp(dest, x64_reg_names[i]) == 0 || - (dest[0] == 'e' && strcmp(dest + 1, x64_reg_names[i] + 1) == 0)) { - stmt->dest = e9_ir_reg(dc, i); + if (mnemonic_is(mnemonic, "div")) { + return lift_muldiv_op(dc, insn, E9_IR_DIV, false); + } + + /* ========== Logic ========== */ + + /* and */ + if (mnemonic_is(mnemonic, "and")) { + return lift_binary_op(dc, insn, E9_IR_AND); + } + /* or */ + if (mnemonic_is(mnemonic, "or")) { + return lift_binary_op(dc, insn, E9_IR_OR); + } + /* xor */ + if (mnemonic_is(mnemonic, "xor")) { + /* Check for xor reg, reg = 0 optimization */ + char dest[64], src[64]; + if (sscanf(insn->operands, "%63[^,], %63s", dest, src) == 2) { + /* Trim leading and trailing whitespace */ + char *d = dest, *s = src; + while (*d == ' ') d++; + while (*s == ' ') s++; + /* Trim trailing whitespace */ + char *de = d + strlen(d) - 1; + while (de > d && *de == ' ') *de-- = '\0'; + char *se = s + strlen(s) - 1; + while (se > s && *se == ' ') *se-- = '\0'; + if (strcmp(d, s) == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + E9IRValue *dest_val = parse_operand(dc, d, 64); + if (dest_val) { + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (ir_dest) { + stmt->dest = ir_dest; stmt->value = e9_ir_const(dc, 0, 64); - break; + return stmt; } } } } + return lift_binary_op(dc, insn, E9_IR_XOR); + } + /* not */ + if (mnemonic_is(mnemonic, "not")) { + return lift_unary_op(dc, insn, E9_IR_NOT); + } + /* test - like and but only sets flags */ + if (mnemonic_is(mnemonic, "test")) { + return lift_cmp_op(dc, insn, E9_IR_AND); + } + /* cmp - like sub but only sets flags */ + if (mnemonic_is(mnemonic, "cmp")) { + return lift_cmp_op(dc, insn, E9_IR_SUB); + } + + /* ========== Shifts and Rotates ========== */ + + /* shl/sal */ + if (mnemonic_is(mnemonic, "shl") || mnemonic_is(mnemonic, "sal")) { + return lift_shift_op(dc, insn, E9_IR_SHL); + } + /* shr */ + if (mnemonic_is(mnemonic, "shr")) { + return lift_shift_op(dc, insn, E9_IR_SHR); + } + /* sar */ + if (mnemonic_is(mnemonic, "sar")) { + return lift_shift_op(dc, insn, E9_IR_SAR); + } + /* rol/ror - rotates + * + * NOTE: Proper rotate semantics require wrapping shifted-out bits to the + * other end. The IR does not have a first-class rotate op, so we emit + * an UNSUPPORTED marker rather than silently emitting a shift (which + * discards bits and produces incorrect results). */ + if (mnemonic_is(mnemonic, "rol") || mnemonic_is(mnemonic, "ror")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* Mark as unlifted rotate - consumer should fall back to asm */ + return stmt; + } + + /* ========== Control Flow ========== */ + + /* jmp - unconditional */ + if (mnemonic_is(mnemonic, "jmp")) { + return lift_jump_op(dc, insn, NULL); + } + /* Conditional jumps */ + if (strcmp(mnemonic, "je") == 0 || strcmp(mnemonic, "jz") == 0) { + return lift_jump_op(dc, insn, "e"); + } + if (strcmp(mnemonic, "jne") == 0 || strcmp(mnemonic, "jnz") == 0) { + return lift_jump_op(dc, insn, "ne"); + } + if (strcmp(mnemonic, "jl") == 0 || strcmp(mnemonic, "jnge") == 0) { + return lift_jump_op(dc, insn, "l"); + } + if (strcmp(mnemonic, "jle") == 0 || strcmp(mnemonic, "jng") == 0) { + return lift_jump_op(dc, insn, "le"); + } + if (strcmp(mnemonic, "jg") == 0 || strcmp(mnemonic, "jnle") == 0) { + return lift_jump_op(dc, insn, "g"); + } + if (strcmp(mnemonic, "jge") == 0 || strcmp(mnemonic, "jnl") == 0) { + return lift_jump_op(dc, insn, "ge"); + } + if (strcmp(mnemonic, "jb") == 0 || strcmp(mnemonic, "jnae") == 0 || strcmp(mnemonic, "jc") == 0) { + return lift_jump_op(dc, insn, "b"); } + if (strcmp(mnemonic, "jbe") == 0 || strcmp(mnemonic, "jna") == 0) { + return lift_jump_op(dc, insn, "be"); + } + if (strcmp(mnemonic, "ja") == 0 || strcmp(mnemonic, "jnbe") == 0) { + return lift_jump_op(dc, insn, "a"); + } + if (strcmp(mnemonic, "jae") == 0 || strcmp(mnemonic, "jnb") == 0 || strcmp(mnemonic, "jnc") == 0) { + return lift_jump_op(dc, insn, "ae"); + } + if (strcmp(mnemonic, "js") == 0) { + return lift_jump_op(dc, insn, "s"); + } + if (strcmp(mnemonic, "jns") == 0) { + return lift_jump_op(dc, insn, "ns"); + } + if (strcmp(mnemonic, "jo") == 0) { + return lift_jump_op(dc, insn, "o"); + } + if (strcmp(mnemonic, "jno") == 0) { + return lift_jump_op(dc, insn, "no"); + } + if (strcmp(mnemonic, "jp") == 0 || strcmp(mnemonic, "jpe") == 0) { + return lift_jump_op(dc, insn, "p"); + } + if (strcmp(mnemonic, "jnp") == 0 || strcmp(mnemonic, "jpo") == 0) { + return lift_jump_op(dc, insn, "np"); + } + /* call */ - else if (strcmp(mnemonic, "call") == 0 || strcmp(mnemonic, "callq") == 0) { + if (mnemonic_is(mnemonic, "call")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + E9IRValue *target = NULL; if (insn->target) { - /* Direct call - create a constant for the address */ target = e9_ir_const(dc, insn->target, 64); } else { - /* Indirect call - parse the operand */ - target = e9_ir_reg(dc, X64_RAX); /* Simplified */ + target = parse_operand(dc, insn->operands, 64); } - stmt->dest = e9_ir_reg(dc, X64_RAX); /* Return value in rax */ + stmt->dest = e9_ir_reg(dc, X64_RAX); stmt->value = e9_ir_call(dc, target, NULL, 0); + return stmt; } + /* ret */ - else if (strcmp(mnemonic, "ret") == 0 || strcmp(mnemonic, "retq") == 0) { + if (mnemonic_is(mnemonic, "ret")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + E9IRValue *ret_val = dc_alloc(sizeof(E9IRValue)); if (ret_val) { ret_val->op = E9_IR_RET; ret_val->unary.operand = e9_ir_reg(dc, X64_RAX); } stmt->value = ret_val; + return stmt; + } + + /* ========== Stack Frame ========== */ + + /* enter - create stack frame: push rbp; mov rbp, rsp; sub rsp, size */ + if (mnemonic_is(mnemonic, "enter")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* Model as: rbp = rsp (the key semantic effect for frame analysis). + * Push of old rbp and rsp adjustment are implicit. */ + stmt->dest = e9_ir_reg(dc, X64_RBP); + stmt->value = e9_ir_reg(dc, X64_RSP); + return stmt; } - /* Default: comment the instruction */ - else { - /* Create a placeholder */ - stmt->dest = NULL; - stmt->value = NULL; + /* leave - destroy stack frame: mov rsp, rbp; pop rbp */ + if (mnemonic_is(mnemonic, "leave")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* Model as: rsp = rbp (restores stack pointer to frame base). + * Pop of rbp is implicit. */ + stmt->dest = e9_ir_reg(dc, X64_RSP); + stmt->value = e9_ir_reg(dc, X64_RBP); + return stmt; } - + + /* ========== Misc ========== */ + + /* nop */ + if (mnemonic_is(mnemonic, "nop")) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* No operation - empty statement */ + return stmt; + } + + /* syscall - Linux x86-64 ABI: number in RAX, args in RDI,RSI,RDX,R10,R8,R9 */ + if (strcmp(mnemonic, "syscall") == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + E9IRValue *syscall = dc_alloc(sizeof(E9IRValue)); + if (syscall) { + syscall->op = E9_IR_CALL; + /* Syscall number comes from RAX (not a constant) */ + syscall->call.func = e9_ir_reg(dc, X64_RAX); + /* Model the six argument registers per Linux x86-64 syscall ABI */ + E9IRValue **args = dc_alloc(6 * sizeof(E9IRValue *)); + if (args) { + args[0] = e9_ir_reg(dc, X64_RDI); + args[1] = e9_ir_reg(dc, X64_RSI); + args[2] = e9_ir_reg(dc, X64_RDX); + args[3] = e9_ir_reg(dc, X64_R10); + args[4] = e9_ir_reg(dc, X64_R8); + args[5] = e9_ir_reg(dc, X64_R9); + syscall->call.args = args; + syscall->call.num_args = 6; + } else { + syscall->call.args = NULL; + syscall->call.num_args = 0; + } + } + stmt->dest = e9_ir_reg(dc, X64_RAX); + stmt->value = syscall; + return stmt; + } + + /* int - software interrupt */ + if (strcmp(mnemonic, "int") == 0 || strcmp(mnemonic, "int3") == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* Interrupt - side effect only */ + return stmt; + } + + /* cdq/cqo/cwd - sign extend rax into rdx:rax + * cdqe is different: sign-extends eax into rax (no rdx involved) */ + if (strcmp(mnemonic, "cdqe") == 0) { + /* cdqe: rax = sign_extend(eax) - modeled as SAR 31 then extend */ + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + stmt->dest = e9_ir_reg(dc, X64_RAX); + stmt->value = e9_ir_reg(dc, X64_RAX); /* Sign extension implicit in 64-bit context */ + return stmt; + } + if (strcmp(mnemonic, "cdq") == 0 || strcmp(mnemonic, "cqo") == 0 || + strcmp(mnemonic, "cwd") == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + /* rdx = (rax < 0) ? 0xFFFFFFFFFFFFFFFF : 0 + * Modeled as arithmetic right shift by 63 (copies sign bit to all bits) */ + stmt->dest = e9_ir_reg(dc, X64_RDX); + stmt->value = e9_ir_binary(dc, E9_IR_SAR, + e9_ir_reg(dc, X64_RAX), + e9_ir_const(dc, 63, 64)); + return stmt; + } + + /* setcc - set byte to 0 or 1 based on condition code suffix */ + if (strncmp(mnemonic, "set", 3) == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + E9IRValue *dest = parse_operand(dc, insn->operands, 8); + if (dest) { + E9IRValue *ir_dest = ir_get_dest(dc, dest); + if (ir_dest) { + stmt->dest = ir_dest; + /* Extract condition code (e.g., "e" from "sete", "ne" from "setne"). + * Encode as: (RFLAGS & cond_hash) != 0 → 0 or 1 + * This gives downstream passes enough info to reconstruct the test. */ + const char *cc = mnemonic + 3; + uint64_t cc_tag = 0; + for (const char *p = cc; *p; p++) + cc_tag = cc_tag * 31 + (unsigned char)*p; + E9IRValue *flags = e9_ir_reg(dc, X64_RFLAGS); + stmt->value = e9_ir_binary(dc, E9_IR_AND, flags, + e9_ir_const(dc, cc_tag, 8)); + } + } + return stmt; + } + + /* cmovcc - conditional move: dest = src IF condition is true + * NOTE: Lifting as unconditional mov is semantically wrong (it always + * moves), but the IR currently lacks a SELECT/ternary node. + * We emit a conditional branch wrapper to mark the conditionality. */ + if (strncmp(mnemonic, "cmov", 4) == 0) { + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + + char d[64], s[128]; + if (sscanf(insn->operands, "%63[^,], %127s", d, s) == 2) { + E9IRValue *dest_val = parse_operand(dc, d, 64); + E9IRValue *src_val = parse_operand(dc, s, 64); + if (dest_val && src_val) { + E9IRValue *ir_dest = ir_get_dest(dc, dest_val); + if (ir_dest) { + /* Encode: dest = cond ? src : dest (keep old value if false) + * We use RFLAGS + condition tag to mark the guard. */ + const char *cc = mnemonic + 4; + uint64_t cc_tag = 0; + for (const char *p = cc; *p; p++) + cc_tag = cc_tag * 31 + (unsigned char)*p; + E9IRValue *guard = e9_ir_binary(dc, E9_IR_AND, + e9_ir_reg(dc, X64_RFLAGS), + e9_ir_const(dc, cc_tag, 64)); + (void)guard; /* TODO: wrap src_val in SELECT(guard, src, dest) */ + stmt->dest = ir_dest; + stmt->value = src_val; /* Still lossy - needs SELECT IR node */ + } + } + } + return stmt; + } + + /* ========== Default: Unhandled ========== */ + + E9IRStmt *stmt = dc_alloc(sizeof(E9IRStmt)); + if (!stmt) return NULL; + stmt->addr = insn->address; + stmt->dest = NULL; + stmt->value = NULL; return stmt; } diff --git a/test_decompile b/test_decompile new file mode 100644 index 0000000..7d470c5 Binary files /dev/null and b/test_decompile differ diff --git a/test_decompile.c b/test_decompile.c new file mode 100644 index 0000000..0a7ef07 --- /dev/null +++ b/test_decompile.c @@ -0,0 +1,59 @@ +/* + * test_decompile.c + * Simple test to output decompiled C code + */ + +#include +#include +#include +#include "src/e9patch/analysis/e9studio_analysis.h" + +#define BUF_SIZE (64 * 1024) + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "Usage: %s [function_name]\n", argv[0]); + return 1; + } + + const char *binary_path = argv[1]; + const char *func_name = argc > 2 ? argv[2] : NULL; + + /* Initialize analysis engine */ + if (e9studio_analysis_init() < 0) { + fprintf(stderr, "Failed to initialize analysis engine\n"); + return 1; + } + + /* Load binary */ + if (e9studio_load_binary(binary_path) < 0) { + fprintf(stderr, "Failed to load binary: %s\n", binary_path); + e9studio_analysis_shutdown(); + return 1; + } + + printf("/* Decompiled from: %s */\n", binary_path); + printf("/* Generated by E9Studio */\n\n"); + printf("#include \n\n"); + + /* Get decompiled output */ + char *buf = malloc(BUF_SIZE); + if (!buf) { + fprintf(stderr, "Memory allocation failed\n"); + e9studio_analysis_shutdown(); + return 1; + } + + const char *result = e9studio_get_decompiled(func_name, buf, BUF_SIZE); + if (result) { + printf("%s\n", result); + } else { + printf("/* No decompilation available */\n"); + } + + free(buf); + e9studio_analysis_shutdown(); + return 0; +} +