编写VMP爆破插件 (上)

使用工具:VS 2022  Ollydbg SDK   Ollydbg sdk插件文档   Ollydbg   Unicorn  Capstone之前发过一个JCC爆破的插件,这里说一下开发的过程,最后会把源码开源出来由于帖子长度原因,本教程分为上下两期,上期为JCC爆破的实现(由于长度原因,只写到寄存器部分没写完),中期为对 JCC爆破插件  的完整代码(整个项目解决方案),下期为对IAT还原的实现(理论通杀全网iat抽取,哪怕虚拟)

第一步,需要去下载OD的SDK
这里用了寞叶修复过后的SDK,未修复前的SDK有bug
这个sdk在最后会有上传

好,第一步就来了
我们需要把所有导出函数都写好

ODBG_Plugindata  插件名
ODBG_Plugininit    插件初始化,可以加上od版本判断
ODBG_Pluginmenu显示菜单项   (也就是我们右键和在上面插件选项的那些东西)
PM_MAIN         主窗口
PM_DISASM     反汇编窗口
PM_CPUDUMP 内存数据窗口
1
2
3
4
5
6
7
8
if (origin == PM_MAIN)
{
        strcpy_s(data, 4096, "0&关于");
}
if (origin == PM_DISASM)
{
        strcpy_s(data, 4096, "JCC{0&第一个,1&第二个,2&关于,3&使用说明}");
}


ODBG_Pluginaction  点击执行函数,也就是我们在菜单选择某个选项,在这里写对应的执行功能
ODBG_Pluginreset    可选回调参数
ODBG_Plugindestroy退出od时调用此函数
以上就是我们od sdk里面的几个函数用途了

现在我们把这些东西写好
下面是我写的,可以参考修改
extern "C" __declspec(dllexport) int ODBG_Plugindata(char* shortname)
{
        // h_exp_handler = AddVectoredExceptionHandler(0, ExceptionHandler);
        // if (h_exp_handler == NULL)
        // {
        //     _Addtolist(0, 1, "注册异常处理函数失败");
        // }
        const char* pluginName = "JCC 爆破插件";  插件Name
        strcpy_s(shortname, strlen(pluginName) + 1, pluginName);
        return PLUGIN_VERSION;
}


extern "C" __declspec(dllexport) int ODBG_Plugininit(int ollydbgversion, HWND hw, ulong * features)
{
        char msg[200] = {};
        sprintf_s(msg, "  编译时间:%s %s", __DATE__, __TIME__);   
        _Addtolist(0, 0, "JCC爆破插件 V:1.0");
        _Addtolist(0, -1, msg);
        if (ollydbgversion < PLUGIN_VERSION)   版本判断
        {
                MessageBoxA(hw, "本插件不支持当前版本OD!", "鹤の妙妙屋 V:1.0", MB_TOPMOST | MB_ICONERROR | MB_OK);    信息框
                return -1;
        }
        //g_hOllyDbg = hw;
        return 0;
}

extern "C" __declspec(dllexport) cdecl int  ODBG_Pluginmenu(int origin, char data[4096], VOID * item)
{
        if (origin == PM_MAIN)
        {
                strcpy_s(data, 4096, "0&关于");   主菜单的关于
        }
        if (origin == PM_DISASM)
        {
                strcpy_s(data, 4096, "JCC{0&不映射代码段,1&映射代码段,2&关于,3&使用说明}");     反汇编窗口的选项
        }
        return 1;
}


extern "C" __declspec(dllexport) cdecl void ODBG_Pluginaction(int origin, int action, VOID * item)
{
        //在主窗口点击
        if (origin == PM_MAIN)
        {
                if (action == 0)
                {
                        char msg[256];
                        sprintf_s(msg, "开发:鹤の妙妙屋  时间:2023年");
                        MessageBoxA(g_hOllyDbg, msg, "关于", MB_TOPMOST | MB_ICONINFORMATION | MB_OK);
                }
        }
        //在反汇编窗口点击
        if (origin == PM_DISASM)
        {
                if (action == 0)
                {
                        UnicornMoNi();    刚刚的第一个选项   不映射代码段
                }
                else if (action == 1)
                {
                        UnicornMoNi2();  刚刚的第二个选项   映射代码段       这里可以用线程,我这里没用,可以自己后面加
                }
                else if (action == 2)
                {
                        char msg[256];
                        sprintf_s(msg, "开发:吾爱汇编  时间:2023年");
                        MessageBoxA(g_hOllyDbg, msg, "关于", MB_TOPMOST | MB_ICONINFORMATION | MB_OK);
                }
                else if (action == 3)
                {
                        char msg[256];
                        sprintf_s(msg, "1.不映射代码段 不映射指定内存的数据,更好定位函数调用流程\n2.映射代码段 映射代码段,方便进行函数分析时跳出vm段执行代码的问题,这里采用retn退出虚拟机,可能会有错误的问题");
                        MessageBoxA(g_hOllyDbg, msg, "说明", MB_TOPMOST | MB_ICONINFORMATION | MB_OK);
                }
}







extern "C" __declspec(dllexport) cdecl void ODBG_Pluginreset()
{
            //本插件不操作此函数
}

extern "C" __declspec(dllexport) cdecl void ODBG_Plugindestroy()
{
           //本插件不操作此函数

}

上面差不多就是一些函数的代码了
然后下面到了Unicorn代码的实现了
如果你看过我以前发的帖子,可以知道我是采用手动dump内存的方式来映射内存模拟,寄存器也是手动输入的,这样效率十分低(主要是累人,之前分析个软件搞了几个小时)现在写成od插件的形式调用十分方便


在这里呢,我们以前的算法也是可用的
然后,第一步需要先导入capstone,unicorn   lib,头文件  这些
导入完成后可以开始代码的编写
定义一个新函数
1
2
3
int Unicorn(){
 
}





然后准备开始写代码,实战
首先在dllmain.cpp加上几个变量声明
uc_err err;    //UC执行返回值
uc_engine* uc;    //UC引擎
cs_insn* insn    //captone 引擎
然后第一步首先初始化unicorn,一定不要忘记(因为这个问题之前分析了一天)


        err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);                  //初始化引擎
        if (err != UC_ERR_OK) {        //如果不等于OK(成功)则进入判断
                _Addtolist(0, 0, "uc模拟器初始化错误 UCOPEN");           //Addtolist   在od日志输出内容     这里没有成功初始化判断错误                                                           
                return -1;
        }

下一步该弹出一个输入框了,在这里输入代码段的地址,

//
函数显示一个用于用户输入 8-, 16- 或 32-位 的任意 3 种格式的整数:十六进制、无符号十进制、有符号十进制,或者仅输入十六进制格式(如果设置了 DIA_HEXONLY 位)。可选的复选框"Entire block"(全局块)和
"Aligned search" (定位搜索)受位 DIA_ASKGLOBAL 和 DIA_ALIGNED 控制,并控制全局标识 globalsearch 和 alignedsearch。返回 0 表示成功,如果用户取消或有错误发生,返回 -1 表示失败,函数 Getlongxy 附加包含对话窗口在屏幕的 xy 坐标位置。


int Getlong(char *title,ulong *data,int datasize,char letter,int mode);
int Getlongxy(char *title,ulong *data,int datasize,char letter,int mode,int x,int y);


参数:


title ?对话框标题;


data ?指向32 位缓冲区,包含初始的整数值。函数返回后,该值是最后修改值,如果用户取消,该值是保留在缓冲区的最后值;


datasize ?整数字节大小(1, 2 或 4),注意:由 data 指向的缓冲区必须是 32 位(4字节)长,其不依赖于 datasize;


letter ?编辑控件缺省已输入的首字符,如果没有输入就为 0。这对已经用本函数让用户输入了相应字符时非常有用;


mode - DIA_xxx 位的组合值指定 Getlong 附加特征:


DIA_HEXONLY        隐藏十进制输入窗口
DIA_ASKGLOBAL        显示复选框 "Entire block"(全部块)来控制全局搜索标识。实际标识值可以通过调用 Plugingetvalue(VAL_GLOBALSEARCH) 来返回
DIA_ALIGNED        显示复选框 "Aligned search"(对齐搜索)来控制对齐搜索标识。实际标识值可以通过调用 Plugingetvalue(VAL_ALIGNEDSEARCH) 来返回
x ?绝对屏幕 X 坐标,像素,为对话框左下角位置,如果需要, 对话框会自动适应位置并保持可见;


y - 绝对屏幕 Y 坐标,像素,为对话框右下角位置。
//


我们采用getlong api 函数
还要定义一个变量来存地址
下面是代码示例

      t_memory* NeiCun;     定义od内存属性
      DWORD data = 0;    //编辑框数据存储变量
        DWORD AddressVMP;   //VMP地址
        if (_Getlong("内存地址", &data, 4, '0', DIA_HEXONLY) == 0)   //输入VMP区段头部地址
        {
                AddressVMP = data;
        }



               if (AddressVMP != 0  &&  AddressVMP !=NULL) {     编辑框返回的内容不为0   且  不为空    这里NULL一定要加,不然如果他点返回就会崩溃
                NeiCun = _Findmemory(AddressVMP);              获取内存地址对应的内存块属性    比如我输入一个401004   他属于401000  就返回401000这个内存块的数据,比如大小之类的
        }
        else {
                _Addtolist(0, 0, "输入地址错误");
                return 0;
        }

这里我们就得到了代码段的内存属性
然后开始哪一步了?
该内存映射了,不然uc怎么运行

关于这里的内存映射,我借助了寞叶的内存映射·-·
        t_table* memory_table = (t_table*)_Plugingetvalue(VAL_MEMORY);
        t_sorted memory_data = memory_table->data;
        for (DWORD i = 0; i < memory_data.n; i++)
        {
                t_memory* memory = (t_memory*)_Getsortedbyselection(&memory_data, i);
                BYTE* buf = new BYTE[memory->size];
                DWORD ret = _Readmemory(buf, memory->base, memory->size, MM_SILENT);
                if (memory->base != NeiCun->base) {                读取的内存块  不等于  代码段内存块    才进行映射操作
                        if (ret != memory->size)
                        {
                                _Addtolist(0, 1, "读取内存 0x%08x-0x%08x 失败 实际读取%08x", memory->base, memory->base + memory->size, ret);    //读取内存失败
                        }
                        if (!MapFromMemory(memory->base, memory->size, buf))            映射内存函数
                        {
                                _Addtolist(0, 1, "映射内存 0x%08x-0x%08x 失败", memory->base, memory->base + memory->size);
                        }
                        delete[] buf;
                        _Progress((i + 1) * 1000 / memory_data.n, "正在映射内存 0x%08x-0x%08x 进度", memory->base, memory->base + memory->size);
                }
                _Progress(0, 0);                //刷新od内存
                }

bool MapFromMemory(const DWORD& base, const DWORD& size, void* buf)
{
        err = uc_mem_map(uc, base, size, UC_PROT_ALL);        先创建一个新的内存,后面才能写代码进去
        if (err != UC_ERR_OK)
        {
                return false;
        }
        err = uc_mem_write(uc, base, buf, size);                    写入内存块数据进UC
        if (err != UC_ERR_OK)
        {
                return false;
        }
        return true;
}



内存映射也完成了,现在可以初始化capstone反汇编引擎了,你后面初始化也行,我就先初始化了
        if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle)) {                                 
                _Addtolist(0, 0, "CS初始化错误 CSOPEN");
                return -1;
        }
        cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);      打开DETAIL属性,打开效率会降低,不过我们分析vm需要这个属性



好,这里反汇编引擎也初始化完毕,下面可以开始初始化寄存器数据了
                t_thread* thread = _Findthread(_Getcputhreadid());       获取当前调试的属性
                const t_reg& treg = thread->reg;                       当前调试属性下的寄存器属性
                DWORD eax, ecx, edx, ebx, esp, ebp, esi, edi, eip, efl = 0;        定义八个变量存储寄存器数据
                eax = treg.r[REG_EAX];         
                ecx = treg.r[REG_ECX];
                edx = treg.r[REG_EDX];
                ebx = treg.r[REG_EBX];
                esp = treg.r[REG_ESP];
                ebp = treg.r[REG_EBP];
                esi = treg.r[REG_ESI];
                edi = treg.r[REG_EDI];
                eip = treg.ip;
                efl = treg.flags;

     //写入UC中的寄存器
        uc_reg_write(uc, UC_X86_REG_EAX, &eax);
        uc_reg_write(uc, UC_X86_REG_ECX, &ecx);
        uc_reg_write(uc, UC_X86_REG_EDX, &edx);
        uc_reg_write(uc, UC_X86_REG_EBX, &ebx);
        uc_reg_write(uc, UC_X86_REG_ESP, &esp);
        uc_reg_write(uc, UC_X86_REG_EBP, &ebp);
        uc_reg_write(uc, UC_X86_REG_ESI, &esi);
        uc_reg_write(uc, UC_X86_REG_EDI, &edi);
        uc_reg_write(uc, UC_X86_REG_EIP, &eip);
        uc_reg_write(uc, UC_X86_REG_EFLAGS, &efl);

 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学编程的闹钟

自愿打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值