during load dll windows always call function RtlDosApplyFileIsolationRedirection_Ustr. it exported, so easy can be hooked. with this api we can redirect (replace ) dell name.
so first try hook this api:
#ifdef _X86_
#pragma warning(disable: 4483) // Allow use of __identifier
#define __imp_RtlDosApplyFileIsolationRedirection_Ustr __identifier("_imp__RtlDosApplyFileIsolationRedirection_Ustr@36")
#endif
EXTERN_C extern PVOID __imp_RtlDosApplyFileIsolationRedirection_Ustr;
NTSTATUS
NTAPI
hook_RtlDosApplyFileIsolationRedirection_Ustr(_In_ ULONG Flags,
_In_ PUNICODE_STRING OriginalName,
_In_ PUNICODE_STRING Extension,
_Out_opt_ PUNICODE_STRING StaticString,
_Out_opt_ PUNICODE_STRING DynamicString,
_Out_opt_ PUNICODE_STRING *NewName,
_Out_opt_ PULONG NewFlags,
_Out_opt_ PSIZE_T FilePathLength,
_Out_opt_ PSIZE_T MaxPathSize);
ULONG dwError = DetourTransactionBegin();
if (NOERROR == dwError)
{
//++ optional
DetourThread* pti = 0;
SuspendThreads(&pti);
//--optional
dwError = DetourAttach(&__imp_RtlDosApplyFileIsolationRedirection_Ustr,
hook_RtlDosApplyFileIsolationRedirection_Ustr);
dwError = NOERROR != dwError ? DetourTransactionAbort() : DetourTransactionCommit();
//++optional
Free(pti);
//--optional
}
implementation of SuspendThreads (optional) can be next:
struct DetourThread
{
DetourThread * pNext;
HANDLE hThread;
};
void Free(_In_ DetourThread* next)
{
if (DetourThread* pti = next)
{
do
{
next = pti->pNext;
NtClose(pti->hThread);
delete pti;
} while (pti = next);
}
}
NTSTATUS SuspendThreads(_Out_ DetourThread** ppti)
{
DetourThread* pti = 0;
HANDLE ThreadHandle = 0, hThread;
NTSTATUS status;
BOOL bClose = FALSE;
HANDLE UniqueThread = (HANDLE)GetCurrentThreadId();
loop:
status = NtGetNextThread(NtCurrentProcess(), ThreadHandle,
THREAD_QUERY_LIMITED_INFORMATION|THREAD_SUSPEND_RESUME|THREAD_GET_CONTEXT|THREAD_SET_CONTEXT,
0, 0, &hThread);
if (bClose)
{
NtClose(ThreadHandle);
bClose = FALSE;
}
if (0 <= status)
{
ThreadHandle = hThread;
THREAD_BASIC_INFORMATION tbi;
if (0 <= (status = NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), 0)))
{
if (tbi.ClientId.UniqueThread == UniqueThread)
{
bClose = TRUE;
goto loop;
}
if (NOERROR == (status = DetourUpdateThread(hThread)))
{
status = STATUS_NO_MEMORY;
if (DetourThread* next = new DetourThread)
{
next->hThread = hThread;
next->pNext = pti;
pti = next;
goto loop;
}
ResumeThread(hThread);
}
}
if (status == STATUS_THREAD_IS_TERMINATING)
{
bClose = TRUE;
goto loop;
}
NtClose(hThread);
}
switch (status)
{
case STATUS_NO_MORE_ENTRIES:
case STATUS_SUCCESS:
*ppti = pti;
return STATUS_SUCCESS;
}
Free(pti);
*ppti = 0;
return status;
}
the DetourUpdateThread suspend and save thread handles in DetourThread list. and resume it in DetourTransactionAbort or DetourTransactionCommit. but it not close saved hThread. as result need by self mantain additional list of threads, for close it handles.. (Free)
ok. let we hook RtlDosApplyFileIsolationRedirection_Ustr. now we need implement hook_RtlDosApplyFileIsolationRedirection_Ustr
let we have next api:
// set path to some plugin (A) folder.
// pszPluginPath - relative path. like plugin/A/
BOOL SetPluginPath(_In_ PCWSTR pszPluginPath);
// return full path to current plugin (A) folder - some like */plugin/A/
PCWSTR AcquirePluginPath();
void ReleasePluginPath();
// called once on start
BOOL InitPluginPath();
// called once on exit
void FreePluginPath();
enclose load plugin in next code:
if (SetPluginPath(L"plugins/A/"))
{
LoadLibraryW(L"some-plugin.dll");
RemovePluginPath();
}
assume that ./plugin/ is folder inside application folder (where exe is located) and it containing subfolders for every plugin ( A, B, .. )
/plugin
/A
/B
so with this code we try load ./plugin/A/some-plugin.dll
and if some-plugin.dll have static dependency (or call LoadLibrary inside dll entry point - this is ok really) from lib-Y.dll and exist ./plugin/A/lob-Y.dll file - we try load exactly ./plugin/A/lob-Y.dll. even if ./lib-Y.dll or/and ./plugin/B/lib-Y.dll already loaded in process.
NTSTATUS
NTAPI
hook_RtlDosApplyFileIsolationRedirection_Ustr(_In_ ULONG Flags,
_In_ PUNICODE_STRING OriginalName,
_In_ PUNICODE_STRING Extension,
_Out_opt_ PUNICODE_STRING StaticString,
_Out_opt_ PUNICODE_STRING DynamicString,
_Out_opt_ PUNICODE_STRING *NewName,
_Out_opt_ PULONG NewFlags,
_Out_opt_ PSIZE_T FilePathLength,
_Out_opt_ PSIZE_T MaxPathSize)
{
if (DynamicString)
{
BOOLEAN fOk = FALSE;
WCHAR lpLibFileName[MAX_PATH], *lpFilePart = 0;
if (PCWSTR pszPluginPath = AcquirePluginPath())
{
if (!wcscpy_s(lpLibFileName, _countof(lpLibFileName), pszPluginPath))
{
size_t s = wcslen(lpLibFileName);
lpFilePart = lpLibFileName + s;
int len = swprintf_s(lpFilePart, _countof(lpLibFileName) - s, L"%wZ", OriginalName);
if (0 < len)
{
static const UNICODE_STRING dot = RTL_CONSTANT_STRING(L".");
USHORT u;
if (0 > RtlFindCharInUnicodeString(0, OriginalName, &dot, &u))
{
swprintf_s(lpFilePart + len, _countof(lpLibFileName) - s - len, L"%wZ", Extension);
}
fOk = RtlDoesFileExists_U(lpLibFileName);
}
}
}
ReleasePluginPath();
if (fOk)
{
if (RtlCreateUnicodeString(DynamicString, lpLibFileName))
{
if (NewName)
{
*NewName = DynamicString;
}
if (NewFlags)
{
*NewFlags = 0;
}
if (FilePathLength)
{
*FilePathLength = lpFilePart - lpLibFileName;
}
if (MaxPathSize)
{
*MaxPathSize = _countof(lpLibFileName);
}
return STATUS_SUCCESS;
}
return STATUS_NO_MEMORY;
}
}
return RtlDosApplyFileIsolationRedirection_Ustr(Flags,
OriginalName,
Extension,
StaticString,
DynamicString,
NewName,
NewFlags,
FilePathLength,
MaxPathSize);
}
and finally - implementation of plugin paths api:
SRWLOCK g_lock = RTL_SRWLOCK_INIT;
ULONG g_cchMaxPlugin = 0;
PWSTR g_pszPluginPath = 0, g_pszPluginRelativePath = 0, g_pszPluginName = 0;
void FreePluginPath()
{
if (g_pszPluginPath)
{
if (g_pszPluginName)
{
__debugbreak();
}
delete [] g_pszPluginPath;
}
}
BOOL InitPluginPath()
{
enum { buf_size = MAXSHORT + 1 };
if (PWSTR psz = new(nothrow) WCHAR[buf_size])
{
if (ULONG cch = GetModuleFileName(0, psz, buf_size))
{
if (NOERROR == GetLastError())
{
PWSTR FileName = psz + cch;
g_cchMaxPlugin = buf_size - cch;
do
{
switch (*--FileName)
{
case '\\':
case '/':
g_pszPluginPath = psz;
g_pszPluginRelativePath = FileName + 1;
return TRUE;
}
} while (g_cchMaxPlugin++, --cch);
}
}
delete [] psz;
}
return FALSE;
}
BOOL SetPluginPath(_In_ PCWSTR pszPluginPath)
{
SIZE_T cch = wcslen(pszPluginPath);
PWSTR pszPluginName = g_pszPluginRelativePath + cch;
if (++cch > g_cchMaxPlugin)
{
return FALSE;
}
AcquireSRWLockExclusive(&g_lock);
memcpy(g_pszPluginRelativePath, pszPluginPath, cch * sizeof(WCHAR));
g_pszPluginName = pszPluginName;
ReleaseSRWLockExclusive(&g_lock);
return TRUE;
}
void ReleasePluginPath()
{
ReleaseSRWLockShared(&g_lock);
}
PCWSTR AcquirePluginPath()
{
AcquireSRWLockShared(&g_lock);
return g_pszPluginName ? g_pszPluginPath : 0;
}
void RemovePluginPath()
{
AcquireSRWLockExclusive(&g_lock);
g_pszPluginName = 0;
ReleaseSRWLockExclusive(&g_lock);
}
on start we must call InitPluginPath() and on exit FreePluginPath()