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

pe,结构,实现,代码段,加密 · 浏览次数 : 37

小编点评

# 代码节解密 **首先,我们打开文件并创建文件映射** ```c++ HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0); ``` **然后,我们定位第一个节并读取其大小** ```c++ 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); ``` **接下来,我们对代码节进行异或处理** ```c++ for (int x = 0; x < pSection->SizeOfRawData; x++) { pByte[x] ^= Key; } ``` **最后,我们写入加密后的数据到文件** ```c++ SetFilePointer(hFile, pSection->PointerToRawData, 0, FILE_BEGIN); WriteFile(hFile, pByte, pSection->SizeOfRawData, 0, FILE_BEGIN); ``` **解密后的代码段会被展开并输出** ```c++ free(pByte); FlushViewOfFile(lpBase, 0); UnmapViewOfFile(lpBase); ``` **解密后的代码段将按照如下样子展开** ``` ... ... ... +-----------------+ |... ... | |... ... | |... ... | +-----------------+ |... ... ... +-----------------+ |... ... | |... ... | |... ... | +-----------------+ |... ... ... +-----------------+ |... ... | |... ... | |... ... | +-----------------+ ``` **解密成功后,程序即可被正确执行了**

正文

代码加密功能的实现原理,首先通过创建一个新的.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 许可协议。转载请注明出处!

与2.13 PE结构:实现PE代码段加密相似的内容:

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

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

Blazor前后端框架Known-V1.2.13

# V1.2.13 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 - Gitee: [https://gitee.com/known/Known](https://gitee.com/known/Known) - Github:[https:

kafka的学习之二_kafka的压测与GUI管理

# kafka的学习之二_kafka的压测与GUI管理 ## 第一部分创建topic ``` cd /root/kafka_2.13-3.5.0 bin/kafka-topics.sh --create --bootstrap-server 10.110.139.184:9093 --command

[转帖]JMeter设置Http代理对web或者app进行录制

https://www.cnblogs.com/jingdenghuakai/p/11125846.html 一、录制web 1、首先保证JMeter的安装环境都正确。启动JMeter:在安装路径的bin目录下双击jmeter.bat (例如:D:\apache-jmeter-2.13\bin) ​

【译】Apache Kafka 快速入门

编译自官方文档。 第 1 步:获取 Kafka 下载最新版本(当前为 v3.3.1)的 Kafka 并解压: $ tar -xzf kafka_2.13-3.3.1.tgz $ cd kafka_2.13-3.3.1 第 2 步:启动 Kafka 环境 注意:本地环境必须安装了 Java 8+。 A

Maven依赖管理

本文主要记录Maven依赖管理中关于依赖传递和依赖范围的知识 Maven项目示例 创建3个maven项目,分配依赖log4j 1.2.12, 1.2.13, 1.2.14版本。 com.leo project1

[转帖]为什么redis的SDS的最大长度限制为512mb?

当客户端操作 client 时,一般不会直接使用 sds ,而是通过对象的方式来使用。比如创建的字符串其实是一个对象,间接使用到了 sds 结构。限制 512M 的逻辑在 t_string.c 的 checkStringLength 方法。 在redis3.2.13、redis4.0.14、redi

Docker 安装 Redis 单机&集群总结

前言 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于 内存 、 分布式 、可选持久性的键值对( Key-Value )存储数据库 redis版本:redis:6.2.13 作者:易墨 安装单机版 安装源:DockerHub 默认配置文件:配置文件示例 6.

13.2 外部DirectX绘制实现

在前一节中我们简单介绍了D3D绘制窗体所具备的基本要素,本节将继续探索外部绘制技术的实现细节,并以此实现一些简单的图形绘制功能,首先外部绘制的核心原理是通过动态创建一个新的窗口并设置该窗口属性为透明无边框状态,通过消息循环机制实现对父窗口的动态跟随附着功能,当读者需要绘制新的图形时只需要绘制在透明窗体之上即可实现动态显示的效果。

[转帖]sar -Linux 上全面的系统性能分析工具(2)

https://zhuanlan.zhihu.com/p/554619823 sar -b[ <时间间隔> [ <次数> ] ] 示例: sar -b 1 3 Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CP