winapi - find DLL path from file name [duplicate] - c++

If I do
LoadLibrary("MyTest.dll")
Windows will locate and load it from "C:\TestFolder\Test\MyTest.dll", because "C:\TestFolder\Test\" is in %PATH% folder.
How can I emulate same function? I need to locate C:\TestFolder\Test\MyTest.dll (C:\TestFolder\Test\ is in %PATH%) by passing MyTest.dll as an argument to a function. Is there such an API? or a function?
P.S. I can't do LoadLibrary and then GetModuleHandle and finding Path, sometimes this DLL could be malicious DLL and I can't load it. So I need to find PATH without having to load it.

To load the DLL without running any malicious code inside, use LoadLibraryEx with the DONT_RESOLVE_DLL_REFERENCES and LOAD_LIBRARY_AS_DATAFILE flags.
Then you can use GetModuleFileName.
You should also read about all the other flags, which allow you to perform all the various searches Windows is capable of.

The accepted answer to this question will not work in all scenarios. More specifically, using GetModuleFileName together with LOAD_LIBRARY_AS_DATAFILE will only work if the library was already loaded prior without this flag. For example, it will work for a library like KERNEL32.DLL which is already loaded by the process, but it will not work with your own library being loaded into the process for the first time.
This is because, to quote The Old New Thing, a library loaded via LOAD_LIBRARY_AS_DATAFILE (or similar flags) doesn't get to play in any reindeer module games.
If you load a library with the LOAD_LIBRARY_AS_DATA­FILE flag, then it
isn’t really loaded in any normal sense. In fact, it’s kept completely
off the books. If you load a library with the
LOAD_LIBRARY_AS_DATA­FILE, LOAD_LIBRARY_AS_DATA­FILE_EXCLUSIVE, or
LOAD_LIBRARY_AS_IMAGE_RESOURCE flag (or any similar flag added in the
future), then the library gets mapped into the process address space,
but it is not a true module. Functions like Get­Module­Handle,
Get­Module­File­Name, Enum­Process­Modules and
Create­Toolhelp32­Snapshot will not see the library, because it was
never entered into the database of loaded modules.
At that point, you might as well just use GetModuleHandle, since it'll only work with previously loaded libraries. Obviously not ideal, and doesn't actually answer the question of getting the path without executing DllMain.
What about the other flag, DONT_RESOLVE_DLL_REFERENCES? Well, technically yes it will work. However, you'll notice in the Microsoft documentation the following note.
Do not use this value; it is provided only for backward compatibility.
If you are planning to access only data or resources in the DLL, use
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE
or both.
The flag is only provided for backwards compatibility, and that is for good reason. To quote The Old New Thing a second time, DONT_RESOLVE_DLL_REFERENCES is a time bomb.
It is common for somebody to call GetModuleHandle to see if a DLL is
loaded, and if so, use GetProcAddress to get a procedure address and
call it. If the DLL had been loaded with DONT_RESOLVE_DLL_REFERENCES,
both the GetModuleHandle will succeed, but the resulting function will
crash when called. The code doing this has no idea that the DLL was
loaded with DONT_RESOLVE_DLL_REFERENCES; it has no way of protecting
itself.
Other threads will see the library is loaded. If they attempt to use the loaded library, as would be entirely normal to do, they will crash the program because it has not actually been initialized. So this flag, while it does work with GetModuleFileName, will cause instability in the program. Still not ideal.
So then, if we can't use DONT_RESOLVE_DLL_REFERENCES or LOAD_LIBRARY_AS_DATAFILE with GetModuleFileName, then what is the solution? Well, the solution is to not use GetModuleFileName - and instead use GetMappedFileName.
At this point, if you know what GetMappedFileName does, you may be confused. Normally, GetMappedFileName is used to get a filename from a File Mapping, created with the File Mapping API. Well, the secret is that under the hood, image loading is accomplished with MapViewOfFile. This is subtly hinted at by the Dbghelp documentation - for example, the ImageNtHeader documentation which states an image base must be...
The base address of an image that is mapped into memory by a call to
the MapViewOfFile function.
This means that not only is a module handle a pointer to a module, but also a mapped file pointer. Unlike GetModuleFileName however, GetMappedFileName has no concept of "the reindeer module games," so it works even with the LOAD_LIBARY_AS_DATAFILE flag of LoadLibraryEx. Not only that, but GetMappedFileName has an additional benefit over GetModuleFileName.
Something you might not know is that simply loading a library with LoadLibrary does not exclusively lock the DLL file. Try it yourself: write a simple program which loads your own library with LoadLibrary, and then while the program is running, cut and paste the DLL file to a different location. This will work (and yes, has always worked regardless of Windows version) so long as no other application has a lock on the DLL file. The File Mapping API just keeps on chugging, regardless of the DLL file's new location.
However, when you call GetModuleFileName, it will always return the path of the DLL file as of whenever the library was loaded with LoadLibrary. This has security ramifications. It would be possible to cut and paste the DLL file to a new location, and put a different one at the old location. If the path returned by GetModuleFileName is used to load the library again, it could actually result in loading a different DLL file altogether. As such, GetModuleFileName is only useful for the purpose of displaying the name or getting the DLL file name passed to LoadLibrary, and can't be depended upon for the current file path.
GetMappedFileName has no such issue, because it has no concept of when LoadLibrary was called. It returns an up to date path to the file, even if it has been moved while it's loaded.
There is one minor downside though: GetMappedFileName returns a device path, in the format of \Device\HarddiskVolume1\Example.DLL. This appears to be due to the File Mapping API originally being from DOS - why a "GetMappedFileNameEx" that returns a drive path doesn't exist is beyond me.
Thankfully, it's a resolvable issue. We can use QueryDosDevice to turn the device path into a drive path.
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 just gets a list of which drives are available (like C:, D:, etc.) in the form of a bitmask (where the first bit corresponds to A:, the second bit corresponds to B:, etc.) We then loop through the available drives, getting their paths, and comparing them against the one in the Mapped File Name. The result of this function is a path that could be passed to the CreateFile function.
The only source I could find for whether these device paths are case-insensitive or not was this book claiming that they used to be case-sensitive, but are case-insensitive as of Windows XP. I'm going to assume you are not targeting Windows 9x anymore, so I just compare them case-insensitively.
Hold on a second though: this still may not be enough. If your intention is, as mine was, to try and get a file handle to the DLL file, but using the DLL search path, then just getting the path and passing it to CreateFile opens us up to a filesystem race condition, like the kind explained in this LiveOverflow video. A technique like this could be abused by a hacker so the handle doesn't actually point to the file we want. There isn't any GetMappedFileHandle function, so what can we do?
I thought about this for a while, and here is the workaround I came up with. The idea is that we call our own getFilePathNameFromMappedView function once just to get the path to pass to CreateFile, and exclusively lock the file in place with the FILE_SHARE_READ flag. However, we then confirm, with a second call to getFilePathNameFromMappedView, that the file is still actually there. If the paths match, knowing that the file at that path is now locked, we can know for sure the handle we got is to the library that was actually loaded. If the file was moved before the call to CreateFile finished, however, the paths will not match, because GetMappedFileName returns the up to date path to the file. At that point, we can try again. I decided to do this as a recursive function, but you could decide to throw an error and have the caller handle it.
inline bool stringsCaseInsensitiveEqual(const char* leftHandSide, const char* rightHandSide) {
return !_stricmp(leftHandSide, rightHandSide);
}
bool getHandleFromModuleHandle(HMODULE moduleHandle, HANDLE &file) {
if (!moduleHandle) {
return false;
}
HANDLE currentProcess = GetCurrentProcess();
// pass the Module Handle as a Mapped View
// to get its current path
std::string filePathName = "";
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;
}
bool recursive = 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
std::string _filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, _filePathName)) {
goto error;
}
if (!stringsCaseInsensitiveEqual(filePathName.c_str(), _filePathName.c_str())) {
// race condition occured
recursive = true;
goto error;
}
}
return true;
error:
if (file && file != INVALID_HANDLE_VALUE) {
if (!CloseHandle(file)) {
return false;
}
file = NULL;
}
if (recursive) {
return getHandleFromModuleHandle(moduleHandle, file);
}
return false;
}
Then we can call it like this...
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;
}
This is just something I thought of, so let me know in the responses if there are issues with it.
Once you have a handle to the file, it can then be passed to GetFileInformationByHandle to confirm it is the same library as is loaded in another process, and subsequently closed with CloseHandle.

Related

Could DropBox interfere with DeleteFile()/rename()

I had the following code which got executed every two
minutes all day long:
int sucessfully_deleted = DeleteFile(dest_filename);
if (!sucessfully_deleted)
{
// this never happens
}
rename(source_filename,dest_filename);
Once every several hours the rename() would fail with errno=13 (EACCES). The files involved were all sitting on a DropBox directory and I had a hunch that DropBox could be the cause. I figured that it might just be possible that the DeleteFile() function may return with a non-zero successfully_deleted but actually DropBox could still be busy doing some stuff in relation to the deletion that prevented rename() from succeeding. What I did next was to change rename() to my_rename() which would attempt a rename() and upon any failure would Sleep() for one second and try a second time. Sure enough that has worked perfectly ever since. What's more, I get a diagnostic message displaying first-attempt-failures every several hours. It has never failed on the second attempt.
So you could say that the problem is entirely solved... but I would like to understand what might be going on so as to better defend myself against any related DropBox issues in the future...
Really I would like to have a new super_delete() function which does not return until the file is properly deleted and finished with in all respects.
under windows request to delete file really never delete file just. it mark it FCB (File Control Block) with special flag (FCB_STATE_DELETE_ON_CLOSE). real deletion will be only when the last file handle will be closed.
The DeleteFile function marks a file for deletion on close. Therefore,
the file deletion does not occur until the last handle to the file is
closed. Subsequent calls to CreateFile to open the file fail with
ERROR_ACCESS_DENIED.
also if exist section ( memory-mapped file ) open on file - file even can not be marked for delete. api call fail with STATUS_CANNOT_DELETE. so in general impossible always delete file.
in case exist another open handles for file (but not section !) begin from windows 10 rs1 exist new functional for delete - FileDispositionInformationEx with FILE_DISPOSITION_POSIX_SEMANTICS. in this case:
Normally a file marked for deletion is not actually deleted until all
open handles for the file have been closed and the link count for the
file is zero. When marking a file for deletion using
FILE_DISPOSITION_POSIX_SEMANTICS, the link gets removed from the visible namespace as soon as the POSIX delete handle has been closed,
but the file’s data streams remain accessible by other existing
handles until the last handle has been closed.
ULONG DeletePosix(PCWSTR lpFileName)
{
HANDLE hFile = CreateFileW(lpFileName, DELETE, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
static FILE_DISPOSITION_INFO_EX fdi = { FILE_DISPOSITION_DELETE| FILE_DISPOSITION_POSIX_SEMANTICS };
ULONG dwError = SetFileInformationByHandle(hFile, FileDispositionInfoEx, &fdi, sizeof(fdi))
? NOERROR : GetLastError();
// win10 rs1: file removed from parent folder here
CloseHandle(hFile);
return dwError;
}
Update
Sorry i didn't get the question correctly the first time. I thought DeleteFile returned error 13.
Now I understand that DeleteFile succeeds but rename fails immediatlely after.
It could be just a sync issue with the filesystem. After calling DeleteFile the file will be deleted when the OS commits the changes to the filesystem. That may not appen immediately.
If you need to perform multiple operations to the same path, you should have a look at transactions https://learn.microsoft.com/it-it/windows/desktop/api/winbase/nf-winbase-deletefiletransacteda.
-- OLD ANSWER --
That is correct. If the another application handles to that file, DeleteFile will fail.
Citing MSDN docs https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-deletefile :
The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).
This applies to dropbox, the antivirus, or in general, any other application that may open those files.
Dropbox may open the file to compute its hash (to look for changes) at any moment. Same goes with the antivirus.

Finding where a handle is open to

I'm messing around with handles / hooks, and have a question. Right now, I have a DLL that I inject into the process that I'm playing with. The DLL hooks the CloseHandle() function. When CloseHandle is called, I do the following:
int WINAPI DetourCloseHandle(HANDLE hObject)
{
OutputDebugStringA("CLOSE HADNLE");
char name[MAX_PATH];
GetFinalPathNameByHandle(hObject, name, MAX_PATH, FILE_NAME_NORMALIZED);
OutputDebugStringA(name);
return oCloseHandle(hObject);
}
My goal in this is to figure out where the handle is open to, and if the handle is open to a certain process, then use the handle to read that processes memory. What gets printed out when CloseHandle is called is usually paths to random files that the application reads, but I also noticed random ASCII characters being printed at times, as the "Name" of where the handle is opened to. This can be seen here.
Sometimes I also notice paths to certain .exe files. This is not unusual, as the application that I'm injecting into does read / look at binary files. My question is, when I see the "name" returned from GetFinalPathNameByHandle as the path to an exe file, how do I know if the handle is opened to the binary file itself, or if the handle file is opened to the actual running process with that name.
I would also like some insight as to what the ASCII characters that are being printed are. Thanks!
For the random data print you pasted, it likely was because it is just uninitialized garbage in name array, you should always check GetFinalPathNameByHandle's return value before do something with name:
DWORD ret = GetFinalPathNameByHandle(hObject, name, MAX_PATH, FILE_NAME_NORMALIZED);
if (ret) {
OutputDebugStringA(name);
} else {
OutputDebugStringA("GetFinalPathNameByHandle");
// check GetLastError()
}
Also, note that GetFinalPathNameByHandle thake the string as TCHAR strings, and you are print it via OutputDebugStringA. So I would suggest either use the ANSI version GetFinalPathNameByHandleA, or use TCHAR name[MAX_PATH]; and print with OutputDebugString instead.

LoadLibrary() : How to handle invalid DLLs?

I have an application that relies heavily on plugins.
On startup it scans a directory for DLLs and loads them one by one, looking for ones that implement a certain exported function. However - if someone were to rename a different type of file to *.dll and put it in the directory, that file would then also be loaded by LoadLibrary(). LoadLibrary() doesn't like that and produces an error [dialog].
Is there a way to simply ignore invalid / incompatible .dll files (either detecting them prior to the call or have LoadLibrary() return NULL rather than throwing a fit)?
You need to set the error mode for your process. Do this once and for all at startup:
UINT oldMode = SetErrorMode(0);
SetErrorMode(oldMode | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
After you've set the process error mode, when LoadLibrary fails no dialog box will be displayed and LoadLibrary will return NULL.
The documentation says:
Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application.
I also recommend adding SEM_NOOPENFILEERRORBOX for reasons that I guess should be obvious.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684175%28v=vs.85%29.aspx
Remarks
To enable or disable error messages displayed by the loader during DLL loads, use the SetErrorMode function.
Don't forget to restore the error mode after you are finished.
If you wanted to do this yourself, in a primitive manner you could parse the PE header to identify obvious cases of bad DLL files, however it is not really possible to determine if a DLL is valid and loadable without a full PE loader which requires a lot of work and is already built into the operating system.
Updated answer after feedback in comments from djgandy and Remy Lebeau. Now a complete
function, better preservation of error mode at entry:
// This function will load the DLL named by pszPath if it is a valid library.
// If the function succeeds, it will return a valid HMODULE for the DLL. This
// handle should be passed to FreeLibrary when it is no longer needed.
// If the function fails, it will return NULL and no annoying dialog boxes will
// be displayed. It is therefore up to the caller to notify the user about what
// happened or take any other appropriate action. The reason for failure can
// be obtained from GetLastError(). Common problems:
// ERROR_BAD_EXE_FORMAT - Bad DLL (tested function with text file)
// ERROR_MOD_NOT_FOUND - Missing DLL (tested with file that did not exist)
//
// Module-loading functions can return several other errors, look at winerror.h
// list starting at ERROR_INVALID_MODULETYPE
//
// Obviously, since it's just a wrapper around LoadLibrary this function is not
// safe to call from DllMain.
//
// NB: GetErrorMode() is only available on Vista / Server 2003 or later.
HMODULE LoadLibraryIfValid(LPCTSTR pszPath)
{
HMODULE hModule = NULL;
UINT prevErrorMode = GetErrorMode();
SetErrorMode(prevErrorMode | SEM_FAILCRITICALERRORS);
hModule = LoadLibrary(pszPath);
SetErrorMode(prevErrorMode);
return hModule;
}
If targeting Windows 7 / Server 2008 R2 or later, the Get/SetThreadErrorMode()
functions are available, but might not be worth it or even a good alternative
(discussion in comments, below)
If anyone cared enough to put the time into it (I sure don't), a version of
this function could easily be written using GetModuleHandle for kernel32 and
GetProcAddress to be compatible with earlier versions of Windows as well as
provide a global/per-thread error mode option for platforms that support it
(truly pointless because it's only changed for the duration of one call anyway).
This is the largest commentary-to-code ratio in my life.

Locate DLL path by giving a DLLName

If I do
LoadLibrary("MyTest.dll")
Windows will locate and load it from "C:\TestFolder\Test\MyTest.dll", because "C:\TestFolder\Test\" is in %PATH% folder.
How can I emulate same function? I need to locate C:\TestFolder\Test\MyTest.dll (C:\TestFolder\Test\ is in %PATH%) by passing MyTest.dll as an argument to a function. Is there such an API? or a function?
P.S. I can't do LoadLibrary and then GetModuleHandle and finding Path, sometimes this DLL could be malicious DLL and I can't load it. So I need to find PATH without having to load it.
To load the DLL without running any malicious code inside, use LoadLibraryEx with the DONT_RESOLVE_DLL_REFERENCES and LOAD_LIBRARY_AS_DATAFILE flags.
Then you can use GetModuleFileName.
You should also read about all the other flags, which allow you to perform all the various searches Windows is capable of.
The accepted answer to this question will not work in all scenarios. More specifically, using GetModuleFileName together with LOAD_LIBRARY_AS_DATAFILE will only work if the library was already loaded prior without this flag. For example, it will work for a library like KERNEL32.DLL which is already loaded by the process, but it will not work with your own library being loaded into the process for the first time.
This is because, to quote The Old New Thing, a library loaded via LOAD_LIBRARY_AS_DATAFILE (or similar flags) doesn't get to play in any reindeer module games.
If you load a library with the LOAD_LIBRARY_AS_DATA­FILE flag, then it
isn’t really loaded in any normal sense. In fact, it’s kept completely
off the books. If you load a library with the
LOAD_LIBRARY_AS_DATA­FILE, LOAD_LIBRARY_AS_DATA­FILE_EXCLUSIVE, or
LOAD_LIBRARY_AS_IMAGE_RESOURCE flag (or any similar flag added in the
future), then the library gets mapped into the process address space,
but it is not a true module. Functions like Get­Module­Handle,
Get­Module­File­Name, Enum­Process­Modules and
Create­Toolhelp32­Snapshot will not see the library, because it was
never entered into the database of loaded modules.
At that point, you might as well just use GetModuleHandle, since it'll only work with previously loaded libraries. Obviously not ideal, and doesn't actually answer the question of getting the path without executing DllMain.
What about the other flag, DONT_RESOLVE_DLL_REFERENCES? Well, technically yes it will work. However, you'll notice in the Microsoft documentation the following note.
Do not use this value; it is provided only for backward compatibility.
If you are planning to access only data or resources in the DLL, use
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE
or both.
The flag is only provided for backwards compatibility, and that is for good reason. To quote The Old New Thing a second time, DONT_RESOLVE_DLL_REFERENCES is a time bomb.
It is common for somebody to call GetModuleHandle to see if a DLL is
loaded, and if so, use GetProcAddress to get a procedure address and
call it. If the DLL had been loaded with DONT_RESOLVE_DLL_REFERENCES,
both the GetModuleHandle will succeed, but the resulting function will
crash when called. The code doing this has no idea that the DLL was
loaded with DONT_RESOLVE_DLL_REFERENCES; it has no way of protecting
itself.
Other threads will see the library is loaded. If they attempt to use the loaded library, as would be entirely normal to do, they will crash the program because it has not actually been initialized. So this flag, while it does work with GetModuleFileName, will cause instability in the program. Still not ideal.
So then, if we can't use DONT_RESOLVE_DLL_REFERENCES or LOAD_LIBRARY_AS_DATAFILE with GetModuleFileName, then what is the solution? Well, the solution is to not use GetModuleFileName - and instead use GetMappedFileName.
At this point, if you know what GetMappedFileName does, you may be confused. Normally, GetMappedFileName is used to get a filename from a File Mapping, created with the File Mapping API. Well, the secret is that under the hood, image loading is accomplished with MapViewOfFile. This is subtly hinted at by the Dbghelp documentation - for example, the ImageNtHeader documentation which states an image base must be...
The base address of an image that is mapped into memory by a call to
the MapViewOfFile function.
This means that not only is a module handle a pointer to a module, but also a mapped file pointer. Unlike GetModuleFileName however, GetMappedFileName has no concept of "the reindeer module games," so it works even with the LOAD_LIBARY_AS_DATAFILE flag of LoadLibraryEx. Not only that, but GetMappedFileName has an additional benefit over GetModuleFileName.
Something you might not know is that simply loading a library with LoadLibrary does not exclusively lock the DLL file. Try it yourself: write a simple program which loads your own library with LoadLibrary, and then while the program is running, cut and paste the DLL file to a different location. This will work (and yes, has always worked regardless of Windows version) so long as no other application has a lock on the DLL file. The File Mapping API just keeps on chugging, regardless of the DLL file's new location.
However, when you call GetModuleFileName, it will always return the path of the DLL file as of whenever the library was loaded with LoadLibrary. This has security ramifications. It would be possible to cut and paste the DLL file to a new location, and put a different one at the old location. If the path returned by GetModuleFileName is used to load the library again, it could actually result in loading a different DLL file altogether. As such, GetModuleFileName is only useful for the purpose of displaying the name or getting the DLL file name passed to LoadLibrary, and can't be depended upon for the current file path.
GetMappedFileName has no such issue, because it has no concept of when LoadLibrary was called. It returns an up to date path to the file, even if it has been moved while it's loaded.
There is one minor downside though: GetMappedFileName returns a device path, in the format of \Device\HarddiskVolume1\Example.DLL. This appears to be due to the File Mapping API originally being from DOS - why a "GetMappedFileNameEx" that returns a drive path doesn't exist is beyond me.
Thankfully, it's a resolvable issue. We can use QueryDosDevice to turn the device path into a drive path.
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 just gets a list of which drives are available (like C:, D:, etc.) in the form of a bitmask (where the first bit corresponds to A:, the second bit corresponds to B:, etc.) We then loop through the available drives, getting their paths, and comparing them against the one in the Mapped File Name. The result of this function is a path that could be passed to the CreateFile function.
The only source I could find for whether these device paths are case-insensitive or not was this book claiming that they used to be case-sensitive, but are case-insensitive as of Windows XP. I'm going to assume you are not targeting Windows 9x anymore, so I just compare them case-insensitively.
Hold on a second though: this still may not be enough. If your intention is, as mine was, to try and get a file handle to the DLL file, but using the DLL search path, then just getting the path and passing it to CreateFile opens us up to a filesystem race condition, like the kind explained in this LiveOverflow video. A technique like this could be abused by a hacker so the handle doesn't actually point to the file we want. There isn't any GetMappedFileHandle function, so what can we do?
I thought about this for a while, and here is the workaround I came up with. The idea is that we call our own getFilePathNameFromMappedView function once just to get the path to pass to CreateFile, and exclusively lock the file in place with the FILE_SHARE_READ flag. However, we then confirm, with a second call to getFilePathNameFromMappedView, that the file is still actually there. If the paths match, knowing that the file at that path is now locked, we can know for sure the handle we got is to the library that was actually loaded. If the file was moved before the call to CreateFile finished, however, the paths will not match, because GetMappedFileName returns the up to date path to the file. At that point, we can try again. I decided to do this as a recursive function, but you could decide to throw an error and have the caller handle it.
inline bool stringsCaseInsensitiveEqual(const char* leftHandSide, const char* rightHandSide) {
return !_stricmp(leftHandSide, rightHandSide);
}
bool getHandleFromModuleHandle(HMODULE moduleHandle, HANDLE &file) {
if (!moduleHandle) {
return false;
}
HANDLE currentProcess = GetCurrentProcess();
// pass the Module Handle as a Mapped View
// to get its current path
std::string filePathName = "";
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;
}
bool recursive = 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
std::string _filePathName = "";
if (!getFilePathNameFromMappedView(currentProcess, moduleHandle, _filePathName)) {
goto error;
}
if (!stringsCaseInsensitiveEqual(filePathName.c_str(), _filePathName.c_str())) {
// race condition occured
recursive = true;
goto error;
}
}
return true;
error:
if (file && file != INVALID_HANDLE_VALUE) {
if (!CloseHandle(file)) {
return false;
}
file = NULL;
}
if (recursive) {
return getHandleFromModuleHandle(moduleHandle, file);
}
return false;
}
Then we can call it like this...
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;
}
This is just something I thought of, so let me know in the responses if there are issues with it.
Once you have a handle to the file, it can then be passed to GetFileInformationByHandle to confirm it is the same library as is loaded in another process, and subsequently closed with CloseHandle.

C++ folder wont delete until I close program

In a game I'm making, folders with text files inside represent world saves, In the load menu of this game I want to have an option to delete a save. I'm using currently this code to try to delete the saves:
hFind = FindFirstFile((dir+"/*").c_str(), &FindFileData);
if (hFind){
do{
string s = FindFileData.cFileName;
if(s.find('.')){//prevents prossesing of "." and ".."
DeleteFile((dir+"/"+s).c_str());
}
}while(FindNextFile(hFind,&FindFileData));
CloseHandle(hFind);
}
rmdir(dir.c_str());
The only things in these folders are 3 text files so this code should be sufficient, however it isn't. What happens is all the file in the directory are deleted but not the folder, and if I try to delete this folder manually, or edit it in any way while the program is running, windows denies me access. But as soon as I close the game the folder is deleted.
I know the files inside are deleted because I tried the above code with out "rmdir(dir.c_str());" and opened the folder and all the files were gone, also with the above code if I "Delete" the save and then try to load it I there is no world and no inventory, indicating the files have been deleted.
I've tried it with removeDirectory and the same thing happens, It also says it was deleted successfully without any errors.
Why does this happen? How can I avoid this, and get it to work properly?
Any help would be greatly appreciated.
The problem was fixxed with the following code:
hFind = FindFirstFile((dir+"/*").c_str(), &FindFileData);
if (hFind){
do{
string s = FindFileData.cFileName;
if(s.find('.')){//prevents prossesing of "." and ".."
DeleteFile((dir+"/"+s).c_str());
}
}while(FindNextFile(hFind,&FindFileData));
CloseHandle(hFind);
}
findClose(hFind);
rmdir(dir.c_str());
According to the RemoveDirectory documentation:
RemoveDirectory function marks a directory for deletion on close. Therefore, the directory is not removed until the last handle to the directory is closed.
Probably your program has the directory as its current working directory, or perhaps otherwise still has a handle to it open.
In windows, rmdir is a comparability function that calls the native windows functions, so it will behave the same.
The root problem is that the code called CloseHandle instead of FindClose on the handle returned by FindFirstFile.
But the code has several more bugs. In the interest of helping future visitors here, this is the corrected code.
HANDLE hFind = FindFirstFile((dir + "\\*").c_str(), &FindFileData); // See 1 below
if (hFind != INVALID_HANDLE_VALUE) { // 2
do {
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { // 3
const std::string s = dir + "\\" + FindFileData.cFileName;
DeleteFile(s.c_str());
}
} while (FindNextFile(hFind, &FindFileData));
// 4
FindClose(hFind); // 5
}
RemoveDirectory(dir.c_str()); // 6
Windows paths use \ rather than / as separators. Many of the APIs will accept either, but eventually you'll encounter one that doesn't, so it's best to use the correct one consistently.
FindFirstFile returns INVALID_HANDLE_VALUE (not NULL) upon failure. Since INVALID_HANDLE_VALUE is non-zero, you cannot simply test if (hFile) { ... }.
The API enumerates files and directories. The old code was trying to filter out the . and .. directories incorrectly, which could have caused it to skip some files and to attempt to use DeleteFile on other directories. It's simpler (and easier-to-understand) to skip all directories.
Don't call CloseHandle on the handle returned by FindFirstFile.
Do call FindClose on the handle returned by FindFirstFile, but do so only in the case you got a valid handle from FindFirstFile.
As long as you're using Windows-specific APIs, you may as well use them consistently and not mix with library wrappers like rmdir. The library wrappers sometimes introduce surprising limitations or behavior, though I think rmdir would work correctly in this instance.
This still leaves a significant problem: It doesn't handle Unicode in the paths (and it requires you to compile for ANSI which limits Unicode handling in other parts of the project).