2.11 PE结构:添加新的节区

简介: 在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据。每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中,可执行文件可以更好地管理和区分不同类型的数据,并为运行时提供必要的信息和功能。节的作用是对可执行文件进行有效的分段和管理,以便操作系统和加载器可以正确加载和执行程序。

在可执行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 许可协议。转载请注明出处!

目录
相关文章
|
8月前
2.14 PE结构:地址之间的转换
在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了`VA`,`RVA`,`FOA`三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通过编程的方式实现转换。VA(Virtual Address,虚拟地址):它是在进程的虚拟地址空间中的地址,用于在运行时访问内存中的数据和代码。VA是相对于进程基址的偏移量。在不同的进程中,相同的VA可能映射到不同的物理地址。RVA(Relative Virtual Address,相对虚拟地址):它是相对于模块基址(Module Base Address)的偏移量,用于定位模块内部的数
148 1
2.14 PE结构:地址之间的转换
|
5月前
|
编译器 C++ Windows
9.4 Windows驱动开发:内核PE结构VA与FOA转换
本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。
40 0
9.4 Windows驱动开发:内核PE结构VA与FOA转换
|
5月前
|
存储 Windows
9.2 Windows驱动开发:内核解析PE结构导出表
在笔者的上一篇文章`《内核特征码扫描PE代码段》`中`LyShark`带大家通过封装好的`LySharkToolsUtilKernelBase`函数实现了动态获取内核模块基址,并通过`ntimage.h`头文件中提供的系列函数解析了指定内核模块的`PE节表`参数,本章将继续延申这个话题,实现对PE文件导出表的解析任务,导出表无法动态获取,解析导出表则必须读入内核模块到内存才可继续解析,所以我们需要分两步走,首先读入内核磁盘文件到内存,然后再通过`ntimage.h`中的系列函数解析即可。
39 0
9.2 Windows驱动开发:内核解析PE结构导出表
|
5月前
|
C语言 Windows
9.3 Windows驱动开发:内核解析PE结构节表
在笔者上一篇文章`《内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。
26 0
9.3 Windows驱动开发:内核解析PE结构节表
|
8月前
|
存储 数据安全/隐私保护
2.13 PE结构:实现PE代码段加密
代码加密功能的实现原理,首先通过创建一个新的`.hack`区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的`ShellCode`汇编指令集,并将程序入口地址修正为`ShellCode`地址位置处,当解密功能被运行后则可释放加密的`.text`节,此时再通过一个`JMP`指令跳转到原始`OEP`位置,则可继续执行解密后的区段。
57 0
2.13 PE结构:实现PE代码段加密
|
8月前
|
存储 Windows
2.4 PE结构:节表详细解析
节表(Section Table)是Windows PE/COFF格式的可执行文件中一个非常重要的数据结构,它记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息,是操作系统加载文件时根据节表来进行各个段的映射和初始化的重要依据。节表中的每个记录则被称为`IMAGE_SECTION_HEADER`,它记录了一个段的各种属性信息和在文件中的位置和大小等信息,一个文件可以由多个`IMAGE_SECTION_HEADER`构成。
99 0
2.4 PE结构:节表详细解析
|
8月前
|
存储 Shell
2.12 PE结构:实现PE字节注入
本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。
85 0
2.12 PE结构:实现PE字节注入
|
8月前
|
存储 算法 编译器
2.7 PE结构:重定位表详细解析
重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变
93 0
2.7 PE结构:重定位表详细解析
|
8月前
|
存储 Windows
2.6 PE结构:导出表详细解析
导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
166 1
|
8月前
|
Windows
2.8 PE结构:资源表详细解析
在Windows PE中,资源是指可执行文件中存放的一些固定不变的数据集合,例如图标、对话框、字符串、位图、版本信息等。PE文件中每个资源都会被分配对应的唯一资源ID,以便在运行时能够方便地查找和调用它们。PE文件中的资源都被组织成一个树形结构,其中最顶层为根节点(Root),下一级为资源类型(Type),再下一级为资源名称(Name),最终是实际的资源内容。PIMAGE_RESOURCE_DIRECTORY是Windows PE可执行文件中的一个结构类型,用于描述资源(Resource)的树形结构,其中包括了每个资源的类型(Type)、名称(Name)和语言(Language),以及指向下一
89 0
2.8 PE结构:资源表详细解析