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()