VMProtect failing to decrypt a string the second time
Posted: Tue Apr 08, 2014 12:28 pm
This issue occurs with VMProtect Ultimate 2.13.5.
My reduced code looks like this:
The compiler optimizes it, by referencing the constant string using a register.
Here are some disasm snippets for it:
This works fine, as long as the executable isn't protected yet.
Once the executable is protected, the second DoStuff() call receives a pointer to garbage as first argument.
In the protected version the disasm looks like this:
ebx: Still some old value from before the function call. Doesn't point to anything interesting at this time.
ebx: Now points to a different address, the properly decrypted string
ebx: Still points to the same address, but the decrypted string is gone
After each ESI call EAX is identical to EBX.
This results in garbage being passed on as string to further DoStuff() calls
For some reason it doesn't occur in a minimal test-application.
But only in the full one. Maybe because different heuristics or optimizations are triggered.
Is this information sufficient for fixing the issue, or should I try harder to generate a minimal application for reproducing the issue?
My reduced code looks like this:
Code: Select all
{
char* p1 = VMProtectDecryptString("IdenticalTest") + 3;
DoStuff(p1, "Foo");
VMProtectFreeString(p1 - 3);
}
{
char* p1 = VMProtectDecryptString("IdenticalTest") + 3;
DoStuff(p1, "Bar");
VMProtectFreeString(p1 - 3);
}
...
Here are some disasm snippets for it:
Code: Select all
00F0DBA0 mov esi,dword ptr [__imp__VMProtectDecryptStringA@4 (1517A84h)]
...
00F0DBC5 mov edi,dword ptr [__imp__VMProtectFreeString@4 (1517A80h)]
...
00F0DBCD mov ebx,offset string "\xb2\xdf\xf6Service" (1578570h) ; Here it goes
00F0DBD2 push ebx
00F0DBD3 call esi ; VMProtectDecryptStringA
... DoStuff()
00F0DC49 mov eax,dword ptr [p1]
00F0DC4C add eax,0FFFFFFFDh
00F0DC4F push eax
00F0DC50 call edi ; VMProtectFreeString
00F0DC52 push ebx
00F0DC53 call esi ; VMProtectDecryptStringA
Once the executable is protected, the second DoStuff() call receives a pointer to garbage as first argument.
In the protected version the disasm looks like this:
ebx: Still some old value from before the function call. Doesn't point to anything interesting at this time.
Code: Select all
00000000`22e11738 b0 08 b4 00 f1 08 b4 00 32 09 b4 00 73 09 b4 00 b4 09 b4 00 33 0a b4 00 ........2...s.......3...
00000000`22e11750 b5 0a b4 00 f1 0a b4 00 2d 0b b4 00 6d 0b b4 00 ad 0b b4 00 e0 0b b4 00 ........-...m...........
00000000`22e11768 12 0c b4 00 44 0c b4 00 76 0c b4 00 9e 0c b4 00 c6 0c b4 00 e1 0c b4 00 ....D...v...............
Code: Select all
005edbcd e9f29ed400 jmp 01337ac4 ; Constant hiding I assume?
005edbcd mov ebx,offset string "\xb2\xdf\xf6Service" ; This *was* the original line, just inserted here as explanation
005edbd2 53 push ebx
Code: Select all
00000000`063c55f8 b2 df f6 53 65 72 76 69 63 65 00 ab ab ab ab ab ab ab ab fe ee fe ee fe ...Service..............
00000000`063c5610 00 00 00 00 00 00 00 00 c8 9a 30 76 32 cd 00 00 c0 68 3c 06 60 51 3c 06 ..........0v2....h<.`Q<.
00000000`063c5628 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ........................
Code: Select all
005edbd3 ffd6 call esi ; Also DecryptString?!
....
005edc09 8b45fc mov eax,dword ptr [ebp-4] ss:002b:0018dba4=063c55fb
005edc0c 83c0fd add eax,0FFFFFFFDh
005edc0f 50 push eax
005edc10 ffd7 call edi <-- FreeString
Code: Select all
00000000`063c55f8 c0 68 3c 06 60 51 3c 06 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe .h<.`Q<.................
00000000`063c5610 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ........................
00000000`063c5628 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ........................
Code: Select all
005edc12 53 push ebx ; Broken EBX?
005edc13 ffd6 call esi ; Also DecryptString?
This results in garbage being passed on as string to further DoStuff() calls
For some reason it doesn't occur in a minimal test-application.
But only in the full one. Maybe because different heuristics or optimizations are triggered.
Is this information sufficient for fixing the issue, or should I try harder to generate a minimal application for reproducing the issue?