You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何创建无操作(no-op)版本DLL以规避危险依赖?

解决方案:创建无操作(No-Op)版的 dangerous.dll

这问题我之前处理过不少,核心思路很明确:因为你的app.exed.dll都是加载时隐式链接,系统启动时会强制寻找dangerous.dll并加载它。我们只需要做一个“占位”的DLL,导出所有d.dll需要从dangerous.dll获取的符号,但每个导出的函数/变量都做无操作实现,这样既满足链接要求,又完全避开原危险DLL的漏洞。

下面是具体步骤:

1. 确定需要导出的符号列表

首先得搞清楚d.dll到底需要从dangerous.dll导入哪些东西。你可以用Windows SDK里的dumpbin工具来查:

  • 直接查看原dangerous.dll的导出表:
    dumpbin /exports "path/to/original/dangerous.dll"
    
  • 或者查看d.dll的导入表(更精准,因为只需要d.dll用到的符号):
    dumpbin /imports "path/to/d.dll" | findstr "dangerous.dll"
    

把输出里的所有函数名、序号记下来,还要注意函数的调用约定(比如__stdcall还是__cdecl)和参数/返回值类型——这些必须和原DLL完全一致,否则可能导致栈溢出或者崩溃。

2. 创建无操作DLL的代码和导出定义

你有两种方式定义导出符号:

方式一:使用.def文件(推荐,更直观)

先写一个dangerous.def文件,格式如下:

LIBRARY dangerous
EXPORTS
  ; 把第一步查到的所有导出函数按序号和名称列出来
  DangerousFunc1 @1
  DangerousFunc2 @2
  DangerousGlobalVar @3 DATA  ; 如果有导出变量,加DATA标记
  ; ... 其他符号

然后写一个简单的C文件dangerous.c,实现空函数和DLL入口:

#include <windows.h>

// DLL入口函数,什么都不用做,直接返回TRUE
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;
}

// 每个导出函数的无操作实现,参数和返回值必须和原DLL匹配
void __stdcall DangerousFunc1(int param1) {
    // 空实现,啥都不做
}

int __cdecl DangerousFunc2() {
    // 如果原函数有返回值,返回一个合理的默认值(比如0)
    return 0;
}

// 导出变量的定义
int DangerousGlobalVar = 0;

方式二:使用__declspec(dllexport)

如果不想写.def文件,可以在代码里直接标记导出:

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    return TRUE;
}

__declspec(dllexport) void __stdcall DangerousFunc1(int param1) {}
__declspec(dllexport) int __cdecl DangerousFunc2() { return 0; }
__declspec(dllexport) int DangerousGlobalVar = 0;

这种方式要注意,编译出来的DLL导出序号可能和原DLL不一致——如果d.dll是按序号导入的,就必须用.def文件来保证序号匹配,否则会加载失败。

3. 编译生成无操作DLL

用Visual Studio的编译器(cl.exe)编译:

  • 如果用.def文件:
    cl /LD dangerous.c dangerous.def
    
  • 如果不用.def文件:
    cl /LD dangerous.c
    

编译后会生成dangerous.dll和对应的.lib文件(其实lib文件你用不上,因为d.dll已经是编译好的)。

4. 测试验证

把生成的dangerous.dll放到app.exe的同一目录下(或者系统的DLL搜索路径里),然后启动app.exe。如果一切正常,app会顺利加载,完全不会用到原危险DLL的任何功能——因为你的app根本不会调用那些依赖危险DLL的接口,而假DLL只是满足了加载要求而已。

关键注意事项

  • 调用约定和类型匹配:一定要保证假函数的参数、返回值、调用约定和原DLL完全一致,否则d.dll调用这些函数时可能出现栈错误或者崩溃。
  • 导出序号:如果d.dll是按序号导入符号的,必须用.def文件指定和原DLL相同的序号,否则系统会找不到对应的符号。
  • 导出变量:如果原DLL有导出全局变量,假DLL里必须定义相同名称和类型的变量,不能只写函数。

内容的提问来源于stack exchange,提问作者M Katz

火山引擎 最新活动