IntroIt's been a while since I wrote anything that interesting, so I figured for Thanksgiving, I'd go ahead and do so. Merry Thanksgiving. The first article in this “series“ is here.Cracking .NET programs can be just like cracking any other program. In this article, I'm going to use the same approach as I did last time. I threw together a quick little program called CrackMe2. CrackMe2 has a really cool feature called “Reverse Text”, however, it's only available to registered users. What's a poor boy to do?TargetFirst, we try registering. Since we don't have a valid code (we don't even know what one looks like), we get an “Invalid serial.“ MessageBox. OK, so now we know that the program does something when we click a button, and if the serial is wrong, we get a MessageBox.Darn, 123 didn't work.Well, the first step in cracking is defining our target and it's location. Our target is the code that's deciding to say “Invalid serial.” instead of “You're registered!”. Where's the “bad code“ that needs to be fixed? Well, with a .NET assembly, our first information is gained by taking a look with IL DASM.View of the obfuscated CrackMe2 assemblyOh no! It's obfuscated (thanks to Ivan Medvedev's Mangler). Let's assume this is a big application and that we'll never find what we're looking for just by going through the IL. Just by glancing at the hierarchy, we don't know that much more than when we started: There's a form with code.Seeing past the namesNow certainly, we can do static analysis and try to find out where the bad code is. One way would be by getting the strings (Ctrl+M in IL DASM, scroll to the bottom), and then grep the IL for ldstr , and work from there. In fact, that's a pretty quick and easy way to locate certain parts. However, lets pretend the strings are encrypted/dynamically generated, and that's not viable. So, let's start debugging.[Michael@MAO C:\]$ cordbg CrackMe2.exeMicrosoft (R) Common Language Runtime Test Debugger Shell Version 1.1.4322.573Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
(cordbg) run CrackMe2.exeProcess 4488/0x1188 created.Warning: couldn't load symbols for c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll[thread 0x1510] Thread created.Warning: couldn't load symbols for C:\CrackMe2.exeWarning: couldn't load symbols for c:\windows\assembly\gac\system.windows.forms\1.0.5000.0__b77a5c561934e089\system.windows.forms.dllWarning: couldn't load symbols for c:\windows\assembly\gac\system\1.0.5000.0__b77a5c561934e089\system.dll
[0004] mov ecx,98543Ch(cordbg)cordbg is a command line debugger that ships with the .NET Framework SDK, and it's just loaded the CrackMe2.exe and related assemblies. Just like before, we're going to go ahead and set a breakpoint and find out where we are in the program, and work from there. So, let's breakpoint the MessageBox.Show function. We use IL-similar syntax to specify the function name: NameSpace.ClassName::Method.(cordbg) b System.Windows.Forms.MessageBox::ShowBreakpoint #1 has bound to c:\windows\assembly\gac\system.windows.forms\1.0.5000.0__b77a5c561934e089\system.windows.forms.dll.#1 c:\windows\assembly\gac\system.windows.forms\1.0.5000.0__b77a5c561934e089\system.windows.forms.dll!System.Windows.Forms.MessageBox::Show:0 Show+0x0(native) [active](cordbg)Then, we tell cordbg to go until it breaks by typing go. The form comes up, and we enter a serial number: 123.(cordbg) goWarning: couldn't load symbols for c:\windows\assembly\gac\system.drawing\1.0.5000.0__b03f5f7f11d50a3a\system.drawing.dllbreak at #1 c:\windows\assembly\gac\system.windows.forms\1.0.5000.0__b77a5c561934e089\system.windows.forms.dll!System.Windows.Forms.MessageBox::Show:0 Show+0x0(native) [active]Source not available when in the prolog of a function(offset 0x0)
[0000] push edi(cordbg)Bingo, we're stopped at a MessageBox. We want to know who called this function, since most likely, that will lead us to the critical code section we need to fix. So, we ask cordbg where are we?(cordbg) whereThread 0x1510 Current State:Normal0)* system.windows.forms!System.Windows.Forms.MessageBox::Show +0000 [no source information available] owner=(0x00ac36b0) text=(0x00ad5854) "Invalid serial."1) CrackMe2!CrackMe2.Form1::AAAAAAAAAAAAAAAAAAAA +0070 [no source information available] AAAAAA=(0x00ac8400) A=(0x00aca86c) 2) system.windows.forms!System.Windows.Forms.Control::OnClick +005e [no source information available] e=(0x00aca86c) 9) system.windows.forms!ControlNativeWindow::OnMessage +0013 [no source information available] m=(0x0012ef04) --- Managed transition ---We see what's expected. Somewhere in Win32 code, a message was sent, and we see the OnMessage called and bubbling up all the way to the Control::OnClick, and then user code. We can look at all the arguments along the way, and that's useful for more complex scenarios (say, when a registration function calls another passing the serial number or validation code).At any rate, we've got something to go on: The name of the function that calls the MessageBox: CrackMe2.Form1::AAAAAAAAAAAAAAAAAAAA (20 A's). We're done with cordbg (quit). Our next stop is to read the bad code.Looking at the bad codeUsing IL DASM (see above), I navigate to the CrackMe2.Form1::AAAAAAAAAAAAAAAAAAAA method. Inside is relatively straighforward code. First, there's a try/catch that has an Int32::Parse call in it. The result is stored in local 0. So we now know the code is numeric. Immediately after the catch handler, we have this snippet: IL_0022: ldloc.0 IL_0023: ldc.i4.1 IL_0024: and IL_0025: ldc.i4.1 IL_0026: bne.un.s IL_0035 IL_0028: ldarg.0 IL_0029: ldstr "Invalid serial." IL_002e: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(class [System.Windows.Forms]System.Windows.Forms.IWin32Window, string)Load the local (the number entered), then load the number 1, and AND them. Then, load one, and if they are not equal, jump to IL_0035. If they are equal, execute the following instructions, which quite obviously say “Invalid serial.”. AND'ing a number with 1 and comparing to 1 is a check to see if the number is odd. So, at this point, we can write a keygenerator that produces... even numbers. A keygenerator is always preferred to a patch, however, generally speaking, finding the algorithm might be a bit harder. Then, there's always the possibility that the check actually does something hard to fake (i.e., uses RSA or talks to a hardware dongle/web service). So, let's go on and patch this code.At IL_0035 (the target of the branch if the number is even), we have some code that does activation work and then proceeds to say “Thank you...”. Simple sample. Now, let's make the fix.Simple PatchingWith IL DASM and IL ASM, we have a really easy way to make patches. Simply run ildasm /out=CrackMe2.il CrackMe2.exe, and IL DASM will dump all the IL required for that assembly to a nicely formatted file. All we have to do is goto the bad method and fix up the IL. I think the most unintrusive fix would be to add “br IL_0035” to the top of the method. That would branch immediately to the good code, and the product would activate on any serial number entered.However, some obfuscators try to stop IL DASM round tripping, and that might stop some posers in their tracks. The IL obfuscator I'm going to give away for free will do this, for example. (Actually, my free obfuscator would make this tutorial a bit harder because of how it handles names -- we'd have to actually get a token instead.)Assuming we can't use IL DASM/ASM, what can we do? Use a hex editor. Binary PatchingWhen we can't reassemble an entire program, we can patch certain opcodes instead. Tools like OllyDbg have a built-in assembler so we can easily make patches to the x86 code. For IL, I'm not aware of any such tool. Another issue with binary patching IL is that we have to ensure the resulting IL is fully correct and is able to be JIT'd to native code. If our patch ends up screwing with the IL in a way that makes it incorrect, we'll get a runtime exception from the execution engine. Let's try to create a binary patch that jumps from the beginning of the method right to the good code, at IL offset 0x0035.First, in IL DASM, turn on “Show bytes”, under the View menu. This allows us to see the actual bytes that make up the opcodes. Now, lets look at the beginning of the critical function: // Method begins at RVA 0x2434 // Code size 78 (0x4e) .maxstack 2 .locals init (int32 V_0) .try { IL_0000: /* 02 | */ ldarg.0 IL_0001: /* 7B | (04)000002 */ ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox CrackMe2.Form1::AAAAAAAAAAAA IL_0006: /* 6F | (0A)000026 */ callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text() IL_000b: /* 28 | (0A)000027 */ call int32 [mscorlib]System.Int32::Parse(string) IL_0010: /* 0A | */ stloc.0 IL_0011: /* DE | 0F */ leave.s IL_0022 } // end .tryThis code is protected in a try block. We could go and remove the try block, but that's modifying more code. Generally speaking, we should aim to patch as little code as possible to ensure we don't accidentally screw something up. So, we're going to deal with the try block and fix it from within. The ECMA specifications for .NET will come in handy here. Specifically, Partition III, CIL. This can be found in the .NET Framework SDK folder, under “Tool Developers Guide\docs”. It's also available from MSDN, here. The first instinct is to say, hey, let's change IL_0000 to a br to IL_0035, and NOP out the remainder of the try block. However, that'd create illegal code, since you can't branch out from a try block, you must use the leave opcode instead. So, let's rewrite the method to simply leave to IL_0035. Here's the description of the leave opcode:The leave instruction unconditionally transfers control to target. Target is represented as a signed offset (4 bytes for leave, 1 byte for leave.s) from the beginning of the instruction following the current instruction.The formats (in hex) are DD <4 bytes> for leave and DE <1 byte> (as shown above), for leave.s. We'll use leave.s, just to be efficient :). Since the total size for leave.s is 2 bytes, we calculate the offset to 0x35 from 0x02 (since our leave instruction is at 0x00). Subtraction tells us we must have an offset of 0x33. Hence, our leave instruction in hex looks like: DE 33. Since that'd leave the IL in an incorrect state, we must nop out the rest of the try block. The hex for nop is 00.Open the assembly in your favorite hex editor, and let's find the method. IL DASM gives us the RVA, but for now we'll just search for a specific byte sequence. The IL DASM Show bytes allows us to easily find our place. Do note that the way tokens are displayed ((04)000002, for example), is reverse from how they are stored. Depending on the size of the app, you might need to search on quite a large number of bytes, since IL sequences are most likely repeated. For this case, we're going to search on the last bit: “0A DE 0F”. No other matches found, so this is the one.As when programming, in cracking we have many ways to solve a problem. Many of them can be considered “right”. We could make a simple one-byte patch by allowing any number as a valid serial. This has the merit of ensuring the local int is assigned, and well, being only a one-byte edit. The leave.s opcode is at offset 0x11, so add 2 to that amount and we get 0x13. 0x35 - 0x13 = 0x22. So by changing “0F” to “22”, we'd have our crack. However, let's stick to the original plan and jump right to the good bits from the beginning.In the hex editor, we back up a bit until we find the 02 7B 02 00 00 04 part (ldarg.0, then load the textbox field). At the 02, we drop our leave.s IL_0035 payload, which is DE 33. Then, we nop out (00) everything until the end of the 0A DE 0F part. The resulting hex for the try block is thus: DE 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00. Save the file as CrackMe2.cracked.exe.SatisfactionRun the program. Type in anything for the serial. “Thank you for registering.” The second textbox activates. We've won access to the coveted “Reverse Text” function. Write up an .NFO, ensuring to remind people to purchase software to support the authors. Then kick back and play a game of KSpaceDuel.Download the program itself (Right click and save as, since it's a .NET assembly and IEExec will try to run it otherwise): CrackMe2.exe (24 KB). Or, download the source: CrackMe2.cs.txt (4.81 KB).Was this post interesting, helpful, stupid, or lame? Leave a comment and help me improve.
Remember Me