Disclaimer: This questions seems to get downvoted because I should use the normal Win32 API (CreateProcess, ShellExecute). I know about these APIs and I'm aware that RtlCreateUserProcess is not supposed to be called directly. However, the native API is a very relevant topic regarding security, that's why I am researching it.
I'm trying to run programs on Windows using the function RtlCreateUserProcess, exported from ntdll.dll. My code works to run calc.exe, however, after trying to run notepad.exe, I receive an error message that reads The ordinal 345 could not be located in dynamic link library "C:\Windows\SysWOW64\notepad.exe". When trying to run other programs it displays various similar messages, always related to some ordinals or DLLs missing.
My example code looks like this:
#include <windows.h>
#include <iostream>
#include <winternl.h>
typedef struct _SECTION_IMAGE_INFORMATION {
PVOID EntryPoint;
ULONG StackZeroBits;
ULONG StackReserved;
ULONG StackCommit;
ULONG ImageSubsystem;
WORD SubSystemVersionLow;
WORD SubSystemVersionHigh;
ULONG Unknown1;
ULONG ImageCharacteristics;
ULONG ImageMachineType;
ULONG Unknown2[3];
} SECTION_IMAGE_INFORMATION, * PSECTION_IMAGE_INFORMATION;
typedef struct _RTL_USER_PROCESS_INFORMATION {
ULONG Size;
HANDLE ProcessHandle;
HANDLE ThreadHandle;
CLIENT_ID ClientId;
SECTION_IMAGE_INFORMATION ImageInformation;
} RTL_USER_PROCESS_INFORMATION, * PRTL_USER_PROCESS_INFORMATION;
typedef VOID(NTAPI* Func1)(PUNICODE_STRING DestinationString, __drv_aliasesMem PCWSTR SourceString);
typedef NTSTATUS(NTAPI* Func2)(OUT PRTL_USER_PROCESS_PARAMETERS* pProcessParameters, IN PUNICODE_STRING ImagePathName, IN PUNICODE_STRING DllPath OPTIONAL, IN PUNICODE_STRING CurrentDirectory OPTIONAL, IN PUNICODE_STRING CommandLine OPTIONAL, IN PVOID Environment OPTIONAL, IN PUNICODE_STRING WindowTitle OPTIONAL, IN PUNICODE_STRING DesktopInfo OPTIONAL, IN PUNICODE_STRING ShellInfo OPTIONAL, IN PUNICODE_STRING RuntimeData OPTIONAL);
typedef NTSTATUS(NTAPI* Func3)(PUNICODE_STRING NtImagePathName, ULONG Attributes, PRTL_USER_PROCESS_PARAMETERS ProcessParameters, PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, HANDLE ParentProcess, BOOLEAN InheritHandles, HANDLE DebugPort, HANDLE ExceptionPort, PRTL_USER_PROCESS_INFORMATION ProcessInformation);
int main()
{
UNICODE_STRING str;
PRTL_USER_PROCESS_PARAMETERS processparameters;
RTL_USER_PROCESS_INFORMATION processinformation = { 0 };
Func1 RtlInitUnicodeString = (Func1)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlInitUnicodeString");
Func2 RtlCreateProcessParameters = (Func2)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlCreateProcessParameters");
Func3 RtlCreateUserProcess = (Func3)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlCreateUserProcess");
RtlInitUnicodeString(&str, L"\\??\\C:\\Windows\\SysWOW64\\notepad.exe"); //Starting calc.exe works, notepad.exe does not.
RtlCreateProcessParameters(&processparameters, &str, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
NTSTATUS works = RtlCreateUserProcess(&str, OBJ_CASE_INSENSITIVE, processparameters, NULL, NULL, NULL, FALSE, NULL, NULL, &processinformation);
if (NT_SUCCESS(works)) {
ResumeThread(processinformation.ThreadHandle);
//Started application crashes at this point + the error message gets shown
}
else {
std::cout << "Failed" << std::endl;
}
return 0;
}
Unfortunately, there is not much information available about using this function, so I would appreciate any answers on how to use this function correctly.
CreateProcess after create new process do much more job, in particular it create activation context for new process based on exe manifest (BasepConstructSxsCreateProcessMessage + CsrClientCallServer) as result new process have initial activation context, stored in PEB (SystemDefaultActivationContextData and ActivationContextData) but in process created with pure call to RtlCreateUserProcess this fields is empty (0). as result your process loaded ComCtl32.dll from system32 (version 5.82 ) and notepad with activation context - 6+ version.
The ordinal 345 could not be located in dynamic link library
really in ComCtl32.DLL pre 6 version (5.82). 345 - this is TaskDialogIndirect api which exist only in ComCtl32.DLL version 6+. but your process load 5.82.. - calling TaskDialogIndirect loader says ordinal 345 not found
so CreateProcess not a thin shell over RtlCreateUserProcess or NtCreateUserProcess, but big and complex api. at it functional very hard if possible at all implement direct
Related
While looking for ways to query specific information about a range of pages in windows I came across two solutions that were used commonly. However these two alternatives seem to return overlapping information.
VirtualQueryEx
Found on MSDN we see that it takes the parameters hProcess, lpAddress, lpBuffer and dwLength to query information for that range of pages. It returns this struct which tells us something about page state, protection and type. Oh well, so a good choice for querying page information right? But wait there is more!
QueryVirtualMemoryInformation
Also found on MSDN and does nearly the same thing. The difference is that it uses a DUMMYSTRUCTNAME and returns a memory structure that overlaps quite perfectly with the struct returned by VirtualQueryEx.
It seems like this could be an oversight and it doesn't matter which one to use. Maybe MS themselves don't even know why there are two overlapping variants inside a single OS. But for someone that does know: What's the difference here?
VirtualQuery and VirtualQueryEx have existed since forever (NT v3.51).
QueryVirtualMemoryInformation is much newer (10 1607) and adds the information class parameter often seen in the NT API. This allows for future expansion. There is currently only one documented class value and the returned information is pretty similar to VirtualQueryEx but not exactly the same (BaseAddress, CommitSize etc).
DUMMYSTRUCTNAME is a compiler thing related to members without a name and has nothing to do with the actual data returned.
exist next NT api - NtQueryVirtualMemory
__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryVirtualMemory(
[in] HANDLE ProcessHandle,
[in, optional] PVOID BaseAddress,
[in] MEMORY_INFORMATION_CLASS MemoryInformationClass,
[out] PVOID MemoryInformation,
[in] SIZE_T MemoryInformationLength,
[out, optional] PSIZE_T ReturnLength
);
where MEMORY_INFORMATION_CLASS can have next values:
enum MEMORY_INFORMATION_CLASS
{
MemoryBasicInformation, // MEMORY_BASIC_INFORMATION
MemoryWorkingSetInformation, // MEMORY_WORKING_SET_INFORMATION
MemoryMappedFilenameInformation, // UNICODE_STRING
MemoryRegionInformation, // MEMORY_REGION_INFORMATION
MemoryWorkingSetExInformation, // MEMORY_WORKING_SET_EX_INFORMATION // since VISTA
MemorySharedCommitInformation, // MEMORY_SHARED_COMMIT_INFORMATION // since WIN8
MemoryImageInformation, // MEMORY_IMAGE_INFORMATION
MemoryRegionInformationEx, // MEMORY_REGION_INFORMATION
MemoryPrivilegedBasicInformation,
MemoryEnclaveImageInformation, // MEMORY_ENCLAVE_IMAGE_INFORMATION // since REDSTONE3
MemoryBasicInformationCapped, // 10
MemoryPhysicalContiguityInformation, // MEMORY_PHYSICAL_CONTIGUITY_INFORMATION // since 20H1
MemoryBadInformation, // since WIN11
MemoryBadInformationAllProcesses, // since 22H1
MaxMemoryInfoClass
} ;
both VirtualQueryEx and QueryVirtualMemoryInformation is thin shell over NtQueryVirtualMemory.
the VirtualQueryEx call NtQueryVirtualMemory with MemoryInformationClass = MemoryBasicInformation
SIZE_T
WINAPI
VirtualQueryEx(
_In_ HANDLE hProcess,
_In_opt_ LPCVOID VirtualAddress,
_Out_writes_bytes_to_(dwLength,return) PMEMORY_BASIC_INFORMATION lpBuffer,
_In_ SIZE_T dwLength
)
{
NTSTATUS status = NtQueryVirtualMemory(hProcess, VirtualAddress, MemoryBasicInformation, dwLength, &dwLength);
if (0 > status)
{
RtlNtStatusToDosError(status);
return 0;
}
return dwLength;
}
the QueryVirtualMemoryInformation call NtQueryVirtualMemory with MemoryInformationClass = MemoryRegionInformationEx. despite MemoryInformationClass parameter still exist, it accept only single value.
BOOL
WINAPI
QueryVirtualMemoryInformation(
_In_ HANDLE Process,
_In_ const VOID* VirtualAddress,
_In_ WIN32_MEMORY_INFORMATION_CLASS MemoryInformationClass,
_Out_writes_bytes_(MemoryInformationSize) PVOID MemoryInformation,
_In_ SIZE_T MemoryInformationSize,
_Out_opt_ PSIZE_T ReturnSize
)
{
NTSTATUS status = MemoryRegionInfo != MemoryInformationClass ? STATUS_INVALID_INFO_CLASS :
NtQueryVirtualMemory(hProcess, VirtualAddress, MemoryRegionInformationEx, MemoryInformationSize, ReturnSize);
if (0 > status)
{
RtlNtStatusToDosError(status);
return FALSE;
}
return TRUE;
}
so because in both case the same NT api called internal - this 2 win32 api very similar.
I have been attempting to run a 64-bit DLL purely in a processes virtual memory without 'manually mapping' it (i.e. manually resolving relocations/imports).
The plan was to inject code into the target application and load the module via conventional means, such as LoadLibrary.
I was under the assumption LoadLibrary would fix the module relocations/imports on it's own, as that's what it is designed to do.
After loading the module, the injected code would obtain information regarding the module with GetModuleInformation, transfer it to a temporary memory buffer, free the module, allocate memory at the same address it was originally loaded at, write it back, and execute the entry point.
That last step is where I believe the error is occurring.
In order to test this theory, I have hard-coding entry point addresses, debugged the remote application via Visual Studio's 'Attach to Process' feature, emulated a similar environment to correct bad pointer arithmetic, all in order to gain a bit more information on what the error might be.
Here is some general information which may or may not be useful:
Both applications (the injector, and DLL) are compiled to run in 64-bit architectures
The test application I have been using to test the injection method is the windows update applicaiton (wuauclt.exe - located in /System32/), it is of course compiled to run as a 64-bit PE
Host machine: Windows 7 Home Premium (system type: 64-bit operating system)
As far as information relating directly to the injector goes:
The primary code injection method works (as far as I can tell), and I have proven this via caveman debugging with MessageBoxA
The project is using a multi-byte character set with code optimizations disabled. The code was compiled using VS 2013 Ultimate (both projects built for Release x64)
SDL checks are off since unsafe functions are used (strcpy and friends)
The injector is debugged with elevated privileges (as high as SE_DEBUG_PRIVILEGES) every time its ran.
Code Preface:
The code exhibited below is not in any which way meant to look pretty or exhibit good programming practices. Keep this in mind when viewing the code. It was specifically designed to test a code-injection method to verify it works. If you have issues with the program layout, structure, etc, feel free to correct them and/or restructure them on your own. It's not the reason I'm here. Unless it is what resulted in the error, then it is entirely the reason I'm here :)
The code for the injector: http://pastebin.com/FF5G9nnR
/*
Some of the code was truncated (functions not pertaining to the injection), but
I have verified the code compiles and works correctly with it's injeteme.dll counterpart
*/
#include <Windows.h>
#include <Psapi.h>
#define TARGET_PID 1124
typedef BOOL(WINAPI* pFreeLibrary)(HMODULE);
typedef HMODULE(WINAPI* pLoadLibraryA)(LPCSTR);
typedef HANDLE(WINAPI* pGetCurrentProcess)(void);
typedef BOOL(WINAPI* DLL_MAIN)(HMODULE, DWORD, LPVOID);
typedef HANDLE(WINAPI* pOpenProcess)(DWORD, BOOL, DWORD);
typedef BOOL(WINAPI* pVirtualFree)(LPVOID, SIZE_T, DWORD);
typedef int(__stdcall* pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
typedef LPVOID(WINAPI* pVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL(WINAPI* pGetModuleInformation)(HANDLE, HMODULE, LPMODULEINFO, DWORD);
typedef BOOL(WINAPI* pWriteProcessMemory)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
//////////////////////////////////////////////////////////////////
struct IINFO
{
LPVOID stubAddr;
LPVOID retStatusPtr;
char fullModulePath[MAX_PATH];
DWORD pId, sizeOfCurrStruct;
// DEBUG
pMessageBoxA messageBox;
pOpenProcess openProcess;
pVirtualFree virtualFree;
pFreeLibrary freeLibrary;
pLoadLibraryA loadLibrary;
pVirtualAlloc virtualAlloc;
pGetCurrentProcess getCurrProc;
pWriteProcessMemory writeMemory;
pGetModuleInformation getModInfo;
};
static DWORD WINAPI stub(IINFO *iInfo)
{
HMODULE hMod;
MODULEINFO mInfo;
DLL_MAIN dllMain;
LPVOID lpNewMod, lpTempModBuff;
PIMAGE_DOS_HEADER pIDH;
PIMAGE_NT_HEADERS pINH;
iInfo->messageBox(NULL, iInfo->fullModulePath, NULL, 0);
hMod = iInfo->loadLibrary(iInfo->fullModulePath);
if (!hMod)
return 0;
if (!iInfo->getModInfo(iInfo->getCurrProc(), hMod, &mInfo, sizeof(MODULEINFO)))
return 0;
lpTempModBuff = iInfo->virtualAlloc(NULL, mInfo.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpTempModBuff)
return 0;
if (!iInfo->writeMemory(iInfo->getCurrProc(), lpTempModBuff, mInfo.lpBaseOfDll, mInfo.SizeOfImage, NULL))
return 0;
if (!iInfo->freeLibrary(hMod))
return 0;
lpNewMod = iInfo->virtualAlloc(mInfo.lpBaseOfDll, mInfo.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpNewMod)
return 0;
// using wpm since we have already acquired the function
if (!iInfo->writeMemory(iInfo->getCurrProc(), lpNewMod, lpTempModBuff, mInfo.SizeOfImage, NULL))
return 0;
if (!iInfo->virtualFree(lpTempModBuff, 0, MEM_RELEASE))
return 0;
/*if (!iInfo->virtualFree(iInfo, 0, MEM_RELEASE))
return 0;
iInfo->messageBox(NULL, NULL, NULL, 0); */
pIDH = (PIMAGE_DOS_HEADER)lpNewMod;
if (!pIDH)
return 0;
pINH = (PIMAGE_NT_HEADERS)((LPBYTE)lpNewMod + pIDH->e_lfanew);
if (!pINH)
return 0;
dllMain = (DLL_MAIN)((LPBYTE)lpNewMod + pINH->OptionalHeader.AddressOfEntryPoint);
if (!dllMain)
return 0;
iInfo->messageBox(NULL, NULL, NULL, 0);
dllMain((HINSTANCE)lpNewMod, DLL_PROCESS_ATTACH, NULL);
return 1;
}
static DWORD WINAPI stubEnd(){ return 0; }
//////////////////////////////////////////////////////////////////
int main()
{
HANDLE hThread = 0;
DWORD dwStubSize = 0;
int sucResp = 0, count = 0;
HMODULE hUser32 = 0, hNtdll = 0;
char fullPathName[] = "C:\\injectme.dll";
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, TARGET_PID);
if (!hProc || hProc == INVALID_HANDLE_VALUE)
return 0;
__int64 SizeOfStub = (LPBYTE)stubEnd - (LPBYTE)stub;
LPVOID lpStub = VirtualAllocEx(hProc, NULL, SizeOfStub, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpStub)
return 0;
hUser32 = LoadLibraryA("user32.dll");
if (!hUser32)
return 0;
hNtdll = LoadLibraryA("kernel32.dll");
if (!hNtdll)
return 0;
IINFO iInfo = {};
iInfo.retStatusPtr = &sucResp;
strcpy(iInfo.fullModulePath, fullPathName);
iInfo.sizeOfCurrStruct = sizeof(IINFO);
iInfo.stubAddr = lpStub;
iInfo.pId = GetCurrentProcessId();
iInfo.messageBox = (pMessageBoxA)GetProcAddress(hUser32, "MessageBoxA");
iInfo.openProcess = (pOpenProcess)GetProcAddress(hNtdll, "OpenProcess");
iInfo.virtualFree = (pVirtualFree)GetProcAddress(hNtdll, "VirtualFree");
iInfo.freeLibrary = (pFreeLibrary)GetProcAddress(hNtdll, "FreeLibrary");
iInfo.loadLibrary = (pLoadLibraryA)GetProcAddress(hNtdll, "LoadLibraryA");
iInfo.virtualAlloc = (pVirtualAlloc)GetProcAddress(hNtdll, "VirtualAlloc");
iInfo.getCurrProc = (pGetCurrentProcess)GetProcAddress(hNtdll, "GetCurrentProcess");
iInfo.writeMemory = (pWriteProcessMemory)GetProcAddress(hNtdll, "WriteProcessMemory");
iInfo.getModInfo = (pGetModuleInformation)GetProcAddress(hNtdll, "K32GetModuleInformation");
LPVOID lpStubInfo = VirtualAllocEx(hProc, NULL, sizeof(IINFO), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpStubInfo)
return 0;
if (!WriteProcessMemory(hProc, lpStub, stub, SizeOfStub, NULL))
return 0;
if (!WriteProcessMemory(hProc, lpStubInfo, &iInfo, sizeof(iInfo), NULL))
return 0;
hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)lpStub, lpStubInfo, 0, NULL);
if (!hThread || hThread == INVALID_HANDLE_VALUE)
return 0;
WaitForSingleObject(hThread, INFINITE);
return 1;
}
The code for the DLL to be injected: http://pastebin.com/8WXxcpu1
#include <Windows.h>
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpParam)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(NULL, "Hello from injectme.dll!", "", MB_OK | MB_ICONINFORMATION);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
The error when running the code above verbatim (assuming you also applied the settings above and have a similar environment) in VS2013's debugger is as follows:
"Unhandled exception at 0x000007FEEA5125D4 in wuauclt.exe: 0xC0000005: Access violation executing location 0x000007FEEA5125D4."
Upon viewing the process "wuauclt.exe" in Process Hacker, I can clearly see the module was allocated originally (upon being loaded via LoadLibrary) at 0x7fef67c0000. This is shown in the context menu->under miscellaneous->unloaded modules.
Once double-clicking "wuauclt.exe", you can browse over the application's virtual memory to ensure everything is working as it should be. I can confirm for this current session, an RWX memory buffer has been allocated at 0x7fef67c0000 with the exact size of the unloaded module, containing the injectme.dll module. When digging into injectme.dll with CFF Explorer, then entry point RVA seems to be 0x132C, which does not add up, considering the error is much further away in memory. Additionally, I can verify two more RWX memory buffers containing the code injection stub, and information structure. Looking back the information structure probably doesn't need RWX. Anyway, I can't for the life of me figure out the error.
I'm hoping one you may be able to assist me. I am extremely grateful for your time.
My gut feeling is that you're lacking the fundamental understanding for such a challenging project. You're mixing concepts from rather distinct realms.
Windows itself cares very, very little about the programming language you used in development. Either you get CLR code (.Net) or native code. In this case it's x64. But Windows really doesn't care about strcpy or SDL checks. That's for the compiler to deal with, not the OS. Chances are strcpy wouldn't even survive, when its code is fully inlined. But you apparently have optimizations turned off, for some strange reason - again a compiler versus OS confusion.
However, Windows does care about other concepts that you don't mention. Chiefly those would be ASLR and DEP - Address Space Layout Randomization and Data Execution Prevention. They're techniques to keep hackers out, and you're hacking. So that's not really a surprise.
I'm not sure if by "RWX" you mean Read Write Execute" because you should know that's asking for problems. DEP is inspired by the more aptly named W^X, Write XOR eXecute.
The more likely culprit is ASLR, though. Windows by design tries to load DLL's at unpredicatble addresses, as that eliminates an entire class of hacks. It appears you're assuming a load address, while Windows really is using another address.
A final mistake might be that you're failing to understand where the relocations are done. To improve the amount of shareable pages, relocations are done on the Import Address Table, not the code itself. The IAT is a trampoline table, and therefore executable. Your failure might also be a missing IAT.
I am developing a kernel mode filter driver, I want this driver to send a UNICODE String to an exe running in user mode. Kindly provide an example for this, as i am a beginner in driver development.
Below is the code of my driver (From where I want to send UNICODE string)
#include "drv_common.h"
#include "ntddk.h"
#include "FsFilter.h"
#define SOME_SIZE
// PassThrough IRP Handler
NTSTATUS FsFilterDispatchPassThrough( __in PDEVICE_OBJECT DeviceObject, __in PIRP Irp )
{
PFSFILTER_DEVICE_EXTENSION pDevExt = (PFSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(pDevExt->AttachedToDeviceObject, Irp);
}
///////////////////////// struct file info ////////////////////////////////////
struct {
OBJECT_NAME_INFORMATION NameInfo;
WCHAR Buffer[64]; // 64 chars must be enough for everybody :)
} InfoBuffer;
///////////////////////////////////////////////////////////////////////////////////////////////////
// IRP_MJ_CREATE IRP Handler
NTSTATUS FsFilterDispatchCreate(
__in PDEVICE_OBJECT DeviceObject,
__in PIRP Irp
)
{
PFILE_OBJECT pFileObject = IoGetCurrentIrpStackLocation(Irp)->FileObject;
PUNICODE_STRING **temp**;
RtlInitUnicodeString( temp, L"\\vs\\vs\\Setup\\eula.txt" );
LONG flag = RtlCompareUnicodeString( temp, &pFileObject->FileName, TRUE );
if ( flag == 0 )
{
DbgPrint("File is opened.\n" );
return STATUS_UNSUCCESSFUL;
}
return FsFilterDispatchPassThrough(DeviceObject, Irp);
}
I want to send &pFileObject->FileName (UNICODE String) from the above code to an executable in the user mode.
Suppose, that executable will just print this string on console.
Below is my exe code in user mode
.......
.......
int main()
{
cout<< getUnicodeStringFromKernel(); // Just supposition
return 0;
}
There are a few different ways that you can "access" a kernel mode driver. The most obvious in this case would be to use the ioctl interface.
Unfortunately, I can't provide you with an example, because to achieve that would require me to install the Windows DDK on my virtual machine, along with actually writing the code for it.
There is, however, an article here which explains how ioctls in filter drivers work.
From your application, you need to use DeviceIoControl.
I'm using ChangeServiceConfig2 API of windows to change the service description. Since the same api is not supported in windows 98, ME I have used LoadLibraray and GetProcAddress to prevent static linking of the API in the exe. Please refer the code for more details:
typedef BOOL (*ChgSvcDesc) (SC_HANDLE hService, DWORD dwInfoLevel, LPVOID lpInfo);
eBool ServiceConfigNT::Install(IN tServiceDesc * pServiceDesc){
SC_HANDLE hservice;
SC_HANDLE hservicemgr;
SERVICE_DESCRIPTION desc;
ULong starttype;
HMODULE hmod;
ChgSvcDesc fpsvcdesc;
// Opens the Service Control Manager
hservicemgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(!hservicemgr){
vPrepareError(_TEXT("Failed to open service control manager!!"));
goto err;
}
// Set start method of service.
starttype = (pServiceDesc->uAutoStart == TRUE)? SERVICE_AUTO_START : SERVICE_DEMAND_START;
// Create the service
hservice = CreateService(hservicemgr, vServiceName, pServiceDesc->uDisplayName, SERVICE_ALL_ACCESS,
pServiceDesc->uServiceType, starttype, SERVICE_ERROR_NORMAL,pServiceDesc->uExePath,
NULL, NULL, NULL, NULL, NULL);
if(!hservice) {
vPrepareError(_TEXT("Failed to create service.!!"));
goto err;
}
// Set the description string
if(pServiceDesc->uServiceDescription && *pServiceDesc->uServiceDescription) {
desc.lpDescription = pServiceDesc->uServiceDescription;
// This cannot be executed since it is not supported in Win98 and ME OS
//(Void)ChangeServiceConfig2 (hservice, SERVICE_CONFIG_DESCRIPTION, &desc);
hmod = LoadLibrary(_TEXT("Advapi32.dll"));
if(hmod) {
// _UNICODE macro is set, hence im using the "W" version of the api
fpsvcdesc = (ChgSvcDesc)GetProcAddress(hmod, "ChangeServiceConfig2W");
if(fpsvcdesc)
// On execution of the below statement, I get the error handle is invalid
fpsvcdesc(hservice, SERVICE_CONFIG_DESCRIPTION, &desc);
}
CloseServiceHandle(hservice);
CloseServiceHandle(hservicemgr);
return TRUE;
err:
if(hservicemgr)
CloseServiceHandle(hservicemgr);
return FALSE;
}
I debugged the code many times to find out why I am getting handle is invalid error ? On calling the API directly the description of service is changing but using the function pointer it gives the error.
I think that the API is writing something to the service handle of the SCM, but I have no clues as to why ?
Can somebody help me with this ?
Your function pointer is declared with no calling convention specified. The default calling convention is __cdecl. But Windows API functions are __stdcall. You need to add the calling convention to your function pointer type.
typedef BOOL (__stdcall *ChgSvcDesc) (SC_HANDLE hService,
DWORD dwInfoLevel, LPVOID lpInfo);
Is there a way to progammatically detect when a module - specifically a DLL - has been unloaded from a process?
I don't have the DLL source, so I can't change it's DLL entry point. Nor can I poll if the DLL is currently loaded because the DLL may be unloaded and then reloaded between polling.
RESULTS:
I ended up using jimharks solution of detouring the dll entry point and catching DLL_PROCESS_DETACH. I found detouring FreeLibrary() to work as well but code must be added to detect when the module is actually unloaded or if the reference count is just being decreased. Necrolis' link about finding the reference count was handy for on method of doing so.
I should note that I had problems with MSDetours not actually unloading the module from memory if a detour existed within it.
One very bad way(which was used by starcraft 2), is to make your program attach to itsself then monitor for the dll unload debug event(http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx), else you'd either need to IAT hook FreeLibrary and FreeLibraryEx in the process or hotpatch the functions in kernel32 them monitor the names being passed and the global reference counts.
Try using LdrRegisterDllNotification if you're on Vista or above. It does require using GetProcAddress to find the function address from ntdll.dll, but it's the proper way of doing it.
Maybe a less bad way then Necrolis's would be to use Microsoft Research's Detours package to hook the dll's entry point to watch for DLL_PROCESS_DETACH notifications.
You can find the entry point given an HMODULE (as returned by LoadLibrary) using this function:
#include <windows.h>
#include <DelayImp.h>
PVOID GetAddressOfEntryPoint(HMODULE hmod)
{
PIMAGE_DOS_HEADER pidh = (PIMAGE_DOS_HEADER)hmod;
PIMAGE_NT_HEADERS pinth = (PIMAGE_NT_HEADERS)((PBYTE)hmod + pidh->e_lfanew);
PVOID pvEntry = (PBYTE)hmod + pinth->OptionalHeader.AddressOfEntryPoint;
return pvEntry;
}
Your entrypoint replacement could take direct action or increment a counter that you check for in your main loop or where it's important to you. (And should almost certainly call the original entrypoint.)
UPDATE: Thanks to #LeoDavidson for pointing this out in the comments below. Detours 4.0 is now licensed using the liberal MIT License.
I hope this helps.
#Necrolis, your link to “The covert way to find the Reference Count of DLL” was just too intriguing for me to ignore because it contains the technical details I needed to implement this alternate solution (that I thought of yesterday, but was lacking the Windows Internals). Thanks. I voted for your answer because of the link you shared.
The linked article shows how to get to the internal LDR_MODULE:
struct _LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;
Right here we have EntryPoint, Window's internal pointer to the module’s entry point. For a dll that’s DllMain (or the language run time function that eventually calls DllMain). What if we just change that? I wrote a test and it seems to work, at least on XP. The DllMain hook gets called with reason DLL_PROCESS_DETACH just before the DLL unloads.
The BaseAddress is the same value as an HMODULE and is useful for finding the right LDR_MODULE. The LoadCount is here so we can track that. And finally FullDllName is helpful for debugging and makes it possible to search for DLL name instead of HMODULE.
This is all Windows internals. It’s (mostly) documented, but the MSDN documentation warns “ZwQueryInformationProcess may be altered or unavailable in future versions of Windows.”
Here’s a full example (but without full error checking). It seems to work but hasn’t seen much testing.
// HookDllEntryPoint.cpp by Jim Harkins (jimhark), Nov 2010
#include "stdafx.h"
#include <stdio.h>
#include <winternl.h>
#include <process.h> // for _beginthread, only needed for testing
typedef NTSTATUS(WINAPI *pfnZwQueryInformationProcess)(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength);
HMODULE hmodNtdll = LoadLibrary(_T("ntdll.dll"));
// Should test pZwQueryInformationProcess for NULL if you
// might ever run in an environment where this function
// is not available (like future version of Windows).
pfnZwQueryInformationProcess pZwQueryInformationProcess =
(pfnZwQueryInformationProcess)GetProcAddress(
hmodNtdll,
"ZwQueryInformationProcess");
typedef BOOL(WINAPI *PDLLMAIN) (
__in HINSTANCE hinstDLL,
__in DWORD fdwReason,
__in LPVOID lpvReserved);
// Note: It's possible for pDllMainNew to be called before
// HookDllEntryPoint returns. If pDllMainNew calls the old
// function, it should pass a pointer to the variable used
// so we can set it here before we hook.
VOID HookDllEntryPoint(
HMODULE hmod, PDLLMAIN pDllMainNew, PDLLMAIN *ppDllMainOld)
{
PROCESS_BASIC_INFORMATION pbi = {0};
ULONG ulcbpbi = 0;
NTSTATUS nts = (*pZwQueryInformationProcess)(
GetCurrentProcess(),
ProcessBasicInformation,
&pbi,
sizeof(pbi),
&ulcbpbi);
BOOL fFoundMod = FALSE;
PLIST_ENTRY pcurModule =
pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList.Flink;
while (!fFoundMod && pcurModule !=
&pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList)
{
PLDR_DATA_TABLE_ENTRY pldte = (PLDR_DATA_TABLE_ENTRY)
(CONTAINING_RECORD(
pcurModule, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks));
// Note: pldte->FullDllName.Buffer is Unicode full DLL name
// *(PUSHORT)&pldte->Reserved5[1] is LoadCount
if (pldte->DllBase == hmod)
{
fFoundMod = TRUE;
*ppDllMainOld = (PDLLMAIN)pldte->Reserved3[0];
pldte->Reserved3[0] = pDllMainNew;
}
pcurModule = pcurModule->Flink;
}
return;
}
PDLLMAIN pDllMain_advapi32 = NULL;
BOOL WINAPI DllMain_advapi32(
__in HINSTANCE hinstDLL,
__in DWORD fdwReason,
__in LPVOID lpvReserved)
{
char *pszReason;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
pszReason = "DLL_PROCESS_ATTACH";
break;
case DLL_PROCESS_DETACH:
pszReason = "DLL_PROCESS_DETACH";
break;
case DLL_THREAD_ATTACH:
pszReason = "DLL_THREAD_ATTACH";
break;
case DLL_THREAD_DETACH:
pszReason = "DLL_THREAD_DETACH";
break;
default:
pszReason = "*UNKNOWN*";
break;
}
printf("\n");
printf("DllMain(0x%.8X, %s, 0x%.8X)\n",
(int)hinstDLL, pszReason, (int)lpvReserved);
printf("\n");
if (NULL == pDllMain_advapi32)
{
return FALSE;
}
else
{
return (*pDllMain_advapi32)(
hinstDLL,
fdwReason,
lpvReserved);
}
}
void TestThread(void *)
{
// Do nothing
}
// Test HookDllEntryPoint
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE hmodAdvapi = LoadLibrary(L"advapi32.dll");
printf("advapi32.dll Base Addr: 0x%.8X\n", (int)hmodAdvapi);
HookDllEntryPoint(
hmodAdvapi, DllMain_advapi32, &pDllMain_advapi32);
_beginthread(TestThread, 0, NULL);
Sleep(1000);
return 0;
}