For those who have had the privilege of reverse engineering heavily obfuscated .NET code, you've probably encountered cases where your decompiler of choice completely fails (or even crashes in an epic fashion) upon attempting to decompile certain methods. Decompilation failure is often one of the intended goals of .NET obfuscator developers. Fortunately, all of the decompiler utilities are also disassemblers and it is exceedingly rare that your tool of choice will fail to disassemble an unruly method. In the cases where you're forced to work with a disassembled method, a basic understanding of .NET bytecode - i.e. Common Intermediate Language (CIL) is required.
Nearly all .NET methods are comprised of an array of CIL instructions and arguments to the instructions. These instructions are all thoroughly documented. CIL instructions manipulate values on what is referred to as the evaluation stack. CIL instructions can push values onto the stack, pop them off the stack, and perform operations on the values at the top off the stack. Let's see this in action. For this example, we're going to analyze a .NET method that implements a bitwise right circular shift - System.Security.Cryptography.SHA256Managed.RotateRight(uint x, int n).
Here is the decompiled method as seen in ILSpy:
This method takes two arguments - a uint32 (x) which represents the value to be rotated and an int32 (n) which represents the number of rotations to perform. For those unfamiliar with the bitwise rotate operation, please read about it here. Since there is no .NET rotate right operator, the code above is the logical equivalent.
Now, let's assume for a moment that the decompiler failed to decompile the method. In that case, change C# in the language tab to IL in ILSpy. You'll be presented with the following CIL disassembly listing:
Before delving into the CIL instructions, there are some additional properties described in the disassembly that were hidden to you in the decompiled output:
1) cil managed
This indicates that the method is implemented with CIL instructions and that "the body of the method is not defined, but is produced by the runtime." - ECMA-335
2) RVA 0x152A65
The relative virtual address of the method in the DLL or EXE that implements the method - i.e. location of the method within the DLL or EXE.
3) .maxstack 8
Specifies the maximum number of elements required on the evaluation stack during the execution of the method.
This number is emitted by the compiler and is required by the .NET runtime. Note: this value can be higher than what is actually required. As we will see, this method actually only requires four stack slots.
4) Code size 17 (0x11)
The total size of all CIL instructions and arguments.
What's interesting in the disassembly is the presence of binary and instructions which are not present in the decompiled output. As you will see, and'ing the shift value (n) by 31 (0x1F) compensates for when the shift value is larger than 31 (the size, in bits on a uint32 minus 1). In our example, we will perform the following operation: 8 ROR 33 which is the equivalent of 8 ROR 1 (32 + 1). The binary and operations serve to convert values greater than 31 to their equivalent value that lies between 0 and 31.
Now, lets validate that when executed, 8 ROR 33 and 8 ROR 1 generate the same result - 4. Since RotateRight is a Nonpublic (i.e. private) method, we'll need to use reflection to invoke it In PowerShell.
$SHA256Managed = [IntPtr].Assembly.GetType('System.Security.Cryptography.SHA256Managed')
$BindingFlags = [Reflection.BindingFlags] 'NonPublic, Static'
$ROR = $SHA256Managed.GetMethod('RotateRight', $BindingFlags)
$ROR.Invoke($null, @([UInt32] 8, 1))
$ROR.Invoke($null, @([UInt32] 8, 33))
They do indeed result in the same value, as expected.
Let's step through each CIL instruction, observing the effect of each instruction on the evaluation stack.
For more information on CIL and .NET internals, I highly recommend you check out the following:
Showing posts with label managed code. Show all posts
Showing posts with label managed code. Show all posts
Sunday, July 13, 2014
Monday, May 14, 2012
Extracting hard-coded credentials using managed code debugging techniques in Windbg
tl;dr version
Using some simple managed code debugging techniques, you can easily pull out hard-coded credentials from a binary claiming to protect them.
1) Fire up the executable in WinDbg.
2) Load the CLR debugging extension for WinDbg - SOS.dll.
SOS.dll is a WinDbg extension used to aid in debugging managed code. More details on SOS.dll can be found here.
3) Let the program run its course and see what happens.
4) First, let's see what method in managed code triggered the exception:
Using some simple managed code debugging techniques, you can easily pull out hard-coded credentials from a binary claiming to protect them.
A friend of mine (@Obscuresec) referred me to a blog post from a software vendor claiming that if you absolutely have to hard-code credentials into a PowerShell script, that you'd at least be safer by using their product to wrap the script into a binary. If all reverse engineers were monkeys this might be true... and there are a lot of monkeys out there. To their credit, they advise their readers to never hard-code credentials into scripts. However, this monkey, when armed with a debugger will dispute (i.e. throw feces at) the vendor's claim that their product offers an additional layer of security.
Today, I'll be analyzing WMIQueryPS1.exe which can be obtained via the link above. WMIQueryPS1.exe is simply a .NET-compiled binary wrapper for a PowerShell script. The script used in this example passes domain credentials to a remote machine in order to execute a WMI query. The goal of this exercise is to pull out the credentials without decompiling the binary. Here's my step-by-step walk-though of how this was achieved.
1) Fire up the executable in WinDbg.
2) Load the CLR debugging extension for WinDbg - SOS.dll.
SOS.dll is a WinDbg extension used to aid in debugging managed code. More details on SOS.dll can be found here.
3) Let the program run its course and see what happens.
Lol. We have a null-pointer dereference and conveniently, a good place to stop and inspect the CLR environment.
4) First, let's see what method in managed code triggered the exception:
The !IP2MD command will tell you what method a particular JITed address belongs to. In this example, an exception was thrown in the PoshExeHostCmd.VBSPoSH.GetValue method.
5) Now let's see the CLR call stack and get some context as to how we got to where we did:
Considering this binary just wraps an obfuscated PowerShell script, it has to pass the deobfuscated script block as a parameter at some point. PoshExeHostCmd.VBSPoSH.Execute(System.String, System.String[]) looks like a good candidate method worth inspecting. Let's launch the assembly again and set a managed breakpoint on that method
6) In WinDbg, you can only set breakpoints on native code. To break on managed code, you rely upon the !bpmd command. Note: You cannot execute CLR debugger extension commands until mscorwks.dll has been loaded. Therefore, I'll set a conditional breakpoint upon the loading of a module and set a breakpoint for the managed method in a single instruction.
The sx command confirms that my 'conditional' breakpoint was set. Now, let the program run and it will break on the method in question.
7) Here we are. Let's check out the string that was passed to the method as an argument.
8) After poking around a bit, it turns out that the address of the 'command' object (0x0309f09c) is what we want to dump
9) Boom! All your creds are belong to me! I've just dumped the secret credentials that this vendor is claiming to protect. Not a difficult exercise.
I could have used these techniques in conjunction with a tool like .NET Reflector to decompile the method (SimpleDecodeData) that pulls the obfuscated scriptblock from the PE file's resource section and then deobfuscate the Powershell scriptblock but that would just be redundant. That, and the vendor might not appreciate it that much.
So what's the lesson in all this? Do not ever, EVER hard-code credentials into a file! If someone wants them bad enough (me), they will get them.
Subscribe to:
Posts (Atom)



