2.13 PE结构:实现PE代码段加密

简介: 代码加密功能的实现原理,首先通过创建一个新的`.hack`区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的`ShellCode`汇编指令集,并将程序入口地址修正为`ShellCode`地址位置处,当解密功能被运行后则可释放加密的`.text`节,此时再通过一个`JMP`指令跳转到原始`OEP`位置,则可继续执行解密后的区段。

代码加密功能的实现原理,首先通过创建一个新的.hack区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的ShellCode汇编指令集,并将程序入口地址修正为ShellCode地址位置处,当解密功能被运行后则可释放加密的.text节,此时再通过一个JMP指令跳转到原始OEP位置,则可继续执行解密后的区段。

如下是一段异或解密功能实现,用于实现循环0x88异或解密代码功能;

00408001  MOV EAX, main.00401000        (代码段首地址)
00408006  XOR BYTE PTR DS : [EAX], 88   (与0x88异或)
00408009  INC EAX
0040800A  CMP EAX, main.00404B46        (代码段结束位置)
0040800F  JNZ SHORT main.00408006       (写入原始OEP)
00408011  POPAD
00408012  MOV EAX, main.00401041        (写入新OEP)
00408017  JMP EAX

有了上述加密流程,则下一步就是对内部的变量进行填充,我们可以提取出这些汇编指令的机器码并存储到Code[]数组内,通过对数组中的特定位置进行替换来完善跳转功能,此处我们需要提取如下几个位置的特征字段;

  • 00408001 数组下标第2位替换为ImageBase + pSection->VirtualAddress
  • 0040800A 数组下标第11位替换为ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize
  • 0040800F 数组下标第19位替换为ImageBase + BaseRVA
  • 00408012 原始OEP位置替换为pSection->VirtualAddress

根据上述流程我们可以编写一个AddPacking函数,该函数通过传入一个待加密程序路径,则可将一段解密代码Code[]写入到程序节表中的最后一个节.hack所在内存空间,并动态修正当前入口地址为.hack节的首地址,最终执行解密后自动跳转回原始OEP位置执行功能,如下代码所示;

// 对文件执行加壳操作
void AddPacking(LPSTR szFileName)
{
   
  // 打开文件
  HANDLE hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  // 创建文件映射
  HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
  HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

  // 找到PE文件头
  PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
  PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);

  DWORD ImageBase = NtHdr->OptionalHeader.ImageBase;
  DWORD BaseRVA = NtHdr->OptionalHeader.AddressOfEntryPoint;
  PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
  PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);

  // 首先得到最后一个节的指针,然后找到里面的虚拟偏移值,填入到程序OEP位置即可。
  DWORD SectionNum = FileHdr->NumberOfSections;

  char Code[] =
  {
   
    "\x60"
    "\xb8\x00\x00\x00\x00"
    "\x80\x30\x88"
    "\x40"
    "\x3d\xff\x4f\x40\x00"
    "\x75\xf5"
    "\x61"
    "\xb8\x00\x00\x00\x00"
    "\xff\xe0"
  };

  DWORD dwWrite = 0;

  // 写入代码节开始位置
  *(DWORD *)&Code[2] = ImageBase + pSection->VirtualAddress;
  printf("[+] 写入代码节开始位置: 0x%08X \n", ImageBase + pSection->VirtualAddress);

  // 写入代码节终止位置
  *(DWORD *)&Code[11] = ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize;
  printf("[+] 写入代码节结束位置:0x%08X \n", ImageBase + pSection->VirtualAddress + pSection->Misc.VirtualSize);

  // 写入原来的的OEP位置
  *(DWORD *)&Code[19] = ImageBase + BaseRVA;
  printf("[+] 写入原始OEP 0x%08X \n", ImageBase + BaseRVA);

  // 拿到最后一个节的文件偏移
  pSection = pSection + (SectionNum - 1);
  printf("[+] 得到最后一个节的实际地址: 0x%08X \n", pSection->PointerToRawData);

  // 设置指针到最后一个节文件偏移位置
  SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
  WriteFile(hFile, (LPVOID)Code, sizeof(Code), &dwWrite, NULL);
  FlushViewOfFile(lpBase, 0);

  // 修正当前入口点地址
  printf("[+] 修正入口点地址为: 0x%08X \n", pSection->VirtualAddress);
  *(DWORD *)&NtHdr->OptionalHeader.AddressOfEntryPoint = pSection->VirtualAddress;
  UnmapViewOfFile(lpBase);
}

读者可自行运行上述代码片段,传入d://lyshark.exe对该程序中的.text节进行解密,运行后读者可打开x64dbg调试器,观察入口地址处的变化,此时入口地址已经跳转到.hack节内,此节中的汇编指令则用于对.text代码节进行解密操作,当解密结束后则会跳转到原始地址0x45C865位置处,如下图所示;

当加密功能写入后,则接下来就可以对.text代码节进行加密了,加密的实现也非常容易,如下函数,通过定位到第一个节,并通过ReadFile函数将这个节读入内存,通过循环对这个区域进行加密,最后调用WriteFile函数再将加密后的数据回写到代码节,此时加密功能就实现了,如下所示;

// 将特定的节进行加密,此处只加密Text节
void EncrySection(LPSTR szFileName, DWORD Key)
{
   
  // 打开文件
  HANDLE hFile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  // 创建文件映射
  HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
  HANDLE lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_SHARE_WRITE, 0, 0, 0);

  // 定位PE文件节
  PIMAGE_DOS_HEADER DosHdr = (PIMAGE_DOS_HEADER)lpBase;
  PIMAGE_NT_HEADERS NtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpBase + DosHdr->e_lfanew);
  PIMAGE_FILE_HEADER FileHdr = &NtHdr->FileHeader;
  PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(NtHdr);

  printf("[-] 节虚拟地址: 0x%08X 虚拟大小: 0x%08X\n", pSection->VirtualAddress, pSection->Misc.VirtualSize);
  printf("[-] 读入FOA基地址: 0x%08X 节表长度: 0x%08X \n", pSection->PointerToRawData, pSection->SizeOfRawData);
  printf("[-] 已对 %s 节 --> XOR加密/解密 --> XOR密钥: %d \n\n", pSection->Name, Key);

  // 分配内存空间
  DWORD dwRead = 0;
  PBYTE pByte = (PBYTE)malloc(pSection->SizeOfRawData);

  SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
  memset(pByte, 0, pSection->SizeOfRawData);
  ReadFile(hFile, pByte, pSection->SizeOfRawData, &dwRead, NULL);

  // 对代码节加密
  for (int x = 0; x < pSection->SizeOfRawData; x++)
  {
   
    pByte[x] ^= Key;
  }

  // 写出加密后的数据
  SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN);
  WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN);
  pSection->Characteristics = 0xE0000020;

  free(pByte);
  FlushViewOfFile(lpBase, 0);
  UnmapViewOfFile(lpBase);
}

读者通过AddPacking函数对文件加壳后,接着就可以调用EncrySection函数对目标程序进行异或处理,此处分别传入d://lyshark.exe路径,以及一个加密密钥0x88,等待加密结束即可,此时运行程序即可实现对代码段的解密运行,这样也就起到了保护了代码段的作用,如下是解密之前的代码段;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/5912e71.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!当解密后,代码段将会被展开,并输出如下图所示的样子,此时程序即可被正确执行;

目录
相关文章
|
7月前
|
存储 Windows
9.2 Windows驱动开发:内核解析PE结构导出表
在笔者的上一篇文章`《内核特征码扫描PE代码段》`中`LyShark`带大家通过封装好的`LySharkToolsUtilKernelBase`函数实现了动态获取内核模块基址,并通过`ntimage.h`头文件中提供的系列函数解析了指定内核模块的`PE节表`参数,本章将继续延申这个话题,实现对PE文件导出表的解析任务,导出表无法动态获取,解析导出表则必须读入内核模块到内存才可继续解析,所以我们需要分两步走,首先读入内核磁盘文件到内存,然后再通过`ntimage.h`中的系列函数解析即可。
50 0
9.2 Windows驱动开发:内核解析PE结构导出表
|
7月前
|
编译器 C++ Windows
9.4 Windows驱动开发:内核PE结构VA与FOA转换
本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。
44 0
9.4 Windows驱动开发:内核PE结构VA与FOA转换
|
7月前
|
C语言 Windows
9.3 Windows驱动开发:内核解析PE结构节表
在笔者上一篇文章`《内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。
31 0
9.3 Windows驱动开发:内核解析PE结构节表
|
10月前
|
存储 Shell
2.12 PE结构:实现PE字节注入
本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。
90 0
2.12 PE结构:实现PE字节注入
|
10月前
|
存储 编译器 数据安全/隐私保护
2.11 PE结构:添加新的节区
在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据。每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中,可执行文件可以更好地管理和区分不同类型的数据,并为运行时提供必要的信息和功能。节的作用是对可执行文件进行有效的分段和管理,以便操作系统和加载器可以正确加载和执行程序。
115 2
|
10月前
|
存储 Windows
2.4 PE结构:节表详细解析
节表(Section Table)是Windows PE/COFF格式的可执行文件中一个非常重要的数据结构,它记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息,是操作系统加载文件时根据节表来进行各个段的映射和初始化的重要依据。节表中的每个记录则被称为`IMAGE_SECTION_HEADER`,它记录了一个段的各种属性信息和在文件中的位置和大小等信息,一个文件可以由多个`IMAGE_SECTION_HEADER`构成。
113 0
2.4 PE结构:节表详细解析
|
10月前
|
存储 Windows
2.6 PE结构:导出表详细解析
导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
183 1
|
10月前
|
存储 算法 编译器
2.7 PE结构:重定位表详细解析
重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变
118 0
2.7 PE结构:重定位表详细解析
|
10月前
|
存储 C语言 Windows
2.2 PE结构:文件头详细解析
PE结构是`Windows`系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,DOS头是PE文件开头的一个固定长度的结构体,这个结构体的大小为64字节(0x40)。DOS头包含了很多有用的信息,该信息可以让Windows操作系统使用正确的方式加载可执行文件。从DOS文件头`IMAGE_DOS_HEADER`的`e_lfanew`字段向下偏移`003CH`的位置,就是真正的PE文件头的位置,该文件头是由`IMAGE_NT_HEADERS`结构定义的,IMAGE_NT_HEADERS是PE文件格式的一部分,它包含了PE
152 0
2.2 PE结构:文件头详细解析
|
Linux Windows
PE格式:实现ELF结构解析工具
ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.
210 0