Introduction
This is my solution for the JoinESET Crackme 2015, It was on my disk for more than a year and I'm sharing it maybe someone is interested in how to solve this challenge.
Working files can be downloaded from here.
Working files can be downloaded from here.
Stage One
The crackme has two files EsetCrackme2015.exe and EsetCrackme2015.dll. Both don’t
seem to be packed.
The crackme
asks for three passwords, lets hunt the first password.
The executable
loads the DLL, which has multiple files appended to it. To enumerate all the
files we have first the file ID which is two bytes then four bytes that
represent the file size followed by the file.
I coded a small
tool to dump all the files and here is the ID list 0x1, 0x2, 0x3, 0x4, 0x101,
0x102, 0x103, 0x104, 0x151, 0x152, 0x153, 0x154, 0x155, 0xAA02, 0xAA06, 0xBB01,
0xBB02, 0xFF00, 0xFF02, 0xFF04 and 0xFF05.
File 0x2 is the
name of the pipe, “\\.\pipe\EsetCrackmePipe” that will be created and will be
used to communicate with the different crakme's processes.
File 0x3
contains "SXJyZW4lMjBpc3QlMjBtZW5zY2hsaWNo" or "Irren%20ist%20menschlich" base64
decoded, and that string will be used to decrypt later files.
The file 0x151
is an executable that gets injected into SVCHOST.exe through a virtual machine
which its engine resides in the file 0x102 and opcode in 0x103. I will explain
the virtual machine later in stage two.
The pipe works
as follows, the main module waits for either a file request (0x1) or
STATUS_COMPLETE (0x2) followed by the file ID and responds with the file
requested or OK message in case of STATUS_COMPLETE.
The injected
code in svchost.exe starts with retrieving the handle to a window, whose class
name is EDIT and then uses SetWindowLong to change the address of the window
procedure to 0x00AD2360.
The files
0xBB01 and 0xBB02, both gets decrypted by xoring with “PIPE”. The file 0xBB01
contains the hashes of the three passwords respectively as follows:
869B39E9F2DB16F2A771A3A38FF656E050BB1882
0F30181CF3A9857360A313DB95D5A169BED7CC37 0B6A1C6651D1EB5BD21DF5921261697AA1593B7E
The crackme
hashes every entered password using a hash algorithm similar to SHA-1 and
compares it with the hashes above.
When entering
the first password we get the window procedure 0x00AD2360 triggered. What it do
is it base64 encode the password then decrement each odd indexed character,
finally it compares it with RFV1aV4fQ1FydFxk which it got from the file 0xBB02.
By reversing
this operation we get our first password “Devin Castle”
Upon entering
the right password a STATUS_COMPLETE with ID 0xBB01 is sent through the pipe,
declaring that we have completed stage one and a drv.zip is written to disk.
The procedure that
handles the progress of the crackme is loaded from the file 0x101.
Stage Two
Unpacking the
drv.zip file, we will have three files crackme_drv.inf, crackme_drv.sys, and
INSTALL.ME which tells us to install the driver as a legacy driver.
Before the
installing I will patch the driver entry with 0xCC, an INT3, to trigger the
debugger at the start of driver. But before the debugging we have to do a
deeply static analysis first.
By loading the
driver into IDA we can see the procedures that will handle certain IRP
requests.
I will start by
analyzing AddDevice.
AddDevice
A new device is
created with DeviceType FILE_DEVICE_DISK. However the disk will not appear
besides the other partitions as the driver doesn't create a symbolic link for
it. For the disk to appear, I used DefineDosDevice(DDD_RAW_TARGET_PATH,"F:","\\Device\\45736574").
The
driver requests the file 0xAA02 and decrypts it using RC4 with the Key
“3531_4ever”, ESET_ForEver, that file contains the name of registry key
“ESETConst”.
Now
the driver assumes its initialization process is done successfully and so it
sends STATUS_COMPLETE message through the pipe with ID 0xAA10. At the user mode
side the main module writes PunchCardReader.exe and PuncherMachine.exe to the
same directory of the crackme, and PunchCard.bmp to the newly created disk.
The
driver keeps reading the registry key
“\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\crackme_drv\EsetCrackme\ESETConst”,
checks for type string then writes the value to a global variable.
The VMEngine
and VMOpCode gets decrypted and loaded into memory. That VMEngine is slightly
different than the one found in the crackme's main module. I believe that the
driver's is the updated version. File 0xAA06 is requested, decrypted and copied
to VMOpCode at offset 0x26D.
By that we are
done with the analysis of AddDevice.
IRP_MJ_READ_WRITE
The driver does
the following; In case of a write request it proceeds with the operation. In
case of a read request it proceeds normally if the read offset is smaller than
0x13200, if larger it checks that the registry key “ESETConst” has been read
successfully and the VMEngine has been loaded. If any of the checks failed the
driver returns random data.
The data starts
from offset 0x13200 serves as encrypted data that gets decrypted through the VM
using a decryption key which the registry key “ESETConst” holds.
I will not jump
into the details of the VMEngine, I will just paste the built code of what the
OpCodes presents.
0x0 Cmp D_R12,0 0x4 Je 0x8a 0xa Cmp D_R13,0 0xe Je 0x11e 0x14 Mov D_R2, D_R12 0x17 Mov D_R3, $295 0x1e Mov D_R4, $268 0x25 Mov D_R5, D_R2 0x28 Add D_R5,D_R13 0x2b Mov D_R14, D_R3 0x2e Mov D_R0, $291 0x35 Add D_R14,DWORD PTR [R0] 0x38 Mov D_R15, D_R4 0x3b Add D_R15,4 0x3f Cmp D_R2,D_R5 0x42 Jnz 0x4b 0x48 Mov D_R2, D_R12 0x4b Cmp D_R4,D_R15 0x4e Jnz 0x5b 0x54 Mov D_R4, $268 0x5b Cmp D_R3,D_R14 0x5e Je 0x100 0x64 Mov D_R0, BYTE PTR [R2]//Key 0x67 Xor D_R0,BYTE PTR [R3]//$295 0x6a Add D_R0,1 0x6e INST_5 B_R0,1 0x72 Xor D_R0,BYTE PTR [R4]//$268 0x75 Mov BYTE PTR [R3], B_R0 0x78 Add D_R2,1 0x7c Add D_R3,1 0x80 Add D_R4,1 0x84 Jmp 0x3f 0x8a Mov D_R3, $271 0x91 Mov D_R4, $268 0x98 Mov D_R14, D_R3 0x9b Mov D_R0, $26d 0xa2 Add D_R14,DWORD PTR [R0] 0xa5 Mov D_R15, D_R4 0xa8 Add D_R15,4 0xac Cmp D_R4,D_R15 0xaf Jnz 0xbc 0xb5 Mov D_R4, $268 0xbc Cmp D_R3,D_R14 0xbf Je 0xe8 0xc5 Mov D_R0, 0 0xc9 Xor D_R0,BYTE PTR [R3]//$271 0xcc Add D_R0,1 0xd0 INST5_ B_R0,1 0xd4 Xor D_R0,BYTE PTR [R4]//$268 0xd7 Mov BYTE PTR [R3], B_R0 0xda Add D_R3,1 0xde Add D_R4,1 0xe2 Jmp 0xac 0xe8 Mov D_R0, $26d 0xef Mov D_R0, DWORD PTR [R0] 0xf2 PUSH D_R0 0xf4 PUSH $271 0xfa Jmp 0x112 0x100 Mov D_R0, $291 0x107 Mov D_R0, DWORD PTR [R0] 0x10a PUSH D_R0 //0x12 0x10c PUSH $295 //R_Key 0x112 PUSH D_R11//0x200 0x114 PUSH D_R10 //Ram2 0x116 Call Part2
The VM is 32bit
and consisted of 15 register. The last six registers are used as arguments.
A D_, W_ and B_
represent the size of the register. The dollar sign represents an offset in the
VMOpCode.
What the above
code do is it checks whether argument three (R_12) and four (R_13) are
supplied. Argument three is the decryption key got from “ESETConst” and
argument four is its length. In case it’s supplied the following algorithm is
used to calculate a key:
unsigned char $295[] = {0xD0,0xFC,0xF9,0xF7,0x63,0xED,0x71,0xFA,0xD6,0xAE,0xE6,0x62,0x36,0xFB,0x63,0x7F,0x77,0xE9}; unsigned char $268[] = {0x45,0x54,0x53,0x45}; for(int i=0;i<0x12;i++) { Key[i] ^= $295[i]; Key[i]++; Key[i] = ( Key[i] << (1 & 0xFF) ) | ( (((signed char )Key[i])&0XFF) >> (unsigned char)( (8-1)&0xFF ) );//INST_5(Key[i],1) Key[i] ^= $268[i%4]; }
Then the
resultant key is used to decrypt the encrypted disk section using RC4
algorithm.
In case there
is no decryption key is supplied the VM generates the right decryption key as
follows:
unsigned char $271[] = {0x82,0x99,0x8F,0x92,0x11,0x9E,0x18,0x94,0xB1,0x8E,0x8F,0x11,0x16,0x9C,0x11,0x1A,0x16,0x9D,0xA8,0xA1,0xA1,0x29,0xA8,0xA1,0xA1,0x29,0xA8,0xA1,0xA1,0x29,0xA8,0xA1}; unsigned char $268[] = {0x45,0x54,0x53,0x45}; unsigned char Key[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; for(int i=0;i<0x12;i++) { Key[i] ^= $271[i]; Key[i]++; Key[i] = ( Key[i] << (1 & 0xFF) ) | ( (((signed char )Key[i])&0XFF) >> (unsigned char)( (8-1)&0xFF ) );//INST_5(Key[i],1) Key[i] ^= $268[i%4]; }
The second
stage key will be “Barbakan Krakowski” and the ESETConst will be “Reversing is
great”. By that, stage two key has been found but just before finishing this
part, I would like to post the rest of the VM code which is a simple decryptor
that decrypts the next VM OpCode followed by RC4 algorithm.
Part2: 0x0 Mov D_R0, D_R7 //Opcode Start $131 0x3 Mov D_R1, BYTE PTR [R0] 0x6 Add D_R0,1 0xa Mov D_R2, BYTE PTR [R0] 0xd Add D_R0,5 0x11 Mov D_R3, DWORD PTR [R0] 0x14 Mov D_R0, D_R8 //0x137 0x17 Add D_R0,D_R7 0x1a Add D_R3,D_R7 0x1d Mov D_R4, BYTE PTR [R3] 0x20 Xor D_R4,D_R1 0x23 Add D_R1,D_R2 0x26 Mov BYTE PTR [R3], B_R4 0x29 Add D_R3,1 0x2d Cmp D_R3,D_R0 0x30 Jnb 0x1d 0x36 Mov D_R0, D_R7 0x39 Mov BYTE PTR [R0], 37 0x3d Add D_R0,1 0x41 Mov BYTE PTR [R0], 13 0x45 Exit 0x46 malloc 100//R0 has the Result 0x4a Mov D_R5, D_R0 0x4d Mov D_R1, 0 0x51 Mov BYTE PTR [R0], B_R1//Sets table from 0 to 0x100 0x54 Add D_R0,1 0x58 Add D_R1,1 0x5c Cmp D_R1,100 0x61 Jnb 0x51 //Sets RC4 Table 0x67 Mov D_R0, 0 0x6b Mov D_R1, 0 0x6f Mov D_R4, D_R12 //R_Key 0x72 Mov D_R14, D_R4 //R_Key 0x75 Add D_R14,D_R13 //R13 = 0x12 0x78 Cmp D_R4,D_R14 0x7b Jnz 0x84 0x81 Mov D_R4, D_R12 0x84 Mov D_R3, D_R5 0x87 Add D_R3,D_R0 0x8a Add D_R1,BYTE PTR [R3] 0x8d Add D_R1,BYTE PTR [R4] 0x90 Modulus D_R1,100 //Result Saved in Destination 0x95 Mov D_R3, D_R5 0x98 Add D_R3,D_R0 0x9b Mov D_R15, D_R5 0x9e Add D_R15,D_R1 0xa1 Mov D_R2, BYTE PTR [R3] 0xa4 Mov BYTE PTR [R3], BYTE PTR [R15] 0xa7 Mov BYTE PTR [R15], B_R2 0xaa Add D_R0,1 0xae Add D_R4,1 0xb2 Cmp D_R0,100 0xb7 Jnb 0x78 0xbd Mov D_R0, 0 0xc1 Mov D_R1, 0 0xc5 Mov D_R3, D_R10//Ram2 0xc8 Mov D_R4, D_R3 0xcb Add D_R4,D_R11//R_11 = 0x200 0xce Cmp D_R3,D_R4 0xd1 Je 0x122 0xd7 Add D_R0,1 0xdb Modulus D_R0,100 //Result Saved in Destination 0xe0 Mov D_R2, D_R5 0xe3 Add D_R2,D_R0 0xe6 Add D_R1,BYTE PTR [R2] 0xe9 Modulus D_R1,100 //Result Saved in Destination 0xee Mov D_R14, D_R5 0xf1 Add D_R14,D_R1 0xf4 Mov D_R15, BYTE PTR [R2] 0xf7 Mov BYTE PTR [R2], BYTE PTR [R14] 0xfa Mov BYTE PTR [R14], B_R15 0xfd Mov D_R15, 0 0x101 Add D_R15,BYTE PTR [R2] 0x104 Add D_R15,BYTE PTR [R14] 0x107 Modulus D_R15,100 //Result Saved in Destination 0x10c Add D_R15,D_R5 0x10f Mov D_R15, BYTE PTR [R15] 0x112 Xor D_R15,BYTE PTR [R3] 0x115 Mov BYTE PTR [R3], B_R15 0x118 Add D_R3,1 0x11c Jmp 0xce 0x122 Free D_R5 0x124 Exit
Stage Three
In stage three
we have PunchCardReader.exe, PuncherMachine.exe, and PunchCard.bmp that has
been decrypted from stage two using RC4 with key “Barbakan Krakowski”.
The
PunchCard.bmp is a 4 bit bmp image with dimensions of 600x259. Nothing catches
my attention there so let’s move to other files.
Punch Card Reader
Let’s start by
analyzing the PunchCardReader. By pressing the “read punch cards” button, a
verification error pops up. It is time for taking a look on what happens inside
there. I used Telerik JustDecompile as a free alternative to reflector and it
just works great!
Obfuscation
Both
PunchCardReader and PuncherMachine are obfuscated. The names of the classes,
variables and methods are obfuscated. Code flow obfuscation is also implemented
using a switch case as the following code snippet.
int num3 = 920829957;
while (true)
{
switch (num3 ^ 920829955)
{
case 0:
{
num3 = 920829954;
continue;
}
case 1:
{
if (num1 < str1.Length)
{
goto case 10;
}
num2++;
num++;
num3 = 920829959;
continue;
}
case 2:
{
num1++;
num3 = 920829954;
continue;
}
case 3:
{
str1 = str;
num3 = 920829955;
continue;
}
...
...
...
case 12:
{
num3 = 920829959;
continue;
}
default:
{
return;
}
}
}
As you see the
code is chunked and at the end of each block num3, which is used as the flow
director, is assigned with the ID of the next block.
I thought at
first of coding a control flow deobfuscator but I then realized that it will be
waste of time to code a one for this crackme only and since also mostly the
order of the execution of the blocks wasn't important. You can already guess
the general idea of what the function do. I deobfuscated manually the
interesting functions only that I will post later.
For easier
analysis I used de4dot and a tool by Kurapika called .NET DeObfuscator to
rename the obfuscated names. Deobfuscated files are attached for reference.
The strings are
also obfuscated. They are joined into one byte array and encrypted using a
simple xor. The byte array gets decrypted in the memory and when a functions
wants to retrieve a string it passes the index of the string and its size.
AntiDebug
At the
entrypoint there is a debugger check using Debugger.IsAttached and
Debugger.IsLogging.
The procedure Class_14_Object.Procedure_3 is fired at start to
constantly check for debugger and terminates the process if found any. At Class_14_Object.Function_Byte_4 couple of functions IL bytes are hashed using md5 and the hash will
be used later to decrypt files.
That's
for the Initialization process now let’s move on to event handlers.
OnLoad
public Class_15_Object(Class_8_Form Parameter44) { this.Field_4 = false; this.Field_5 = Parameter44; try { this.Field_0 = new Class_2_Object();//Generates HashTable Assembly assembly = this.Function_Assembly_2(); Loads Assembly if (assembly == null) { this.Procedure_6(); } else { this.Field_2 = assembly; } } catch (Exception exception) { this.Procedure_6(); } }
Class_2_Object
generates a constant Hash Table, Function_Assembly_2 request the file with ID
0xFF05 through the PIPE “\\.\pipe\EsetCrackmePipe.” then decrypts it using the
key that was generated earlier from hashing the procedures IL bytes. The
encryption algorithm is AES.
To be able to
sniff that key I patched the anti-debug check at the entrypoint then loaded the
executable into DILE and set a breakpoint on Class_14_Object.Function_Byte_4.
Since the entry point is one of the procedures that get hashed, I had to
restore the patched bytes. I just stepped till the procedure bytes get loaded
into memory and then restored the bytes using cheat engine.
The
decryption key is “a26d11dee294284f38db8a724c119d74”.
The
decrypted file is attached as “ff05_AES_PunchCardReader_DynMethod.exe”
Procedure_4
protected void Procedure_4(object Parameter46) { try { Class_15_Object.Class_16_MulticastDelegate<bool> class16MulticastDelegate = this.Function_Class_16_5(this.Field_0.Function_String_1()); if (class16MulticastDelegate()) { ((Class_2_Object.Class_3_MulticastDelegate)Parameter46).Procedure_1(Class_2_Object.Enumeration_1.Member_2, null); } } catch (Exception exception) { ((Class_2_Object.Class_3_MulticastDelegate)Parameter46).Procedure_1(Class_2_Object.Enumeration_1.Member_3, exception); } this.Field_6.Set(); }
If
the class16MulticastDelegate is true we will get our verification passed
public string[] Function_String_1() { int num = 0; Bitmap bitmap = null; byte[] numArray = null; StringBuilder stringBuilder = null; int num1 = 0; int num2 = 0; int num3 = 0; int num4 = 0; List<string> strs = new List<string>(); num = 0; while (num < 1000 || !this.Field_12) { string str1 = string.Format(Class_21_Object.Function_String_88(), Convert.ToString(num).PadLeft(3, '0'));//Punch_Card_{0}.bmp if (!File.Exists(str1)) { return strs.ToArray(); } bitmap = new Bitmap(str1, true); if (bitmap.PixelFormat != PixelFormat.Format4bppIndexed || bitmap.Width != 600 || bitmap.Height != 259) { throw new Class_4_Exception(Class_21_Object.Function_String_89()); } BitmapData bitmapDatum = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); numArray = new byte[bitmapDatum.Height * bitmapDatum.Stride]; Marshal.Copy(bitmapDatum.Scan0, numArray, 0, (int)numArray.Length); bitmap.UnlockBits(bitmapDatum); stringBuilder = new StringBuilder(); num1 = 22; while (num1 < 582) { num2 = 0; num3 = 1; num4 = 197; while (num4 > -7)//8 { if (this.Function_Boolean_2(numArray, num1, num4)) { num2 = num2 | num3; } num3 = num3 << 1; num4 = num4 - 17; } if (this.Field_13.ContainsKey(num2)) { stringBuilder.Append(this.Field_13[num2]); } num1 = num1 + 7; } string str = stringBuilder.ToString(); char[] chrArray = new char[] { ' ' }; strs.Add(str.TrimEnd(chrArray)); bitmap.Dispose(); num++; } return strs.ToArray(); }
This
function simply iterates through all files named Punch_Card_{0}.bmp with
dimensions 600*259, as our PunchCard.bmp, and do the following,
It
iterates through certain pixels and every pixel is used to set a bit so that
eight pixels form a byte.
this.Function_Boolean_2
Decides whether the pixel will set the bit or not
protected bool Function_Boolean_2(byte[] Parameter1, int Parameter2, int Parameter3) { int parameter3 = 300 * Parameter3 + (Parameter2 >> 1); if ((Parameter2 & 1) == 0)//Parameter2 = Column { return (Parameter1[parameter3] & 240) == 0; } return (Parameter1[parameter3] & 15) == 0; }
If
the column is even and the left nibble of the pixel is zero the bit is set.
If
the column is odd and the right nibble of the pixel is zero the bit is set.
The
generated byte is then used as a key for a constant hash table that was
generated earlier.
Each
key is corresponded to a charterer. A string is then formed.
private Class_15_Object.Class_16_MulticastDelegate<bool> Function_Class_16_5(string[] Parameter47) { DynamicMethod dynamicMethod = null; MethodInfo method = this.Field_2.GetType(Class_21_Object.Function_String_109()).GetMethod(Class_21_Object.Function_String_110());//110 CreateMethod // 109 DynMethodFactory object[] parameter47 = new object[] { Parameter47 }; dynamicMethod = (DynamicMethod)method.Invoke(null, parameter47); return (Class_15_Object.Class_16_MulticastDelegate<bool>)dynamicMethod.CreateDelegate(typeof(Class_15_Object.Class_16_MulticastDelegate<bool>)); }
DynMethodFactory.CreateMethod
gets invoked with the pixels generated string as a parameter.
public static DynamicMethod createMethod(string[] instructions) { OpCode[] array = ( from x in (IEnumerable<FieldInfo>)typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public) where x.FieldType == typeof(OpCode) select (OpCode)x.GetValue(null) into x where !x.Name.Equals("break") select x).ToArray<OpCode>(); Hashtable hashtables = new Hashtable(); OpCode[] opCodeArray = array; for (int i = 0; i < (int)opCodeArray.Length; i++) { OpCode opCode = opCodeArray[i]; hashtables.Add(opCode.Name, opCode); } Type[] typeArray = new Type[0]; DynamicMethod dynamicMethod = new DynamicMethod("", typeof(bool), typeArray); ILGenerator lGenerator = dynamicMethod.GetILGenerator(); MethodInfo method = typeof(Encoding).GetMethod("get_ASCII", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null); Type type = typeof(Encoding); Type[] typeArray1 = new Type[] { typeof(string) }; MethodInfo methodInfo = type.GetMethod("GetBytes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, typeArray1, null); Type type1 = typeof(BitConverter); Type[] typeArray2 = new Type[] { typeof(byte[]), typeof(int) }; MethodInfo method1 = type1.GetMethod("ToUInt32", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, typeArray2, null); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(byte[])); lGenerator.DeclareLocal(typeof(uint)); lGenerator.DeclareLocal(typeof(bool)); lGenerator.DeclareLocal(typeof(bool)); Label label = lGenerator.DefineLabel(); Label label1 = lGenerator.DefineLabel(); lGenerator.Emit(OpCodes.Nop); lGenerator.Emit(OpCodes.Ldc_I4, 57005);//0xDEAD lGenerator.Emit(OpCodes.Stloc_0); lGenerator.Emit(OpCodes.Ldc_I4, 48879);//0xBEAF lGenerator.Emit(OpCodes.Stloc_1); lGenerator.Emit(OpCodes.Ldc_I4, 51966);//0xCAFE lGenerator.Emit(OpCodes.Stloc_2); lGenerator.Emit(OpCodes.Ldc_I4, 47806);//0xBABE lGenerator.Emit(OpCodes.Stloc_3); lGenerator.Emit(OpCodes.Ldc_I4, 64206);//0xFACE lGenerator.Emit(OpCodes.Stloc_S, 4); lGenerator.Emit(OpCodes.Ldloc_0); lGenerator.Emit(OpCodes.Ldloc_1); try { lGenerator.Emit((OpCode)hashtables[instructions[0]]); } catch (Exception exception) { } lGenerator.Emit(OpCodes.Ldloc_2); lGenerator.Emit(OpCodes.Ldloc_3); try { lGenerator.Emit((OpCode)hashtables[instructions[1]]); } catch (Exception exception1) { } lGenerator.Emit(OpCodes.Xor); lGenerator.Emit(OpCodes.Ldloc_S, 4); lGenerator.Emit(OpCodes.Xor); lGenerator.Emit(OpCodes.Ldc_I4, -229612108);//0xf25065b4 lGenerator.Emit(OpCodes.Xor); lGenerator.Emit(OpCodes.Stloc_S, 5); lGenerator.Emit(OpCodes.Call, method); lGenerator.Emit(OpCodes.Ldstr, "ESET"); lGenerator.Emit(OpCodes.Callvirt, methodInfo); lGenerator.Emit(OpCodes.Stloc_S, 6); lGenerator.Emit(OpCodes.Ldloc_S, 6); lGenerator.Emit(OpCodes.Ldc_I4_0); lGenerator.Emit(OpCodes.Call, method1); lGenerator.Emit(OpCodes.Stloc_S, 7); lGenerator.Emit(OpCodes.Ldloc_S, 5); lGenerator.Emit(OpCodes.Ldloc_S, 7); lGenerator.Emit(OpCodes.Ceq); lGenerator.Emit(OpCodes.Ldc_I4_0); lGenerator.Emit(OpCodes.Ceq); lGenerator.Emit(OpCodes.Stloc_S, 9); lGenerator.Emit(OpCodes.Ldloc_S, 9); lGenerator.Emit(OpCodes.Brtrue_S, label); lGenerator.Emit(OpCodes.Ldc_I4_1); lGenerator.Emit(OpCodes.Stloc_S, 8); lGenerator.Emit(OpCodes.Br_S, label1); lGenerator.MarkLabel(label); lGenerator.Emit(OpCodes.Ldc_I4_0); lGenerator.Emit(OpCodes.Stloc_S, 8); lGenerator.Emit(OpCodes.Br_S, label1); lGenerator.MarkLabel(label1); lGenerator.Emit(OpCodes.Ldloc_S, 8); try { lGenerator.Emit((OpCode)hashtables[instructions[2]]); } catch (Exception exception2) { } return dynamicMethod; }
What
createMethod do is it simply generates a dynamic method and uses the pixels
generated strings as the name for three instructions. The third one is easy to
guess, a “Ret”.
The generated
dynamicMethod does some arithmetic on six constants and compares the result
with 0x45534554(ESET). However there are two arithmetic operations that we had
to guess. I of course didn't guess them … I bruteforced them!
The first one
is “Mul” and the second is “Add” and the third that I already guessed “Ret”.
And now we can
get the dynamicMethod returns true.
Now, just one
last thing before finishing the analysis of this file.
Class_10_Object.Function_Boolean_2 sends an STATUS_COMPLETE message with ID
0xFF01 through the pipe indicating that the verification has been passed.
Now that all we
need to know about PunchCardReader. Sure we can generate valid punch cards but
that will be the job of the PuncherMachine!
PuncherMachine
The
PuncherMachine uses the same anti-debug as the PunchCardReader. Let’s have a
look on Class_18 which gets loaded during the initialization through Onload
Event Handler.
Class_18
public Class_18(Class_3 a_0) { this.Field_6 = false; this.Field_7 = a_0; try { this.Field_10 = new ArrayList(); this.Field_8 = new ManualResetEvent(true); byte[] numArray = this.Function_3(); if (numArray != null) { File.WriteAllBytes(Class_25.Function_130(), numArray); using (MD5 mD5 = MD5.Create()) { this.Field_3 = mD5.ComputeHash(numArray); } } else if (this.Field_3 == null) { this.Procedure_8(); } Assembly assembly = this.Function_4(); if (assembly == null) { this.Procedure_8(); } else { this.Field_4 = assembly; } } catch (Exception exception) { this.Procedure_8(); } }
this.Function_3
simply requests two files 0xFF01 and 0xFF02 and returns 0xFF01 file bytes.
0xFF02 contains the hash “95eceaa118dd081119e26be1c44da2cb” while 0xFF01
doesn't exist in the DLL. In case 0xFF01 exists it will be written to
PunchCard.bmp and the md5 hash will be calculated and saved in Field_3 instead
of 0xFF02 hash.
At
the start of the Puncher Machine it asks for punch card, by selecting the punch
card we got from stage 2 we get an invalid punch card message as the md5 hash
of the file isn't equal to “95eceaa118dd081119e26be1c44da2cb” and there is no
0xFF01 file.
Okay
no problem, we will add the PunchCard.bmp from stage two as the file 0xFF01 but
first we have to do couple of things to get it working.
First
we have to set all the pixels to 0xFF, this step is for later punching. Second
we need to encrypt the bmp with the key “3cc021f8bc623ec0f5450c55418ba120”.
Just
after the end of the last file in the DLL we will write the file ID 0xFF01 then
the file size and finally paste the encrypted bmp. The modified DLL is
attached.
Now
we have a new window, it requests two calibration codes lets jump to the procedure
that handles the inputs.
Procedure_9
private void Procedure_9(object sender, EventArgs e) { if (this.Field_14.Function_2().Function_2(this.Field_9.Text, this.Field_8.Text)) { this.Field_3.Visible = true; this.Field_13.Visible = false; this.Field_2.Visible = false; this.Field_4.Visible = true; this.Field_5.Visible = true; this.Field_6.Visible = false; this.Field_14.Function_7(); } else { MessageBox.Show(Class_25.Function_28(), Class_25.Function_27());//Calibration Error // Error! } }
Function_2
gets called with the two calibration codes as parameters and the return value
decides whether to show Calibration Error or not. So let's jump into Function_2.
public bool Function_2(string string_0, string string_1) { int num = 0; ulong num1 = 0L; bool flag = false; string str; List<uint> nums = new List<uint>(); num = 0; while (num < string_0.Length) { try { nums.Add(Convert.ToUInt32(string_0.Substring(num, 8), 16));//Instructions Hashes } catch (Exception exception1) { } num = num + 8; } if (string_1.Length <= this.Field_12.Count) { if (string_1.Length != 0) { try { Class_6.Class_8<ulong, string> class8 = this.Function_4(nums.ToArray()); //Pushes Instruction Hashes and get the Dynamic Method this.Procedure_3(); this.Field_15 = new Hashtable(); num = 0; while (num < this.Field_12.Count) { Class_6.Class_8<ulong, string> class81 = class8; if (num < string_1.Length) { string str1 = this.Field_12[num].ToString(); char string1 = string_1[num]; str = string.Concat(str1, string1.ToString()); } else { str = this.Field_12[num].ToString(); } num1 = class81(str); if (!this.Field_14.ContainsKey(num1)) { flag = false; return flag; } this.Field_15.Add(this.Field_12[num], this.Field_14[num1]);//Field_15 is the HashTable used to generate the PunchCard .. Same as PunchCardReader HashTable num++; } flag = true; return flag; } catch (Exception exception) { flag = false; } return flag; } return false; } else { return false; } }
Each
eight characters of the first calibration codes are converted to Uint32, that
will be used then as instructions hashes ... We will get to that later.
Function_4
invokes DynMethodFactory.CreateMethod from the assembly with ID 0xFF04.
If
we had a quick look at CreateMethod we will find that it hashes the OpCode
names at the following code
for (int i = 0; i < (int)opCodeArray.Length; i++) { OpCode opCode = opCodeArray[i]; hashtables.Add(oneParameter(opCode.Name), opCode); }
Then
just like before, our calibration code is used to generate two instructions.
NOP Ldc_I8, 2AAAAAAAAAAAAB67 Stloc_0 Ldc_I4_0 Stloc_1 Br_S Label Label1: Nop Ldloc_0 //2AAAAAAAAAAAAB67 XXX Ldloc_1 Get_Chars Conv_u8 Add Stloc_0 Ldloc_0 Ldc_I8, 2AAAAAAAAAAAAB6F YYY Stloc_0 Nop Ldloc_1 Ldc_I4_1 Add Stloc_1 label: Ldloc_1 Ldarg_0 GetLength Clt Stloc_3 Ldloc_3 Brtrue_S label1 Ldloc_0 Stloc_2 br_S label2 label2: Ldloc_2 Ret
Hmm
let’s guess the first instruction. Our mysterious instruction, zero pushed into
the stack then a call to Get_Chars. So our mysterious instruction must be
pushing the argument into the stack. So XXX will be Ldarg_0 which corresponds
to the hash 0364abe7.
Apparently
the above IL code does some arithmetic on each byte of the supplied string then
returns the result. YYY is just after pushing a constant so it likely to be an
arithmetic operation, but which one?
It
was kind of hard for me to guess the arithmetic instruction at this stage as I
don't know any of the expected output results, so for now let's move on.
Back
to Function_2, Each character of calibration code two gets joined to its index
and supplied to the DynamicMethod.
For
example if our calibration code is XYZ then 0X will be supplied to the
DynaimcMethod then 1Y and so on.
A
new hash table, Field_15, gets its values built from Field_14 hash table and
the return of the DynamicMethod, and its keys depend on Field_12 and the index.
That
hash table will be used later to punch the cards, so it must be identical to
the one in the PunchCardReader!
If
we took a look on the values of Field_14 we will find it is very divergent so
It must not have been generated using a sub or add instruction. I guessed it
might be a “Mul” then I bruteforced the first character of the calibration code
till a value that lies within Filed_14 is generated and luckily I found the
first one, an “I”.
Bruteforcing
the rest of the code and this appeared “Infant Jesus of Prague”
Great!
So our first calibration code will be “0364abe72d29c96c” and the second is
“Infant Jesus of Prague”
Now
one last thing, Generate the long waited punch cards by entering the following:
mul add ret
Now
the Punch Card Reader verification has been passed!
Mshetta: Join Eset Crackme 2015 Solution >>>>> Download Now
ReplyDelete>>>>> Download Full
Mshetta: Join Eset Crackme 2015 Solution >>>>> Download LINK
>>>>> Download Now
Mshetta: Join Eset Crackme 2015 Solution >>>>> Download Full
>>>>> Download LINK