|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis utility (RegSvrEx) is similar to RegSvr32 but with certain extra functionality. The extra features which this utility offers are :-
The utility is meant to work only on Windows 2000, XP and Windows Server 2003, as current user registration is a feature available only on these operating systems. BackgroundIt all started when I felt the need for a tool that could :-
I found an answer for the second problem in the tool RegCap.exe which comes with Microsoft Visual Studio.NET. This tool can be found in the sub directory - \Common7\Tools\Deployment within the main installation directory of Visual Studio.NET. If you want to create a .reg file instead of registering a COM server (dll or an exe), you can simply invoke this tool from the command prompt as:- RegCap /O OutputFile.reg MyComServer.dll
This will output all the registration entries of MyCOMServer.dll to OutputFile.reg without actually registering the COM server. This is very useful in creating windows installer packages where the best practice is not to self register a COM server. This tool is internally used by the IDE when building the setup projects. About HKEY_CLASSES_ROOT in Windows 2000 and aboveWindows 2000 and above versions of Windows provide a merged view of RegSvr32 can only register COM servers for the entire machine and not just only for the current user - as when subkeys for COM registration info like User only registration of COM serversSo what are the advantages of registering a COM server for only the current user? The principal advantage is that, the user can still register and use the COM server even if he is not an administrator of the machine or does not have write access to Using the toolThe tool has been developed as a console application. Here is the syntax for using the tool:- RegSvrEx [/u] [/c] servername
The post build step of a default ATL project created in Visual Studio.NET can be modified to use RegSvrEx instead of RegSvr32 and the post build command can read as:- RegSvrEx /c $(TargetPath) Same thing can be done in custom build step of Visual Studio 6.0 projects. Thus a developer no longer needs to be an administrator to test and build ATL or COM projects. RegOverridePredefkeyWhen I initially started to think about developing the tool I immediately thought to use API hooking. I could hook the LONG RegOverridePredefKey(HKEY hKey, HKEY hNewHKey); The first parameter DLL registrationNow that we understand the magic of HRESULT RegisterDll(LPCWSTR szDll, bool bUnregister, bool bCurrentUser) { LPCSTR szFunction = bUnregister ? "DllUnregisterServer" : "DllRegisterServer"; HRESULT hr = S_OK; HMODULE hMod = LoadLibrary(szDll); if (hMod != NULL) { typedef HRESULT (_stdcall *DLLPROC)(); DLLPROC pfnDllProc = reinterpret_cast<DLLPROC> (GetProcAddress(hMod, szFunction)); if (pfnDllProc) { if (bCurrentUser) { //Override HKEY_CLASSES_ROOT hr = OverrideClassesRoot(HKEY_CURRENT_USER, L"Software\\Classes"); } if (SUCCEEDED(hr)) hr = (*pfnDllProc)(); } else hr = GetHresultFromWin32(); FreeLibrary(hMod); } else hr = GetHresultFromWin32(); return hr; } The function
The code is pretty straight forward.
Exe registrationRegistering an executable is not as straight forward. In order to register an executable it needs to be invoked with /RegServer command line and in order to unregister it it needs to be invoked with /Unregserver command line. The problem here is HRESULT RegisterExe(LPCWSTR szExe, bool bUnregister, bool bCurrentUser) { LPWSTR szCmdLine = bUnregister ? L"/UnregServer" : L"/RegServer"; STARTUPINFO si = {0}; si.cb = sizeof(STARTUPINFO); si.wShowWindow = SW_SHOWDEFAULT; PROCESS_INFORMATION pi = {0}; HRESULT hr = S_OK; BOOL b = CreateProcess(szExe, szCmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED , NULL, NULL, &si, &pi); The WIN32 API functions //If the registration is required for the //current user inject code to override the keys if (bCurrentUser) { CONTEXT ctxt = {0}; ctxt.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.hThread, &ctxt); LPVOID pvDestAddr = VirtualAllocEx(pi.hProcess, NULL, sizeof(LoadLibraryCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE); DWORD dwDestAddr = PtrToUlong(pvDestAddr); LoadLibraryCode code(ctxt.Eip, dwDestAddr); WriteProcessMemory(pi.hProcess, pvDestAddr, &code, sizeof(LoadLibraryCode), NULL); ctxt.Eip = code.GetRemoteCodeAddr(dwDestAddr); SetThreadContext(pi.hThread, &ctxt); } ResumeThread(pi.hThread); CloseHandle(pi.hThread); WaitForSingleObject(pi.hProcess, INFINITE); DWORD dwResult = 0; GetExitCodeProcess(pi.hProcess, &dwResult); hr = GetHresultFromWin32(dwResult); CloseHandle(pi.hProcess); The structure #pragma pack(1) struct LoadLibraryCode { WCHAR m_szLibPath[_MAX_PATH + 1]; //Save registers BYTE m_pushEAX; BYTE m_pushECX; BYTE m_pushEDX; //push m_szLibPath BYTE m_push; DWORD m_dwAddrLibPath; //Address of lib path in the traget process //call LoadLibraryW BYTE m_call; DWORD m_dwRelAddrLoadLibraryW; BYTE m_popEDX; BYTE m_popECX; BYTE m_popEAX; BYTE m_jmp; DWORD_PTR m_dwRelAddr; //jump back to original address LoadLibraryCode(DWORD dwAddrToJump, DWORD dwRemoteAddrOfThis) { ::GetModuleFileNameW(NULL, m_szLibPath, _MAX_PATH + 1); lstrcpyW(::PathFindFileNameW(m_szLibPath), L"RegInDll.Dll"); m_pushEAX = 0x50; // push eax m_pushECX = 0x51; // push ecx m_pushEDX = 0x52; // push edx m_push = 0x68; m_dwAddrLibPath = dwRemoteAddrOfThis; m_call = 0xE8; // call DWORD dwAddrLoadLibraryW = PtrToUlong(GetProcAddress (GetModuleHandle(L"kernel32.dll"), "LoadLibraryW")); m_dwRelAddrLoadLibraryW = dwAddrLoadLibraryW - (dwRemoteAddrOfThis + ((BYTE*)&&m_dwRelAddrLoadLibraryW - (BYTE*)this) + sizeof(DWORD)); m_popEDX = 0x5A; // pop edx m_popECX = 0x59; // pop ecx m_popEAX = 0x58; // pop eax m_jmp = 0xE9; // jmp m_dwRelAddr = dwAddrToJump - (dwRemoteAddrOfThis + sizeof(LoadLibraryCode)) ; } DWORD GetRemoteCodeAddr(DWORD dwRemoteAddr) { return dwRemoteAddr + ((BYTE*)&m_pushEAX - (BYTE*)this); } }; #pragma pack(pop) In order to get the machine language instruction codes, I actually wrote the code in assembly and compiled it and observed the assembly listing to see the actual machine instruction byte values (I could have of course looked at the Pentium instruction manual to find that out). The assembly language looks like following:- ;Save the registers push EAX push ECX push EDX push <address of the string containing the path of the library> call LoadLibraryW pop EDX pop ECX pop EAX jmp <relative address of the process start point> This is a very standard code and technique used to inject DLLs in a process before the process starts. The BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
//Failed to override the key for
//some reason so just terminate the process
LONG l = OverrideClassesRoot(HKEY_CURRENT_USER,
L"Software\\Classes");
if (l != ERROR_SUCCESS)
ExitProcess(l);
}
return TRUE;
}
Thus at the start of the process ConclusionThis utility works in most cases but there are some times when it will not work. One such case might be when the registration code requires to read an entry from the
|
||||||||||||||||||||||