2.13 PE结构:实现PE代码段加密
代码加密功能的实现原理,首先通过创建一个新的.hack
区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的ShellCode
汇编指令集,并将程序入口地址修正为ShellCode
地址位置处,当解密功能被运行后则可释放加密的.text
节,此时再通过一个JMP
指令跳转到原始OEP
位置,则可继续执行解密后的区段。
如下是一段异或解密功能实现,用于实现循环0x88
异或解密代码功能;
00408001 MOV EAX, main.00401000 (代码段首地址)
00408006 XOR BYTE PTR DS : [EAX], 88 (与0x88异或)
00408009 INC EAX
0040800A CMP EAX, main.00404B46 (代码段结束位置)
0040800F JNZ SHORT main.00408006 (写入原始OEP)
00408011 POPAD
00408012 MOV EAX, main.00401041 (写入新OEP)
00408017 JMP EAX
有了上述加密流程,则下一步就是对内部的变量进行填充,我们可以提取出这些汇编指令的机器码并存储到Code[]
数组内,通过对数组中的特定位置进行替换来完善跳转功能,此处我们需要提取如下几个位置的特征字段;
- 00408001 数组下标第
2
位替换为ImageBase + pSection->VirtualAddress
- 0040800A 数组下标第
11
位替换为ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize
- 0040800F 数组下标第
19
位替换为ImageBase + BaseRVA
- 00408012 原始
OEP
位置替换为pSection->VirtualAddress
根据上述流程我们可以编写一个AddPacking
函数,该函数通过传入一个待加密程序路径,则可将一段解密代码Code[]
写入到程序节表中的最后一个节.hack
所在内存空间,并动态修正当前入口地址为.hack
节的首地址,最终执行解密后自动跳转回原始OEP位置执行功能,如下代码所示;
// 对文件执行加壳操作
void AddPacking(LPSTR szFileName)
{
// 打开文件
HANDLE hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// 创建文件映射
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);
// 找到PE文件头
PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
DWORD ImageBase = NtHdr->OptionalHeader.ImageBase;
DWORD BaseRVA = NtHdr->OptionalHeader.AddressOfEntryPoint;
PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);
// 首先得到最后一个节的指针,然后找到里面的虚拟偏移值,填入到程序OEP位置即可。
DWORD SectionNum = FileHdr->NumberOfSections;
char Code[] =
{
"\x60"
"\xb8\x00\x00\x00\x00"
"\x80\x30\x88"
"\x40"
"\x3d\xff\x4f\x40\x00"
"\x75\xf5"
"\x61"
"\xb8\x00\x00\x00\x00"
"\xff\xe0"
};
DWORD dwWrite = 0;
// 写入代码节开始位置
*(DWORD *)&Code[2] = ImageBase + pSection->VirtualAddress;
printf("[+] 写入代码节开始位置: 0x%08X \n", ImageBase + pSection->VirtualAddress);
// 写入代码节终止位置
*(DWORD *)&Code[11] = ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize;
printf("[+] 写入代码节结束位置:0x%08X \n", ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize);
// 写入原来的的OEP位置
*(DWORD *)&Code[19] = ImageBase + BaseRVA;
printf("[+] 写入原始OEP 0x%08X \n", ImageBase + BaseRVA);
// 拿到最后一个节的文件偏移
pSection = pSection + (SectionNum - 1);
printf("[+] 得到最后一个节的实际地址: 0x%08X \n", pSection->PointerToRawData);
// 设置指针到最后一个节文件偏移位置
SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
WriteFile(hFile, (LPVOID)Code, sizeof(Code), &dwWrite, NULL);
FlushViewOfFile(lpBase, 0);
// 修正当前入口点地址
printf("[+] 修正入口点地址为: 0x%08X \n", pSection->VirtualAddress);
*(DWORD *)&NtHdr->OptionalHeader.AddressOfEntryPoint = pSection->VirtualAddress;
UnmapViewOfFile(lpBase);
}
读者可自行运行上述代码片段,传入d://lyshark.exe
对该程序中的.text
节进行解密,运行后读者可打开x64dbg
调试器,观察入口地址处的变化,此时入口地址已经跳转到.hack
节内,此节中的汇编指令则用于对.text
代码节进行解密操作,当解密结束后则会跳转到原始地址0x45C865
位置处,如下图所示;
当加密功能写入后,则接下来就可以对.text
代码节进行加密了,加密的实现也非常容易,如下函数,通过定位到第一个节,并通过ReadFile
函数将这个节读入内存,通过循环对这个区域进行加密,最后调用WriteFile
函数再将加密后的数据回写到代码节,此时加密功能就实现了,如下所示;
// 将特定的节进行加密,此处只加密Text节
void EncrySection(LPSTR szFileName, DWORD Key)
{
// 打开文件
HANDLE hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// 创建文件映射
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);
// 定位PE文件节
PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);
printf("[-] 节虚拟地址: 0x%08X 虚拟大小: 0x%08X\n", pSection->VirtualAddress, pSection->Misc.VirtualSize);
printf("[-] 读入FOA基地址: 0x%08X 节表长度: 0x%08X \n", pSection->PointerToRawData, pSection->SizeOfRawData);
printf("[-] 已对 %s 节 --> XOR加密/解密 --> XOR密钥: %d \n\n", pSection->Name, Key);
// 分配内存空间
DWORD dwRead = 0;
PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);
SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
memset(pByte, 0, pSection->SizeOfRawData);
ReadFile(hFile, pByte, pSection->SizeOfRawData, &dwRead, NULL);
// 对代码节加密
for (int x = 0; x < pSection->SizeOfRawData; x++)
{
pByte[x] ^= Key;
}
// 写出加密后的数据
SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);
pSection->Characteristics = 0xE0000020;
free(pByte);
FlushViewOfFile(lpBase, 0);
UnmapViewOfFile(lpBase);
}
读者通过AddPacking
函数对文件加壳后,接着就可以调用EncrySection
函数对目标程序进行异或处理,此处分别传入d://lyshark.exe
路径,以及一个加密密钥0x88
,等待加密结束即可,此时运行程序即可实现对代码段的解密运行,这样也就起到了保护了代码段的作用,如下是解密之前的代码段;
当解密后,代码段将会被展开,并输出如下图所示的样子,此时程序即可被正确执行;
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/5912e71.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 点赞
- 收藏
- 关注作者
评论(0)