本文经原作者授权,节选自《Windows黑客编程技术详解》一书。文末有福利哦!!
-----------------------------------------------------------------
病毒木马之所以会广泛使用资源释放技术,是因为它可以使程序变得更简洁。如果程序额外需要加载一些DLL文件、文本文件、图片文件,或者其他的音/视频文件等,则可以把它们作为资源插入到程序里,等到程序运行后,再把它们释放到本地上。这样做的好处是编译出来的程序只有一个exe文件,而不需要附带其他文件,因而程序变得很简洁。只需把exe植入到用户计算机上,而不需要连同其他文件一起植入,这降低了被发现的风险。
在介绍资源释放技术之前,先介绍如何向程序中插入资源。资源插入不需要编码操作,只需手动设置VS开发环境即可完成。
本节以“520”这个没有文件类型的文件作为演示实例,向大家介绍文件作为资源插入到程序中的步骤,其他类型的插入也是类似的。其中,“520”的文件内容如图2-5所示。
打开项目工程之后,在解决方案中,选择“添加”,选中“资源”。本节演示的是插入自定义资源,所以单击“自定义(C)...”按钮。资源添加对话框,如图2-6所示。
然后,在“新建自定义资源”对话框中,输入“资源类型”,如“MYRES”,然后单击“确定”。新建自定义资源对话框,如图2-7所示。
在设置好自定义资源的类型之后,接着回到“添加资源”对话框。选中刚新建的“MYRES”资源类型,然后单击右侧的“导入(M)...”按钮来选择导入文件。
经过上述步骤后便完成了插入资源的操作。
确定模块中指定类型和名称的资源所在位置。
函数声明
HRSRC FindResource(
HMODULE hModule,
LPCWSTR lpName,
LPCWSTR lpType)
参数
hModule[in]
处理包含资源的可执行文件模块。若hModule为NULL,则系统从当前进程的模块中装载资源。
lpName[in]
指定资源名称。
lpType[in]
指定资源类型。
返回值
如果函数运行成功,那么返回值为指定资源信息块的句柄。可将这个句柄传递给LoadResource函数来获得这些资源。如果函数运行失败,则返回值为NULL。
获取指定资源的字节数。
函数声明
DWORD SizeofResource(
HMODULE hModule,
HRSRC hResInfo)
参数
hModule[in]
包含资源的可执行文件模块的句柄。若hModule为NULL,则系统从当前进程的模块中装载资源。
hResInfo[in]
资源句柄。此句柄必须由函数FindResource或FindResourceEx来创建。
返回值
如果函数运行成功,则返回值为资源的字节数;如果函数运行失败,则返回值为零。
装载指定资源到全局存储器。
函数声明
HGLOBAL LoadResource(
HMODULE hModule,
HRSRC hResInfo)
参数
hModule[in]
处理资源可执行文件的模块句柄。若hModule为NULL,则系统从当前进程的模块中装载资源。
hResInfo[in]
资源句柄。此句柄必须由函数FindResource或FindResourceEx来创建。
返回值
如果函数运行成功,则返回值为相关资源数据的句柄。如果函数运行失败,则返回值为NULL。
锁定资源并得到资源在内存中第一个字节的指针。
函数声明
LPVOID LockResource(
HGLOBAL hResData)
参数
hResData[in]
装载资源的句柄。函数LoadResource可以返回这个句柄。
返回值
如果装载资源被锁住了,则返回值是资源第一个字节的指针;反之则为NULL。
为方便开发人员获取程序里的资源,Windows提供了一系列带有操作资源的WIN32 API函数。所以,程序实现也是基于这些WIN32 API函数进行操作的。
首先,通过FindResource定位程序里的资源,主要是根据“资源类型”和“资源名称”进行定位,从而获取资源信息块的句柄。
其次,根据上面获取的资源信息块的句柄,利用SizeofResource获取资源的大小之后,再通过LoadResource把资源加载到程序内存中。
接着,通过LockResource锁定加载到内存中的资源,防止程序中的其他操作影响这块内存。其中,返回值就是资源在进程内存中的起始地址。
最后,根据资源大小以及进程内存的起始地址,可将资源数据读取出来并保存为本地文件。
经过上述4个步骤,便可以定位出资源,并将其释放到本地磁盘。它的原理就是通过PE文件结构,确定资源在PE文件中的偏移和大小。
在资源释放过程中,要特别注意一点就是,必须明确资源所在的模块,要指明所在模块句柄并且统一。因为文件可以以资源的形式插入到DLL文件中,所以当DLL加载到其他进程时,资源所在模块仍是该DLL模块。要想成功释放资源,则需要先通过GetModuleHandle函数获取该DLL模块的句柄。否则,资源释放会因为指定了错误模块而失败。
BOOL FreeMyResource(UINT uiResouceName, char *lpszResourceType, char *lpszSaveFileName)
{
// 获取指定模块里的资源
HRSRC hRsrc = ::FindResource(NULL, MAKEINTRESOURCE(uiResouceName), lpszResourceType);
if (NULL == hRsrc)
{
ShowError("FindResource");
return FALSE;
}
// 获取资源的大小
DWORD dwSize = ::SizeofResource(NULL, hRsrc);
if (0 >= dwSize)
{
ShowError("SizeofResource");
return FALSE;
}
// 将资源加载到内存里
HGLOBAL hGlobal = ::LoadResource(NULL, hRsrc);
if (NULL == hGlobal)
{
ShowError("LoadResource");
return FALSE;
}
// 锁定资源
LPVOID lpVoid = ::LockResource(hGlobal);
if (NULL == lpVoid)
{
ShowError("LockResource");
return FALSE;
}
// 保存资源为文件
FILE *fp = NULL;
fopen_s(&fp, lpszSaveFileName, "wb+");
if (NULL == fp)
{
ShowError("LockResource");
return FALSE;
}
fwrite(lpVoid, sizeof(char), dwSize, fp);
fclose(fp);
return TRUE;
}
本节创建一个MFC工程项目,按照上述步骤插入资源,并按照上述的实现原理来编码实现,调用封装好的资源释放函数进行资源释放的测试。资源释放的时候,将其保存为txt格式文件。
单击对话框中“释放”按钮后,提示资源释放成功,如图2-8所示。然后查看目录,本地成功地生成“520.txt”文件,打开文件查看内容,它与之前插入的“520”文件中的内容相同,如图2-9所示。资源释放成功。
资源释放技术的实现原理并不是很复杂,只需理清WIN32 API函数的调用关系以及函数作用即可。要特别注意一点,明确资源所在的模块,如果资源包含在DLL文件中,则可以在DllMain中或是通过GetModuleHandle函数获取DLL模块的句柄。
可以根据PE结构中的资源表IMAGE_RESOURCE_DIRECTORY来解析PE文件中包含的所有资源,并且获取资源的偏移地址及数据大小。例如,常用的资源编辑工具eXeScope就是根据资源表来枚举PE文件中的资源的。