x64dbg插件脱壳vmp3.2-3.5

查看 75|回复 8
作者:yoyoRev   
事先说明,对加壳程序选项没有包含反调试,资源保护以及虚拟化特定代码片段,vmp会对相关的资源函数进行hook,修复了也无法运行(好像易语言可以 恢复原始api就可以,我尝试了其他程序,修复之后无法运行,放弃),目前没有能力去修复资源啥的,vm的反调试也没有具体研究过,学逆向也挺久了,也没正经脱过壳,看过相关脱壳教程,知道脱壳流程但没有实践过,最近几天突然想研究一下脱壳了,通过写插件来脱vmp3.2的壳
首先就是查找oep,网上也有很多找oep的方法,比如什么esp定律,二次内存断点,断特定api啥的,在这里我都没有使用,而是利用了vmp的特点,先进性加壳,加壳选项如下,虚拟化检测选不选无所谓,因为脱壳之后也没有意义了,这是在真正oep
之前做的事情(已经进行过测试,加了虚拟化检测脱壳之后程序可以正常在虚拟机运行),


bc97054e-6c49-4b66-8c4b-01b2a67dd207.png (44.51 KB, 下载次数: )
下载附件
加壳选项
2025-7-8 14:45 上传

拿一个自己的程序进行加壳测试,便于根据vmp特点来找oep,在这里我以xp记事本程序作为测试,真正入口点是0x100739d,然后打开加了壳之后的程序,在0x100739d处设置硬断,然后f9让程序断下来


ac4dc257-e2df-407d-9b49-edd09786f7c9.png (437.87 KB, 下载次数: )
下载附件
加壳程序断在真正oep
2025-7-8 14:56 上传

可以看到此时[esp-4]的值恰好是oep,因此推断vmp最后是通过ret跳转到真正的oep,有了这个想法,我在esp-4处设置硬断,经过多次中断,发现这个[esp-4]里面虽然会有各种垃圾值,但是一定有真正oep,那么就可以通过插件来实现
代码如下
//断点回调函数
PLUG_EXPORT void CBBREAKPOINT(CBTYPE cbType, PLUG_CB_BREAKPOINT* info)
{
    //info->breakpoint->type == 2说明是硬件断点 根据flag判断是否处理
    //一般硬断不处理 只是为了查找oep处理特定的硬件断点
    if (static_cast[U](info->breakpoint->type) == 0x2 && flag == 1) {
        _plugin_logprintf("PLUG_CB_BREAKPOINT:%d,PLUG_CB_BREAKPOINT:%x\n", static_cast[U](info->breakpoint->type), info->breakpoint->addr);
        //从断点处即初始的esp-4的地址处读值 读取[esp-4]的值判断在不在OEP_CODE_SEG段 如果在 说明就是oep了
        duint oep = Script::Memory::ReadDword(info->breakpoint->addr);
        if (vminfo.codebase breakpoint->addr);
            DbgCmdExec("go");
        }
        else {
            DbgCmdExec("go");
        }
    }
    return;
}
就是每次在回调函数中读取[esp-4]的值,如果发现这个值在.text节区,那么就应该是oep了 进行插件菜单点击函数处理
void find_oep() {
    flag = 1;
    //获取当前esp,在esp-4处下硬件断点 通过断点回调查找oep
    unsigned int ESP = Script::Register::GetESP();
    Script::Debug::SetHardwareBreakpoint(ESP - 4, (Script::Debug::HardwareType)0);
    DbgCmdExec("go");
}
PLUG_EXPORT void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info) {
    switch (info->hEntry) {
    case MENU_FIND_OEP:
        find_oep();
        break;
    case MENU_IAT_SEARCH:
        // 实现iat search功能
        CreateThread(
            NULL,                   // 默认安全属性
            0,                      // 默认堆栈大小
            iat_search,         // 线程函数
            NULL,                // 传递的参数
            0,                      // 默认创建标志
            NULL              // 存储线程ID
        );
        break;
    case MENU_IAT_REBYILD:
        CreateThread(
            NULL,                   // 默认安全属性
            0,                      // 默认堆栈大小
            iatrebiuld,         // 线程函数
            NULL,                // 传递的参数
            0,                      // 默认创建标志
            NULL              // 存储线程ID
        );
        break;
    case MENU_TEST: {
        _plugin_logputs("yoyo");
        }
        break;
    }
}
在点击MENU_FIND_OEP菜单之后会调用find_oep函数,该函数很简单,就是获取程序当前esp的值,然后在esp-4下硬断,也利用了esp定律吧,在壳运行到oep的时候,堆栈是平衡的,在运行此功能的时候调试器要设置调试事件,在系统断点不会中断
只在入口下断,这样基本都能断在程序真正的oep处,该方法也适合vmp3.8(已进行测试)看效果


01b603ca-f31a-49ac-997c-b05754845186.png (60.91 KB, 下载次数: 0)
下载附件
find_oep函数运行效果
2025-7-8 18:17 上传



8337a8af-9807-4eb0-b0aa-a8cd3aec468f.png (73.13 KB, 下载次数: )
下载附件
设置调试事件
2025-7-8 15:13 上传

找到oep,那么接下来就该查找被加密的iat了,一般会有这几种情况,mov reg,[xxxxxxxx], call reg 这是一种call [xxxxxxxx]这是一种,还有就是jmp [xxxxxxxx]这种一般开了增量链接的程序常见,mov reg,[xxxxxxxx]这种的
除了mov eax,[xxxxxxxx]是五个字节,其他就是6个字节 call [xxxxxxxx]这种是ff 15是6字节,jmp [xxxxxxxx]是ff 25也是6字节 接下来看被加密的api函数和原始的区别


41ee5c32-ade5-480a-a923-fbe3c817779c.png (392.58 KB, 下载次数: 0)
下载附件
api函数加密前后区别
2025-7-8 18:20 上传

可以看到这种情况属于mov reg,[xxxxxxxx], call reg这种情况,被vmp加密之后变成了push reg,call xxxxxxxx,有时候会变成pop reg,call xxxxxxxx这种情况,反正就是保证6字节,不会影响到后面的代码,call xxxxxxxx后面这个地址
刚开始我以为都属于.vmp0节区,经过几次dump 运行报错调试之后,发现有一两个api call的节区是.text,经过我的观察,我发现只要是被加密的api函数,跟到call后面的地址发现第一个字节都是0x90,也就是nop指令


33c86ca2-7216-427f-9022-11263a3b463f.png (145.83 KB, 下载次数: 0)
下载附件
api1
2025-7-8 18:29 上传



f514fe0b-16a4-46f0-9a88-c8609b372fe0.png (136.04 KB, 下载次数: 0)
下载附件
api2
2025-7-8 18:29 上传

在这里例举两个,那么思路就有了,遍历.text节区,在这里我使用了zydis反汇编引擎,插件自带的反汇编引擎太垃圾了,后续跟不上我们的要求,代码如下
#define V_EAX 0
#define V_EBX 1
#define V_ECX 2
#define V_EDX 3
#define V_ESI 4
#define V_EDI 5
#define V_EBP 6
#define V_ESP 7
UINT8 GetRegisterIndex(ZydisDecodedOperand& op) {
    switch (op.reg.value)
    {
        case ZYDIS_REGISTER_EAX:
            return V_EAX;
        case ZYDIS_REGISTER_EBX:
            return V_EBX;
        case ZYDIS_REGISTER_ECX:
            return V_ECX;
        case ZYDIS_REGISTER_EDX:
            return V_EDX;
        case ZYDIS_REGISTER_ESI:
            return V_ESI;
        case ZYDIS_REGISTER_EDI:
            return V_EDI;
        case ZYDIS_REGISTER_EBP:
            return V_EBP;
        case ZYDIS_REGISTER_ESP:
            return V_ESP;
    default:
        return 0xff;
    }
}
UINT8 GetMemRegisterIndex(ZydisDecodedOperand& op) {
    switch (op.mem.base)
    {
    case ZYDIS_REGISTER_EAX:
        return V_EAX;
    case ZYDIS_REGISTER_EBX:
        return V_EBX;
    case ZYDIS_REGISTER_ECX:
        return V_ECX;
    case ZYDIS_REGISTER_EDX:
        return V_EDX;
    case ZYDIS_REGISTER_ESI:
        return V_ESI;
    case ZYDIS_REGISTER_EDI:
        return V_EDI;
    case ZYDIS_REGISTER_EBP:
        return V_EBP;
    case ZYDIS_REGISTER_ESP:
        return V_ESP;
    default:
        return 0xff;
    }
}
DWORD WINAPI iat_search(LPVOID lpParam) {
    unsigned char data[16] = {0};
    EMULATOR_CPU_CONTEXT cpu_context = { 0 };
    UINT8 code[16] = { 0 };
    char lable[MAX_LABEL_SIZE] = { 0 };
    ZyanStatus status;
    ZydisDecoder decoder;
    UINT32 address = 0;
    duint count = 0;
    iatinfo iat_info;
    memset(&iat_info, 0, sizeof(iatinfo));
    duint current = vminfo.codebase;
    int callCount = 0;
    reset_module_map();
    g_call_map.clear();
    ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32);
    // 初始化格式化器
    ZydisFormatter formatter;
    ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
    // 反汇编上下文
    ZydisDecodedInstruction instruction;
    ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
    while (current
解释call [0x1001234](举例地址)这种情况


796ea6f7-1191-4a63-918a-90e8c3b3b40a.png (203.36 KB, 下载次数: 0)
下载附件
0x1001240地址处内容
2025-7-8 19:37 上传

注意看0x1001240处的内容,发现这是个api函数地址啊,查看引用


43da471e-d6e5-4d03-aa98-26d38e7ce283.png (200.09 KB, 下载次数: 0)
下载附件
0x1001240引用
2025-7-8 19:40 上传

有这么多处地方引用了,要么是mov reg,[0x1001240], call reg ,要么就是直接call [0x1001240],那么我们直接dump放到其他机器肯定是不能运行的,因为函数地址不一样啊,所以这种情况要修复,将0x1001240修改成其他地址,即重建的iat地址
修复在后面,现在不提,这种情况我猜测是vmp没有把所有的导入表全部加密,留了几个在壳中事先解密出函数地址,回填到特定内存地址处
接下来看被加密的api函数 即call xxxxxxxx这种情况,基本逻辑如下(大致逻辑,并不准确):
push reg
xchg [esp],reg
mov reg,imm32
mov reg,[reg+imm32]
lea reg,[reg+imm32]
ret
其中还夹杂一些jmp跳转以及一些垃圾指令,大概逻辑就是这么一个样子,我的方法是模拟执行,模拟这几种相关指令,遇到ret指令停止模拟,此时读取模拟的esp栈顶值,这个值可能是api函数地址,也可能不是,
为啥可能不是(还有可能被hook,对资源保护之后,解密出来是vm虚拟机,在这里不考虑这种情况),看下面
0x10001233                                                push edi
0x10001234        mov edi,[0x1005678]                        call xxxxxxxx        此时模拟的esp栈顶值就是0x1001239而不是api函数地址 真正的api函数地址已经解密到edi寄存器中
                                        ------------>
0x10001239        call edi                                call edi
看模拟执行的代码,当然写的比较粗糙,只模拟重要指令,最终得到模拟栈顶esp存储的值
typedef struct _EMULATOR_CPU_CONTEXT {
    DWORD reg[8];
    DWORD eip; // 指令指针
    //DWORD eflags;
    // 模拟内存块 (包含代码、数据、栈)
    //BYTE* mem_base;      // 分配的模拟内存起始地址
    //SIZE_T mem_size;      // 分配的模拟内存大小
} EMULATOR_CPU_CONTEXT,*PEMULATOR_CPU_CONTEXT;
UINT32 Emulator(UINT32 emulataddress, EMULATOR_CPU_CONTEXT& cpu_context, iatinfo& iat_info, vmpinfo& vminfo) {
    UINT32 emulatstack[64] = {0}; //模拟所需堆栈 模拟指令较少 因此栈不需要很大
    UINT8 code[16] = {0};
    UINT8 JmpFlag =  0 ;
    char buffer[256] = { 0 };
    UINT32 imm32 = 0;
    ZyanStatus status;
    UINT32 initaddress = emulataddress;
    cpu_context.reg[V_ESP] = (UINT32)&emulatstack[32];
    UINT32 init_vesp = cpu_context.reg[V_ESP];
    cpu_context.eip = emulataddress;
    // 初始化反汇编器(x86模式)
    ZydisDecoder decoder;
    ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32);
    // 初始化格式化器
    ZydisFormatter formatter;
    ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
    // 反汇编上下文
    ZydisDecodedInstruction instruction;
    ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
    while (1) {
        if (DbgMemRead(emulataddress, code, sizeof(code))) {
            status = ZydisDecoderDecodeFull(
                &decoder,
                code,
                sizeof(code),
                &instruction,
                operands
            );
            if (ZYAN_SUCCESS(status)) {
                ZydisFormatterFormatInstruction(&formatter, &instruction, operands, instruction.operand_count_visible, \
                    buffer, sizeof(buffer), ZYDIS_RUNTIME_ADDRESS_NONE, ZYAN_NULL);
                switch (instruction.mnemonic)
                {
                case ZYDIS_MNEMONIC_CALL: { //模拟call xxxxxxxx指令
                    if (operands[0].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
                        cpu_context.reg[V_ESP] -= 4; //esp-4
                        *(UINT32*)cpu_context.reg[V_ESP] = emulataddress + instruction.length; //写入返回地址
                        cpu_context.eip += operands[0].imm.value.s + instruction.length; //修正eip
                        emulataddress = cpu_context.eip; //修正反汇编地址
                    }
                    break;
                }
                case ZYDIS_MNEMONIC_JMP: {
                    cpu_context.eip = emulataddress + operands[0].imm.value.s + instruction.length;//模拟jmp指令 修正eip
                    emulataddress = cpu_context.eip;
                    break;
                }
                case ZYDIS_MNEMONIC_PUSH: {
                    cpu_context.reg[V_ESP] -= 4;
                    cpu_context.eip += instruction.length;
                    emulataddress += instruction.length;
                    if (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) { //push reg
                        UINT8 regindex = GetRegisterIndex(operands[0]);
                        *(UINT32*)cpu_context.reg[V_ESP] = cpu_context.reg[regindex];
                    }
                    else { //push imm32 好像有点多次一举 模拟执行获取api函数地址的过程似乎用不到
                        *(UINT32*)cpu_context.reg[V_ESP] = operands[0].imm.value.s;
                    }
                    break;                  
                }
                case ZYDIS_MNEMONIC_POP: {
                    if (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) { //pop reg
                        UINT8 regindex = GetRegisterIndex(operands[0]);
                        cpu_context.reg[regindex]  = *(UINT32*)cpu_context.reg[V_ESP];
                    }
                    cpu_context.reg[V_ESP] += 4;
                    cpu_context.eip += instruction.length;
                    emulataddress += instruction.length;
                    break;
                }
                case ZYDIS_MNEMONIC_MOV: {
                    if (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) {//左操作数是reg
                        UINT8 indexleft = GetRegisterIndex(operands[0]);
                        if (indexleft == 0xff) {
                            cpu_context.eip += instruction.length;
                            emulataddress += instruction.length;
                            break;
                        }
                        if (operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) { //右操作数也是reg的情况
                            UINT8 indexright = GetRegisterIndex(operands[1]);
                            cpu_context.reg[indexleft] = cpu_context.reg[indexright]; //完成赋值模拟操作
                        }
                        else if(operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE){//mov reg,imm32
                            cpu_context.reg[indexleft] = operands[1].imm.value.s;
                        }
                        else if (operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY) {//mov reg,[reg+imm32]
                            UINT8 baseregister = GetMemRegisterIndex(operands[1]);
                            if (baseregister == V_ESP) {
                                cpu_context.reg[indexleft] = *(UINT32*)(cpu_context.reg[baseregister] + operands[1].mem.disp.value);
                            }
                            //读取出reg+imm32的值 然后作为地址去读取内存
                            else if (DbgMemRead(cpu_context.reg[baseregister] + operands[1].mem.disp.value, &imm32, sizeof(imm32))) {
                                cpu_context.reg[indexleft] = imm32;
                            }                       
                        }
                    }
                    else {
                        UINT8 membaseregister = GetMemRegisterIndex(operands[0]);
                        UINT8 indexright = GetRegisterIndex(operands[1]);
                        if (membaseregister == V_ESP) {
                            *(UINT32*)(cpu_context.reg[membaseregister] + operands[0].mem.disp.value) = cpu_context.reg[indexright];
                        }
                    }
                    cpu_context.eip += instruction.length;
                    emulataddress += instruction.length;
                    break;
                }
                case ZYDIS_MNEMONIC_LEA: {
                    UINT8 indexleft = GetRegisterIndex(operands[0]);
                    UINT8 baseregister = GetMemRegisterIndex(operands[1]);
                    cpu_context.reg[indexleft] = cpu_context.reg[baseregister] + operands[1].mem.disp.value;
                    cpu_context.eip += instruction.length;
                    emulataddress += instruction.length;
                    break;
                }
                case ZYDIS_MNEMONIC_XCHG: {
                    if (operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY) { //xchg [reg(esp)+imm32],reg 这种情况 \
                        极端情况暂不考虑 插件用不到,需要再加
                        UINT8 memregindex = GetMemRegisterIndex(operands[0]);
                        UINT8 regindex = GetRegisterIndex(operands[1]);
                        UINT32 temp = *(UINT32*)((UINT32)cpu_context.reg[memregindex] + operands[0].mem.disp.value);
                        *(UINT32*)((UINT32)cpu_context.reg[memregindex] + operands[0].mem.disp.value) = cpu_context.reg[regindex];
                        cpu_context.reg[regindex] = temp;
                    }
                    else if (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY) {//xchg reg(esp),[reg+imm32]
                        UINT8 memregindex = GetMemRegisterIndex(operands[1]);
                        UINT8 regindex = GetRegisterIndex(operands[0]);
                        UINT32 temp = cpu_context.reg[regindex];
                        cpu_context.reg[regindex] = *(UINT32*)((UINT32)cpu_context.reg[memregindex] + operands[1].mem.disp.value);
                        *(UINT32*)((UINT32)cpu_context.reg[memregindex] + operands[1].mem.disp.value) = temp;
                    }
                    cpu_context.eip += instruction.length;
                    emulataddress += instruction.length;
                    break;
                }
                case ZYDIS_MNEMONIC_RET: {
                    iat_info.EmulatorReturn = *(UINT32*)cpu_context.reg[V_ESP];
                    if (DbgMemRead(emulataddress, &JmpFlag, sizeof(JmpFlag)) && JmpFlag == 0xc3) {
                        iat_info.IsJMP = 0;
                        if (iat_info.EmulatorReturn > vminfo.vmp0base + vminfo.vmp0size) { //call [apiaddress]
                            iat_info.RegFlag = 0;
                            if (*(UINT32*)(cpu_context.reg[V_ESP] + 4) - 6 == initaddress) {
                                iat_info.CallFlag = 0;
                            }
                            else {
                                iat_info.CallFlag = 1;
                            }
                        }
                        else if (iat_info.EmulatorReturn
类似代码虚拟化吧 虚拟一个模拟所需要的堆栈和寄存器环境 主要模拟push reg,pop reg,xchg [reg],reg,mov reg,imm32,mov reg,[reg+imm32],
lea reg,[reg+imm32],jmp xxxxxxxx,ret等必要指令,先说一下模拟执行的基本原理吧,比如我要模拟mov eax,ebx这条指令,我要读取虚拟v_ebx的值,
然后赋值给v_eax,cpu_context.reg[v_eax] = cpu_context.reg[v_ebx],大概就是这样
由于模拟指令少而且比较简单,所以只需要一个堆栈和一些寄存器即可,检测到ret指令停止模拟,读取虚拟栈[esp]
和[esp+4]值,因为被加密的api分为两种情况,一种是push/pop reg, call xxxxxxxx, 另一种则是call xxxxxxxx,ret 这种情况所以我们要区分到底是
哪一种情况,不同情况还原是不一样的,push/pop reg, call xxxxxxxx这种情况要地址-1,就是要覆盖前面的push/pop reg的字节 如何区分这两种情况,之前我的
方法是读取call xxxxxxxx所在地址+5的值,如果是c3或者cb,说明是call xxxxxxxx,ret这种情况,但是我忽略了push/pop reg, call xxxxxxxx,ret这种情况,
只能想其他方法,后来观察得到如果是call xxxxxxxx,ret并且模拟执行后的地址是api函数,那么我读取虚拟栈[esp+4]的值 看图


133d7be3-1c22-4935-b8b0-ccef58eab5e5.png (313.9 KB, 下载次数: 0)
下载附件
模拟执行结果
2025-7-8 20:39 上传

[esp+4]的值就是返回地址啊,[esp]是api函数地址 就跟我们正常调用一样,类似下面的情况,压入参数,call api会把返回地址压栈,那么执行ret指令之后,栈就变成了下面所示
的样子,那么读取[esp+4]的值减去调用处地址的值,如果这个值等于6,说明就是call xxxxxxxx,ret这种情况,因为这两条指令刚好6个字节,注意,综上所述针对call [xxxxxxxx]
这种加密方式,如果是mov reg,[xxxxxxxx], call reg这种情况,那么应该读取[esp]的值减去调用处地址的值等于6就是call xxxxxxxx,ret这种情况,这是我目前的想法,没有
其他的方法了
返回地址
arg1
arg2
那么如何辨别是jmp [xxxxxxxx]这种情况呢,这个比较简单,这种情况最后都是ret 4,读取是不是c2字节就可以判断 问题又来了jmp [xxxxxxxxx]也会被加密成call xxxxxxxx,ret和
push/pop reg, call xxxxxxxx 这种情况,这种情况读取[esp+4]的值减去调用处地址的值不再适用,又得想办法了,经过观察模拟执行后esp减少的值不一样,一个-8,一个-4,多说无益,看图


589dd641-8c80-4e1c-a136-295602f66135.png (355.65 KB, 下载次数: 0)
下载附件
模拟前esp的值
2025-7-8 20:53 上传

这是jmp这种情况,模拟前esp=0xdff78


6ecaf30c-964e-46c8-a86b-9b139d995f75.png (196.38 KB, 下载次数: 0)
下载附件
模拟后的esp值
2025-7-8 20:55 上传

模拟后变成了0xdff70,同时这里也能看到是ret 4
接下来看这种push reg,call xxxxxxxx这种情况 模拟前esp也是0xdff78 模拟都是从call开始模拟的,call xxxxxxxx前面不管


025ef950-b716-44bd-a55f-68d1075f124f.png (612.18 KB, 下载次数: 0)
下载附件
模拟前
2025-7-8 20:58 上传



6a974be3-3c5d-4036-af32-04c038b21b2b.png (216.14 KB, 下载次数: 0)
下载附件
模拟后
2025-7-8 21:01 上传

模拟后变成了0xdff74,这里是减少了4,我是这样判断的,除此之外没有想到其他的 最终封装成static std::map[U] g_iat_map
typedef struct _iatinfo {
        //UINT32 currentaddress;//当前反汇编地址
        
        UINT32 EmulatorReturn;
        UINT8 RegIndex;
        UINT8 RegFlag; //如果这个是0,那么RegIndex无效 EmulatorReturn是api函数地址
        UINT8 IsJMP;
        UINT8 CallFlag; //如果是0,说明是call ret这种类型 否则push/pop call这种 esp-8
}iatinfo;
最后通过这个map来修复:map[U] UINT32是call xxxxxxxx的当前地址
然后就是mov reg,[xxxxxxxx],call reg这种情况,如何判断当前reg是哪个,简单,反汇编当前地址,一直遇到call reg32暂停,读取reg32就可以知道 也就是iatinfo.RegIndx
代码如下:
//由于有的api函数是通过mov reg,xxxxxxxx, call reg实现 修复的时候需要知道具体是哪个reg
UINT32 GetCallNextRegIndex(UINT32 disasmaddress) {
    UINT8 code[16] = { 0 };
    // 初始化反汇编器(x86模式)
    ZydisDecoder decoder;
    ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32);
    // 初始化格式化器
    ZydisFormatter formatter;
    ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
    // 反汇编上下文
    ZydisDecodedInstruction instruction;
    ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
    ZyanStatus status;
    while (1) {
        if (DbgMemRead(disasmaddress, code, sizeof(code))) {
            status = ZydisDecoderDecodeFull(
                &decoder,
                code,
                sizeof(code),
                &instruction,
                operands
            );
            if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZYDIS_MNEMONIC_CALL && operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) {
                return GetRegisterIndex(operands[0]);
            }
            disasmaddress += instruction.length;
        }
    }
}
整个查找过程大致结束,然后我重建了一个iat表,直接在vmp0节区重建


f73d394a-e78b-4f7f-8fa2-941b125f11ce.png (347.96 KB, 下载次数: 0)
下载附件
重建iat
2025-7-8 21:10 上传

然后根据g_iat_map和新iat地址表进行修复
代码如下:
void iat_rebuild(std::map[U]& iat_map,UINT32 iat_address_table) {
    UINT32 apiaddress = 0;
    UINT32 indirectaddress = 0;
    UINT8 callcode[6] = { 0xff,0x15,0x00,0x00,0x00,0x00 };
    UINT8 jmp_mem[6] = { 0xff,0x25,0x00,0x00,0x00,0x00 };
    UINT8 mov_eax[5] = { 0xA1,0x00,0x00,0x00,0x00 };
    UINT8 mov_reg[6] = { 0x8B,0x00,0x00,0x00,0x00,0x00 };
    for (auto iatmap = iat_map.begin(); iatmap != iat_map.end(); ++iatmap) {
        unsigned int i = 0;
        //_plugin_logprintf("api address:%x\n", iatmap->second.EmulatorReturn);
        while (1) {
            if (DbgMemRead(iat_address_table+i*4, &apiaddress, sizeof(UINT32))) {
                if (apiaddress == iatmap->second.EmulatorReturn) {
                    indirectaddress = iat_address_table + i * 4;
                    break;
                }
                i++;
            }
        }
        if (iatmap->second.RegFlag) {
            switch (iatmap->second.RegIndex)
            {
            case V_EAX: {
                memcpy(&mov_eax[1], &indirectaddress, 4);
                break;
            }
            case V_EBX: {
                memset(&mov_reg[1], 0x1d, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            case V_ECX: {
                memset(&mov_reg[1], 0x0d, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            case V_EDX: {
                memset(&mov_reg[1], 0x15, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            case V_ESI: {
                memset(&mov_reg[1], 0x35, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            case V_EDI: {
                memset(&mov_reg[1], 0x3d, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            case V_EBP: {
                memset(&mov_reg[1], 0x2d, 1);
                memcpy(&mov_reg[2], &indirectaddress, 4);
                break;
            }
            default:
                break;
            }
        }
        else {
            memcpy(&callcode[2], &indirectaddress, 4);
        }
        if (iatmap->second.CallFlag == 0) { //call xxxxxxxx,ret
            if (iatmap->second.IsJMP) {
                memcpy(&jmp_mem[2], &indirectaddress, 4);
                DbgMemWrite(iatmap->first, jmp_mem, sizeof(jmp_mem));
            }
            else if (iatmap->second.RegFlag) {
                if (iatmap->second.RegIndex == V_EAX) {
                    DbgMemWrite(iatmap->first, mov_eax, sizeof(mov_eax));
                }
                else {
                    DbgMemWrite(iatmap->first, mov_reg, sizeof(mov_reg));
                }
            }
            else {
                DbgMemWrite(iatmap->first, callcode, sizeof(callcode));
            }
        }
        else {
            if (iatmap->second.IsJMP) {
                memcpy(&jmp_mem[2], &indirectaddress, 4);
                DbgMemWrite(iatmap->first-1, jmp_mem, sizeof(jmp_mem));
            }
            else if (iatmap->second.RegFlag) {
                if (iatmap->second.RegIndex == V_EAX) {
                    DbgMemWrite(iatmap->first-1, mov_eax, sizeof(mov_eax));
                }
                else {
                    DbgMemWrite(iatmap->first-1, mov_reg, sizeof(mov_reg));
                }
            }
            else {
                DbgMemWrite(iatmap->first-1, callcode, sizeof(callcode));
            }
        }
    }
    _plugin_logprintf("iat rebuild successful\n");
}
最终效果如下


c1391385-28a9-4505-9543-e44583cfbcc0.png (356.9 KB, 下载次数: 0)
下载附件
修复前
2025-7-8 21:14 上传



7e0809c8-13a3-48a9-91ea-718c7b27fe5d.png (352.52 KB, 下载次数: 0)
下载附件
修复后
2025-7-8 21:15 上传

此时可以看到0x10073ac处已经被修为mov edi,[0x0100B114],然后将程序dump下来,用scylla重建导入表,不要用插件自带的scylla,这个有bug(被坑过)用scylla.exe这个


6c817118-e690-41fb-b0a7-ff45b8569c79.png (583.8 KB, 下载次数: 0)
下载附件
scylla修复
2025-7-8 21:18 上传

可以看到导入表已经被扫描到,然后fix_dump(scylla新增节对iat进行导入表的重建)就好,然后我拿了三个软件进行测试,结果如下


475023d6-444e-4a66-9ba5-c4d83cf02fee.png (751.28 KB, 下载次数: 0)
下载附件
最终效果
2025-7-8 21:26 上传

可以看到三个程序都可以在虚拟机中正常运行,至此脱壳之旅到此结束,最后附上插件,有需要的朋友可以下载测试,顺便还有一个事情需要注意,为了方便这几个程序都是固定基址,如果是随机基址,
那么插件仍要继续修复重定位?(我的想法,没有实践)

这种情况, 下载次数

Atendu1992   

谢谢分享
BrutusScipio   

手动脱壳吗 能不能添加更多特征
jodieo   

学习一下
理想的海洋   

学习一下   脱强壳vmp   高手
子哲Cai   

学习一下,你好
vihp   

学习一下   脱强壳vmp   高手
Wakessr   

学习一下也好
fjzpchm   

谢谢,学习了,受益非浅!
您需要登录后才可以回帖 登录 | 立即注册

返回顶部