Title: Comparison of Two c # local code encryption software
-- Remotesoft Protector and MaxtoCode
【Author】 henryouly
[Statement] This article is purely a technical discussion. Retain the author's information during reprinting. I am not familiar with the. NET platform. I have only recently been familiar with cracking and. NET platforms. I do not have a full understanding of the latest technologies and may not be biased in my analysis. Please advise.
Several articles have discussed the encryption technology of C # in detail, mainly studying the general principle of obfuscators to protect. NET programs and their cracking methods. Obviously, the features of the language itself make the protection on the IL level very pale. So another type of software-protected product is generated-local code compilation.
Remotesoft Protector is an encryption product of a foreign software company, while MaxtoCode is a masterpiece of Jason. NET. These two products share the same features: a) release of a locally compiled dll file, and B) function code is no longer directly implemented in the class, any method that can be seen by the IL anti-assembler is empty.
[Where is the function body?]
Of course, when a program is executed, the function body must be filled back with content like a magic. Next, let's take a look at what kind of protection software is playing.
Let's take a look at the Remotesoft Protecter-encrypted WebGrid. NET 3.5. This ASP. NET program can be run only when ISNet. WebUI. WebGrid. dll and rscoree. dll are put together in the bin. Use Reflector to open ISNet. WebUI. WebGrid. dll.
Internal class <PrivateImplementationDetails>
{
// Methods
[MethodImpl (MethodImplOptions. NoInlining)]
Internal static void $ method-1 ();
[MethodImpl (MethodImplOptions. NoInlining)]
Internal static void $ method-2 ();
[MethodImpl (MethodImplOptions. ForwardRef), DllImport ("rscoree. dll", CharSet = CharSet. Ansi, ExactSpelling = true)]
Private static extern void _ RSEEStartup (int A_0 );
[MethodImpl (MethodImplOptions. ForwardRef), DllImport ("rscoree. dll", CharSet = CharSet. Ansi, ExactSpelling = true)]
Private static extern void _ RSEEUpdate (IntPtr A_0 );
// Fields
Private static bool $ started-1;
Private static bool $ started-2;
}
[MethodImpl (MethodImplOptions. NoInlining)]
Internal static unsafe void $ method-1 ()
{
If (! <PrivateImplementationDetails>. $ started-1) // ensure that the initialization is only performed once.
{
Fixed (char * local1 = "")
{
<PrivateImplementationDetails>. _ RSEEStartup (int) local1 );
// Here we use a small trick to get the RVA of the code segment in the memory through local1 and pass it to _ RSEEStartup
}
<PrivateImplementationDetails>. $ started-1 = true;
}
}
[MethodImpl (MethodImplOptions. NoInlining)]
Internal static void $ method-2 ()
// Note: This class was originally obfuscated (and non-flow obfuscation is to construct a special stack structure to prevent Reflector from being interpreted as a C # statement)
// The author of Remotesoft Protector intentionally wrote it manually. Use the methods described in the previous articles to confuse C # code.
{
If (! <PrivateImplementationDetails>. $ started-2) // ensure that the initialization is only performed once.
{
StackTrace trace1 = new StackTrace ();
If (trace1.FrameCount> 2)
{
<PrivateImplementationDetails>. _ RSEEUpdate (trace1.GetFrame (2). GetMethod (). MethodHandle. Value );
// Obtain the method name in the call stack and pass it as a parameter to _ RSEEUpdate. Why?
<PrivateImplementationDetails>. $ started-2 = true;
}
}
}
In addition, we note that many classes have added a static constructor with the same content, as shown below:
[MethodImpl (MethodImplOptions. NoInlining)]
Static WebGrid ()
{
<PrivateImplementationDetails>. $ method-1 ();
<PrivateImplementationDetails>. $ method-2 ();
}
This constructor is called only when this class is used for the first time. The function is to backfill the extracted parts of the class to the assembly in the memory.
It is obvious that before each class is used for the first time, the static constructor is called to call method-1 () and method-2 (), the role of method-1 is to locate the location of the current code in the memory, while GetFrame (2) in method-2 is the handle to retrieve the constructor of this class (such as WebGrid. These two functions can be combined to implement the working principle of stolen byte similar to the Win32 PE file. before using the class, modify the constructor and use the constructor to fill in other code.
I tried a little and found that it is not easy to extract the assembly from the memory .. NET Framework does not provide a way to directly access the IL code in the memory, so it is impossible to obtain the function body. However, we tried to use LordPE to dump out the memory and found that the function body is still empty. Obviously, Remotesoft does not directly fill the memory area, instead, we use a ResolveHandler-like method to dynamically Add code and link the original Assembly.
In addition, by viewing rscoree. dll and ISNet. WebUI. WebGrid. dll, we found that the modification date is the same. I believe that the function is statically compiled into rscoree. dll. ISNet. WebUI. WebGrid. dll is purely a body that does not contain any function.
Analyze the principle of MaxtoCode. The trial version of MaxtoCode 2.0 is downloaded online. The MaxtoCode download page indicates that the operation depends on ilasm and ildasm. It is obvious that MaxtoCode must first convert the assembly file dasm into an IL file, then add its own functions, and then re-assemble it into an assembly.
After a brief analysis, the MaxtoCode construction principle should be:
1. Call ildasm to decompile the assembly file into IL code
2. Clear all comments in the IL File
3. Add a new entry point to the IL file.
. Method private hidebysig static void
_ 1 () cel managed
{
. Entrypoint
. Custom instance void [mscorlib] System. STAThreadAttribute:. ctor () = (01 00 00 00)
. Maxstack 5
. Locals init (string Jason_0)
IL_0000: lds1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_0007: ldc. i4.0
IL_0008: bne. un. s IL_0021
IL_000a: call class [mscorlib] System. Reflection. Assembly [mscorlib] System. Reflection. Assembly: GetExecutingAssembly ()
IL_000f: callvirt instance string [mscorlib] System. Reflection. Assembly: get_Location ()
IL_0014: stloc. s Jason_0
IL_0015: ldloca. s Jason_0
IL_0017: call int32 'reflector '. 'application': AABBCCDDEE12345 (string &)
IL_001c: sts1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_0021: lds1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_0026: call bool 'reflector '. 'application': JasonIsGood_Actions (int32)
IL_0028: pop
IL_0029: lds1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_002d: ldc. i4 0x86da
IL_0032: ldc. i4.2
IL_0036: ldc. i4.0
IL_0037: ldc. i4.1
IL_0038: call bool 'reflector '. 'application': JasonIsGood_Actions_1 (int32,
Int32,
Int32,
Int32,
Int32)
IL_003d: pop
IL_0042: call void 'reflector '. 'application': mtc0000_000086da ()
IL_0043: ret
}
4. Change the original entry point to mtc0000_000086da and add the following content:
. Method private hidebysig static void
Mtc1__1_86da () cel managed
{
. Locals init (class Reflector. Application V_0, string Jason_0)
IL_0000: lds1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_0007: ldc. i4.0
IL_0008: bne. un. s IL_0021
IL_000a: call class [mscorlib] System. Reflection. Assembly [mscorlib] System. Reflection. Assembly: GetExecutingAssembly ()
IL_000f: callvirt instance string [mscorlib] System. Reflection. Assembly: get_Location ()
IL_0014: stloc. s Jason_0
IL_0015: ldloca. s Jason_0
IL_0017: call int32 'reflector '. 'application': AABBCCDDEE12345 (string &)
IL_001c: sts1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_0021: nop
IL_0028: lds1_int32 'reflector '. 'application': Locate _ Assembly _ Images _ 1
IL_002d: ldc. i4 0x86da
IL_0032: ldc. i4.2
IL_0036: ldc. i4.0
IL_0037: ldc. i4.0
IL_0038: call bool 'reflector '. 'application': JasonIsGood_Actions_1 (int32,
Int32,
Int32,
Int32,
Int32)
IL_003d: sts0000bool 'reflector '. 'application': mtc0000_000086da _ field
...... Main original content ......
5. Add the following to the class to be encrypted:
. Field private static int32 Locate _ Assembly _ Images _ 1
. Field private static bool Locate _ Assembly _ Images _ 2
. Method private hidebysig static pinvokeimpl ("kernel32" as "GetModuleHandleA" nomangle ansi lasterr winapi)
Int32 'aabbccddee12345 '(string & marshal (byvalstr) 'lpmodulename') cel managed preservesig
{
}
. Method private hidebysig static pinvokeimpl ("MShare. dll" as "EC1DB9C1620C48588C4701045B242FA9" nomangle ansi lasterr winapi)
Bool 'jasonisgood _ Actions '(int32 'A') cel managed preservesig
{
}
. Method private hidebysig static pinvokeimpl ("MShare. dll" as "F1B0C9B05CF2496c8873B60602A22743" nomangle ansi lasterr winapi)
Bool 'jasonisgood _ Actions_1 '(int32 'A', int32' B ', int32 'C', int32 'D', int32 'E' e') cel managed preservesig
{
}
6. Add PrivateImplementationDetails, which will be detailed later.
7. obfuscation: replace Class Name, variable name, and method name with invisible characters (for example, '\ r \ n ')
8. recompile and copy Attick. dll in the installation directory to MShare. dll.
The first five steps were analyzed directly using the Reflector main program. Do you still remember that Reflector will crash ildasm? Haha is to use its crash to wait for a specific time to slowly flip the temporary files.
Because Reflector will cause a crash, it is impossible to get the final exe file, so I wrote another small demo file for testing. Use Reflector to view the encrypted result:
Internal class <PrivateImplementationDetails>
{
// Methods
[DllImport ("kernel32", EntryPoint = "GetModuleHandleA", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern int // The function name is \ r \ n.
([Financialas (UnmanagedType. VBByRefStr)] ref string lpModuleName );
[DllImport ("MShare. dll", EntryPoint = "EC1DB9C1620C48588C4701045B242FA9", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern bool // same as above
(Int );
[DllImport ("MShare. dll", EntryPoint = "F1B0C9B05CF2496c8873B60602A22743", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern bool
(Int a, int B, int c, int d, int e );
// Fields
Private static bool
;
Private static int
;
Internal static $ struct0x6000001-1 $ method0x6000001-1 = {0x35, 0x46, 0x42, 0x32, 0x38, 0x31, 0x46, 0x36 };
// Nested Types
[StructLayout (LayoutKind. Explicit, Size = 8, Pack = 1)]
Private struct $ struct0x6000001-1
{
}
}
Internal class Class1
{
// Methods
Private static void
();
[DllImport ("kernel32", EntryPoint = "GetModuleHandleA", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern int
([Financialas (UnmanagedType. VBByRefStr)] ref string lpModuleName );
[DllImport ("MShare. dll", EntryPoint = "EC1DB9C1620C48588C4701045B242FA9", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern bool
(Int );
[DllImport ("MShare. dll", EntryPoint = "F1B0C9B05CF2496c8873B60602A22743", CharSet = CharSet. Ansi, SetLastError = true, ExactSpelling = true)]
Private static extern bool
(Int a, int B, int c, int d, int e );
Public Class1 ();
[STAThread]
Private static void Main ();
// Fields
Private static bool
;
Private static bool
;
Private static int
;
}
[STAThread]
Private static void Main ()
{
If (Class1.
= 0)
{
Class1.
= Class1.
(Ref Assembly. GetExecutingAssembly (). Location );
}
Class1.
(Class1.
);
Class1.
(Class1.
, 0x2e, 2, 0, 1 );
Class1.
();
}
The method0x6000001-1 should be the encrypted function body. The idea of MaxtoCode is similar to that of Remotesoft, but the implementation method is different. MaxtoCode Mshare. the dll file is pre-written by MaxtoCode and does not contain any encryption software functions. It only determines the decryption algorithm and decrypts it (according to the author's introduction, there are 7 different encryption algorithms or variants in the Enterprise Edition ). The extracted feature (stolen byte) is encrypted and stored in assembly in a static manner. Therefore, the encryption strength of MaxtoCode lies in the difficulty of analyzing the decryption algorithm of the function body.
Currently, I have not found a convenient solution to these two encryption methods (but it does not necessarily mean no, maybe I don't know ).. NET Framework does not provide a way to convert the Assembly content in the memory to a local exe/dll. If yes.. NET exe file, you can also debug through OD (of course, at this time, the IL code has been automatically compiled by CLR and turned into local code), if it is ASP.. NET dll files, it seems that they cannot be called from the exe. They can only be generated by static analysis encryption software. Maybe after the development of. NET's dynamic tracking technology is mature, there will be a general method to crack this type of locally compiled protective shell.
[Postscript]
At the reminder of JasonNET, The Remotesoft Protector was found to store the class content in rscoree. dll, which is too arbitrary. After further analysis, it is indeed possible that the class content is stored in the original dll, based on the following:
1) Check the Section Table of ISNet. WebUI. WebGrid. dll. It is found that the Section. rdata is more than the compiled dll, And the size is more than 100 k.
2) After ildasm is used for decompilation, ilasm is used for re-compilation. The file size is significantly reduced. Obviously, the ildasm of. NET directly ignores the content for processing. rdata.
3) view rscoree. dll, called between all modules, and find APIs such as ImageRvaToVa, ImageNtHeader, and VirtualAlloc.
4) _ abstract. _ encryption fields with unknown meanings and a large number of Hashtable related to the program structure
The results indicate that the extracted function content may still be stored in IsNet. WebUI. WebGrid. dll. Add this note to correct it.
_____________________________@
Four major focuses of software: data structures and algorithms are the soul. Software Engineering is the skeleton. Assembly languages are the seven major systems. The idea of object-oriented design is blood and meat.