2.11 PE结构:添加新的节区

本文介绍了PE文件中的节(sections)及其作用,包括代码节、数据节、导入表、导出表等,详细讲解了如何在可执行文件中添加新的节,以及所需的关键步骤。作者还提供了使用C++实现的示例函数,展示了如何使用ImageHlp.h库操作PE文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在可执行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)传值,此时分别传入参数为需要修改的文件名,需要增加的节名称,以及创建节长度,在运行后读者可自行观察是否创建成功,如下图所示;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微软技术分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值