实例一:一个简单的入口跳转
使用一个简单的exe文件作为示例,使用010Editer打开
Step1:修改NT头中的NumberOfSections字段加一

重新加载文件就能看到新增的section


Name:字段名
PhysicalAddress
VirtualSize:与PA一致,为区段字节数(此处200h)
VirtualAddress:上一seciton的VirtualAddress加上内存对齐的大小(此处1000h)
SizeOfRawData:文件对其后的大小(此处200h)
PointerToRawData:上一个区段PointerToRawData + SizeOfRawData
设置完成如下:

Step3:设置section属性

这里设置为了可执行可读可写
Step4:添加数据

Step5:修改SizeofImage
SizeofImage =( 最后一个section)VirtualAddress + SizeOfRawData
Step6:去除随机基址参数,避免入口点变化

40 80 -> 00 80
现在字段的修改就告一段落了
Step7:LordPE,修改程序入口点为新区段

原本的EntryPoint是0x00001005,记下

EntryPoint改为新区段的Voffset:0x00008000
Step8:
X64dbg打开修改后的PE文件调试:

可以看到EntryPoint已经在新section了,全是00
现在的目标是要让程序跳转回正确入口点,那么直接修改

OK,打上补丁,重新载入


跳转正确入口点

运行成功
总结以下,实现程序入口点跳转如下步骤:
- 构建/寻找一个位置存放入口跳转代码,这里采用的是新建一个自定义区段
- 重新设置程序入口点为添加代码处(这个地方理论可以插入任意多的代码,我们可以在原程序执行前做相当多的工作)
- 保证壳代码执行完后顺利跳转到原OEP处,为了保证寄存器值常常使用pushad和popad指令
实例二:简单加密壳
对代码节进行加密处理,添加壳代码使程序完成自解密->正常运行的流程
使用C++编写了一个简单的加密脚本,对代码节异或加密
注意:这里是根据OEP定位的代码节,若需要加密其他区段,应该采用的方法是
1 2 3
| if (!strcmp((char*)pSection->Name,".text")){ }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include <Windows.h> #include <iostream> using namespace std; char *pFileSrc = NULL; int iTextVirtualAddr = 0; int iTextVirtualLen = 0;
int main(){ char szFileName[] = { "G:\\ReverseTest\\section.exe" }; HANDLE hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); DWORD dwFileSize = GetFileSize(hFile, NULL); pFileSrc = new char[dwFileSize]; ReadFile(hFile, pFileSrc, dwFileSize, NULL, NULL); CloseHandle(hFile);
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)pFileSrc; PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); DWORD dwSectionCount = pNtHeader->FileHeader.NumberOfSections; PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)(&pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader); for (int i = 0; i < dwSectionCount; i++){ if (pNtHeader->OptionalHeader.AddressOfEntryPoint >= pSection->VirtualAddress && pNtHeader->OptionalHeader.AddressOfEntryPoint < (pSection->VirtualAddress + pSection->Misc.VirtualSize)){ cout << (int)(pFileSrc + pSection->PointerToRawData) << endl; for (int iCount = 0; iCount < pSection->Misc.VirtualSize; iCount++){ *(char*)(pFileSrc + pSection->PointerToRawData + iCount) ^= 0xF2; } pSection->Characteristics |= 0x80000000;
cout << (int)(pFileSrc + pSection->PointerToRawData + pSection->Misc.VirtualSize-1) << endl; break; } pSection++; }
HANDLE hNewFile = CreateFileA("G:\\ReverseTest\\section-Encrypt.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL); LPDWORD iNum = NULL; WriteFile(hNewFile, pFileSrc, dwFileSize, iNum, NULL); CloseHandle(hNewFile); system("pause"); return 0; }
|
使用编辑器或调试器打开,代码节已经完全加密了
那么我们现在需要考虑如何解密
大概流程就是:
- 确定被加密的地址范围
- 确定单次解密操作的内存大小
- 设计一个循环代码块,对范围内的数据依次解密
这里继续上个实例的方式,将代码添加到新的section:

x64dbg打上补丁,调试运行程序

成功
Hello World!
参考资料:
简单加壳程序编写以及加壳思路的分享 - 『编程语言区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
[原创]C++写壳详解之基础篇-加壳脱壳-看雪论坛-安全社区|安全招聘|bbs.pediy.com
浅谈代码段加密原理(防止静态分析) - __阿阿阿怪 - 博客园 (cnblogs.com)