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