In this article, I explain how to mitigate the problems caused by DCOM hardening, by reverse engineering a non-compliant .NET application. The approach I take here is to identify the source of the problem, disassemble the offending component, and then re-assemble the program.
Introduction
In my previous article, I explained the non-invasive ways in which you could mitigate the client side impact of DCOM hardening. However, there may be scenarios where that is not possible and you as a developer come into the picture.
Before we get started, I do want to mention that this involves what my kids call ‘hacking’ and is therefore not something you should never do in a stable production system with vendor support for obvious reasons. We are talking about a scenario where there is no possible other support, and the only two options are a) stay down until the entire system is migrated, or b) hobble on until the entire system is migrated.
In this scenario, we have a .NET application on a Windows 2008R2 system that refuses to connect to a server via DCOM. This one was a very small EXE with a ton of libraries and components that get loaded when it starts. This is pretty typical for business applications which are often modular and designed to work with a plethora of different interfaces that may or may not apply to this particular system.
I should also point out there are no guarantees. Getting an application to work in a manner that was not intended is a task that comes without instruction manual. We don’t know how the application was designed in the first place, so we have to first try to figure that out, and then see if we can do anything to mitigate.
Background
In my previous article, I explained that this problem can be fixed if the OS is still supported, or if the application uses default settings. Given that we have an application that doesn't work, the 1 thing we can safely assume is that ‘somewhere’, there is a win32 COM API being called with non-default authentication specification because we already know that changing the DCOM settings (either defaults or component specific ones via DCOMCnfg
) didn’t work. The most obvious candidate is somewhere, someone is calling CoInitializeSecurity
with bad parameters. The easiest way to establish this is using the ‘strings
’ utility from SysInternals on a file to determine this, and using powershell to automate this for the entire folder.
Get-ChildItem C:\temp\Lib -Filter "*.dll" -Recurse |
foreach {$_.FullName; c:\temp\strings $_.FullName |
where {$_ -match "CoInitializeSecurity"}}
As it happens, in this case that returns a hit. There is one component being used which does an explicit call to CoInitializeSecurity
:
To avoid problems, I've decided to hide the name of the specific component. For the rest of this article, I'm going to rename it to DodgyDll.dll. Having identified the culprit, we're going to have to look at it in more detail. We can do this in several ways. One approach is to use reflector.
If we look at the declaration for CoInitializeSecurity
:
HRESULT CoInitializeSecurity(
[in, optional] PSECURITY_DESCRIPTOR pSecDesc,
[in] LONG cAuthSvc,
[in, optional] SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
[in, optional] void *pReserved1,
[in] DWORD dwAuthnLevel,
[in] DWORD dwImpLevel,
[in, optional] void *pAuthList,
[in] DWORD dwCapabilities,
[in, optional] void *pReserved3
);
We see the authentication level is set to 1 (RPC_C_AUTHN_LEVEL_NONE
) and the impersonation level to 2 (RPC_C_IMP_LEVEL_IDENTIFY
). We'd need this to be 5 (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
) and 3 (RPC_C_IMP_LEVEL_IMPERSONATE
) respectively, according to the documentation of the vendor of the server.
Having identified the problem, now we have to make that happen.
Disassembling and Reassembling the DLL
If you have Visual Studio installed, you already have ildasm
on your system. If you don't, then you can get it for free by installing the Windows SDK. ildasm
is a disassembly tool which can take a .NET module and convert it back to .NET intermediate language. It will also extract the embedded resource file.
You can open the il file in Notepad, and find the CoInitializeSecurity
function call. Basically, you see the different values being loaded on the stack, in the order in which CoInitializeSecurity
expects them before the function call is made. The 5th and 6th values on the stack are the ones causing the problem. Because IL is fairly easy to read and understand, it's also easy enough to edit.
The instructions starting with ld
load something on the stack. Simply counting the number of times something is loaded on the stack makes it easy to identify the problematic values 1 and 2, which need to be 5 and 3. All we need to do is edit the values.
That's it. Now, we have to reverse the process and create a DLL again. However, note that we need to do this on the target system where we got the DLL from. The reason is that ilasm will create a dependency on a .NET version, and using the wrong version of ilasm will create a DLL which may be incompatible with the application that uses it. Luckily, when the .NET framework is installed on a system, ilasm and other tools are installed along with it.
This will convert the intermediate language file back into a DLL. However when we try this, we hit a snag.
The version of ilasm
we use cannot understand that particular line (the -nan
to be precise). Things like this are not wholly unexpected. When using tools from different tool chains, such things can happen. This is easy enough to fix by modifying ldc.r8 -nan(ind)
to ldc.r8 (00 00 00 00 00 00 F8 FF)
. FWIW, this is a known issue at the time of writing, and is caused by a regression in ildasm
.
After we make the required change, the command completes successfully! On that high, we swap out the DLL, fire up the application and.... nothing happens. Or rather, the following happens:
It makes sense of course. The original vendor used strong name signing, and while we can reverse engineer the DLL and make some changes, re-signing is not an option.
Removing Signature Verification
I want to re-iterate what I said at the beginning: we are working on a obsolete system, which is -hopefully- already having a migration ongoing. We're already in a situation where there are no best or good options left, and we're already making the best of a worst case situation.
The reason I mention this anew is that after destroying all hope of vendor support, we are going one step further and also disable strong name verification and hamstring application security. In other words, we're going to tell the system we don't care about assembly signing anymore. It should be obvious that under no circumstances is this ever a good idea except if not doing it is going to be a bigger disaster.
As it turns out, this is relatively simple., and can be done using the registry. You need to create the following keys:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification\*,*]
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\StrongName\Verification\*,*]
And then you create the following values:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
"AllowStrongNameBypass"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework]
"AllowStrongNameBypass"=dword:00000001
It is possible to use these registry keys to only allow this for assemblies with specific signatures, but in our case, we choose a blanket approach.
NOTE: In our case, simply disabling strong names is safe enough, because that server is in a segregated VLAN which only allows specific point to point connections, and only admins and the relevant service accounts can even log in on that system. If your setup is less secure, it may still be a good idea to selectively disable strong name signing only for the relevant assembly.
I did not find a proper Microsoft article but this link shows how it should be done.
<code>[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification\MyAssemblyName,publicKeyToken]</code>
Success
After bypassing the strong name verification, we can fire up the application and...
Success! Our application can finally connect to the DCOM server and business can continue.
Points of Interest
In this article, I showed how you can dis-assemble and re-assemble a .NET DLL for the purpose of making parameter changes. In this case, it was for dealing with DCOM hardening, but the principle is more generally applicable.
This approach is definitely not the best for solving your problems, and should be considered a 'last resort' but it's always good to understand something and have an extra option in your toolbox.
History
- 15th November, 2023: First version
- 12th December, 2023: Second version. Added selective strong name disable