代码加密功能的实现原理,首先通过创建一个新的.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,等待加密结束即可,此时运行程序即可实现对代码段的解密运行,这样也就起到了保护了代码段的作用,如下是解密之前的代码段;

当解密后,代码段将会被展开,并输出如下图所示的样子,此时程序即可被正确执行;

2.13 PE结构:实现PE代码段加密的更多相关文章

  1. TLS实现代码段加密

    刚开始见到这个思路是看到周大师用这个东西做的免杀,当时感觉这个想法很好,但是由于当时对PE结构了解的少,看到二进制的东西就打怵,所以当时也没能成功的去实现这个思路,只是简单的记录了一下TLS的特性,直 ...

  2. 【PE结构】恶意代码数字签名验证

    说明 恶意代码数字签名验证功能,WinverityTrust.CryptQueryObject 代码实现 WinVerifyTrust //------------------------------ ...

  3. OD 实验(五) - 对 PE 结构的简单分析

    载入程序,按 Alt+M 查看内存空间 双击进入程序的 PE 头 这些为 DOS 环境下才会运行的 这个执行 PE 的地址,PE 结构的偏移地址为 C0 找到这个地址 以 PE 开头 SizeOfCo ...

  4. 驱动开发:内核特征码扫描PE代码段

    在笔者上一篇文章<驱动开发:内核特征码搜索函数封装>中为了定位特征的方便我们封装实现了一个可以传入数组实现的SearchSpecialCode定位函数,该定位函数其实还不能算的上简单,本章 ...

  5. PE代码段中的数据

    PE代码段中可能包含一些数据,比如 optional header中的data directory会索引到一些数据,比如import/export table等等: 还有一些jump table/sw ...

  6. PE头结构解析(代码实现)

    PE头结构解析(代码实现) 图表实现在:https://www.cnblogs.com/juicyhumberger/articles/17064764.html #include "std ...

  7. 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)

    0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...

  8. PE结构详解

    1 基本概念 下表描述了贯穿于本文中的一些概念: 名称 描述 地址 是“虚拟地址”而不是“物理地址”.为什么不是“物理地址”呢?因为数据在内存的位置经常在变,这样可以节省内存开支.避开错误的内存位置等 ...

  9. 编写自定义PE结构的程序(如何手写一个PE,高级编译器都是编译好的PE头部,例如MASM,TASM等,NASM,FASM是低级编译器.可以自定义结构)

    正在学PE结构...感谢个位大哥的文章和资料...这里先说声谢谢 一般高级编译器都是编译好的PE头部,例如MASM,TASM等一直都说NASM,FASM是低级编译器.可以自定义结构但是苦于无人发布相关 ...

  10. 【转】pe结构详解

    (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等, 事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是 ...

随机推荐

  1. 用 three.js 绘制三维带箭头线 (线内箭头)

    在LineMaterial.js基础上修改的ArrowLineMaterial.js代码: /** * @author WestLangley / http://github.com/WestLang ...

  2. TCP 拥塞控制对数据延迟的影响

    哈喽大家好,我是咸鱼 今天分享一篇文章,是关于 TCP 拥塞控制对数据延迟产生的影响的.作者在服务延迟变高之后进行抓包分析,结果发现时间花在了 TCP 本身的机制上面:客户端并不是将请求一股脑发送给服 ...

  3. Java 21 新特性:Unnamed Patterns and Variables

    Java 21中除了推出JEP 445:Unnamed Classes and Instance Main Methods之外,还有另外一个预览功能:未命名模式和变量(Unnamed Patterns ...

  4. Codeforces Round #666 (Div. 2) 题解报告

    https://codeforces.com/contest/1397/problem/A 题意: 给定n个字符串,问重新组合以后是否能构成相同的n个字符串 思路: 直接判断所给的字符串的每种字母是否 ...

  5. Educational Codeforces Round 108 (Rated for Div. 2) (A思维,Bmath,C前缀和,D枚举)

    1519A. Red and Blue Beans 问题简述 给定 \(r\) 个红豆,\(b\) 个蓝豆,差值 \(d\) ,要求我们进行为红蓝豆分组,使得红豆和蓝豆绝对值差值不大于 \(d\) , ...

  6. 如虎添翼!高德地图+Serverless 护航你的假日出行

    ​ 作者 | 刘金龙(福辰) 高德团队 引言 ​ "前方事故多发地段,请注意保持车距..." "您已疲劳驾驶,请注意休息..." "前方经过泰山旅游景 ...

  7. zznu 1632

    表达式求值 递归求解~~ #include<cstdio> #include<cstring> #include<cstdlib> #include<cmat ...

  8. git或gitee 提交代码到远程仓库

    本文为博主原创,未经允许不得转载: 1. 选中远程仓库,并fork 指定的项目到自己的私仓: fork 之后,打开我的仓库便能看到刚刚fork 的项目. 2. clone 项目代码到自己电脑的本地仓库 ...

  9. 【荐】开源Winform控件库:花木兰控件库

    微信好友推荐,挺好看的Winfrom控件库,下面来看看. 介绍 基于 C#(语言) 4.0 . VS2019 . Net Framework 4.0(不包括Net Framework 4.0 Clie ...

  10. Go-竞态条件-锁

    1. 产生环境 多个进程(process).线程(threading)或协程(routine)存在对同一个资源访问顺序敏感(时间上的错误) 2. 概念 临界区 -- 时间上对同一资源的读写产生的数据不 ...