C++写壳详细教程(下)

2. 处理stub.dll配置stub工程
将工程设置release版本,如果不想代码被优化,可以禁止优化。
大概流程如下:
① 将数据段,只读数据段和代码段进行合并
② 编写代码获取api的地址
③ 加入混淆指令,反调试
④ 解密/解压缩
⑤ 加密iat等等
之后会把存根文件stub.dll的.data,.rdata这2个区段合并到.text段并设置为可读可写可执行属性,需要前置代码
//把数据段融入代码段#pragma comment(linker,/merge:.data=.text)//把只读数据段融入代码段#pragma comment(linker,/merge:.rdata=.text)//设置代码段为可读可写可执行#pragma comment(linker,/section:.text,rwe)根据之前说的已经知道壳区段就是新添加的区段了,里面将保存移植过来的stub的.text段里的所有内容,称之为壳代码。
而使用壳代码的时候要注意,因为加完壳后,在壳代码中无法使用导入表,因此,需要自己动态获取需要使用的api函数的地址。
只要获取到loadlibraryexa和getprocaddress两个函数的地址,我们就可以根据loadlibraryexa来获取任意模块dll的基地址,再使用getprocaddress函数获取到任意api函数的地址了。
根据kernel32基址可获取到getprocaddress地址。
下面是我获取kernel32基址的内联汇编代码。
__asm{push esi;mov esi, fs:[0x30]; //得到peb地址mov esi, [esi + 0xc]; //指向peb_ldr_data结构的首地址mov esi, [esi + 0x1c];//一个双向链表的地址mov esi, [esi]; //得到第2个条目kernelbase的链表mov esi, [esi]; //得到第3个条目kernel32的链表(win10系统)mov esi, [esi + 0x8]; //kernel32.dll地址mov g_hkernel32, esi;pop esi;}然后是获取getprocaddress函数的汇编代码,可以使用c语言方式获取,但我觉得用汇编写,它就这样赤裸裸呈现,能更加清晰的了解找到一个函数地址的过程。
//获取getprocaddress函数地址void mygetfunaddress(){__asm{pushad;mov ebp, esp;sub esp, 0xc;mov edx, g_hkernel32;mov esi, [edx + 0x3c]; //nt头的rvalea esi, [esi + edx]; //nt头的vamov esi, [esi + 0x78]; //export的rvalea edi, [esi + edx]; //export的vamov esi, [edi + 0x1c]; //eat的rvalea esi, [esi + edx]; //eat的vamov[ebp - 0x4], esi; //保存eatmov esi, [edi + 0x20]; //ent的rvalea esi, [esi + edx]; //ent的vamov[ebp - 0x8], esi; //保存entmov esi, [edi + 0x24]; //eot的rvalea esi, [esi + edx]; //eot的vamov[ebp - 0xc], esi; //保存eotxor ecx, ecx;jmp _first;_zero:inc ecx;_first:mov esi, [ebp - 0x8]; //ent的vamov esi, [esi + ecx * 4]; //funname的rvalea esi, [esi + edx]; //funname的vacmp dword ptr[esi], 050746547h;// 47657450 726f6341 64647265 7373;jne _zero; // 上面的16进制是getprocaddress的asciicmp dword ptr[esi + 4], 041636f72h;jne _zero;cmp dword ptr[esi + 8], 065726464h;jne _zero;cmp word ptr[esi + 0ch], 07373h;jne _zero;xor ebx,ebxmov esi, [ebp - 0xc]; //eot的vamov bx, [esi + ecx * 2]; //得到序号mov esi, [ebp - 0x4]; //eat的vamov esi, [esi + ebx * 4]; //funaddr的rvalea eax, [esi + edx]; //funaddrmov mygetprocaddress, eax;add esp, 0xc;popad;}}然后再获取下messageboxw函数,弹出一个对话框,测试是否成功。
//运行函数void runfun(){myloadlibraryexa = (fuloadlibraryexa)mygetprocaddress(g_hkernel32, loadlibraryexa);g_huser32 = myloadlibraryexa(user32.dll, 0, 0);mymessageboxw = (fumessageboxw)mygetprocaddress(g_huser32, messageboxw);mymessageboxw(0, l大家好我是一个壳, l提示, 0);}它在运行原代码之前先运行了壳代码,测试成功。
四、代码段加密
我们在逆向破解的时候通常第一方法是找到关键字符串,关键代码等,他们都是存在于代码段的,那么只要把代码段进行加密,这种方式就不可行了。
先在加壳器中加密,这使用简单的亦或加密。
//加密代码段//1.获取代码段首地址char* ptartext = getsecheader(ptarbuff, .text)->pointertorawdata + ptarbuff;//2.获取代码段实际大小int nsize = getsecheader(ptarbuff, .text)->misc.virtualsize;for (int i = 0; i fileheader.numberofsections;auto psec = image_first_section(pnt);//找到代码区段for (size_t i = 0; i virtualaddress + (char*)g_hmodule;int nsize = psec->misc.virtualsize;dword old = 0;//解密代码段myvirtualprotect(ptartext, nsize, page_readwrite, &old);for (int i = 0; i datadirectory;pdata[9].size = 0;pdata[9].virtualaddress = 0;运行时,先在壳代码中进行解压缩,再解密,然后程序就能正常运行了。
到此一个简单的加密压缩壳就完成了,在这个过程中实际出现了很多bug,因为涉及到dll文件无法用vs调试, 所以使用od或者x64dbg进行调试,推荐使用x64dbg(x32dbg),这个软件一直在更新,而且字符串提示更友好,更方便快捷。od主要用于脱壳破解,逆向还是x64dbg更方便。
最后再说一下vs2017使用配置:
有2个工程文件 一个是加壳器,一个是sutb。
加壳器使用x32debug编译
sutb使用x32release编译
找到工程所在文件夹,新建一个bin目录,把这两个工程属性中的输出目录改为bin,这样操作起来方便一些,不改也行,但是加载stub时路径就要填写正确才行。
一个壳的基本框架就搭建完成了,而加壳主要是为了防止被别人破解,所以接下来就可以执行加密操作了,下一次再说说iat加密,hash加密,动态解密,反调试等技术吧。

三菱MRJ飞机已取得了型号合格证并将在美国进行试飞
小米6、小米5X区别对比评测:配置必须是小米6,颜值不一定是小米6
I2C总线数字式温湿度传感器SHT11及其在单片机系统的应用
加工条件不明确,选刀难免有偏差
用C语言完美实现2048数字方块游戏
C++写壳详细教程(下)
数码摄像头对焦方式/范围
盘点那些正在飞速发展的智能家居产品
是德科技的光纤接口如何应对5G测试
跨网段网络耦合器
小牛电动上市李一男身影难寻 尽管身价或超20亿
小米5C错过双11将在双12亮相,采用自主松果八核CPU支持NFC
软开关型脉冲MIG焊接电源系统原理设计
行业大咖如何看待智能车生态?
光纤熔接机的保养与清洁技巧
工信部截至7月底已核发5G设备进网批文7张进网标志37万个
小米手表Color全新体验,或将代替智能手环
对基于SoC系统设计的探查
前“锁”未有---信驰达智能门锁创新解决方案
如何配置STM32低功耗时的引脚