Remotesoft's Salamander 1.1.6.0 (Native Compiling)
The version (taken from the rscoree.dll) of the Salamander is some months old: I didn't bother to try a new version. However, the things I'm going to say are still valid.
This is no reversing article (you do know reversing is illegal, right?). I'm not going to debug, disassemble or do any other thing to other people's code. The only code I'm going to show is my own code. That's legal, alright?
Let's spend two words about this protection. It's quite famous because on the homepage it says explicitly that the protection compiles MSIL into native
x86 code. Also, on a lot of forums it's being advised because of this feature.
To obtain a demo of this protection isn't so easy: you have to do it through mail exchange. The protected exe I have (a simple hello world .NET windows application) is provided with the protection's dll: rscoree.dll. I
haven't touched the dll, because it would violate the rights of Remotesoft.
If I open my protected executable with ildasm I can see that there's no way to disassemble the original methods, just as Remotesoft's homepage says. In fact, the result of this operation is something like this:
.method private hidebysig static void Main() cil managed noinlining { .entrypoint .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Form1::Main |
When I looked at the exe with the CFF Explorer I noticed that it contained a section more than regular .NET assemblies. Moreover, the #US (Unicode Strings used in the application) stream
was empty.
I executed the application, opened the process with WinHex, went to the additional section of data and saw this:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 004080C0 00 0F 62 00 75 00 74 00 74 00 6F 00 6E 00 31 00 ..b.u.t.t.o.n.1. 004080D0 00 09 54 00 65 00 73 00 74 00 00 0B 46 00 6F 00 ..T.e.s.t...F.o. 004080E0 72 00 6D 00 31 00 00 05 4F 00 6B 00 00 00 00 00 r.m.1...O.k..... |
That's my original #US stream... Strange enough. A few bytes after the original #US stream I saw this:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 004080F0 13 30 01 00 0B 00 00 00 00 00 00 00 73 06 00 00 .0..........s... 00408100 06 28 24 00 00 0A 2A 00 00 00 00 00 00 00 00 00 .($...*......... |
Uhm 13 30 01... Seems to be the first dword of a fat header method, the next dword is the size of the code and the signature dword is null. The code ends with the return opcode 2A. I linked the RVA of a method of my exe to this code and opened ildasm. The disassembled code is:
.method private hidebysig static void Main() cil managed noinlining // SIG: 00 00 01 { .entrypoint .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x80f0 // Code size 11 (0xb) .maxstack 1 IL_0000: /* 73 | (06)000006 */ newobj instance void HelloWorld.Form1::.ctor() IL_0005: /* 28 | (0A)000024 */ call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form) IL_000a: /* 2A | */ ret } // end of method Form1::Main |
Ok this is the code of my original Main. That's VERY strange. I thought: maybe I find the other methods as well... But the section didn't contain other code... I had something in mind and decided (of course) to give it a try: I was already very skeptical about the native code. I took the biggest method of my executable:
.method private hidebysig instance void InitializeComponent() cil managed // SIG: 20 00 01 { // Method begins at RVA 0x209c // Code size 186 (0xba) .maxstack 4 IL_0000: /* 02 | */ ldarg.0 IL_0001: /* 73 | (0A)000010 */ newobj instance void [System.Windows.Forms]System.Windows.Forms.Button::.ctor() IL_0006: /* 7D | (04)000001 */ stfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_000b: /* 02 | */ ldarg.0 IL_000c: /* 28 | (0A)000011 */ call instance void [System.Windows.Forms]System.Windows.Forms.Control::SuspendLayout() IL_0011: /* 02 | */ ldarg.0 IL_0012: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0017: /* 1F | 0C */ ldc.i4.s 12 IL_0019: /* 1F | 10 */ ldc.i4.s 16 IL_001b: /* 73 | (0A)000012 */ newobj instance void [System.Drawing]System.Drawing.Point::.ctor(int32, int32) IL_0020: /* 6F | (0A)000013 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Location(valuetype [System.Drawing]System.Drawing.Point) IL_0025: /* 02 | */ ldarg.0 IL_0026: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_002b: /* 72 | (70)000001 */ ldstr "button1" IL_0030: /* 6F | (0A)000014 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Name(string) IL_0035: /* 02 | */ ldarg.0 IL_0036: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_003b: /* 16 | */ ldc.i4.0 IL_003c: /* 6F | (0A)000015 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_TabIndex(int32) IL_0041: /* 02 | */ ldarg.0 IL_0042: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0047: /* 72 | (70)000011 */ ldstr "Test" IL_004c: /* 6F | (0A)000016 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string) IL_0051: /* 02 | */ ldarg.0 IL_0052: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0057: /* 02 | */ ldarg.0 IL_0058: /* FE06 | (06)000005 */ ldftn instance void HelloWorld.Form1::button1_Click(object, class [mscorlib]System.EventArgs) IL_005e: /* 73 | (0A)000017 */ newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int) IL_0063: /* 6F | (0A)000018 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class [mscorlib]System.EventHandler) IL_0068: /* 02 | */ ldarg.0 IL_0069: /* 1B | */ ldc.i4.5 IL_006a: /* 1F | 0D */ ldc.i4.s 13 IL_006c: /* 73 | (0A)000019 */ newobj instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) IL_0071: /* 6F | (0A)00001A */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Form::set_AutoScaleBaseSize(valuetype [System.Drawing]System.Drawing.Size) IL_0076: /* 02 | */ ldarg.0 IL_0077: /* 20 | 24010000 */ ldc.i4 0x124 IL_007c: /* 20 | 11010000 */ ldc.i4 0x111 IL_0081: /* 73 | (0A)000019 */ newobj instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) IL_0086: /* 28 | (0A)00001B */ call instance void [System.Windows.Forms]System.Windows.Forms.Form::set_ClientSize(valuetype [System.Drawing]System.Drawing.Size) IL_008b: /* 02 | */ ldarg.0 IL_008c: /* 28 | (0A)00001C */ call instance class [System.Windows.Forms]System.Windows.Forms.Control/ControlCollection [System.Windows.Forms]System.Windows.Forms.Control::get_Controls() IL_0091: /* 02 | */ ldarg.0 IL_0092: /* 7B | (04)000001 */ ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0097: /* 6F | (0A)00001D */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control/ControlCollection::Add(class [System.Windows.Forms]System.Windows.Forms.Control) IL_009c: /* 02 | */ ldarg.0 IL_009d: /* 72 | (70)00001B */ ldstr "Form1" IL_00a2: /* 28 | (0A)000014 */ call instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Name(string) IL_00a7: /* 02 | */ ldarg.0 IL_00a8: /* 72 | (70)00001B */ ldstr "Form1" IL_00ad: /* 6F | (0A)000016 */ callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string) IL_00b2: /* 02 | */ ldarg.0 IL_00b3: /* 16 | */ ldc.i4.0 IL_00b4: /* 28 | (0A)00001E */ call instance void [System.Windows.Forms]System.Windows.Forms.Control::ResumeLayout(bool) IL_00b9: /* 2A | */ ret } // end of method Form1::InitializeComponent |
And extracted the first opcodes, this doesn't mean all the bytes, keep in mind that tokens change when an assembly is recompiled by the reflection. I wanted to search for the opcodes in the process memory using as wildcard for the tokens bytes 3F (the default wildcard of WinHex). So the hex string I typed in WinHex was:
02733F3F3F3F7D3F3F3F3F02283F3F3F3F027B3F3F3F3F1F0C1F10733F3F3F3F
And it took me to:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 02F443C0 02 73 14 00 00 0A 7D 03 00 00 04 02 .s....}..... 02F443D0 28 15 00 00 0A 02 7B 03 00 00 04 1F 0C 1F 10 73 (.....{........s 02F443E0 16 00 00 0A 6F 17 00 00 0A 02 7B 03 00 00 04 72 ....o.....{....r 02F443F0 09 00 00 70 6F 18 00 00 0A 02 7B 03 00 00 04 16 ...po.....{..... 02F44400 6F 19 00 00 0A 02 7B 03 00 00 04 72 19 00 00 70 o.....{....r...p 02F44410 6F 1A 00 00 0A 02 7B 03 00 00 04 02 FE 06 0A 00 o.....{.....þ... 02F44420 00 06 73 1B 00 00 0A 6F 1C 00 00 0A 02 1B 1F 0D ..s....o........ 02F44430 73 1D 00 00 0A 6F 1E 00 00 0A 02 20 24 01 00 00 s....o..... $... 02F44440 20 11 01 00 00 73 1D 00 00 0A 28 1F 00 00 0A 02 ....s....(..... 02F44450 28 20 00 00 0A 02 7B 03 00 00 04 6F 21 00 00 0A ( ....{....o!... 02F44460 02 72 23 00 00 70 28 18 00 00 0A 02 72 23 00 00 .r#..p(.....r#.. 02F44470 70 6F 1A 00 00 0A 02 16 28 22 00 00 0A 2A po......("...* |
I copied and disassembled it (just to make absolutely sure):
.method private hidebysig instance void InitializeComponent() cil managed noinlining { // Code size 186 (0xba) .maxstack 4 IL_0000: ldarg.0 IL_0001: newobj instance void [System.Windows.Forms]System.Windows.Forms.Button::.ctor() IL_0006: stfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_000b: ldarg.0 IL_000c: call instance void [System.Windows.Forms]System.Windows.Forms.Control::SuspendLayout() IL_0011: ldarg.0 IL_0012: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0017: ldc.i4.s 12 IL_0019: ldc.i4.s 16 IL_001b: newobj instance void [System.Drawing]System.Drawing.Point::.ctor(int32, int32) IL_0020: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Location(valuetype [System.Drawing]System.Drawing.Point) IL_0025: ldarg.0 IL_0026: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_002b: ldstr [ERROR: INVALID TOKEN 0x70000009] IL_0030: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Name(string) IL_0035: ldarg.0 IL_0036: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_003b: ldc.i4.0 IL_003c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_TabIndex(int32) IL_0041: ldarg.0 IL_0042: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0047: ldstr [ERROR: INVALID TOKEN 0x70000019] IL_004c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string) IL_0051: ldarg.0 IL_0052: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0057: ldarg.0 IL_0058: ldftn instance void HelloWorld.Form1::button1_Click(object, class [mscorlib]System.EventArgs) IL_005e: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int) IL_0063: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class [mscorlib]System.EventHandler) IL_0068: ldarg.0 IL_0069: ldc.i4.5 IL_006a: ldc.i4.s 13 IL_006c: newobj instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) IL_0071: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Form::set_AutoScaleBaseSize(valuetype [System.Drawing]System.Drawing.Size) IL_0076: ldarg.0 IL_0077: ldc.i4 0x124 IL_007c: ldc.i4 0x111 IL_0081: newobj instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) IL_0086: call instance void [System.Windows.Forms]System.Windows.Forms.Form::set_ClientSize(valuetype [System.Drawing]System.Drawing.Size) IL_008b: ldarg.0 IL_008c: call instance class [System.Windows.Forms]System.Windows.Forms.Control/ControlCollection [System.Windows.Forms]System.Windows.Forms.Control::get_Controls() IL_0091: ldarg.0 IL_0092: ldfld class [System.Windows.Forms]System.Windows.Forms.Button HelloWorld.Form1::button1 IL_0097: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control/ControlCollection::Add(class [System.Windows.Forms]System.Windows.Forms.Control) IL_009c: ldarg.0 IL_009d: ldstr [ERROR: INVALID TOKEN 0x70000023] IL_00a2: call instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Name(string) IL_00a7: ldarg.0 IL_00a8: ldstr [ERROR: INVALID TOKEN 0x70000023] IL_00ad: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string) IL_00b2: ldarg.0 IL_00b3: ldc.i4.0 IL_00b4: call instance void [System.Windows.Forms]System.Windows.Forms.Control::ResumeLayout(bool) IL_00b9: ret } // end of method Form1::InitializeComponent |
Ok, this is really the MSIL code of my method, no mistakes. The ldstr instructions show an invalid token because I haven't fixed the #US stream, but this is not important. To be accurate I searched even for the code of the last method left:
.method private hidebysig instance void button1_Click(object sender, class [mscorlib]System.EventArgs e) cil managed // SIG: 20 02 01 1C 12 11 { // Method begins at RVA 0x217c // Code size 12 (0xc) .maxstack 1 IL_0000: /* 72 | (70)000027 */ ldstr "Ok" IL_0005: /* 28 | (0A)000021 */ call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string) IL_000a: /* 26 | */ pop IL_000b: /* 2A | */ ret } // end of method Form1::button1_Click |
Same procedure:
723F3F3F3F283F3F3F3F262A
Takes us to:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 02F43780 72 2F 00 00 70 28 25 00 00 0A 26 2A ....r/..p(%...&* |
And that's it. Now we have the MSIL code of all methods. Where's the Native Code? I don't want to imply that the Remotesoft's linker is a schwindle, but this assembly raises some reasonable doubts, in my opinion. I won't dig into the criteria used by the protection to displace the code in memory, this wouldn't be right. I honestly hope for the costumers of this product that I'm wrong (which is possible: maybe Remotesoft applied the wrong protection to this assembly, who knows..), otherwise they paid (much) for something that is not real. Moreover, the protected code is totally alike to the original, so every common code obfuscator would protect MSIL code better than this. Maybe Remotesoft will clarify this situation.
Epilogue:
The Remotesoft programmer (Huihong) posted this message on my blog:
Hello,
Please email your assembly to me, and I will take a look. MSIL instructions
should not be found from memory. It seems the protected code is from some of my
debug builds, where IL code is embedded within the assembly for debugging
purpose.
I will post a sample, and you are welcome to try to get the IL code. I gurantee
that you won't find any IL code, with everything compiled down to x86 code.
Huihong
He provided a new exe and the same version of
the dll. And now it did make sense: the new exe was totally different from the
old one, it didn't have a section more and even the imports were different. The
IL code is not present, since the dll simply puts a ngen created native image in
the assembly cache (no reversing, just Filemon). With the .NET Framework 1 I
managed to do the same (and it did surprise me that others didn't do the same
because it's quite easy), the problem is that with the version 2 of the
framework native images changed: they have more sections and contain the
MetaData and the MSIL code of the original assembly. If the new native images
work even after removing the IL code I don't know, I have still to test this. So
in a few words, the code protection offered by the Salamander on what regards
native images of the first version of the framework is very good, since those
images don't seem to me to contain any IL code. What happens with 2.0 images I
have no idea. The license protection should be quite good as long as you don't
have the decrypted native image. The string protection is quite easy to break
once decrypted. Also, Huihong asked if maybe I had the instructions of the old
assembly cached before in memory, but this is not possible, because the original
exe was given to me (it wasn't really my exe) only after I saw the first method
in memory. So I guess it was just an honest mistake on both sides. Anyway, I'd
like to thank Huihong for his courtesy in clarifying this whole thing.
Daniel Pistelli