#include #include #include // global storage used to temporarily save the host RSP // while executing the emulated instruction. uint64_t g_savedRsp = 0; //uint64_t g_savedRax = 0; /* * Implements: mov r64, imm64 * * buffer -> output code buffer * offset -> current write offset (updated by reference) * regCode -> register index (r64) * value -> immediate 64-bit value (imm64) * isExtended -> true for R8–R15 (REX.B = 1) */ void WriteMovRegImm64(BYTE* buffer, SIZE_T& offset, BYTE regCode, uint64_t value, bool isExtended = false) { // REX prefix: 0x48 = REX.W, 0x49 = REX.W | REX.B buffer[offset++] = isExtended ? 0x49 : 0x48; // opcode B8–BF: MOV r64, imm64 buffer[offset++] = 0xB8 + (regCode & 0x7); // immediate 64-bit value *(uint64_t*)(buffer + offset) = value; offset += 8; } /* * builds a code stub (execPtr) that: * - saves the host CPU CONTEXT * - loads a virtual CONTEXT (shellCtx) * - executes one instruction * - writes back modified registers and flags * - restores the host CONTEXT and returns */ SIZE_T ShadowStep( BYTE* execPtr, const BYTE* instruction, SIZE_T instrSize, CONTEXT* shellCtx // virtual CPU state ) { SIZE_T offset = 0; CONTEXT& ctx = *shellCtx; // optional NOP byte //replace it with 0xcc to enable debug mode BYTE debug = 0x90; /* * save the host CPU state: * - all general-purpose registers * - EFLAGS */ const BYTE saveHost[] = { debug, 0x50, // push rax 0x51, // push rcx 0x52, // push rdx 0x53, // push rbx 0x55, // push rbp 0x56, // push rsi 0x57, // push rdi 0x41, 0x50, // push r8 0x41, 0x51, // push r9 0x41, 0x52, // push r10 0x41, 0x53, // push r11 0x41, 0x54, // push r12 0x41, 0x55, // push r13 0x41, 0x56, // push r14 0x41, 0x57, // push r15 0x9C // pushf; save EFLAGS }; memcpy(execPtr + offset, saveHost, sizeof(saveHost)); offset += sizeof(saveHost); // --------------------------------------------------------------------- // save host RSP to global memory (needed to restore after emulation) // --------------------------------------------------------------------- // mov [g_savedRsp], rsp // mov rax, rsp execPtr[offset++] = 0x48; execPtr[offset++] = 0x89; execPtr[offset++] = 0xE0; // here: rax = rsp // mov rcx, imm64 (address of g_savedRsp) WriteMovRegImm64(execPtr, offset, 1, (uint64_t)&g_savedRsp); // here: rcx = &g_savedRsp // mov [rcx], rax execPtr[offset++] = 0x48; execPtr[offset++] = 0x89; execPtr[offset++] = 0x01; // here: [rcx] <- rax // --------------------------------------------------------------------- // load EFLAGS from CONTEXT (ctx.EFlags) // --------------------------------------------------------------------- // mov rax, imm64 (ctx->EFlags) WriteMovRegImm64(execPtr, offset, 0, ctx.EFlags); // rax = ctx->EFlags // push rax execPtr[offset++] = 0x50; // popfq execPtr[offset++] = 0x9D; // --------------------------------------------------------------------- // load general-purpose registers from CONTEXT // --------------------------------------------------------------------- WriteMovRegImm64(execPtr, offset, 0, ctx.Rax); // mov rax, ctx.Rax WriteMovRegImm64(execPtr, offset, 1, ctx.Rcx); // mov rcx, ctx.Rcx WriteMovRegImm64(execPtr, offset, 2, ctx.Rdx); // mov rdx, ctx.Rdx WriteMovRegImm64(execPtr, offset, 3, ctx.Rbx); // mov rbx, ctx.Rbx WriteMovRegImm64(execPtr, offset, 4, ctx.Rsp); // mov rsp, ctx.Rsp WriteMovRegImm64(execPtr, offset, 5, ctx.Rbp); // mov rbp, ctx.Rbp WriteMovRegImm64(execPtr, offset, 6, ctx.Rsi); // mov rsi, ctx.Rsi WriteMovRegImm64(execPtr, offset, 7, ctx.Rdi); // mov rdi, ctx.Rdi WriteMovRegImm64(execPtr, offset, 0, ctx.R8, true); // mov r8, ctx.R8 WriteMovRegImm64(execPtr, offset, 1, ctx.R9, true); // mov r9, ctx.R9 WriteMovRegImm64(execPtr, offset, 2, ctx.R10, true); // mov r10, ctx.R10 WriteMovRegImm64(execPtr, offset, 3, ctx.R11, true); // mov r11, ctx.R11 WriteMovRegImm64(execPtr, offset, 4, ctx.R12, true); // mov r12, ctx.R12 WriteMovRegImm64(execPtr, offset, 5, ctx.R13, true); // mov r13, ctx.R13 WriteMovRegImm64(execPtr, offset, 6, ctx.R14, true); // mov r14, ctx.R14 WriteMovRegImm64(execPtr, offset, 7, ctx.R15, true); // mov r15, ctx.R15 // --------------------------------------------------------------------- // execute the target instruction // --------------------------------------------------------------------- memcpy(execPtr + offset, instruction, instrSize); offset += instrSize; // save rax and EFLAGS after execution // push rax execPtr[offset++] = 0x50; // pushfq execPtr[offset++] = 0x9C; // mov rax, imm64 (ctx pointer) WriteMovRegImm64(execPtr, offset, 0, (uint64_t)shellCtx); // here: rax = &ctx // helper lambda: // writes general-purpose registers back to CONTEXT (using rax as base) // mov [rax + disp32], reg auto storeRegToCtx = [&](BYTE regCode, DWORD ctxOffset, bool extended) { execPtr[offset++] = extended ? 0x4C : 0x48; // REX.W + R/M execPtr[offset++] = 0x89; // MOV [rax + disp32], reg execPtr[offset++] = 0x80 | ((regCode & 0x7) << 3); // ModRM: [rax + disp32] *(DWORD*)(execPtr + offset) = ctxOffset; offset += 4; }; // add rsp, 0x10 // to avoid overwriting rax or EFLAGS // (to compensate for push rax + pushfq) execPtr[offset++] = 0x48; execPtr[offset++] = 0x83; execPtr[offset++] = 0xC4; execPtr[offset++] = 0x10; // save general-purpose registers back into CONTEXT // (notice that RAX is not saved here) storeRegToCtx(1, offsetof(CONTEXT, Rcx), false); // mov [rax + offsetof(CONTEXT, Rcx)], rcx storeRegToCtx(2, offsetof(CONTEXT, Rdx), false); // mov [rax + offsetof(CONTEXT, Rdx)], rdx storeRegToCtx(3, offsetof(CONTEXT, Rbx), false); // mov [rax + offsetof(CONTEXT, Rbx)], rbx storeRegToCtx(4, offsetof(CONTEXT, Rsp), false); // mov [rax + offsetof(CONTEXT, Rsp)], rsp storeRegToCtx(5, offsetof(CONTEXT, Rbp), false); // mov [rax + offsetof(CONTEXT, Rbp)], rbp storeRegToCtx(6, offsetof(CONTEXT, Rsi), false); // mov [rax + offsetof(CONTEXT, Rsi)], rsi storeRegToCtx(7, offsetof(CONTEXT, Rdi), false); // mov [rax + offsetof(CONTEXT, Rdi)], rdi storeRegToCtx(0, offsetof(CONTEXT, R8), true); // mov [rax + offsetof(CONTEXT, R8)], r8 storeRegToCtx(1, offsetof(CONTEXT, R9), true); // mov [rax + offsetof(CONTEXT, R9)], r9 storeRegToCtx(2, offsetof(CONTEXT, R10), true); // mov [rax + offsetof(CONTEXT, R10)], r10 storeRegToCtx(3, offsetof(CONTEXT, R11), true); // mov [rax + offsetof(CONTEXT, R11)], r11 storeRegToCtx(4, offsetof(CONTEXT, R12), true); // mov [rax + offsetof(CONTEXT, R12)], r12 storeRegToCtx(5, offsetof(CONTEXT, R13), true); // mov [rax + offsetof(CONTEXT, R13)], r13 storeRegToCtx(6, offsetof(CONTEXT, R14), true); // mov [rax + offsetof(CONTEXT, R14)], r14 storeRegToCtx(7, offsetof(CONTEXT, R15), true); // mov [rax + offsetof(CONTEXT, R15)], r15 // sub rsp, 0x10 // (restore stack state) execPtr[offset++] = 0x48; execPtr[offset++] = 0x83; execPtr[offset++] = 0xEC; execPtr[offset++] = 0x10; // --------------------------------------------------------------------- // store EFLAGS into CONTEXT // --------------------------------------------------------------------- // pop rax (EFLAGS) execPtr[offset++] = 0x58; // here: rax = EFLAGS // mov rcx, imm64 (&ctx) WriteMovRegImm64(execPtr, offset, 1, (uint64_t)shellCtx); // here: rcx = &ctx // mov [rcx + offsetof(CONTEXT, EFlags)], rax execPtr[offset++] = 0x48; execPtr[offset++] = 0x89; execPtr[offset++] = 0x81; *(DWORD*)(execPtr + offset) = offsetof(CONTEXT, EFlags); offset += 4; // --------------------------------------------------------------------- // store RAX into CONTEXT // --------------------------------------------------------------------- // pop rax (rsp points at the previously saved rax value) execPtr[offset++] = 0x58; // write back RAX to CONTEXT using RCX as base // mov rcx, imm64 (&ctx) WriteMovRegImm64(execPtr, offset, 1, (uint64_t)shellCtx); // here: rcx = &ctx // mov [rcx + offset(Rax)], rax execPtr[offset++] = 0x48; // REX.W execPtr[offset++] = 0x89; // MOV r/m64, r64 execPtr[offset++] = 0x81; // ModRM: reg=RAX, r/m=RCX (with disp32) *(DWORD*)(execPtr + offset) = offsetof(CONTEXT, Rax); // disp32 = offset of Rax offset += 4; // --------------------------------------------------------------------- // restore original host RSP // --------------------------------------------------------------------- // mov rax, imm64 (&g_savedRsp) WriteMovRegImm64(execPtr, offset, 0, (uint64_t)&g_savedRsp); // here: rax = &g_savedRsp // mov rax, [rax] execPtr[offset++] = 0x48; execPtr[offset++] = 0x8B; execPtr[offset++] = 0x00; // here: rax = [rax] // mov rsp, rax execPtr[offset++] = 0x48; execPtr[offset++] = 0x89; execPtr[offset++] = 0xC4; // here: rsp = rax // --------------------------------------------------------------------- // restore host registers and return // --------------------------------------------------------------------- const BYTE restoreHost[] = { 0x9D, // popf ; restore EFLAGS 0x41, 0x5F, // pop r15 0x41, 0x5E, // pop r14 0x41, 0x5D, // pop r13 0x41, 0x5C, // pop r12 0x41, 0x5B, // pop r11 0x41, 0x5A, // pop r10 0x41, 0x59, // pop r9 0x41, 0x58, // pop r8 0x5F, // pop rdi 0x5E, // pop rsi 0x5D, // pop rbp 0x5B, // pop rbx 0x5A, // pop rdx 0x59, // pop rcx 0x58, // pop rax 0xC3 // ret ; return to caller }; memcpy(execPtr + offset, restoreHost, sizeof(restoreHost)); return offset + sizeof(restoreHost); } BYTE* GeneratePushReturnAddressBytes(uint64_t returnOffset, uint64_t baseAddress, SIZE_T& outSize) { // mov rax, (10 bytes) // push rax (1 byte) // returnAddress = base + offset uint64_t returnAddress = baseAddress + returnOffset; outSize = 10 + 1; BYTE* buffer = (BYTE*)malloc(outSize); if (!buffer) return nullptr; SIZE_T offset = 0; // === mov rax, buffer[offset++] = 0x48; buffer[offset++] = 0xB8; memcpy(buffer + offset, &returnAddress, 8); offset += 8; // === push rax buffer[offset++] = 0x50; return buffer; }