最近一段时间,看了《加密与解密》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
};
// 还有大量分析,等续
- 标 题:虚拟机完整分析
- 作 者:shecx
- 时 间:2010-07-11 13:01:01
- 链 接:http://bbs.pediy.com/showthread.php?t=116601