How to use `/DELAYLOAD` on a DLL from another DLL - c++

I have a solution with two DLLs. The first one is the "main" DLL. It happens to be an ODBC driver, but I think that is not important for this question.
The second DLL contains all the UI logic for the first one. As the UI is not always needed, I want to use the /DELAYLOAD feature which explicitly says:
The delayed loading of a DLL can be specified during the build of
either a .EXE or .DLL project.
The main DLL's project correctly references the UI ones. If I don't use /DELAYLOAD, everythin works just fine. The two DLLs will be installed into the same directory, so I thought loading one DLL from within the other should be easy. But apparently, it's not.
As soon as the first function from the UI DLL is called, the application (any ODBC client in my case) crashes.
GetLastError() yields 126 which apparently means that the target DLL could not be found in any of the search paths.
And indeed, according to this answer LoadLibrary() does have a look into the directory of the calling executable, but not into the one of the currently executed DLL. I'm assuming /DELAYLOAD is also just using LoadLibrary() under the hood, is that correct?
If I copy the executable into the installation directory of my driver, it works just fine, which proves my assumption that it just doesn't look in the current DLL's directory.
Appart from that, I was also able to make it run by calling
LoadLibrary(L"C:\\absolute\\path\\to\\UI.dll");
just before the first function of the UI DLL is loaded.
I was also able to determine this path programmatically using
wchar_t buffer[512];
GetModuleFileName(hThisDLL, buffer, sizeof(buffer));
But then I would have to cover every single UI call with this logic. So I wouldn't see much advantage anymore that /DELAYLOAD has over the "old-school" way of using LoadLibrary() and GetProcAddress().
Question
Is there a simple way to make /DELAYLOAD find the target DLL from another DLL in the same directory?

There is. My suggestion would be to create a delay-load-failure hook function.
https://learn.microsoft.com/en-us/cpp/build/reference/failure-hooks?view=vs-2019
Basically, you write a function inside your main DLL that gets notified in the event of a delay load failure. In that function, when the given code indicates failure, you try manually calling LoadLibrary with of a path consisting of the folder in which your main DLL resides plus the name of the DLL that failed to load
How you get the your main DLL from within your main DLL is up to you. There are many ways.
Something like this:
FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
FARPROC fpRet = NULL;
switch (dliNotify)
{
case dliStartProcessing:
break;
case dliNotePreLoadLibrary:
break;
case dliNotePreGetProcAddress:
break;
case dliFailLoadLib:
{
std::string newPath = GetMyModulePath();
newPath += "\\";
newPath += pdli->szDll;
fpRet = reinterpret_cast<FARPROC>(::LoadLibrary(csDir));
}
break;
case dliFailGetProc:
break;
case dliNoteEndProcessing:
break;
default:
break;
}
return fpRet;
}
//
// Set access to our delay load hook.
//
PfnDliHook __pfnDliFailureHook2 = delayHook;

Related

Avoid dll pre-loading vulnerability by reloading dll, any better way?

To avoid dll pre-loading I tried to use SetDllDirectory("") as it is mentioned in MSDN to remove current directory from search path.
The directory to be added to the search path. If this parameter is an
empty string (""), the call removes the current directory from the
default DLL search order. If this parameter is NULL, the function
restores the default search order.
Surprisingly it didn't work. I can see dlls are still getting loaded from current directory if dll present there. I also tired to set dll directory path as system 32 path, but still it picks dll from current directory. Lastly I decide to get all the modules loaded in my application and reloading them again. My code looks like this,
if(wcsstr(szModName,L"TestLibrary.dll"))
{
FreeLibrary(hMods[i]);
LoadLibrary("SomeRelavantPath\TestLibrary.dll");
}
Do you see any problem with my code?
#Edit:
My Complete POC code,
int main( void )
{
SetDllDirectory(L"");
LPWSTR s = new WCHAR[100];
GetDllDirectory(100,s);
HINSTANCE myDLL = LoadLibrary(L"TestLibrary.dll");
//myDLL returns non-null, there is file in current dir and not at any other location, it should have return null.
return 0;
}
Unloading and loading again with a new path is pointless from a security perspective, it is already too late because a evil .dll could execute code in its DllMain!
The best solution is to directly link to as few DLLs as possible and call SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32) before anything else in your WinMain function.
If the string specifies a full path, the function searches only that path for the module.
If the string specifies a relative path or a module name without a path, the function uses a standard search strategy to find the module; for more information, see the Remarks.
From LoadLibrary function # MSDN.
Calls to SetDllDirectory will do nothing against full paths.

Which module called my DLL exported function?

Background
I'm developing a C++ windows DLL module that exports a single function
__declspec(dllexport) void Run()
Motivation
I would like to implement some sort of accessing rights to my function. I wish to prevent unauthorized modules from activating my DLL proc.
I don't need a robust/bullet proof mechanism. I only like to "defend" this proc from other modules running under my own app.
Approach
Get the calling module name and decide based on the name if access is granted.
Question
Would this approach suffice?
if so, how do I get the name of the calling module?
if so, how do I get the name of the calling module?
get return address by call _ReturnAddress
get base address of the image that contains this return address -
RtlPcToFileHeader
finally call GetModuleFileName function
so code can be like this
HMODULE hmod;
if (RtlPcToFileHeader(_ReturnAddress(), (void**)&hmod))
{
WCHAR sz[MAX_PATH];
if (GetModuleFileName(hmod, sz, MAX_PATH))
{
DbgPrint("%p %S\n", hmod, sz);
}
}
about - are this work in XP ? yes, but with one note. _ReturnAddress is CL intrinsic - so not depended from os version (for say gcc exist __builtin_return_address (0) ) GetModuleFileName also very old api function and exist in win2000, xp, everywhere. about RtlPcToFileHeader
- it exported (and implemented) in ntdll.dll in all windows versions from xp to latest. also begin from win2003 it also exported from kernel32.dll but implementation here - simply jump to ntdll.RtlPcToFileHeader - so if want use this on xp also - link with ntdll.lib and place it before kernel32.lib in libs order or can get it it runtime by GetProcAddress(GetModuleHandle(L"ntdll"), "RtlPcToFileHeader");
or even if somebody afraid that RtlPcToFileHeader will be removed from ntdll (this is of course no) can use this
GetProcAddress(GetModuleHandle(g_xp ? L"ntdll" : L"kernel32"), "RtlPcToFileHeader");

Call "main" function programmatically in Windows

I have a third-party console application. I need run it from my application but I cannot run it as a separate process (because I need to work with its dependencies: fill Import tables manually, setup hooks etc.). So probably I should call main function of this executable manually. Here is how I'm trying to do this:
Load this EXE using auto hMod = LoadLibrary("console_app.exe")
Fill Import table of this exe manually
Get entry point of this EXE and call it
And I'm stuck with the last step.
Here is how I'm trying to call entry point:
void runMain(HINSTANCE hInst)
{
typedef BOOL(WINAPI *PfnMain)(int, char*[]);
auto imageNtHeaders = ImageNtHeader(hInst);
auto pfnMain = (PfnMain)(DWORD_PTR)(imageNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)hInst);
char* args[] = { R"(<console_app_path>)", R"(arg1)", R"(arg2)" };
pfnMain(3, args);
}
It works. But it works as if there is no arguments.
Where am I wrong? How can I run an executable inside my process with arguments? Thanks.
UPDATE:
I've investigated how my particular third-party exe gets cmd arguments and found that:
It doesn't import GetCommandLine at all and do not call it
After call _initterm call argc and argv arguments are available through cs:argc and cs:argv (see pictures below)
CMD arguments that I pass to my main console app are transferred to
child EXE too.
Can you explain, please, what _initterm actually do and where CMD arguments are actually stored?
You're calling the entry point of the application, not int main(int, char**). Now you may have read that the entry point of a C++ program is int main(int, char**) but that's just a C++ perspective.
The Win32 perspective is different; the entry point is a int (*)(void);. The Visual Studio linker looks for int mainCRTStartup(void); and uses that, unless you specify another entry point with /ENTRY. The default implementation of mainCRTStartup calls GetCommandLine() to fill in argv[] before calling main(argc,argv). There are also other things in mainCRTStartup which you might want to happen: run global ctors, initialize the CRT state, ...
Of course, that's assuming the other program was compiled with Visual C++, but whatever language it's been written in, it must be calling GetCommandLine.
Now, for your problem, here's an interesting observation: GetCommandLine() returns a writeable pointer. You can overwrite the existing command line. Of course, if you control the import tables, you decide what GetCommandLine means. (Remember, as usual there are A and W variants).
One warning: the MSVCRT isn't designed to be initialized twice, neither the static version nor the DLL one. So practically speaking you can't use it, and that will hurt.
[edit]
Your update shows a call to _initterm. That's a MSVCRT function, as I already hinted. Specifically,
/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
...
/*
* routine in DLL to do initialization (in this case, C++ constructors)
*/
extern int __cdecl _initterm_e(_PIFV *, _PIFV *);
extern void __cdecl _initterm(_PVFV *, _PVFV *);
The MSVCRT DLL calls GetCommandLine() on behalf of the EXE.
the entrypoint of executable (EP) have no arguments - so you and can not direct call it with arguments.
usual application got arguments by parsing command line. [w]mainCRTStartup do this - if you have console application linked to c/c++ runtime - this is real EP .
so if you Fill Import table of this exe manually - set exception for GetCommandLineA and GetCommandLineW functions - redirect it to self implementation and return your custom command line.
but if app used not static linked CRT it can import __getmainargs or __wgetmainargs or even _acmdln, or _wcmdln from msvcrt.dll - so already task become complex.
and you assume that relocs exits in EXE, you not handle TLS if it exist, you not handle application manifest, possible dl redirections, etc.
but I cannot run it as a separate process
this is not true. you can and must run it as separate process - this is the best solution.
exec your app by CreateProcess with CREATE_SUSPENDED flag. here you free easy set any CommandLine which you need. you not need manually and not fully correct load EXE but system do this task for you.
after process is created you need inject self DLL to it by using QueueUserAPC (but not CreateRemoteThread !!) and finally call ResumeThread
as result your DLL will be loaded and executed in first EXE thread, just before application EP - and here you can do all needed tasks

Load dll function from other directory

in my new project, I got a problem about loading dll.
My file structure is like below:
D:\dll_directory\calculate.dll
D:\myproject\myproject.vcxproj
D:\running_place\myproject.exe
There're my code,
HMODULE h = LoadLibrary("D:\\dll_directory\\calculate.dll");
if(!h)
return GetLastError();
pfnInit = (PInit)GetProcAddress(h, "initialize");
if(!pfnInit())
{
// Fail
}
In my code of calculate.dll, I need to get the working directory so I use
int initialize() {
...
GetModuleFileName(...,myPath,...);
currentPath = ...; // get directory from path
filePath = currentPath + ...
if(!DoesExist(filePath))
return 0;
...
}
The problem is there. Because I loadlibrary dll from "D:\dll_directory\" in other directory, currentPath value is wrong.
In debugging, currentPath is "D:\myproject\debug..." and in executing, it's "D:\running_place\".
Only if place myproject.exe into the same place with calculate.dll, it shows correctly, currentPath = "D:\dll_directory\".
But I don't want to place dll manually because of many files and many places.
Additionally, I can not change code in dlls, it's stable.
Are there any ways to make it work?
Thanks in advance.
Updated: I modify my question to more clearer. Sr about this. It's my first time on stackoverflow.
In function Init of DLL, it need to run other files that are placed in the same place with it. The expected of currentPath is directory of DLL.
EXE in dir1 loads DLL in dir2, EXE calls function Init of DLL -> currentPath=dir1 (wrong).
Try to change DLL code with GetCurrentDirectory -> currentPath=dir1; still wrong.
Put EXE and DLL in dir2, EXE calls function Init of DLL -> currentPath=dir2 (expected).
You're indeed using the wrong function t get the current directory. GetCurrentDirectory returns the current directory (i.e. where .\thisFile.txt would be sought).
GetModuleFileName returns the name where the module came from. That's in general not the current working directory. That working directory can be pretty much anything, really. The variation you see between debug and release is just a small sample of the possible variation. Expect anything.
It also appears that you're looking for another file, other than the 3 mentioned in the question. It's unclear where that other file is located, so it's possible that this answer is incomplete. Is there a reason to expect that other file in the current working directory?
I find out these code that return the DLL current path.
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
WCHAR DllPath[MAX_PATH] = {0};
GetModuleFileName((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
Actually, I don't know what _ImageBase is and its cons/pros, am still investigating.
If ok, maybe I use it in DLL code.
The first argument of GetModuleFileName (which you left out) determines which path to get. From the observed behavior, we can now deduce that you passed NULL or 0, which means you get the filename of the EXE module.
You should have passed HMODULE h from your first snippet, the DLL module.
(This value is also available inside your DllMain, 1st argument. Save that 1st argument to a global variable in your DLL, so you can pass it to GetModuleFileName)

C++ Builder XE2: Initializing a Data Module in a dll

I'm trying to create a dll that contains a VCL data module - the idea being that various applications can all load the same dll and use the same database code.
The data module itself is tested ok as part of an application - I've copied the form over to my dll project.
So in the dll entry point method, I need to initialize the data module:
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
//if I don't call this, I get an exception on initializing the data module
CoInitialize(NULL);
//initialize a standard VCL form; seems to works fine
//I'm not using Application->CreateForm as I don't want the form to appear straight away
if(!MyForm) MyForm = new TMyForm(Application);
//this doesn't work - the thread seems to hang in the TDataModule base constructor?
//I've also tried Application->CreateForm; same result
if(!MyDataModule) MyDataModule = new TMyDataModule(Application);
}
I've also seen something about how I need to call Application->Initialize before creating the form but this doesn't seem to make any difference.
Any ideas?
Thanks
You really should not be doing very much work in your DllEntryPoint() at all. Certainly not calling CoInitialize(), anyway. It is not the DLL's responsibility to call that when loaded. It is the calling app's responsibility before loading the DLL.
You should either:
export an additional function to initialize your DLL and then have the app it after loading the DLL (same for uninitialing the DLL before unloading it)
don't create your TForm/TDataModule until the first time the DLL actually needs them.
move your TForm/TDataModule into their own worker thread inside the DLL. In this case, you would then call CoIniitalize().
And in all cases, don't relay on the DLL's Application object to manage the lifetime of your TForm/TDataModule. Free them yourself instead before the DLL is unloaded.