Piracy in .NET /Silverlight Code – Part 1 – Even when the code is obfuscated

This is going to be a series of posts where I am going to demonstrating how someone with little advanced knowledge in .NET can hack in to the code, and circumventing licensing logic. I know there are other ways to prevent this ,which I am not going to be discussing about.

The usual assumption is that, if the code is obfuscated and signed , then it is close to impossible for someone to hack in to the code because the method names and variables are jumbled up, which would prevent someone from figuring what is happening.

In this post I am going to demonstrate a simple application which would prevent the user from updating the DataGrid because the user had downloaded only a trial version of the software. The software was supposed to disable grid and prevent the consumer from adding or updating the existing data, in a trial version. This was customer case, whom I helped in figuring out the vulnerability.

Here is the list of things the software did to prevent users from not accessing features that they were not entitled to

  1. Obfuscated the code, that prevented the code from being disassembled.
  2. The code that validated the consumers role either paid / trial alone was maintained in a separate assembly .So that for trial consumer’s, assembly version always returned false, for key features and for the paid subscribers got the assembly version that would return true. The organization assumption was ,by not even having the assembly ,trial users would never be able to circumvent the licensing logic. The customer could not maintain two versions of entire software, one for the trial another one paid because of the cost involved in maintenance and that’s the driving factor behind having a separate assembly.
  3. The code was signed and this prevented the users from hacking into the code and changing the code.

So here is code for figuring out the user role

public interface ICustomer{
bool HasAccess();}

And the trial assembly version code for figuring the role had

public class TrialCustomer : ICustomer {
public bool HasAccess() {
return false;
}
}

And here is the code that actually use the above code

private void Button1Click(object sender, EventArgs e)
{
if (!this.customer.HasAccess()) {
MessageBox.Show("Available only for paid customer");
}
else {
this.dataGridView1.Enabled = true;
}
}

So the idea behind this was, the trial version user would always get false as response, and would never be able to hack, because consumer does not even have library that would return true.

So I was asked to check for vulnerabilities. The first step was to disassemble the code using reflector and you can imagine the code had all weird names because of obfuscation.

The next step was to run the application and check when I get a dialog box for “Available only for paid customer”. The reason behind this was to get a callstack ,because all the methods names are jumbled and I didn’t know where to start.

When the messagebox popped up I attached the application to windbg and here is callstack

0:000> !clrstack

OS Thread Id: 0×4850 (0)

Child SP IP       Call Site

004ce758 752b438d [InlinedCallFrame: 004ce758]

004ce754 636308ec DomainBoundILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)

004ce758 636f4a53 [InlinedCallFrame: 004ce758] System.Windows.Forms.SafeNativeMethods.MessageBox(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)

004ce7ac 636f4a53 System.Windows.Forms.MessageBox.ShowCore(System.Windows.Forms.IWin32Window, System.String, System.String, System.Windows.Forms.MessageBoxButtons, System.Windows.Forms.MessageBoxIcon, System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxOptions, Boolean)

004ce7b0 001b0513 [InlinedCallFrame: 004ce7b0]

004ce84c 001b0513 c.a(System.Object, System.EventArgs)

004ce85c 630bfd6c System.Windows.Forms.Control.OnClick(System.EventArgs)

004ce874 630beb1e System.Windows.Forms.Button.OnClick(System.EventArgs)

004ce88c 636574b8 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)

004ce8a8 63629639 System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)

004ce93c 639d1a87 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)

004ce940 639f141d [InlinedCallFrame: 004ce940]

004ce994 639f141d System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)

004ce9d8 6312f8e0 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)

004ce9e4 630ce493 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)

004ce9ec 630ce411 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)

004cea00 630ce356 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)

004ceba4 007a09e5 [InlinedCallFrame: 004ceba4]

004ceba0 631347dc DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)

004ceba4 630de59f [InlinedCallFrame: 004ceba4] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)

004cebe8 630de59f System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)

004cebec 630de1cc [InlinedCallFrame: 004cebec]

004cec84 630de1cc System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)

004cecdc 630de021 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)

004ced0c 630c5ecd System.Windows.Forms.Application.Run(System.Windows.Forms.Form)

004ced20 001b009a e.a()

004cef58 6d1d213b [GCFrame: 004cef58]

Notice that there is a method c.a(System.Object, System.EventArgs) on the top of stack the before the framework code. This was my starting point, and I looked up for it in the reflector

private void a(object A_0, EventArgs A_1)
{
if (!this.d.a())
{
MessageBox.Show("Available only for paid customer");
}
else
{
this.b.Enabled = true;
}
}

So my next step was to disassemble the call to “this.d.a()” and here is output from reflector

public class d : b
{
// Methods
public bool a()
{
return false;
}
}

And I was hoping there would be a class variable that I could update inside the debugger to activate the feature.  Like I mentioned I before ,I couldn’t update the assembly using ILASM or anything, because the assemblies were signed and there wasn’t any class variable to update. This assembly was for the trial version consumers which were supposed to always return false.

But there was one trick that I had. I could update the register on the function return, So what I mean is, when the function “a” is invoked by the button click to validate the consumer , I would update the return register  from false to true. By doing this my grid would be enabled and I circumvent the logic with the existing constraints.

So here are the steps to do it

  • So I looked for the type “d”  !dumpheap -type d, remember the TrialCustomer class  was the renamed  to d by obfuscator .
  • Go the method table as 00146b9c from the above command , using  the method table  I had to get the entry address for the function public bool a() because the buttonclick  was invoking the function.
  • I used the command  !dumpmt -md 00146b9c   to get the entry address for   d.a()  and here was the entry address 001b0540  .

0:005> !dumpmt -md 00146b9c

EEClass:      0020054c

Module:       00142e9c

Name:         d

mdToken:      02000009

File:         C:\Users\naveen\Documents\Visual Studio 2010\Projects\SecureApplication\bin\Debug\Dotfuscated\SecureApplication.exe

BaseSize:        0xc

ComponentSize:   0×0

Slots in VTable: 6

Number of IFaces in IFaceMap: 1

————————————–

MethodDesc Table

Entry MethodDesc      JIT Name

649f5b34   64795750   PreJIT System.Object.ToString()

649c8be0   64795758   PreJIT System.Object.Equals(System.Object)

649c8af0   64795778   PreJIT System.Object.GetHashCode()

649e8aa0   6479578c   PreJIT System.Object.Finalize()

001b0540   00146b8c      JIT d.a()

0014c085   00146b94     NONE d..ctor()

  • The idea behind getting the address was to set a break-point on 001b0540  . I used the command bp 001b0540 . So when the break-point hits I would create another break-point for the function return, which  is stored in the register @esp  bp poi(@esp). So what bp poi(@esp) essentially means is,   create a break-point on the return of function  , it is almost like having a break-point on the last line inside a function within VS.NET.
  • So when the break-point hits for the function return ,the return value is stored in the @eax register and here is the output

0:000> g

Breakpoint 1 hit

eax=00000000 ebx=0230e6f8 ecx=0230cfcc edx=02328884 esi=0230ce7c edi=02328884

eip=001b04f2 esp=004ce8a0 ebp=004ce8a4 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

001b04f2 85c0            test    eax,eax

Now the trick is to update the @eax register from 00000000 to 00000001, by doing this we are changing the value from false to true, using the command  “r eax=00000001” and here is the output after updating the register

0:000> r

eax=00000001 ebx=0230e6f8 ecx=0230cfcc edx=02328884 esi=0230ce7c edi=02328884

eip=001b04f2 esp=004ce8a0 ebp=004ce8a4 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

001b04f2 85c0            test    eax,eax

Now we have managed to update return of the function, which in turn has let us update the grid ,which was supposed to be available only to the paid customers.

In the forth coming posts I will continue to post of few more things that I figured out.

About Naveen
Write code.

8 Responses to Piracy in .NET /Silverlight Code – Part 1 – Even when the code is obfuscated

  1. Pingback: DotNetShoutout

  2. Pingback: Piracy in .NET /Silverlight Code – Part 1 – Even when the code is obfuscated « Naveen’s Blog | Head.SmackOnTable();

  3. Timm says:

    Excellent article. The morale of the story is that there are no software locks that will keep out a truly motivated hacker.

    However, you can make your .NET application much more difficult to hack by using one of the better obfuscators on the market today. I noticed that you were able to view your obfuscated .NET assembly in Reflector. The member names were scrambled, but you could still see the method contents. Yet the best .NET obfuscators available today will actually crash Reflector, making the tool useless for disassembly. That won’t make it impossible to hack your assembly, but it sure makes it harder.

    In addition, some obfuscators enable you to embed all of your libraries (including .NET) into a single executable. The eliminates the need to publish a public API on your libraries, allowing all method names (not just private ones) to be scrambled, making it yet even more difficult to hack.

    Bottom line: If you’re going to spend all that money on an obfuscator, make sure you buy a good one.

    • Naveen says:

      Thanks Timm. I agree there are better obfuscators that can do the job. But at the end of the day,everything only compiles to IL. I was demonstrating in Reflector ,because most of us are aware of this tool. As long as you are aware of IL instructions then you can disassemble and hack it.

      The idea behind this series is to make Dev’s understand the problem and help them make better design decisions.

      In my forth coming posts I was going to explore some of the things you had mentioned.

  4. Pingback: iAwaaz-News-by-People

  5. Pingback: uberVU - social comments

  6. imak says:

    Thanks for the cool article. I just have one question about your remarks

    “3.The code was signed and this prevented the users from hacking into the code and changing the code”

    My understanding is that its easy enough to break the signed assembly with something like this.
    http://www.codeproject.com/KB/security/NeCoder03.aspx

    Am I missing something here?

  7. imak says:

    one more question, can the technique describe here work when the application is running under IE? meaning can you attach windbg to IE and change register value?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: