本文中的代码均来自《黑客免杀攻防》,如要转载,需写明来源,请勿用于非法用途,作者对此文章中的代码造成的任何后果不负法律责任。本文接上一篇简单的壳1.
2.加壳部分
写这个部分的时候我自己对于这部分的代码也没有做到很熟悉,因为这部分相对于前一部分来说复杂了许多,后面就会看见。所以我也是通过再次巩固来进一步理解加壳的基本步骤。废话不多说,首先还是从声明部分说起。对了在这之前,最好是把上一部分获得的dll文件作为这个项目中的资源,以方便后面的操作。
2.1声明
这个项目中有两个自己定义的头文件,一个是和上面一样的声明文件,还有一个就是类CProcessingPE的头文件,这个类是专门做一些关于PE文件的操作的,可想而知这部分对PE文件的操作将会是多频繁。好了,那就一个一个头文件来看吧。
首先肯定是用于给Stub部分(就是上面的壳部分)传递参数的结构体,前面说过了不再细说:
typedef struct _GLOBAL_PARAM
{
BOOL bShowMessage; // 是否显示解密信息
DWORDdwOEP; // 程序入口点
PBYTElpStartVA; // 起始虚拟地址(被异或加密区)
PBYTElpEndVA; // 结束虚拟地址(被异或加密区)
}GLOBAL_PARAM,*PGLOBAL_PARAM;
接下来是一个导出函数,因为加壳这个部分生成的也是dll文件以提供界面部分调用,而调用的函数就是下面要导出的,两个参数分别是需要加壳的PE文件路径和是否显示解密之后的信息:
A1PACK_BASE_API bool A1Pack_Base(LPWSTRstrPath,bool bShowMsg);
好了,在定义CProcessingPE类之前,还有一个结构体需要定义,这个结构体用于存储被加壳的PE文件信息,里面的成员很容易理解,就不细说了:
typedef struct _PE_INFO
{
DWORD dwOEP; // 入口点
DWORD dwImageBase; // 映像基址
PIMAGE_DATA_DIRECTORYpDataDir; // 数据目录指针
IMAGE_DATA_DIRECTORY stcExport; // 导出目录
PIMAGE_SECTION_HEADERpSectionHeader; // 区段表头部指针
}PE_INFO,*PPE_INFO;
下面再来看类的定义,这里面也都有注释,各个函数的具体代码等用到的时候再详细说明:
class CProcessingPE
{
public:
CProcessingPE(void);
~CProcessingPE(void);
public:
DWORDRVAToOffset(ULONG uRvaAddr); // RVA转文件偏移
DWORDOffsetToRVA(ULONG uOffsetAddr); // 文件偏移转RVA
BOOL GetPeInfo(LPVOID lpImageData, DWORDdwImageSize, PPE_INFO pPeInfo); // 获取PE文件的信息
void FixReloc(DWORD dwLoadImageAddr); // 修复重定位信息
PVOIDGetExpVarAddr(LPCTSTR strVarName); // 获取导出全局变量的文件偏移
void SetOEP(DWORD dwOEP); // 设置新OEP
PVOIDAddSection(LPCTSTR strName, DWORD dwSize, DWORD dwChara, PIMAGE_SECTION_HEADERpNewSection, PDWORD lpSize); // 添加区段
private:
DWORD m_dwFileDataAddr; // 目标文件所在缓存区的地址
DWORD m_dwFileDataSize; // 目标文件大小
PIMAGE_DOS_HEADERm_pDos_Header; // DOS头指针
PIMAGE_NT_HEADERSm_pNt_Header; // NT头指针
PE_INFO m_stcPeInfo; // PE关键信息
};
简单了解完了一些声明之后再根据程序的流程来详细了解加壳是如何运作的。
2.2加壳代码
整个加壳的部分并没有用到Dllmain函数,而是通过MFC界面程序调用导出函数来完成的,因此需要从导出函数入手。导出函数分为八个部分,下面依次来看。
(1)生成输出文件路径
LPWSTRstrSuffix = PathFindExtension(strPath); // 获取文件的后缀名
wcsncpy_s(szOutPath,MAX_PATH,strPath,wcslen(strPath));// 备份目标文件路径到szOutPath
PathRemoveExtension(szOutPath); // 将szOutPath中保存路径的后缀名去掉
wcscat_s(szOutPath,MAX_PATH,L"_Pack"); // 在路径最后附加“_Pack”
wcscat_s(szOutPath,MAX_PATH,strSuffix); // 在路径最后附加刚刚保存的后缀名
代码很简单,这是在原路径上在原文件名后面加上一个“_Pack”以表示这是已经加壳的代码,然后产生最终的输出文件的全路径备用。
(2)获取文件信息,并映射进内存中
if( INVALID_HANDLE_VALUE ==(hFile_In=CreateFile(strPath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL)))
{ return false; }
if( INVALID_FILE_SIZE == ( dwFileSize=GetFileSize(hFile_In,NULL)) )
{ CloseHandle(hFile_In);
returnfalse;
}
if( !(lpFileImage=VirtualAlloc(NULL,dwFileSize*2,MEM_COMMIT,PAGE_READWRITE)) )
{ CloseHandle(hFile_In);
returnfalse;
}
DWORDdwRet;
if( !ReadFile(hFile_In,lpFileImage,dwFileSize,&dwRet,NULL) )
{ CloseHandle(hFile_In);
VirtualFree(lpFileImage,0,MEM_RELEASE);
returnfalse;
}
这段代码也是比较容易,显示打开文件然后获取文件大小,之后分配内存空间并将其复制进内存,这样做其实是为了不破坏原有的未加壳的PE文件。
(3)获取PE文件信息
objProcPE.GetPeInfo(lpFileImage,dwFileSize,&stcPeInfo);
这部分虽然只有一条语句,却是相当关键的,这里调用了类的成员函数GetPeInfo,三个参数分别为:文件在内存中的首地址,文件大小和输出参数(用于存储该PE文件的结构体)。接下来便是分析GetPeInfo函数。
这个函数中开始先是判断当前OEP是否为空,不为空则直接将objProcPE对象中的PE信息复制给第三个参数,直接返回,否则从文件的