Reversing an MFC program with IDA (bye bye Wdasm) | ||
|
When I first decided to crack this little program I had only version 3.3.3. So I began with this version but as I am 2 lazy I first used wdasm and looked for some good hardcoded serials in the String References, but found too many! Therefore I decided to use one randomly choosen, but wait it tells me I'm a pirate! I uninstalled it and decided to have a go with a newer version, 4.1.2, but it seems it remembers I used a blacklisted serial. So let's crack this elephant's memory protection.
As most of you already know, before cracking a program one must always play a little bit with it in order to understand where to begin his cracking/reversing session. Well after I ran getright I rightclicked on the little icon on the system tray. Here a popup menu (shortcut menu) is displayed, I chose "About", and pushed the third button in the dialog box to enter my code. I wrote in a random number and pushed OK, but a message box tells me "Invalid Registration Code...". Ok let's run IDA and disassemble getright.exe. Well this won't be a tutorial on how to use IDA, look for them on Fravia's last mirror. Therefore if you know a very little or nothing at all about it you'd better read some tutorials on the subject and return here back later;). After IDA has finished ( it will take about half an hour ), let's look for some interesting strings. Select View menu and choose Names, ALT-T and write "regist" (without quotes, please;). Remember we are looking for strings, so we will skip all other names but those beginning with an "a" (anyway it depends on how you configured strings to be displayed) before the string name. I only found aRegistrationCode. Hmmm, this could be good, but it is not our message string, let's double click on the address on its right side anyway. IDA will bring us to a data section where you will find a typical assembly definition for strings, I mean: IDA autogenerated name (that one beginning with an "a") db 'Real string name',0 and on its right side some or all Cross References (go to the Options menu, choose "Cross References..." and give a number to "Number of xrefs to display"). Well I must admit that as this string is referenced "just" 18 times, I decided to look at the first cross reference because I didn't know what purpose this string was used for. Well it is used to keep your registration code in the system registry once registered in HKEY_ CURRENT_USER\Software\HeadLight\GetRight\Config\RegistrationCode. After some studying, i found out that only 2 references to this key are interesting for our purposes. Infact they are passed to some routines to write some values in the registry. Let's see them: (1) 0040AC76 push edi 0040AC77 mov dword_0_542F00, 1 0040AC81 call sub_0_49CFC6 0040AC86 test eax, eax 0040AC88 pop ecx 0040AC89 jz short good_guy ; see below 0040AC8B push offset unk_0_542E98 0040AC90 mov ecx, edi 0040AC92 call class CString const & __thiscall CString::operator=(char const *) 0040AC97 push offset unk_0_542E98 0040AC9C lea ecx, [esi+200h] 0040ACA2 call void __thiscall CWnd::SetWindowTextA(char const *) 0040ACA7 push 7Bh ; Invalid registration code... 0040ACA9 push esi 0040ACAA call sub_0_48D2B0 0040ACAF pop ecx 0040ACB0 pop ecx 0040ACB1 jmp loc_0_40ADEE 0040ACB6 ; ---------------------------------------------------------------------------------- 0040ACB6 0040ACB6 good_guy: ; CODE XREF: 0000:0040AC89 0040ACB6 push dword ptr [edi] 0040ACB8 lea eax, [ebp-10h] 0040ACBB push offset "%s" 0040ACC0 push eax 0040ACC1 call void __cdecl CString::Format(char const *,...) 0040ACC6 add esp, 0Ch 0040ACC9 lea ecx, [ebp-10h] (2) 0040ACCC call sub_0_46CF06 ; modify 0046d406 from 8B45F8 to 33C040 0040ACCC ; in this way at the test following every 0040ACCC ; call to this routine you always have 1 0040ACCC ; and avoid "don't pirate this software..." 0040ACD1 test eax, eax 0040ACD3 jz Window00is0? 0040ACD9 push ebx ; ebx = 0, this will disable the window ; whose handle is [esi+188] 0040ACDA lea ecx, [esi+188h] 0040ACE0 call int __thiscall CWnd::ShowWindow(int) 0040ACE5 push ebx 0040ACE6 lea ecx, [esi+200h] 0040ACEC call int __thiscall CWnd::ShowWindow(int) 0040ACF1 push ebx 0040ACF2 lea ecx, [esi+1C4h] 0040ACF8 call int __thiscall CWnd::ShowWindow(int) 0040ACFD push ebx 0040ACFE lea ecx, [esi+98h] 0040AD04 call int __thiscall CWnd::ShowWindow(int) 0040AD09 push 5 ; SW_SHOWDEFAULT 0040AD0B lea ecx, [esi+14Ch] 0040AD11 call int __thiscall CWnd::ShowWindow(int) 0040AD16 mov edi, [edi] 0040AD18 call class AFX_MODULE_STATE * __stdcall AfxGetModuleState(void) 0040AD1D mov eax, [eax+4] 0040AD20 push edi 0040AD21 push offset Registrationcode ; here it is our key 0040AD26 mov ecx, eax 0040AD28 push dword_0_543098 (3) 0040AD2E call int __thiscall CWinApp::WriteProfileStringA(char const *,char const *,char const *) 0040AD33 push esi 0040AD34 lea ecx, [ebp-0B0h] 0040AD3A call sub_0_48B8B0 0040AD3F lea ecx, [ebp-0B0h] 0040AD45 mov byte ptr [ebp-4], 2 0040AD49 call int __thiscall CDialog::DoModal(void) 0040AD4E lea ecx, [ebp-54h] 0040AD51 mov byte ptr [ebp-4], 3 0040AD55 call sub_0_4D3639 0040AD5A lea ecx, [ebp-0B0h] 0040AD60 mov byte ptr [ebp-4], 1 0040AD64 call sub_0_4C2BFD 0040AD69 jmp skip_dont_pirate (4) 0040AD6E ; ----------------------------------------------------------------------------------- 0040AD6E 0040AD6E Window00is0?: ; CODE XREF: 0000:0040ACD3 0040AD6E call class AFX_MODULE_STATE * __stdcall AfxGetModuleState(void) 0040AD73 mov eax, [eax+4] 0040AD76 push ebx 0040AD77 push offset Window00 ; must be 0 0040AD7C mov ecx, eax 0040AD7E push dword_0_543098 0040AD84 call unsigned int __thiscall CWinApp::GetProfileIntA(char const *,char const *,int) 0040AD89 test eax, eax 0040AD8B jnz short dont_pirate 0040AD8D call sub_0_4951B5 0040AD92 test eax, eax 0040AD94 jz short skip_dont_pirate 0040AD96 0040AD96 dont_pirate: ; CODE XREF: 0000:0040AD8B 0040AD96 push 1CBh ; don't pirate this software 0040AD9B lea ecx, [ebp-14h] 0040AD9E call int __thiscall CString::LoadStringA(unsigned int) 0040ADA3 push dword ptr [ebp-14h] 0040ADA6 lea edi, [esi+14Ch] 0040ADAC mov ecx, edi 0040ADAE call void __thiscall CWnd::SetWindowTextA(char const *) 0040ADB3 push 5 ; SW_SHOWDEFAULT 0040ADB5 mov ecx, edi 0040ADB7 call int __thiscall CWnd::ShowWindow(int) 0040ADBC push ebx 0040ADBD lea ecx, [esi+188h] 0040ADC3 call int __thiscall CWnd::ShowWindow(int) 0040ADC8 lea edi, [esi+200h] 0040ADCE push ebx 0040ADCF mov ecx, edi 0040ADD1 call int __thiscall CWnd::ShowWindow(int) 0040ADD6 push ebx 0040ADD7 lea ecx, [esi+98h] 0040ADDD call int __thiscall CWnd::ShowWindow(int) 0040ADE2 push offset unk_0_542E98 ; text to display 0040ADE7 mov ecx, edi 0040ADE9 call void __thiscall CWnd::SetWindowTextA(char const *) 0040ADEE 0040ADEE skip_dont_pirate: ; CODE XREF: 0000:0040ACB1 0040ADEE ; 0000:0040AD69 0040ADEE ; 0000:0040AD94 0040ADEE mov dword_0_542F00, ebx 0040ADF4 0040ADF4 loc_0_40ADF4: ; CODE XREF: 0000:0040AC70 0040ADF4 lea ecx, [ebp-14h] 0040ADF7 mov [ebp-4], bl 0040ADFA call __thiscall CString::~CString(void) 0040ADFF or dword ptr [ebp-4], 0FFFFFFFFh 0040AE03 lea ecx, [ebp-10h] 0040AE06 call __thiscall CString::~CString(void) 0040AE0B mov ecx, [ebp-0Ch] 0040AE0E pop edi 0040AE0F pop esi 0040AE10 mov large fs:0, ecx 0040AE17 pop ebx 0040AE18 leave 0040AE19 retn I know I know it's little hard to follow, but I'll try to summarize the more important points: (1) the call at 0040AC81 is a function (eax, the return value, is tested). The return value will establish whether to display the bad_guy message or jump to good_guy. We'll patch this to always jump to good_guy. The routine beginning at 0049CFC6 is called a second time at 00471C05, and we will patch there as well. (2) This is a function that is repeated all over the file. If it returns 0, you won't be able to enter a registration code anymore. It will disable any way to register offline, and in some dialog boxes it will display the annoying "*** Please do not pirate this software ***" string. So let's modify this as shown, so that after every test eax will not be equal to 0 (instead of: mov eax, dword ptr [ebp-08] we will have: xor eax, eax inc eax ). (3) This function with its counterpartite GetProfileStringA need a little explanation. Actually they are not the well known APIs, which share just their names. Infact the first function calls RegSetValueExA and RegCloseKey and GetProfileStringA calls RegQueryValueExA and RegCloseKey, and they are passed three strings (pointers to char), the first one being the name of the key, the second one is the name of the value (RegistrationCode in this case), the last one is the name of the data (edi = what we entered as registration number). (4) We come here from point (1) if no patch is applied. Well, this snippet of code verifies, by calling GetProfileIntA, if the data at HKCU\Software\HeadLight\GetRight\Config\Window00 is equal to 0. If it is not you are a pirate. GetProfileIntA is very similar to GetProfileStringA, but its last parameter is an integer number, not a string, being the default value assigned to the key if this one is not yet present in the registry. So that's why if you delete this key at the next run you will have the same key with the same value in the registry! Well, that's enough for this large piece of code. We still have to patch some bytes in other places to make this elephant think it's all sugar what we give it. Let see where to patch function at 0046CF06: sub_0_46CF06 . . . . 0046D3EB cmp [ebp+var_1], 0 0046D3EF jz short loc_0_46D3F5 0046D3F1 and [ebp+var_8], 0 ; zeroes ebp+var_8 0046D3F5 0046D3F5 loc_0_46D3F5: ; CODE XREF: sub_0_46CF06+4BA 0046D3F5 ; sub_0_46CF06+4E9 0046D3F5 push edi 0046D3F6 push edi 0046D3F7 mov ecx, ebx 0046D3F9 call sub_0_46D40E 0046D3FE test eax, eax 0046D400 jz short change_me 0046D402 and [ebp+var_8], 0 ; zeroes ebp+var_8 0046D406 0046D406 change_me: ; CODE XREF: sub_0_46CF06+4FA 0046D406 mov eax, [ebp+var_8] ; modify this to 33C040 to always exit 0046D406 ; with eax different from 0 0046D409 pop esi 0046D40A 0046D40A loc_0_46D40A: ; CODE XREF: sub_0_46CF06+26 0046D40A pop edi 0046D40B pop ebx 0046D40C leave 0046D40D retn 0046D40D sub_0_46CF06 endp Once edited offset change_me, every call to this function will always return 1. This value is compared everywhere always in the same way, so that's why we patch this way. Ok, this is enough to be registered with every registration code ( must be 12 numbers long). But don't forget that when I installed this version it remembered that I am a pirate (they say ;). So let's suppose I cannot enter my reg info (it's disabled). What can I do? Well the answer is a little above. Do you remember that the value of Window00 is checked against 0? We have to find where this value is set to something and after a fast search I came to this interesting snippet of code: 0049506B 0049506B ; ------------------- S U B R O U T I N E --------------------------------------- 0049506B 0049506B 0049506B sub_0_49506B proc near ; CODE XREF: 0000:0041F59E 0049506B ; sub_0_46D40E+E2 0049506B ; sub_0_46D40E+16F5 0049506B ; sub_0_46D40E+19CA 0049506B call class AFX_MODULE_STATE * __stdcall AfxGetModuleState(void) 00495070 mov eax, [eax+4] 00495073 push 0 00495075 push offset Registrationcode 0049507A mov ecx, eax 0049507C push dword_0_543098 00495082 call int __thiscall CWinApp::WriteProfileStringA(char const *,char const *,char const *) 00495087 call class AFX_MODULE_STATE * __stdcall AfxGetModuleState(void) 0049508C mov eax, [eax+4] 0049508F push 1 ; must be changed to 0 00495091 push offset Window00 00495096 mov ecx, eax 00495098 push dword_0_543098 0049509E call int __thiscall CWinApp::WriteProfileInt(char const *,char const *,int) 004950A3 push 1 ; must be changed to 0 004950A5 push offset ID 004950AA push dword_0_543148 004950B0 push 80000000h 004950B5 call sub_0_49CC5C 004950BA add esp, 10h 004950BD retn 004950BD sub_0_49506B endp Changing bytes as shown, you'll never be a pirate, and then you'll need to patch the code to be registered. That's all.
Well, I know I wrote too much code, but I love to be as clear as it is needed to understand not only how and where to patch, but more important than all why. I promise anyway that in my next tutorial, if any, I'll include less code and more explanations. For any comment or just to get in touch with me: gkaizerAThotmail_dot_com