DLL延时加载技术与资源释放
DLL延时加载技术与资源释放
0x00 前言
诸如调用非Windows的第三方库,我们或许会使用到dll文件,而这个时候原本程序运行需要相应的dll文件才能加载启动。通过DLL延时加载技术,使用延时加载的方式编译链接可执行的文件,可以先运行程序,再在依赖的DLL正式被调用时加载DLL。通常可通过资源文件写入dll,在释放完资源后,正式调用DLL时加载执行,避免丢失缺少DLL文件,从而单exe落地文件,避免麻烦。
0x01 介绍
在使用Visual Studio的场景下,如果使用DLL延时加载,只需要对项目的链接选项进行手动设置即可。
具体步骤如下:
属性—>链接器—>输入—>延迟加载的DLL—>输入:dll名称
DLL延迟加载技术的原理,其实就是在导入表中去掉xxx.dll这一项,等到DLL被正式调用的时候,才会加载DLL文件。这样,程序在正式调用DLL之前,都是可以正常执行的。
具体利用场景可以结合资源释放,在需要单exe的条件下,通过释放DLL资源,再延迟加载DLL,从而达到更方便的使用效果。
而资源释放,其实就是把一些需要额外加载的图片、文本、DLL作为资源的形式插入到主程序内,在必要的时候进行释放,使我们的主程序只有一个exe文件,降低落地计算机磁盘前期直接被发现的风险。
简单点来理解资源释放的原理,其实就是从资源中获取文件,写入到指定文件位置。
我们可以通过右键项目增加资源文件,如果文件类型不确定,可以增加自定义类型的资源文件。
0x02 编码实现
首先类似自定义的DLL,这里就写了一个小demo,起名为hello_world.dll:
Dolman.cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <cstring>
using namespace std;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) int hello()
{
int a = 666;
return a;
}
生成即自定义DLL。
再者,主程序,设置资源放入自定义dll,并设置延时加载hello_world.dll。
say_hello.cpp
// say_hello.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<windows.h>
#include "resource.h"
BOOL FreeMyResource(UINT uiResouceName, char* lpszResourceType, char* lpszSaveFileName);
void GetCurrentPath(char* lpszCurrentPath, DWORD dwSize);
void FreeRes_ShowError(char* pszText);
// CDelayLoadDll_TestDlg 消息处理程序
void FreeRes_ShowError(char* pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK);
}
// 释放资源
BOOL FreeMyResource(UINT uiResouceName, char* lpszResourceType, char* lpszSaveFileName)
{
HRSRC hRsrc = ::FindResource(NULL, MAKEINTRESOURCE(uiResouceName), lpszResourceType);
if (NULL == hRsrc)
{
FreeRes_ShowError("FindResource");
return FALSE;
}
DWORD dwSize = ::SizeofResource(NULL, hRsrc);
if (0 >= dwSize)
{
FreeRes_ShowError("SizeofResource");
return FALSE;
}
HGLOBAL hGlobal = ::LoadResource(NULL, hRsrc);
if (NULL == hGlobal)
{
FreeRes_ShowError("LoadResource");
return FALSE;
}
LPVOID lpVoid = ::LockResource(hGlobal);
if (NULL == lpVoid)
{
FreeRes_ShowError("LockResource");
return FALSE;
}
FILE* fp = NULL;
fopen_s(&fp, lpszSaveFileName, "wb+");
if (NULL == fp)
{
FreeRes_ShowError("LockResource");
return FALSE;
}
fwrite(lpVoid, sizeof(char), dwSize, fp);
fclose(fp);
return TRUE;
}
// 获取当前目录
void GetCurrentPath(char* lpszCurrentPath, DWORD dwSize)
{
::GetModuleFileName(NULL, lpszCurrentPath, dwSize);
char* p = ::strrchr(lpszCurrentPath, '\\');
p[0] = '\0';
}
int main()
{
typedef int (*_pHello)();
//std::cout << "Hello World!\n";
// 释放DLL
// 获取当前目录
char szCurrentPath[MAX_PATH] = { 0 };
GetCurrentPath(szCurrentPath, MAX_PATH);
// 构造路径
::lstrcat(szCurrentPath, "\\hello_world.dll");
FreeMyResource(IDR_TEST1, "test", szCurrentPath);
HINSTANCE hDll = LoadLibrary("hello_world.dll");
_pHello hello = (_pHello)GetProcAddress(hDll, "hello");
int nHello = hello();
std::cout << nHello << std::endl;
}
编译即可。
0x03 实测
打包生成主程序后,拿到另一台机器运行测试。单exe运行后,释放dll资源到当前目录(可指定别的目录,如temp临时目录下),调用导出函数成功,输出666.
0x04 关于报错
Tips:
关于如果Visual Studio 2019 出现const char *“ 类型的实参与 “LPCWSTR“ 类型的形参不兼容这类错误。
解决方法:
右击项目文件 — 单击属性 — 配置属性 — 高级 — 高级属性 — 字符集
使用 Unicode 字符集”改为“使用多字节字符集
关于如果Visual Studio 2019 出现4996类错误。
解决方法:
右击项目文件 — 单击属性 — 配置属性 — c/c++ — 语言 — 符合模式
修改符合模式为否。