通过给出DLLName来定位DLL路径
Locate DLL path by giving a DLLName
如果我做了
LoadLibrary("MyTest.dll")
Windows将从"C:TestFolderTestMyTest.dll"
找到并加载它,因为"C:TestFolderTest"
在%PATH%
文件夹中。
如何模拟相同的函数?我需要通过传递MyTest.dll
作为函数的参数来定位C:TestFolderTestMyTest.dll
(C:TestFolderTest
在%PATH%
中)。有这样的API吗?还是一个函数?
注:我不能做LoadLibrary,然后GetModuleHandle和查找路径,有时这个DLL可能是恶意的DLL,我不能加载它。所以我需要找到PATH而不需要加载它
要加载DLL而不运行任何恶意代码,请使用LoadLibraryEx
和DONT_RESOLVE_DLL_REFERENCES
和LOAD_LIBRARY_AS_DATAFILE
标志。
那么你可以使用GetModuleFileName
。
您还应该阅读所有其他标志,它们允许您执行Windows所能执行的所有各种搜索。
这个问题的公认答案并不适用于所有情况。更具体地说,使用GetModuleFileName
和LOAD_LIBRARY_AS_DATAFILE
只会在库已经加载之前没有这个标志的情况下工作。例如,它可以工作在一个像KERNEL32.DLL这样的库中,这个库已经被进程加载了,但是它不能工作在你自己的库第一次被加载到进程中。
这是因为,引用The Old New Thing,通过LOAD_LIBRARY_AS_DATAFILE(或类似标志)加载的库不会在任何驯鹿模块游戏中发挥作用。
如果您加载带有LOAD_LIBRARY_AS_DATA-FILE标志的库,则它没有正常意义上的装载。事实上,它保存完好不入账。加载一个库LOAD_LIBRARY_AS_DATA-FILE、LOAD_LIBRARY_AS_DATA-FILE_EXCLUSIVE或LOAD_LIBRARY_AS_IMAGE_RESOURCE标志(或在(未来),然后库被映射到进程地址空间,但它不是一个真正的模块。像Get-Module-Handle,Get-Module-File-Name, Enum-Process-Modules和Create-Toolhelp32-Snapshot将看不到库,因为它是从未进入加载模块的数据库。
此时,您可能只使用GetModuleHandle
,因为它只适用于先前加载的库。显然不是理想的,并且实际上没有回答在不执行DllMain的情况下获取路径的问题。
另一个标志DONT_RESOLVE_DLL_REFERENCES
呢?嗯,从技术上讲,它是可行的。然而,你会在微软文档中注意到以下注释:
不要使用这个值;它只提供向后兼容性。如果您计划仅访问DLL中的数据或资源,请使用LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE或LOAD_LIBRARY_AS_IMAGE_RESOURCE或者两者兼有。
这个标志只是为了向后兼容而提供的,这是有充分理由的。DONT_RESOLVE_DLL_REFERENCES是一个定时炸弹。
有人调用GetModuleHandle来查看DLL是否存在是很常见的如果是,则使用GetProcAddress获取过程地址和调用它。如果DLL是用DONT_RESOLVE_DLL_REFERENCES加载的,GetModuleHandle都将成功,但是结果函数将成功调用时崩溃。执行此操作的代码不知道DLL是什么加载了DONT_RESOLVE_DLL_REFERENCES;它没有办法保护本身。
其他线程将看到库已加载。如果它们试图使用加载的库(这是完全正常的),它们将使程序崩溃,因为它实际上还没有初始化。所以这个标志,虽然它与GetModuleFileName
一起工作,但会导致程序不稳定。还是不理想
那么,如果我们不能将DONT_RESOLVE_DLL_REFERENCES
或LOAD_LIBRARY_AS_DATAFILE
与GetModuleFileName
一起使用,那么解决方案是什么?那么,解决方案是不使用GetModuleFileName
,而是使用GetMappedFileName
。
GetMappedFileName
的作用,您可能会感到困惑。通常,GetMappedFileName
用于从使用文件映射API创建的文件映射中获取文件名。好吧,秘密是在引擎盖下,图像加载是由MapViewOfFile
完成的。Dbghelp文档巧妙地暗示了这一点——例如,ImageNtHeader文档规定图像库必须是…
通过调用映射到内存中的映像的基址MapViewOfFile函数
这意味着模块句柄不仅是指向模块的指针,也是映射文件指针。然而,与GetModuleFileName
不同的是,GetMappedFileName
没有"驯鹿模块游戏"的概念。所以它甚至可以与LoadLibraryEx
的LOAD_LIBARY_AS_DATAFILE
标志一起工作。不仅如此,GetMappedFileName
比GetModuleFileName
有更多的好处。
你可能不知道的是,简单地用LoadLibrary加载一个库并不会独占地锁定DLL文件。你自己试试:写一个简单的程序,用LoadLibrary
加载你自己的库,然后在程序运行时,将DLL文件剪切并粘贴到不同的位置。只要没有其他应用程序对DLL文件有锁,这就可以工作(是的,无论Windows版本如何都一直工作)。无论DLL文件的新位置如何,文件映射API都会继续运行。
然而,当你调用GetModuleFileName
时,它将始终返回DLL文件的路径,无论何时使用LoadLibrary加载库。这有安全隐患。可以将DLL文件剪切并粘贴到新位置,并将另一个DLL文件放在旧位置。如果使用GetModuleFileName
返回的路径再次加载库,实际上可能导致加载完全不同的DLL文件。因此,GetModuleFileName
仅用于显示名称或获取传递给LoadLibrary
的DLL文件名,而不能依赖于当前文件路径。
GetMappedFileName
没有这样的问题,因为它没有LoadLibrary
何时被调用的概念。它返回文件的最新路径,即使它在加载时已被移动。
有一个小缺点:GetMappedFileName
返回一个设备路径,格式为DeviceHarddiskVolume1Example.DLL
。谢天谢地,这是一个可以解决的问题。我们可以使用QueryDosDevice
将设备路径转换为驱动器路径。
bool getFilePathNameFromMappedView(HANDLE process, LPVOID mappedView, std::string &filePathName) {
if (!process) {
return false;
}
if (!mappedView) {
return false;
}
CHAR mappedFileName[MAX_PATH] = "";
if (!GetMappedFileName(process, mappedView, mappedFileName, MAX_PATH - 1)) {
return false;
}
// the mapped file name is a device path, we need a drive path
// https://learn.microsoft.com/en-us/windows/win32/fileio/defining-an-ms-dos-device-name
const SIZE_T DEVICE_NAME_SIZE = 3;
CHAR deviceName[DEVICE_NAME_SIZE] = "A:";
// the additional character is for the trailing slash we add
size_t targetPathLength = 0;
CHAR targetPath[MAX_PATH + 1] = "";
// find the MS-DOS Device Name
DWORD logicalDrives = GetLogicalDrives();
do {
if (logicalDrives & 1) {
if (!QueryDosDevice(deviceName, targetPath, MAX_PATH - 1)) {
return false;
}
// add a trailing slash
targetPathLength = strnlen_s(targetPath, MAX_PATH);
targetPath[targetPathLength++] = '';
// compare the Target Path to the Device Object Name in the Mapped File Name
// case insensitive
// https://flylib.com/books/en/4.168.1.23/1/
if (!_strnicmp(targetPath, mappedFileName, targetPathLength)) {
break;
}
}
deviceName[0]++;
} while (logicalDrives >>= 1);
if (!logicalDrives) {
return false;
}
// get the drive path
filePathName = std::string(deviceName) + "\" + (mappedFileName + targetPathLength);
return true;
}
GetLogicalDrives
只是以位掩码的形式获得可用驱动器(如C:, D:等)的列表(其中第一个位对应于a:,第二个位对应于B:等),然后我们循环遍历可用驱动器,获得它们的路径,并将它们与映射文件名中的路径进行比较。这个函数的结果是一个可以传递给CreateFile
函数的路径。
关于这些设备路径是否不区分大小写,我能找到的唯一来源是这本书,它声称它们曾经是区分大小写的,但在Windows XP中是不区分大小写的。我假设你的目标不再是Windows 9x,所以我只是比较它们不区分大小写。
编辑:在写完这个之后,我才意识到有一种更好的方法来写这个函数。只需在路径前面加上"\?GLOBALROOT"该文件可以直接由CreateFile
打开,而不需要经过QueryDosDevice
。据我所知,自Windows NT以来,这一直有效,所以这样做没有缺点,但我保留了最初的实现。下面是getFilePathNameFromMappedView
的简单实现。
bool getFilePathNameFromMappedView(HANDLE process, LPVOID mappedView, std::string &filePathName) {
if (!process) {
return false;
}
if (!mappedView) {
return false;
}
CHAR mappedFileName[MAX_PATH] = "";
if (!GetMappedFileName(process, mappedView, mappedFileName, MAX_PATH - 1)) {
return false;
}
filePathName = "\\?\GLOBALROOT" + std::string(mappedFileName);
return true;
}
稍等:这可能还不够。如果你的意图是,就像我一样,试图获得一个文件句柄到DLL文件,但是使用DLL搜索路径,那么只需获得路径并将其传递给CreateFile
,就可以打开一个文件系统竞争条件,就像这个LiveOverflow视频中解释的那样。这样的技术可能会被黑客滥用,因此句柄实际上并没有指向我们想要的文件。没有任何GetMappedFileHandle
函数,我们该怎么办?
我想了一会儿,这是我想出的变通办法。我们的想法是,我们调用自己的getFilePathNameFromMappedView
函数一次,只是为了获得传递给CreateFile
的路径,并使用FILE_SHARE_READ
标志专门锁定文件。然而,我们随后通过对getFilePathNameFromMappedView
的第二次调用来确认文件实际上仍然在那里。如果路径匹配,知道该路径上的文件现在被锁定,我们就可以确定得到的句柄指向实际加载的库。但是,如果在对CreateFile
的调用完成之前移动了文件,则路径将不匹配,因为GetMappedFileName
返回文件的最新路径。到那时,我们可以再试一次。我使用scope_guard是为了确保句柄在失败时关闭。
inline bool stringsCaseInsensitiveEqual(const char* leftHandSide, const char* rightHandSide) {
return !_stricmp(leftHandSide, rightHandSide);
}
inline bool closeHandle(HANDLE &handle) {
if (handle && handle != INVALID_HANDLE_VALUE) {
if (!CloseHandle(handle)) {
return false;
}
}
handle = NULL;
return true;
}
bool getHandleFromModuleHandle(HMODULE moduleHandle, HANDLE &file) {
if (!moduleHandle) {
return false;
}
bool result = true;
HANDLE currentProcess = GetCurrentProcess();
std::string filePathName = "";
std::string filePathName2 = "";
const int MAX_ATTEMPTS = 10;
for (int i = 0; i < MAX_ATTEMPTS; i++) {
// pass the Module Handle as a Mapped View
// to get its current path
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, filePathName)) {
return false;
}
{
// prevent the Example File from being written to, moved, renamed, or deleted
// by acquiring it and effectively locking it from other processes
file = CreateFile(filePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file || file == INVALID_HANDLE_VALUE) {
return false;
}
MAKE_SCOPE_EXIT(fileCloseHandleScopeExit) {
if (!closeHandle(file)) {
result = false;
}
};
// we now know this path is now protected against race conditions
// but the path may have changed before we acquired it
// so ensure the File Path Name is the same as before
// so that we know the path we protected is for the Mapped View
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, filePathName2)) {
return false;
}
if (stringsCaseInsensitiveEqual(filePathName.c_str(), filePathName2.c_str())) {
fileCloseHandleScopeExit.dismiss();
return result;
}
}
// if an error occured, return
if (!result) {
return result;
}
}
return false;
}
那么我们可以这样称呼它
HMODULE exampleModuleHandle = LoadLibraryEx("Example.DLL", NULL, LOAD_LIBRARY_AS_DATAFILE);
if (!exampleModuleHandle) {
return false;
}
// we want this to be a handle to the Example File
HANDLE exampleFile = NULL;
if (!getHandleFromModuleHandle(exampleModuleHandle, exampleFile)) {
return false;
}
这只是我想到的一些东西,所以如果有问题,请在回复中告诉我。
一旦你有了一个文件的句柄,它就可以被传递给GetFileInformationByHandle
,以确认它与另一个进程中加载的库是相同的,然后用CloseHandle
关闭。
- 挂起和取消挂起一个文件DLL
- std::threads可以从Windows DLL中的全局变量创建/销毁吗?
- 导入库可以跨dll版本工作吗
- 从C++dll访问C#中的一行主要参数
- 链接到自行创建的dll失败
- 为什么使用 P/Invoke 调用 dll 时,某些计算机中的 LoadLibrary 失败?
- 在调用FreeLibrary后,释放动态链接到具有相同版本的CRT堆的DLL的内存
- 如何指定我希望我的LIB链接到的DLL文件?-Visual Studio 2019
- 如何将图像传输到c++(dll)中的缓冲区,然后在c#的缓冲区中读/写
- C++:将外部库链接到dll库
- 在 Windows 上,是否可以让 dll 在不使用 PATH 环境变量的情况下在另一个文件夹中查找依赖项?
- 不同的Visual Studio版本中缺少.dll
- 从DLL中删除类的实例
- 如何包装第三方DLL在R中使用
- 重新定位图像时如何前进到下一个内存块
- 使用c#访问c++dll中带有char*参数的函数时发生AccessViolationException
- 系统.将数组移交给c#中动态加载的c++DLL时发生AccessViolationException
- 为什么导入Mixed native/CLR lib.dll的本机C++应用程序没有在Mixed lib.dll中的外部变
- 通过给出DLLName来定位DLL路径
- 跨DLL边界的矢量内存重定位