欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

虚拟机完整分析

虚拟机完整分析

最近一段时间,看了《加密与解密》3版里的虚拟机,感觉挺有意思,现将自已的分析发表如下(里面有些是原作者的分析,但大部分我已作更详细分析),与大家共享
本想将其发表至《加密与解密》版块中,但自已是新人,只好发表在这里了
// 发表的内容以文件为单元(因为比较容易,懒得去整理所有的思路了)
// ----------------------------------------------- 源程序文件部分 -------------------------------------------------------

// ------------------------------------------ VCommand.cpp --------------------------------------
// 虚拟机的一部分指令,用于模拟某些汇编指令
#include "stdafx.h"
#include "VCommand.h"
// 需手工编写的虚拟机指令
/**
 * 进入虚拟机
 */
void _declspec(naked) VStartVM()  // *
{
  _asm
  {
    // 将寄存器压入堆栈
    push eax
    push ebx
    push ecx
    push edx
    push esi
    push edi
    push ebp
    pushfd
    // edi -> VMContext
    // esi -> 当前字节码地址
    // ebp -> 真实堆栈
    mov    esi, [esp+0x20]      // 伪代码开始的地址
    mov     ebp, esp        // ebp保存原堆栈指针
    sub    esp, 0x200
    mov     edi, esp        // edi指向VMContext
    sub    esp, 0x40        // esp现在是vm用的堆栈了
    mov    ebx, esi        // 把伪代码base_addr做key
    movzx  eax, byte ptr [esi]    // 获得bytecode
    lea    esi, [esi+1]      // 跳过这个字节
    
    VM_END              // VM结束标记
  }
}
/**
 * 检测真实堆栈是否已接近VMContext所在的位置,如果是
 * 则将VMContext结构复制到更远的位置存放
 */
void DCheckESP()  // *
{
  _asm
  {
    // edi -> 指向VMContext
    // ebp -> 指向真实的堆栈
    // 判断VMContext往上0x100处是否已接近真实堆栈
    lea     eax, dword ptr [edi + 0x100]
    cmp     eax, ebp
    nop
    nop
    // 计算VMContext与虚拟机堆栈之间的长度
    mov     edx, edi
    mov     ecx, esp
    sub     ecx, edx
    push  esi            // 保存虚拟指令IP指针
    mov    esi, esp
    sub    esp, 0x60
    mov    edi, esp
    push  edi            // 保存新的edi基地址
    sub    esp, 0x40
    cld
    rep movsb            // 开始复制
    // 恢复IP指针
    pop edi
    pop esi
    VM_END              // VM结束标记
  }
}
/**
 * 将32位寄存器压入堆栈中
 */
void _declspec(naked) DPushReg32()  // *
{
  _asm
  {
    mov    eax, dword ptr [esi]  // 得到寄存器偏移
    add    esi, 4
    mov    eax, dword ptr [edi + eax]  // 得到寄存器的值
    push  eax              // 压入寄存器
    VM_END              // VM结束标记
  }
}
/**
 * 将32位直接数压入堆栈中
 */
void _declspec(naked) DPushImm32()  // *
{
  _asm
  {
    // 取得直接数
    mov    eax, dword ptr [esi]
    add    esi, 4
    push  eax
    VM_END              // VM结束标记
  }
}
/**
 * 将32位内存数压入堆栈中
 *
 * 格式:[rax + rcx * 8 + 0x11223344]
 */
void _declspec(naked) DPushMem32()    // *
{
  _asm
  {
    mov    edx, 0
    mov    ecx, 0
    mov    eax, dword ptr [esp]  // 第1个寄存器偏移
    test   eax, eax
    // edx = 第1个寄存器值
    cmovge  edx, dword ptr [edi + eax]  // 如果不是负数则赋值
    mov    eax, dword ptr [esp + 4]  // 第2个寄存器偏移
    test   eax, eax
    // ecx = 第2个寄存器值
    cmovge  ecx, dword ptr [edi + eax]
    imul  ecx, dword ptr [esp + 8]  // 第2个寄存器的乘积
    add    ecx, dword ptr [esp + 0x0C]    // 第三个为常量
    add    edx, ecx
    add    esp, 0x10        // 释放参数
    // 压入该数
    // 问题:这里压入的似乎不是内存数,而是该数的地址
    push  edx
    VM_END              // VM结束标记
  }
}
/**
 * 将堆栈中的数据弹回寄存器
 */
void _declspec(naked) DPopReg32()  // *
{
  _asm
  {
    mov    eax, dword ptr [esi]  // 得到reg偏移
    add    esi, 4
    pop    dword ptr [edi + eax]  // 弹回寄存器
    VM_END              // VM结束标记
  }
}
/**
 * 释放堆栈
 */
void _declspec(naked) DFree()  // *
{
  _asm
  {
    add esp, 4
    VM_END              // VM结束标记
  }
}
// ------------------------------------------------ comm.cpp ------------------------------------------------------------
// 此文件包含一些公共的函数
#include "stdafx.h"
#include "comm.h"
/**
 * 显示提示信息对话框
 *
 * content: 显示的提示信息
 * nType: 显示的提示图标的类型
 */
void MsgBox(char * content, UINT nType)    // *
{
  if (!content)
    return;
  MessageBoxA(NULL, content, VMPACKERTITLE, nType);
}
/**
 * 去除str字符串中的空格,并将其能守pstr返回
 *
 * 返回值: 成功返回TRUE,否则FALSE
 */
BOOL Trim(char * pstr, char * str)    // *
{
  int i, j = 0;
  // 检测参数是否有效
  if (!pstr || !str)
    return FALSE;
  // 将pstr复制至str并计算其长度
  strcpy(str, pstr);
  j = strlen(str);
  i = 0;
  // 遍历str中的字符
  for (i = 0; i < j; i ++)
  {
    // 如果i字符是换行或回车,则替换为0
    if ((str[i] == 0x0a) || (str[i] == 0x0d))
    {
      str[i] = 0;
    }
  }
  j = strlen(str);
  i = 0;
  // 统计str头部的空格数
  while (str[i] == ' ')
    i++;
  // 如果str中都是空格,则处理失败
  if (i >= j)
  {
    return FALSE;
  }
  // 去除头部的空格
  memmove(str, str + i, j - i + 1);
  j = strlen(str);
  i = 0;
  // 统计str尾部的空格数
  while (str[i] == ' ')
    i--;
  if (i)
    str[i] = '\0';
  if (i >= j)
  {
    return FALSE;
  }
  return TRUE;
}
/**
 * 将指定字符串strSource转为16进制数并返回
 *
 * strSource: 欲转为16进制数的数字字符串
 *
 * 返回值:成功返回转换后的结果,否则-1
 */
DWORD StringToHex(char * strSource)    // *
{ 
  DWORD nTemp = 0;
  char strTemp[64];
  // 如果字符串为空,则不需要转,直接返回
  if (strSource == NULL)
    return -1;
  // 将源数据复制到临时变量中
  strcpy(strTemp, strSource);
  // 16进制只能包含A、B、C、D、E、F字符,所以,如果包含除此以外其他字符则出错返回
  for (char cc = 'G', dd = 'g'; cc <= 'Z', dd <= 'z';  cc++, dd++)
  {
    if (strchr(strTemp, cc) != NULL || strchr(strTemp, dd) != NULL)
    {
      return -1;
    }
  }
  // 遍历strSource中所有的字符
  for (int i = 0; i<(int)::strlen(strSource); i++)
  {
    int nDecNum;
    // 根据每个字符进行处理
    switch (strSource[i])
    {
    case 'a':
    case 'A':
      nDecNum = 10;
      break;
    case 'b':
    case 'B':
      nDecNum = 11;
      break;
    case 'c':
    case 'C':
      nDecNum = 12;
      break;
    case 'd':
    case 'D':
      nDecNum = 13;
      break;
    case 'e':
    case 'E':
      nDecNum = 14;
      break;
    case 'f':
    case 'F':
      nDecNum = 15;
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      nDecNum = strSource[i] - '0';
      break;
    default:
      return 0;
    }
    nTemp += (DWORD)nDecNum * (long double)pow((long double)16, (int)(strlen(strSource) - i - 1));
  }
  return nTemp;
}
/**
 * 按指定的某种格式输出字符串str
 */
void OutPutStr(char *str, ...)  // *
{
  // #if _DEBUG
  char strbuf[1024]; 
  // 处理可变参数
  va_list  vl;
  va_start(vl, str);
  vsprintf(strbuf, str, vl);
  va_end(vl);
  OutputDebugString(strbuf);
  // #endif
}
// ------------------------------------------------ main.cpp:------------------------------------------------------------
// 此文件主要是读取要保护的文件的PE信息,加载跟文件有关的MAP符号信息,在该PE文件的最后区块后加多一个
// 区块,此区块将用于保存虚拟机的各种信息,如虚拟机指令,跳转表等。接下来,对文件的代码进行反编译后,根
// 据生成的汇编指令进行虚拟机保护,最后生成受虚拟机保护的新PE文件
#include "stdafx.h"
#include "CCodeILFactory.h"
#include ".\pestructure.h"
/**
 * 程序入口
 */
int main()    // *
{
  // PE文件信息
  CPEStructure pestruct;
  CCodeILFactory codefactory;
  list<CodeNode *> CodeList;
  list<CodeNode *> CodeList1;
  // 打开文件,读取头部,并读取区块数据
  pestruct.OpenFileName("G:\\项目\\VMPacker\\ConvertCrackMe\\Release\\ConvertCrackMe.exe");
  // 读取map文件,解析其中的符号信息并存于pestruct.MapVector中
  // 注:MAP文件是一个文本文件,记录了程序的入口地址、基地址、符号及其对应的段、文件偏址等信息
  pestruct.LoadMap("G:\\项目\\VMPacker\\ConvertCrackMe\\Release\\ConvertCrackMe.map");
  getchar();
  // 为PE文件pestruct分配一个新的区块,并对该区块进行初始化,并生成某
  // 些虚拟机核心指令,并将指令代码复制到该区块的某个子块中
  // 注:具体此函数分新的区块结构怎样,可查看“PE新区块结构图.jpg”
  codefactory.Init(0x400000 + pestruct.GetNewSection());
  // 查找CrackMe()函数的符号信息
  MapStructrue * stu = pestruct.GetMap("CrackMe(struct HWND__ *)");
  // 找不到,则直接返回
  if (!stu)
    return 0;
  // 得到符号的基地址
  char * Base_Addr = pestruct.image_section[stu->Segment - 1] + stu->Offset;
  // 反汇编Base_Addr的代码,即函数CrackMe()的代码
  codefactory.DisasmFunction(&CodeList, Base_Addr, stu->VirtualAddress);
  // 
  list<CodeNode*>::iterator itr;
  
  itr = CodeList.begin();
  // 获取CodeList指令列表中起始指令
  CodeNode * code = *itr;
  itr = CodeList.end();
  itr--;
  // 获取CodeList指令列表中结束指令
  CodeNode * endcode = *itr;
  // 计算CodeList的总代码长度
  int asmlen = endcode->disasm.ip + endcode->disasm.codelen - code->disasm.ip;
  // 组织一段进入虚拟机的指令(即jmp VStartVM),存储于Base_Addr
  codefactory.VMFactory.CompileEnterStubCode(Base_Addr, code->disasm.ip, asmlen);
  // 将文件各头部与区块数据写回至PE文件中
  pestruct.UpdateHeadersSections(FALSE);
  char errtext[255] = {0};
  // 将CodeList指令列表编译为虚拟机字节码
  codefactory.BuildCode(Base_Addr, &CodeList, errtext);
  // 计算存放虚拟机各个部件的虚存空间长度
  int len = codefactory.m_JumpTable.m_addrlen + codefactory.m_CodeEngine.m_addrlen +
        codefactory.m_EnterStub.m_addrlen + codefactory.m_VMEnterStubCode.m_addrlen + 
        codefactory.m_VMCode.m_addrlen;
  // 分配足够的空间
  char * newdata = new char[len];
  char * p = newdata;
  // 将跳转表复制到p空间中
  memcpy(p, codefactory.m_JumpTable.m_BaseAddr, codefactory.m_JumpTable.m_addrlen);
  p += codefactory.m_JumpTable.m_addrlen;
  // 将虚拟机其他部分复制到p空间中
  memcpy(p, codefactory.m_CodeEngine.m_BaseAddr, codefactory.m_CodeEngine.m_addrlen);
  p += codefactory.m_CodeEngine.m_addrlen;
  memcpy(p, codefactory.m_EnterStub.m_BaseAddr, codefactory.m_EnterStub.m_addrlen);
  p += codefactory.m_EnterStub.m_addrlen;
  memcpy(p, codefactory.m_VMEnterStubCode.m_BaseAddr, codefactory.m_VMEnterStubCode.m_addrlen);
  p += codefactory.m_VMEnterStubCode.m_addrlen;
  memcpy(p, codefactory.m_VMCode.m_BaseAddr, codefactory.m_VMCode.m_addrlen);
  p += codefactory.m_VMCode.m_addrlen;
  // 为PE文件添加.bug的新区块,即虚拟机
  pestruct.AddSection(newdata,len,".bug");
  pestruct.UpdateHeaders(FALSE);
  pestruct.UpdateHeadersSections(TRUE);
  pestruct.UpdateHeadersSections(FALSE);
  // 创建一个新的可执行文件
  pestruct.MakePE("G:\\项目\\VMPacker\\ConvertCrackMe\\Release\\ConvertCrackMe.vm.exe", len);
  return 0;
}

// ----------------------------------------------------- PEStructure.cpp ----------------------------------------------------------
// 此文件中的代码用于对PE文件的各种操作,包括读取等
#include "StdAfx.h"
#include ".\pestructure.h"
#include <ImageHlp.h>
#pragma comment(lib, "ImageHlp")  // 链接到ImageHlp.lib
CPEStructure::CPEStructure(void)
{
  Init();
}
CPEStructure::~CPEStructure(void)
{
  Free();
}
/**
 * 打开指定的文件并读取文件头,区块数据
 *
 * FileName: 欲打开的文件路径名
 *
 * 返回值:FALSE表示失败,否则成功
 */
BOOL CPEStructure::OpenFileName(char * FileName)    // *
{
  // 已读取文件的字节数
  DWORD dwBytesRead = 0;
  // 释放分配的内存资源
  Free();
  // 打开FileName文件
  hFile = CreateFileA(FileName, GENERIC_READ,  FILE_SHARE_WRITE | FILE_SHARE_READ,
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  // 打开失败,则显示提示信息并直接返回
  if (hFile == INVALID_HANDLE_VALUE)
  {
    MsgBox("打开文件失败", MB_ICONERROR);
    return FALSE;
  }
  // 获取文件大小
  dwFsize = GetFileSize(hFile, 0);
  // 文件不存在内容,则关闭文件并提示错误信息
  if (dwFsize == 0)
  {
    CloseHandle(hFile);
    MsgBox("错误的文件长度", MB_ICONERROR);
    return FALSE;
  }
  // 打开成功,则将文件名复制至m_FileName中
  strcpy_s(m_FileName, 256, FileName);
  // 计算文件与壳的长度和,以便分配足够的内存空间
  dwOutPutSize = dwFsize + PackerCode_Size + ALIGN_CORRECTION;
  // 分配内存,并将其清0
  pMem = (char*)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwOutPutSize);
  // 内存分配失败,则关闭文件并提示错误信息
  if (pMem == NULL)
  {
    CloseHandle(hFile);
    MsgBox("申请内存出错", MB_ICONERROR);
    return FALSE;
  }
  // 将文件读取至pMem中,dwBytesRead表示真正读取的数据长度
  ReadFile(hFile, pMem, dwFsize, &dwBytesRead, NULL);
  // 读取完文件后,就可以关闭文件了
  CloseHandle(hFile);
  // 将PE文件的DOS头部复制到image_dos_header头部中
  CopyMemory(&image_dos_header, pMem, sizeof(IMAGE_DOS_HEADER));
  // 检测是否是一个有效的PE文件,不是则提示错误信息
  if (image_dos_header.e_magic != IMAGE_DOS_SIGNATURE)
  {
    MsgBox("不是一个有效的PE文件", MB_ICONERROR);
    return FALSE;
  }
  // 保存DOS头部长度
  ReservedHeaderRO = sizeof(IMAGE_DOS_HEADER);
  // 保存保留头部长度值
  // 注:e_lfanew是PE头部的偏移,所以,这里的保留头部指的是DOS-PE头部之间的部分
  ReservedHeaderSize = image_dos_header.e_lfanew - sizeof(IMAGE_DOS_HEADER);
  reservedheader = new char[ReservedHeaderSize];
  // 复制PE头部
  CopyMemory(&image_nt_headers, pMem + image_dos_header.e_lfanew, sizeof(IMAGE_NT_HEADERS));
  // PE文件区块表在文件中的偏移
  dwRO_first_section = image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
  // 读取文件的头部信息与区块数据
  UpdateHeadersSections(TRUE);
  
  return TRUE;
}

//----------------------------------------------------------------
void CPEStructure::UpdateHeaders(BOOL bSaveAndValidate)
{
  DWORD SectionNum = image_nt_headers.FileHeader.NumberOfSections;
  if( bSaveAndValidate )//TRUE = 保存数据
  {
    CopyMemory(&image_dos_header,pMem,sizeof(IMAGE_DOS_HEADER));
    ReservedHeaderSize=image_dos_header.e_lfanew - sizeof(IMAGE_DOS_HEADER);
    if( ( ReservedHeaderSize & 0x80000000 ) == 0x00000000)
    {
      CopyMemory(reservedheader, pMem+ReservedHeaderRO, ReservedHeaderSize);
    }
    CopyMemory(&image_nt_headers,
      pMem+image_dos_header.e_lfanew,
      sizeof(IMAGE_NT_HEADERS));
    dwRO_first_section = image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
    CopyMemory(&image_section_header, pMem+dwRO_first_section, SectionNum * sizeof(IMAGE_SECTION_HEADER));
  }
  else        //FALSE = 恢复数据
  {
    CopyMemory(pMem, &image_dos_header, sizeof(IMAGE_DOS_HEADER));
    ReservedHeaderSize=image_dos_header.e_lfanew - sizeof(IMAGE_DOS_HEADER);
    if( (ReservedHeaderSize & 0x80000000) == 0x00000000)
    {
      CopyMemory(pMem+ReservedHeaderRO,reservedheader,ReservedHeaderSize);
    }
    CopyMemory(pMem+image_dos_header.e_lfanew,
      &image_nt_headers,
      sizeof(IMAGE_NT_HEADERS));
    dwRO_first_section=image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
    CopyMemory(pMem+dwRO_first_section, &image_section_header, SectionNum * sizeof(IMAGE_SECTION_HEADER));
  }
}
/**
 * 更新PE文件的头部与区块数据
 *
 * bSaveAndValidate: true表示读取PE文件中的各头部与区块数据
 *           false表示是将文件各头部与区块数据写回至PE文件中
 */
void CPEStructure::UpdateHeadersSections(BOOL bSaveAndValidate)    // *
{
  DWORD i = 0;
  // 文件区块数
  DWORD SectionNum = image_nt_headers.FileHeader.NumberOfSections;
  // 读取文件头部与区块数据
  if (bSaveAndValidate)
  {
    // 复制文件DOS头部
    CopyMemory(&image_dos_header, pMem, sizeof(IMAGE_DOS_HEADER));
    // 复制文件保留头部
    ReservedHeaderSize = image_dos_header.e_lfanew - sizeof(IMAGE_DOS_HEADER);
    // 0x80000000: 10000000000000000000000000000000b,即32位最高位置1
    if ((ReservedHeaderSize & 0x80000000) == 0x00000000)
    {
      CopyMemory(reservedheader, pMem + ReservedHeaderRO, ReservedHeaderSize);
    }
    // 复制文件PE头部
    CopyMemory(&image_nt_headers, pMem + image_dos_header.e_lfanew,  sizeof(IMAGE_NT_HEADERS));
    // 区块表在文件中的偏移
    dwRO_first_section = image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
    // 复制区块表
    CopyMemory(&image_section_header, pMem + dwRO_first_section, SectionNum * sizeof(IMAGE_SECTION_HEADER));
    // 遍历所有的区块
    for (i = 0; i < SectionNum; i++)
    {
      // 为每个区块分配内存
      image_section[i] = (char*)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
        PEAlign(image_section_header[i].SizeOfRawData, image_nt_headers.OptionalHeader.FileAlignment));
      // 复制该区块数据
      CopyMemory(image_section[i], pMem + image_section_header[i].PointerToRawData,
        image_section_header[i].SizeOfRawData);
    }
    SectionAssigned = TRUE;    // 已经为区块分配空间
  }
  // 将数据写回文件头部与区块数据
  else
  {
    // 写回DOS头部
    CopyMemory(pMem, &image_dos_header, sizeof(IMAGE_DOS_HEADER));
    ReservedHeaderSize = image_dos_header.e_lfanew - sizeof(IMAGE_DOS_HEADER);
    if ((ReservedHeaderSize & 0x80000000 ) == 0x00000000)
    {
      CopyMemory(pMem + ReservedHeaderRO, reservedheader, ReservedHeaderSize);
    }
    CopyMemory(pMem + image_dos_header.e_lfanew, &image_nt_headers,  sizeof(IMAGE_NT_HEADERS));
    dwRO_first_section = image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
    CopyMemory(pMem + dwRO_first_section, &image_section_header, SectionNum * sizeof(IMAGE_SECTION_HEADER));
    for (i = 0; i < SectionNum; i++)
    {
      CopyMemory(pMem + image_section_header[i].PointerToRawData,
        image_section[i], image_section_header[i].SizeOfRawData);
    }
  }
}
/**
 * 根据dwAlignTo对齐值对dwTarNum进行对齐操作
 *
 * dwTarNum: 欲进行对齐的值
 * dwAlignTo: 进行对齐的参照值
 */
DWORD CPEStructure::PEAlign(DWORD dwTarNum, DWORD dwAlignTo)    // *
{  
  DWORD dwtemp;
  dwtemp = dwTarNum / dwAlignTo;
  if ((dwTarNum % dwAlignTo) != 0)
  {
    dwtemp++;
  }
  dwtemp = dwtemp * dwAlignTo;
  return(dwtemp);
}

/**
 * 获取strAddress中的段与文件偏移信息
 *
 * StrAddress:如0001:00000003
 * Segment: 用于返回段信息,即0001
 * FileOffset: 用于返回文件偏移量,即00000003
 *
 * 返回值:成功返回TRUE,否则FALSE
 */
BOOL CPEStructure::GetFileAddr(char * StrAddress, int * Segment, int * FileOffset)  // *
{
  // 检测参数是否有效
  if (!StrAddress || !Segment || !FileOffset)
    return FALSE;
  char StrSegment[4 + 1] = {0};
  char StrOffset[8 + 1] = {0};
  // 解析StrAddress
  memcpy(StrSegment, StrAddress, 4);
  memcpy(StrOffset, StrAddress + 5, 8);
  // 获取段与文件偏移信息
  *Segment = StringToHex(StrSegment);
  *FileOffset = StringToHex(StrOffset);
  return TRUE;
}
/**
 * 读取指定pmapfilename文件,解析其中的符号信息,将每个符号的信息存放在MapVector中
 *
 * pmapfilename: MAP文件的路径名
 *
 * 注:MAP文件是一个文本文件,记录了程序的入口地址、基地址、符号及其对应的段、偏址等信息
 */
void CPEStructure::LoadMap(char * pmapfilename)    // *
{
  if (!pmapfilename)
    return;
  FILE * mapfile = NULL;
  // 打开MAP文件
  mapfile = fopen(pmapfilename, "r+");
  if (!mapfile)
    return;
  // 释放MapVector所有符号信息占用的资源
  FreeMapVector();
  char linestr[512] = {0};  // 行数据
  int  readcnt = 0;    // 已读取的行数
  BOOL bBegin = FALSE;    // 开始
  // 循环读取MAP文件,直至文件结束
  while (!feof(mapfile))
  {
    // 清0行数据
    memset(linestr, 0, 512);
    // 读取MAP文件的每一行数据,如果读取不到数据,则结束
    if (fgets(linestr, 512, mapfile) == NULL)
    {
      break;
    }
    readcnt++;  // 累计读取的行数
    // 如果该行是模块入口地址数据,则跳过
    if (strstr(linestr, "entry point at") != NULL)
    {
      continue;
    }
    // 如果该行表示行号数据,则跳过
    if (strstr(linestr, "Line numbers for") != NULL)
    {
      break;
    }
    // 符号信息包括符号在节内的偏移地址、加载地址及符号出处等,其格式如下:
    // Address      Publics by Value      Rva+Base  Lib:Object
    // 0000:00000003  ___safe_se_handler_count  00000003  <absolute>
    // 或者搜索静态符号
    if ((strstr(linestr, "Publics by Value") != NULL && strstr(linestr, "Rva+Base") != NULL) ||
      (strstr(linestr, "Static symbols") != NULL ))
    {
      // 如果搜索到,则标注为开始
      bBegin = TRUE;
      continue;
    }
    char StrLine[512] = {0};
    // 将linestr去掉空格后放到StrLine中,如果该行是空行,则跳过
    if (!Trim(linestr, StrLine))
    {
      continue;
    }
    // 开始分析StrLine符号行数据信息
    if (bBegin)
    {
      // 这里的地址指的是文件中的地址
      char StrAddress[16] = {0};  // 段: 文件偏移
      char StrSymbol[512] = {0};  // 符号名
      char StrVirtualAddr[16] = {0};  // 虚拟地址
      char StrLibObject[512] = {0};  // 输入文件名
      int Segment = 0;
      int FileOffset = 0;
      // 复制符号地址,如:0001:00000003
      memcpy(StrAddress, StrLine, 13);
      int nstrlen = strlen(StrLine);
      memmove(StrLine, StrLine + 13, nstrlen - 13 + 1);  // 跳过段偏移
      // 获取符号所在的段与文件偏移
      GetFileAddr(StrAddress, &Segment, &FileOffset);
      // 段是不能为0的
      if (Segment == 0)
        continue;
      // 将剩余内容去掉空格
      Trim(StrLine, StrLine);
      // 复制符号名
      strcpy_s(StrSymbol, 512, StrLine);
      // 符号名结束
      *strchr(StrSymbol, ' ') = '\0';
      // 计算符号名的长度
      int nlen = strlen(StrSymbol);
      // 计算剩余部分的长度
      nstrlen = strlen(StrLine);
      memmove(StrLine, StrLine + nlen, strlen(StrLine) - nlen + 1);  // 跳过符号名
      // 去除空格
      Trim(StrLine, StrLine);
      // 获取符号的虚拟地址
      memcpy(StrVirtualAddr, StrLine, strchr(StrLine, ' ') - StrLine);
      nstrlen = strlen(StrLine);
      memmove(StrLine, StrLine + 8, nstrlen - 8 + 1);    // 跳过VirtualAddress
      nstrlen = strlen(StrLine);
      memmove(StrLine, StrLine + 5, nstrlen - 5 + 1);  // 跳过f i ...
      // 输入文件名
      strcpy(StrLibObject, StrLine);
      // 分配符号信息结构,将获取的符号信息保存起来
      MapStructrue * mapstruct = new MapStructrue;
      mapstruct->Segment = Segment;        // 段
      mapstruct->Offset = FileOffset;        // 偏移
      strcpy(mapstruct->SymbleName, StrSymbol);    // 符号名
      mapstruct->VirtualAddress = StringToHex(StrVirtualAddr);  // 线性地址
      strcpy(mapstruct->LibObject, StrLibObject);    // 输入文件名
      // 添加文件的符号信息
      MapVector.push_back(mapstruct);
    }
  }
  // 关闭文件
  fclose(mapfile);
  mapfile = NULL;
  // 遍历MapVector中的所有符号信息
  for (vector<MapStructrue*>::iterator itr = MapVector.begin(); itr != MapVector.end(); itr++)
  {
    MapStructrue * stu = * itr;
    // 获取符号段、文件偏移信息
    int a = stu->Segment;
    int b = stu->Offset;
    int nlen = strlen("__ehhandler$");
    // 如果符号名存在__e...,则将其截掉
    if (strstr(stu->SymbleName, "__ehhandler$"))
      memmove(stu->SymbleName, stu->SymbleName + nlen, strlen(stu->SymbleName) - nlen + 1);
    // 将修饰后名称转换成函数签名,失败则直接跳出循环
    // 注: 由于不同的编译器采用不同的名字修饰方法,必然会导致由不同编译器编译产生的目标
    //    文件无法正常相互链接,这是导致不同编译器之间不能互操作的主要原因之一
    if (!UnDecorateSymbolName(stu->SymbleName, stu->SymbleName, 512, 0xFFF))  // no UNDNAME_COMPLETE
    {
      // UnDecorateSymbolName failed
      DWORD error = GetLastError();
      char strerr[255] = {0};
      sprintf(strerr, "解析符号名时出现错误 %d\n", error);
      MsgBox(strerr, MB_ICONERROR);
      break;
    }
    // 输出符号信息,主要为了调试
    OutPutStr("%s %08X %s\n", stu->SymbleName, stu->VirtualAddress, stu->LibObject);
  }
}
/**
 * 根据函数名funcname获取相应的符号信息
 *
 * funcname:欲获取符号信息的函数(符号)名
 *
 * 返回值:找到相应符号信息则返回,否则返回NULL
 */
MapStructrue* CPEStructure::GetMap(char* funcname)  // *
{
  // 遍历文件所有符号信息
  for (vector<MapStructrue*>::iterator itr = MapVector.begin(); itr != MapVector.end(); itr++)
  {
    MapStructrue * stu = * itr;
    // 检测是否是欲查找符号信息
    if (_stricmp(stu->SymbleName, funcname) == 0)
      return stu;
  }
  return NULL;
}
/**
 * 为文件新区块分配一个虚拟区间,并返回其虚拟区间起始地址
 */
DWORD CPEStructure::GetNewSection()    // *
{
  // 获取PE文件的区块数
  DWORD SectionNum = image_nt_headers.FileHeader.NumberOfSections;
  return PEAlign(image_section_header[SectionNum - 1].VirtualAddress + image_section_header[SectionNum - 1].Misc.VirtualSize,
      image_nt_headers.OptionalHeader.SectionAlignment);
}

//----------------------------------------------------------------
// return values:
// 0 - no room for a new section
// 1 - file already encrypted
// else: returns a pointer to the IMAGE_SECTION_HEADER struct of the new section
/**
 * 在PE文件中添加新的区块
 *
 * Base:新区块的数据基址
 * len:新区块的数据长度
 * SectionName:新区块的名称
 *
 * 返回值:成功返回新区块的结构信息,否则NULL
 */
PIMAGE_SECTION_HEADER CPEStructure::AddSection(char * Base, int len, char * SectionName)  // *
{
  DWORD newSectionOffset;
  // 获取PE文件区块数
  DWORD SectionNum = image_nt_headers.FileHeader.NumberOfSections;
  // 计算新区块在区块表中的偏移
  newSectionOffset = dwRO_first_section + image_nt_headers.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
  // 检测是否有足够空间在放置新区块
  if (image_nt_headers.OptionalHeader.SizeOfHeaders < (newSectionOffset + sizeof(IMAGE_SECTION_HEADER)))
  {
    return NULL;
  }
  // 创建一个新的区块
  // 遍历前面所有区块,设置每个区块为可写状态
  for (DWORD i = 0; i < (SectionNum - 1); i++)
  {
    image_section_header[i].Characteristics |= IMAGE_SCN_MEM_WRITE;
  }
  // 创建一个新的区块,将最后区块表中的记录复制作为新区块的记录
  CopyMemory(&image_section_header[SectionNum], &image_section_header[SectionNum - 1], sizeof(IMAGE_SECTION_HEADER));
  // 设置新区块的虚拟地址
  image_section_header[SectionNum].VirtualAddress = PEAlign(image_section_header[SectionNum - 1].VirtualAddress +
    image_section_header[SectionNum - 1].Misc.VirtualSize, image_nt_headers.OptionalHeader.SectionAlignment);
  // 设置新区块的长度
  image_section_header[SectionNum].Misc.VirtualSize = len;
  image_section_header[SectionNum].SizeOfRawData = PEAlign(image_section_header[SectionNum].Misc.VirtualSize,
                           image_nt_headers.OptionalHeader.FileAlignment);  // bughoho
  dwOutPutSize += image_section_header[SectionNum].SizeOfRawData;
  
  // 计算区块名长度
  int l = (int)strlen(SectionName);
  FillMemory(image_section_header[SectionNum].Name, 8, 0x00);
  // 设置区块名
  CopyMemory(image_section_header[SectionNum].Name, SectionName, l);
  
  // 设置新区块的特性
  image_section_header[SectionNum].Characteristics = IMAGE_SCN_MEM_EXECUTE |
                          IMAGE_SCN_MEM_WRITE | 
                          IMAGE_SCN_MEM_READ |
                          IMAGE_SCN_MEM_EXECUTE |
                          IMAGE_SCN_CNT_UNINITIALIZED_DATA |
                          IMAGE_SCN_CNT_INITIALIZED_DATA |
                          IMAGE_SCN_CNT_CODE;
  // RawOffset
  image_section_header[SectionNum].PointerToRawData = PEAlign(image_section_header[SectionNum-1].PointerToRawData
                            + image_section_header[SectionNum - 1].SizeOfRawData,
                            image_nt_headers.OptionalHeader.FileAlignment);
  // 递增PE文件区块数
  image_nt_headers.FileHeader.NumberOfSections++;
  
  // 重新设置PE文件映像长度
  image_nt_headers.OptionalHeader.SizeOfImage = image_section_header[SectionNum].VirtualAddress +
                          image_section_header[SectionNum].Misc.VirtualSize;
  // 复制新区块的数据
  memcpy(pMem + image_section_header[SectionNum].PointerToRawData, Base, len);
  return ((PIMAGE_SECTION_HEADER)&image_section_header[SectionNum]);
}
/**
 * PE文件初始化操作
 */
void CPEStructure::Init()  // *
{
  // 未分配区块内存
  SectionAssigned       = FALSE;
  // 未分配PE保留头部
  reservedheader        = NULL;
  // 保留头部长度为0
  ReservedHeaderSize    = 0;
  // DOS头部长度为0
  ReservedHeaderRO      = 0;
  hFile                 = 0;
  dwFsize               = 0;
  dwOutPutSize          = 0;
  // 清0文件名
  memset(m_FileName, 0, 255);
  memset(m_MapFileName, 0, 255);
}
/**
 * 释放MapVector中存储的符号信息占用的资源
 *
 * 注:MapVector存放PE文件的符号信息
 */
void CPEStructure::FreeMapVector()  // *
{
  // 遍历PE文件的符号信息
  for (vector<MapStructrue*>::iterator itr = MapVector.begin(); itr != MapVector.end(); itr++)
  {
    MapStructrue * stu = * itr;
    if (stu)
    {
      delete stu;
    }
  }
  MapVector.clear();
}
/**
 * 释放为PE文件分配的资源
 */
void CPEStructure::Free()  // *
{
  // 如果PE文件的保留头部内存占用着,则释放之
  if (reservedheader)
  {
    delete[] reservedheader;
    reservedheader = NULL;
  }
  // 如果未为PE文件分配区块内存,则不需要释放
  if (!SectionAssigned)
    return;
  // 获取PE文件区块数
  DWORD SectionNum = image_nt_headers.FileHeader.NumberOfSections;
  // 遍历所有的区块,释放为其分配的内存
  for (DWORD i = 0 ;i < SectionNum; i++)
  {
    GlobalFree(image_section[i]);
    image_section[i] = NULL;
  }
  Init();
}

/**
 * 根据指定的文件名filename,创建一个新的文件,并写入虚拟机的数据
 *
 * filename:欲创建的新文件名
 * len:文件中数据的长度
 */
void CPEStructure::MakePE(char * filename, int len)    // *
{
  HANDLE handle;
  DWORD Num;
  // 根据文件名创建一个新文件
  handle= ::CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  // 文件打开失败,则出错返回
  if (INVALID_HANDLE_VALUE == handle)
  {
    MessageBox(0, "打开文件失败!", "错误", MB_OK);
    return;
  }
  ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
  // 将虚拟机写回文件
  ::WriteFile(handle, pMem, dwOutPutSize, &Num, NULL);
  ::CloseHandle(handle);
}
// ------------------------------------------------------- 相关数据结构 --------------------------------------------------------
// ------------------------------------------------------- PEStructure.h ------------------------------------------------------
// 文件中包含PE文件相关的结构信息

// #pragma once:用来防止某个头文件被多次include
#pragma once
#include "comm.h"
#include <math.h>
#include <vector>
using namespace std;
// PE文件最大区块数
#define MAX_SECTION_NUM 20
const DWORD PackerCode_Size = 0;  // 壳代码的长度
const DWORD ALIGN_CORRECTION = 0;  // 文件对齐值
/**
 * 代表PE文件的信息、数据及其相应MAP文件的信息
 *
 * 注:MAP文件是一个文本文件,记录了程序的入口地址、基地址、
 *     符号所在段、偏移信息的文本文件
 */
class CPEStructure
{
public:
  CPEStructure(void);
  ~CPEStructure(void);
private:
  // PE文件保留头部指的是DOS头部与PE头部之间的部分
  DWORD   ReservedHeaderSize;    // PE文件保留头部长度
  DWORD   ReservedHeaderRO;    // PE文件DOS头部长度
  // 表明是否已为PE文件的区块分配了存储数据的内存空间
  BOOL    SectionAssigned;
  HANDLE  hFile;          // PE文件句柄
  DWORD   dwFsize;        // PE文件长度
  DWORD   dwOutPutSize;      // 对PE文件处理后的输出长度,为PE文件长度 + 壳长度
  // 指向PE文件的缓存区,该缓存区用于存文PE文件处理后的结果
  char    * pMem;
private:
  char    m_FileName[256];    // PE文件名
  char    m_MapFileName[256];    // PE文件对应的MAP文件名
public:
  // 存储PE文件的符号信息,其每个元素是一个MapStructure,代表PE文件的符号信息
  vector<MapStructrue*> MapVector;
  // 加载并解析MAP文件的符号信息
  void LoadMap(char * pmapfilename);
  // 用于根据StrAddress符号地址得到符号的段号与文件偏移
  BOOL GetFileAddr(char * StrAddress, int * Segment, int * FileOffset);
public:
  MapStructrue * GetMap(char * funcname);
public:
  // PE文件区块表在文件中的偏移
  DWORD          dwRO_first_section;
  // DOS头部
  IMAGE_DOS_HEADER    image_dos_header;
  // 指向PE文件的保留头部信息
  char          * reservedheader;
  // PE文件头部
  IMAGE_NT_HEADERS    image_nt_headers;
  // 区块表,最多20项
  IMAGE_SECTION_HEADER  image_section_header[MAX_SECTION_NUM];
  // 为区块分配的内存,每个元素指向区块内存
  char          * image_section[MAX_SECTION_NUM];
  BOOL OpenFileName(char * FileName);
  void UpdateHeaders(BOOL bSaveAndValidate);             // 更新PE头
  void UpdateHeadersSections(BOOL bSaveAndValidate);         // 更新PE节
  DWORD PEAlign(DWORD dwTarNum, DWORD dwAlignTo);           // PE节对齐
  PIMAGE_SECTION_HEADER AddSection(char * Base, int len, char * SectionName);  // 添加节
  DWORD GetNewSection();                       // 获得新节的VirtualAddress地址
  void Init();
  void Free();
  void FreeMapVector();
  void MakePE(char * filename, int len);    // 制造一个PE
};

// 还有大量分析,等续

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功