|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
BackgroundAlice is a Windows programmer in company A. Bob is a network administrator at company B. Company B uses a product P of company A. It so happened that there was a problem in the product P at B's site. The problem could not be replicated elsewhere, so Alice was asked to go to B's site and find out more about the problem and if possible fix it. The problem was that the product P had an NT service component and it was failing for some reasons. Luckily, Visual C++ was installed on the machine and company B had purchased the source code of the product P. Therefore, Alice could debug at B's site. Bob gave a user name and password so that Alice could log on to the machine and do debugging. When Alice started the debugger and attached it to the NT service, she got an "Access denied" error. The logon credentials provided by Bob was that of a regular user with very minimal privileges (for security reasons Bob could not give the password of an administrative account to Alice). Alice being an experienced Windows programmer knew that she needed the
Double clicking on "Debug Programs" pops up the following dialog box.
An Administrator can add or remove users or groups in the above dialog, to grant or deny them the privilege. Bob adds Alice to the list and she successfully managed to debug the service. It turned out that the network account under whose credentials the service was running, did not have access to a directory. The problem was fixed and Alice got a big thank you from Bob. The problemTwo years later the product P was migrated to .NET - it was rewritten in C#. Bob installed the new version of the software on his server. It turned out that the new version of the product gave the same access denied problem. Bob checked to make sure that he set permissions correctly on different directories used by the application. It turned out that everything was set correctly. Alice was once again asked to go to B's site to fix the problem. Bob prepared the machine for Alice by installing Visual Studio .NET on it and also giving her the privilege to debug programs. So once again Alice tries to attach the debugger. Since the program was now running under Common Language Runtime, she selected the program type to be Common Language Runtime, from the list of program types. As soon as she clicked OK, she got the access denied error as shown in the screen shot.
Alice then decided to attach the native debugger by selecting the program type to be native and it worked. Alice double checks by trying to attach the common language runtime debugger and it failed again. So, she asked Bob to run the debugger under his credentials. She was able to successfully attach the debugger. Unfortunately, she could not do much debugging this way, as Bob refused to allow Alice to run the debugger under his credentials. The rest of this article tries to find some means to help Alice. Understanding the problemHere is a statement of the problem - Next, I viewed the debuggee process by using the procexp tools from sysinternals. Here is how a typical .NET application looks in procexp.
The following objects are of interest:-
Investigating more .NET applications using procexp revealed that as soon as CLR loads in a process, it creates these three events. The number at the end of the objects' name is the process ID of the process. The procexp tool also allows you to check the security settings of each object (recall that you can set security for each kernel object in windows NT based OSs). Here is how the security setting of one of these objects, looks like.
Basically, only the Administrators and the account under which the process is running (aspnet in the example above) have full access to the object. No one else is granted any access. It turns out to be that this is the default security setting for any new kernel object. Remember the What this proves is that CLR obviously uses default settings to create the three kernel objects required for debugging. As a result any attempt to debug the application by another non-admin account fails, while accessing any of these kernel objects. The solutionFortunately, security settings of any kernel object can be modified after the kernel object is created using CHandle hProcess(OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID));
HandleCheckNotNull(hProcess);
TCHAR szPath[_MAX_PATH + 1];
LPVOID lpvAddress = VirtualAllocEx(hProcess, NULL, sizeof(szPath),
MEM_COMMIT, PAGE_READWRITE);
if (lpvAddress == NULL)
AtlThrowLastWin32();
GetModuleFileName(_AtlModule.GetModuleInstance(), szPath, _MAX_PATH);
szPath[_MAX_PATH] = 0;
TCHAR* szFileName = PathFindFileName(szPath);
int nAllowedLen = PtrToInt(szFileName - szPath);
lstrcpyn(szFileName, TEXT("aclchg.dll"), _MAX_PATH - nAllowedLen);
Win32Check(WriteProcessMemory(hProcess, lpvAddress,
szPath, sizeof(szPath), NULL));
//Setup communication data file mapping
CAtlFileMapping<SID> mapping;
CString strMappingName;
strMappingName.Format(szACLChgCommData, dwProcessID);
mapping.MapSharedMem(MAX_SID_SIZE, strMappingName);
//Allow access to everyone so that
//the target process can read from the object
CDacl dacl;
dacl.AddAllowedAce(Sids::World(), FILE_MAP_ALL_ACCESS);
Win32Check(AtlSetDacl(mapping.GetHandle(), SE_KERNEL_OBJECT, dacl));
CAccessToken token;
Win32Check(token.GetProcessToken(TOKEN_QUERY));
CSid sidUser;
Win32Check(token.GetUser(&sidUser));
memcpy(mapping, sidUser.GetPSID(), sidUser.GetLength());
DWORD dwThreadID = 0;
CHandle hThread(CreateRemoteThread(hProcess, NULL, 0,
reinterpret_cast<LPTHREAD_START_ROUTINE>(LoadLibraryW),
lpvAddress, 0, &dwThreadID));
HandleCheckNotNull(hThread);
The code does the following:-
Changing the ACLsThe new ATL security classes simplify programming Windows security, a lot. The code for ACLChg.dll which is responsible for modifying the ACL of the target process, makes use of the new security classes. This is all done in the CAtlFileMapping<SID> mapping; CString strMappingName; strMappingName.Format(szACLChgCommData, GetCurrentProcessId()); HRCheck(mapping.MapSharedMem(MAX_SID_SIZE, strMappingName)); CSid sidToAdd(*mapping); //Now we need to allow access to this sid for //the kernel objects required for debugging AdjustObjectSecurity(szDebuggerAttachedEvent, sidToAdd); AdjustObjectSecurity(szDBIPCSetupSyncEvent, sidToAdd); AdjustObjectSecurity(szPrivateIPCBlock, sidToAdd); The code reads the SID supplied in the shared memory section created by the process injecting the DLL and adjusts the security of the three kernel objects used by the CLR debugging system. Here is how the void AdjustObjectSecurity(LPCTSTR szObjetNamePrefix, CSid& sidToAdd)
{
CString strObjectName;
strObjectName.Format(szObjetNamePrefix, GetCurrentProcessId());
CDacl dacl;
Win32Check(AtlGetDacl(strObjectName, SE_KERNEL_OBJECT, &dacl));
Win32Check(dacl.AddAllowedAce(sidToAdd, GENERIC_ALL));
Win32Check(AtlSetDacl(strObjectName, SE_KERNEL_OBJECT, dacl));
}
It gets the existing DACL of the kernel object and adds a new access allowed ACE which grants all permissions to the supplied SID. Finally it replaces the DACL of the kernel object with the new DACL. Using the add-inLets see how our friend Alice can use the add-in to solve her debugging problem. Here are the steps:-
Final thoughtsThe code currently works with both CLR 1.0 and 1.1. Since the code uses undocumented features from CLR, which I found out from my own research, it may or may not work with the next major release of CLR.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||