La idea de virtualización de código es hacer complicado el análisis de ingeniería inversa de cualquier programa, creando una CPU imaginaria con sus propios conjunto de instrucciones y reemplazando el código original de assembler de la CPU real por la CPU imaginaria.
Código
#include <iostream> #include "BloodVM.h" int __declspec(naked) __stdcall Sumar() { __asm { call BloodVM_Init; //MOV_R_N => mov eax, 4; _emit 0x02; //OPCODE::MOV_R_N _emit 0x05; //EAX offset _emit 0x04; //number 4 bytes _emit 0x00; _emit 0x00; _emit 0x00; //ADD_R_N => add eax, 5; _emit 0x04; //OPCODE::ADD_R_N _emit 0x05; //EAX offset _emit 0x05; //number 4 bytes _emit 0x00; _emit 0x00; _emit 0x00; //ADD_R_R => add eax, eax; _emit 0x03; //OPCODE::ADD_R_R _emit 0x05; //EAX offset _emit 0x05; //EAX offset //QUIT => Exit VM _emit 0x00; call BloodVM_End; ret; } } int main() { std::cout << "Suma virtualizada (4 + 5) + (4 + 5) = " << Sumar() << std::endl; std::cin.ignore(); }
El código de ejemplo tiene apenas 5 instrucciones pero se podría implementar más. Al verse en un desamblador la función virtualizada se vería algo como esto:
Lo cuál forzaría al que quiera ver como funciona realmente el programa a analizar toda la CPU imaginaria siendo usualmente una tarea bastante compleja. Se podría crear también un programa que detecte las llamadas del inicio y fin de la virtualización y reemplazar el código real por el imaginario pero esa es una tarea bastante compleja que llevaría mucho tiempo.
Dejo el resto del código por si a alguien le interesa:
BloodVM.cpp
Código
#include "BloodVM.h" BloodVM gBloodVM; uint32_t __declspec(naked) __stdcall GetCaller()//uint32_t dwESP) { //return *(uint32_t*)(dwESP+4); __asm { mov eax, [esp + 4]; ret; } } void __declspec(naked) __stdcall BloodVM_Init() { __asm { pushad; pop gBloodVM.EDI; pop gBloodVM.ESI; pop gBloodVM.EBP; pop gBloodVM.ESP; pop gBloodVM.EBX; pop gBloodVM.EDX; pop gBloodVM.ECX; pop gBloodVM.EAX; //push gBloodVM.ESP; call GetCaller; mov gBloodVM.EIP, eax; lea ecx, gBloodVM; call BloodVM::RunVirtualMachine; mov eax, gBloodVM.EIP; mov [esp], eax; ret; } } void __declspec(naked) __stdcall BloodVM_End() { __asm { mov edi, gBloodVM.EDI; mov esi, gBloodVM.ESI; mov ebx, gBloodVM.EBX; mov edx, gBloodVM.EDX; mov ecx, gBloodVM.ECX; mov eax, gBloodVM.EAX; mov ebp, gBloodVM.EBP; mov esp, gBloodVM.ESP; ret; } } void BloodVM::RunVirtualMachine() { do { this->currentOpcode = (uint8_t)(*((uint8_t*)this->EIP)); uint32_t nextEIP = this->vInstructions[this->currentOpcode].sizeOfInstruction; (this->*vInstructions[this->currentOpcode].operate)(); this->EIP += nextEIP; } while (this->currentOpcode != OPCODE::QUIT); } void BloodVM::QUIT() { } void BloodVM::MOV() { uint32_t* firstRegister; uint32_t* secondRegister; uint32_t firstNumber; switch (this->currentOpcode) { case OPCODE::MOV_R_R: firstRegister = &this->EDI + *(uint8_t*)(this->EIP + 1); secondRegister = &this->EDI + *(uint8_t*)(this->EIP + 2); *firstRegister = *secondRegister; break; case OPCODE::MOV_R_N: firstRegister = &this->EDI + *(uint8_t*)(this->EIP + 1); firstNumber = *(uint32_t*)(this->EIP + 2); *firstRegister = firstNumber; break; } } void BloodVM::ADD() { uint32_t* firstRegister; uint32_t* secondRegister; uint32_t firstNumber; switch (this->currentOpcode) { case OPCODE::ADD_R_R: firstRegister = &this->EDI + *(uint8_t*)(this->EIP + 1); secondRegister = &this->EDI + *(uint8_t*)(this->EIP + 2); *firstRegister += *secondRegister; break; case OPCODE::ADD_R_N: firstRegister = &this->EDI + *(uint8_t*)(this->EIP + 1); firstNumber = *(uint32_t*)(this->EIP + 2); *firstRegister += firstNumber; break; } }
BloodVM.h
Código
#pragma once #include <cstdint> #include <vector> enum OPCODE { QUIT, MOV_R_R, MOV_R_N, ADD_R_R, ADD_R_N, }; class BloodVM { public: BloodVM() { vInstructions = { /* All opcodes sizes are always 1 byte All registers (R) sizes are also 1 byte All numbers (N) sizes are always 4 bytes */ {OPCODE::QUIT, 1, &BloodVM::QUIT}, {OPCODE::MOV_R_R, 3, &BloodVM::MOV}, {OPCODE::MOV_R_N, 6, &BloodVM::MOV}, {OPCODE::ADD_R_R, 3, &BloodVM::ADD}, {OPCODE::ADD_R_N, 6, &BloodVM::ADD}, }; } uint32_t EDI, ESI, EBX, EDX, ECX, EAX, EBP, EIP, ESP; uint8_t currentOpcode; void RunVirtualMachine(); void MOV(); void ADD(); void QUIT(); struct INSTRUCTION { uint8_t opcode; uint8_t sizeOfInstruction; void (BloodVM::* operate)() = nullptr; }; std::vector<INSTRUCTION> vInstructions; }; void __stdcall BloodVM_Init(); void __stdcall BloodVM_End();
B#