在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据。每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中,可执行文件可以更好地管理和区分不同类型的数据,并为运行时提供必要的信息和功能。节的作用是对可执行文件进行有效的分段和管理,以便操作系统和加载器可以正确加载和执行程序。
可执行文件常用的节包括了:
节名 | 作用 | 功能说明 | 权限 |
---|---|---|---|
.text | 代码节 | 包含可执行代码,例如程序的指令和函数体 | 执行和读取 |
.data | 数据节 | 包含程序的全局和静态变量的初始化数据。 | 读取和写入 |
.rdata | 只读数据节 | 包含只读的静态数据,如常量字符串和只读的全局变量。 | 只读 |
.idata | 导入表节 | 包含程序所依赖的外部函数和符号的引用信息,用于动态链接。 | 读取和写入 |
.edata | 导出表节 | 包含程序导出的函数和符号的信息,用于供其他程序或模块使用。 | 读取 |
.rsrc | 资源节 | 包含程序所使用的资源数据,如图标、位图、字符串等。 | 读取 |
.reloc | 重定位节 | 包含程序的重定位信息,用于在加载时修正代码和数据的内存地址。 | 读取和写入 |
当然了并不是每一个标准的PE
文件都具备这些节,某些程序可能会使用特殊的PE
编辑工具新增一些特殊的节,或者当程序被加密压缩后该程序的节区也会发生不同的变化,对于新增节来说需要具备如下几个关键步骤:
- 计算新节的偏移量和大小:确定要添加的新节的偏移量和大小。偏移量是新节在文件中的位置,大小是新节的长度。
- 更新PE文件头:修改PE文件头中的相关字段,更新文件头中的
NumberOfSections
字段和SizeOfImage
字段。 - 创建新节:在PE文件末尾添加新的节表项,并填充新节的各个字段,例如名称、虚拟大小、文件大小、内存对齐等。
对于操作PE文件来说,在头文件中需要引入ImageHlp.h
并且包含#pragma comment(lib,"Imagehlp.lib")
库,该库提供了用于处理PE文件的函数和结构体,是Image Help Library
库的一部分,读者可自行在项目内引入这个库。
当引入后我们来实现第一个功能,为可执行文件分配空间,如下AllocateSpace
函数,该函数通过使用CreateFileA
打开一个文件,并调用SetFilePointer
将文件指针移动到FILE_END
文件末尾处,接着通过循环的方式WriteFile
填充开辟空间。
// 计算取整
DWORD AlignSize(int nSecSize, DWORD Alignment)
{
int nSize = nSecSize;
if (nSize %Alignment != 0)
{
nSecSize = (nSize / Alignment + 1) * Alignment;
}
return nSecSize;
}
// 为可执行文件分配空间
DWORD AllocateSpace(char *FileName, int FileSize)
{
HANDLE hFile = CreateFileA(FileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE |
FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL, NULL);
DWORD ret = SetFilePointer(hFile, 0, 0, FILE_END);
if (ret != NULL)
{
for (int x = 0; x < FileSize; x++)
{
WriteFile(hFile, "", 1, 0, 0);
}
CloseHandle(hFile);
}
return ret;
}
上述程序的使用方法很简单,通过AllocateSpace("d://lyshark.exe",4096)
传入两个参数,分别是需要开辟空间的进程,以及开辟空间的长度,该长度可以与节区内的大小保持一致,当程序执行后则返回开辟位置,读者可使用WinHex
工具跳转到程序末尾自行查看,如下图所示;
接着我们来实现添加节区功能,如下代码ImplantSection
则可实现增加新节功能,该函数传入三个参数,分别是可执行文件地址,节区名称,以及节区长度,程序中通过映射方式打开文件,分别寻找到当前节表首地址,以及节的数量,通过复制一个节,并对该节内存参数进行更新(节内存大小,节文件大小,节内存属性)等,当这些数据被更正后,则加下来就是保存文件,并返回pTmpSec->PointerToRawData
节所在文件地址。
// 计算取整
DWORD AlignSize(int nSecSize, DWORD Alignment)
{
int nSize = nSecSize;
if (nSize %Alignment != 0)
{
nSecSize = (nSize / Alignment + 1) * Alignment;
}
return nSecSize;
}
// 添加新的节区 szFileName = 打开exe文件 szSecName = 节名称 nSecSize = 节大小
DWORD ImplantSection(LPSTR szFileName, char szSecName[8], int nSecSize)
{
HANDLE m_hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE m_hMap = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0, 0, 0);
HANDLE m_lpBase = MapViewOfFile(m_hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);
// ------------------------------------------------------------------------
// 定位到DOS/NT头部
// ------------------------------------------------------------------------
// 定位DOS头
PIMAGE_DOS_HEADER m_pDosHdr = (PIMAGE_DOS_HEADER)m_lpBase;
printf("[-] 当前DOS头: 0x%08X \n", m_pDosHdr);
// 定位NT头
PIMAGE_NT_HEADERS m_pNtHdr = (PIMAGE_NT_HEADERS)((DWORD)m_lpBase + m_pDosHdr->e_lfanew);
printf("[-] 当前NT头: 0x%08X \n", m_pNtHdr);
// 定位节表首地址
PIMAGE_SECTION_HEADER m_pSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)&(m_pNtHdr->OptionalHeader) +
m_pNtHdr->FileHeader.SizeOfOptionalHeader);
printf("[-] 定位当前节表首地址: 0x%08X \n", m_pSecHdr);
// 定位节区数量
int nSecNum = m_pNtHdr->FileHeader.NumberOfSections;
DWORD dwFileAlignment = m_pNtHdr->OptionalHeader.FileAlignment;
DWORD dwSecAlignment = m_pNtHdr->OptionalHeader.SectionAlignment;
PIMAGE_SECTION_HEADER pTmpSec = m_pSecHdr + nSecNum;
// 复制节名
strncpy((char *)pTmpSec->Name, szSecName, 7);
printf("[+] 拷贝节名称: %s \n", szSecName);
// ------------------------------------------------------------------------
// 节的内存大小
// ------------------------------------------------------------------------
pTmpSec->Misc.VirtualSize = AlignSize(nSecSize, dwSecAlignment);
printf("[+] 节表内存大小: %d \n", nSecSize);
// 节的内存起始位置
pTmpSec->VirtualAddress = m_pSecHdr[nSecNum - 1].VirtualAddress +
AlignSize(m_pSecHdr[nSecNum - 1].Misc.VirtualSize, dwSecAlignment);
printf("[+] 节内存起始位置: 0x%08X \n", pTmpSec->VirtualAddress);
// ------------------------------------------------------------------------
// 节的文件大小
// ------------------------------------------------------------------------
pTmpSec->SizeOfRawData = AlignSize(nSecSize, dwFileAlignment);
printf("[-] 节的文件大小: %d \n", pTmpSec->SizeOfRawData);
// 节的文件起始位置,这里直接在文件末尾分配空间
pTmpSec->PointerToRawData = SetFilePointer(m_hFile, 0, 0, FILE_END);
printf("[-] 节的文件起始位置: 0x%08X \n", pTmpSec->PointerToRawData);
// 这里开始循环在文件末尾分配nSecSize存储空间
for (int x = 0; x < nSecSize; x++)
WriteFile(m_hFile, "", 1, 0, 0);
// ------------------------------------------------------------------------
// 节访问属性,设置内存属性为可读可执行代码段
// ------------------------------------------------------------------------
pTmpSec->Characteristics = 0xE0000020;
// 修正节区数量
m_pNtHdr->FileHeader.NumberOfSections++;
// 修正映像大小
m_pNtHdr->OptionalHeader.SizeOfImage += pTmpSec->Misc.VirtualSize;
FlushViewOfFile(m_lpBase, 0);
return pTmpSec->PointerToRawData;
}
读者可自行调用上述函数,通过ImplantSection("d://Win32Project.exe", ".hack", 4096)
传值,此时分别传入参数为需要修改的文件名,需要增加的节名称,以及创建节长度,在运行后读者可自行观察是否创建成功,如下图所示;
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/19540578.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!