mIRC v5.71 & v5.81 - Tutorial by Rith

"Rith provides an update to his earlier v5.71 tutorial by examining the key generation algorithm of mIRC (which has remained the same for some versions now), a rudimentary affair as it turns out. Text slightly edited for profanities and some irrelevant ramblings (I suspect Rith was either tired or under the influence ;-) )."

v5.81 Update

Let's be real specific here : the version I reversed was mIRC 5.82t, the t meaning it is the 32-bit version. If you are dealing with a different version, then the addresses shown will almost certainly not match. However, since the protection scheme seems to have changed little over time, I expect that you could still use this as a guide to reversing a future version. Also, I expect that the final keygen will work for at least a few versions.

Part 1 : Finding the protection routine

Alright, let's get going. This is a semi-advanced tutorial, not in the sense of a hard protection, but in the sense that I am not going to explain every SoftICE command to you. I expect you to know bpx, bpr, etc. Go ahead and dissassemble this in whatever you plan to, it will make it easier to follow the code.

We start out by trying our tried and true GetDlgItemText, GetDlgItemInt, and GetWindowText breakpoints. They don't break (when we click enter on our bogus reg info… you knew that though). So we fall back on HMEMCPY. For those of you who are now running Windows 2000, this doesn't work for you. I'm not sure why they did things without this on 2000 (but 2000 is certainly more stable), but it isn't there to break on. There is a MEMCPY, but don't bother, it won't break. Since this is the only time you need this in this tutorial, and I am going to give you the address anyway, you will still be able to continue on here. It is because of this very reason that I keep a copy of Windows 98 installed on a small drive, so that when I need to, I can use HMEMCPY breakpoints. Mini-rant over. OK, be sure to set the breakpoint after you enter the info, right before pressing register.

(BTW, if you have registered your copy already, and you need to get your "Help->Register" button back, mIRC stores the reg info at: HKEY_CURRENT_USER\Software\mIRC\UserName in the registry, and HKEY_CURRENT_USER\Software\mIRC\License for the name and code respectively. Delete these keys, and you will be unregged once more).

You should break in the middle of a nasty system routine (the wonders of HMEMCPY) and trace back carefully until we are in mIRC. For those of you that can't manage that, or use Windows 2000, we end up here (this is pasted from IDA because it is simpler than typing stuff I see in SoftICE) :

0049F894 push offset byte_5480C0 ; get info from user
0049F899 push 3E7h
0049F89E push 0Dh
0049F8A0 push 83h
0049F8A5 push [ebp+arg_0]
0049F8A8 call j_SendDlgItemMessageA ; get name
0049F8AD push offset word_5484A7 ; location for code we enter
0049F8B2 push 3E7h
0049F8B7 push 0Dh
0049F8B9 push 82h
0049F8BE push [ebp+arg_0]
0049F8C1 call j_SendDlgItemMessageA ; get code
0049F8C6 push offset word_5484A7 ; location for code we enter
0049F8CB push offset byte_5480C0 ; original location of our name
0049F8D0 call sub_49F425 ; Main authentication function
0049F8D5 test eax, eax
0049F8D7 jz loc_49F98D ; If eax is zero, bye

If you aren't familiar with IDA's syntax, this may be slightly confusing. For the real idiots out there, the stuff after the ; is my comments that I've added, so don't think that IDA is just that brilliant. Apparently the call I didn't think of that was used was SendDlgItemMessage. Oh well. Anyhow, on that call, the last parameter of the four (and hence the first one pushed onto the stack) is the destination address for the string returned. If we dump those locations, we can confirm that they contain our name and code respectively. These two addresses are then sent to a call, after which eax is tested. On a hunch (looks real likely) we can see if that is the check routine. Use F10 to step over the call, then on the test eax, eax do an "r fl z" to flip the zero flag. (Be sure to disable the HMEMCPY breakpoint first). Now Ctrl-D out and wait for the message that says thanks for registering. We get it. Stop shouting, and sit down. You aren't done. All that we did was make it think that it was valid… this time. If you want to know how to keep it that way with a patch, go read my other tutorial on patching mIRC 5.71. This one is about finding a valid key, so all we have done is verify the location of the check routine. Now head over to 0049F425, which we now know is the validation routine.

Step 2: Reversing the protection

Ok, let's continue. We look down a little and find this (the first few lines are just some stack work) :

0049F42B mov esi, [ebp+arg_4] ; store address of code
0049F42E mov ebx, [ebp+arg_0] ; store address of name
0049F431 push esi ; save address of code
0049F432 mov esi, offset dword_559814 ; destination for some code fun?
0049F437 mov edi, ebx ; get the length of our name
0049F439 xor eax, eax
0049F43B or ecx, 0FFFFFFFFh
0049F43E repne scasb
0049F440 not ecx
0049F442 sub edi, ecx

OK, first put the code and our name into esi and ebx respectively, then we push esi (our code address) for future use. We then prepare esi with a new address we haven't seen. Now we put ebx into edi (copying the address of our name over). Now we zero eax. This little bit here we can see is just going to get the length of our name into ecx, and restore edi to the beginning of the name.

0049F444 xchg esi, edi ; swap so that name is source, unknown location is destination
0049F446 mov eax, edi ; make a backup copy? of the location
0049F448 mov edx, ecx ; put name length into edx
0049F44A shr ecx, 2
0049F44D repe movsd
0049F44F mov ecx, edx
0049F451 and ecx, 3
0049F454 repe movsb ; we have now copied the name to a new location (559814)

Looks like our friendly compiler optimisations went overboard. All we are doing is copying our name to the new memory address (0059814), but it insists on doing it in dwords, with the remainder done in single bytes. A single "repe movsb" with ecx holding the length would do the same as all this mess. Now we do the same thing for our code. Done with that mess.

0049F47C pop esi ; restore (now the ORIGINAL) address of our code into esi
0049F47D push offset dword_559918 ; now we put the new address for the code on the stack
0049F482 push offset dword_559814 ; same for the new name address
0049F487 call sub_49F332 ; call auth routine
0049F48C test eax, eax ; passed?
0049F48E jz short loc_49F497 ; if not go here
0049F490 mov eax, 1 ; yep, eax gets 1
0049F495 jmp short loc_49F50B

OK, here it looks like we are putting the new addresses on the stack, and then calling another function which does the real check. So now we head over to 0049F332.

0049F33B mov esi, [ebp+arg_4] ; put address of new location of code into esi
0049F33E push [ebp+arg_0] ; push the address of our name
0049F341 call _strlen ; get length of name (why NOW call a standard function?)
0049F346 pop ecx
0049F347 cmp eax, 5 ; check if name length is at least 5 characters
0049F34A jnb short loc_49F353 ; if so, leap onwards
0049F34C xor eax, eax ; if not, bye
0049F34E jmp loc_49F41C

This is our first actual check of whether we have a valid name/serial. We see that our name length must be at least 5. That means Rith won't work, so if I was serial fishing, I would go back and enter a longer string.

0049F353 push 2Dh ; dash character
0049F355 push esi ; address of code we entered
0049F356 call _strchr ; find first occurence of dash in code
0049F35B add esp, 8 ; correct the stack after the call
0049F35E mov ebx, eax ; move result (location found) over to ebx
0049F360 test ebx, ebx ; found?
0049F362 jnz short loc_49F36B ; if so...
0049F364 xor eax, eax ; not found. bye
0049F366 jmp loc_49F41C

2Dh is the value for a - on the ASCII chart and is a commonly used value in registration routines. Now, this routine looks for the first location of a dash within our entered code. If it doesn't find one, we fail. So we now know that there must be a dash in the code. Continuing on with the good jump…

0049F36B mov byte ptr [ebx], 0 ; replace the dash with a 0 (null termination)
0049F36E push esi ; yet again, our famous code location
0049F36F call _atol ; convert portion before dash from text to a long (dword) returned in eax
0049F374 pop ecx
0049F375 mov [ebp+var_4], eax ; save the result from the atol call
0049F378 mov byte ptr [ebx], 2Dh ; replace dash in code
0049F37B inc ebx ; go past dash
0049F37C cmp byte ptr [ebx], 0 ; was dash last character?
0049F37F jnz short loc_49F388 ; if not continue
0049F381 xor eax, eax ; bye
0049F383 jmp loc_49F41C

Comments are mostly sufficient here. Just note that we check to see if the dash was the last character, if so, we fail. Therefore the dash must not be the end of the code (no surprise there). Continuing :

0049F388 push ebx ; do ascii to long conversion on second half
0049F389 call _atol
0049F38E pop ecx
0049F38F mov [ebp+var_8], eax ; save the result
0049F392 push [ebp+arg_0] ; check the length of our name
0049F395 call _strlen
0049F39A pop ecx
0049F39B mov [ebp+var_C], eax ; store length

For those of you who have been scrolling along in confusion, this section coming is about to be really important… it is the actual code generation.

0049F39E xor eax, eax ; zero eax
0049F3A0 xor ebx, ebx ; and ebx
0049F3A2 mov edx, 3 ; put 3 in edx
0049F3A7 mov ecx, [ebp+arg_0] ; address of name into ecx
0049F3AA add ecx, 3 ; and add three... ? skips first 3 characters
0049F3AD cmp edx, [ebp+var_C] ; compare edx and name length
0049F3B0 jge short loc_49F3CE ; if edx is greater or equal...

0049F3B2 movzx esi, byte ptr [ecx] ; put char from name into esi
0049F3B5 imul esi, dword_536370[eax*4] ; multiply character value by value from lookup table
0049F3BD add ebx, esi ; add this into a total
0049F3BF inc eax ; increment eax
0049F3C0 cmp eax, 26h ; if eax is less than or equal to 26h (38 dec) continue, else zero eax
0049F3C3 jle short loc_49F3C7 ; increment edx (character of name)
0049F3C5 xor eax, eax

0049F3C7 inc edx ; increment edx (character of name)
0049F3C8 inc ecx ; increment ecx (address of character in name)
0049F3C9 cmp edx, [ebp+var_C] ; "are we done yet?" compares character with length
0049F3CC jl short loc_49F3B2 ; loop and do it again if characters remain in name

0049F3CE cmp ebx, [ebp+var_4] ; compare the calculated code (first half) with the entered one
0049F3D1 jz short loc_49F3D7 ; if we match, continue, if not, bye
0049F3D3 xor eax, eax
0049F3D5 jmp short loc_49F41C

OK, looks like ebx is the cumulative holder for our code here. Eax is an index into a… LOOKUP TABLE J. Study the comments I put in. We are looping through the name, starting at the FOURTH character. Ecx is the address of the next character in memory, edx is that character index. (So ecx is the base address of the string plus edx). At 0049F3B5 we see a reference to an unknown memory location, being multiplied against our name character… your first thought should be a lookup table, even without my comments, and it is one. It was hard to tell looking in IDA because it was doing all the helpful dup stuff for me. The significant characters are separated by whitespace (3 characters of it) in order to try to prevent the table from showing as a string.

I looked at it in HIEW and was able to see clearly that the characters were all there. I am not going to paste in the table here. It will appear in my keygen source at the bottom. All we care about now is that there is a table there with 39 elements in it. So, the program takes a character of the name, starting with the fourth one, multiplies it by a value from the lookup table, then adds the result into our code total. The lookup table index, and the name indexes are incremented. If the lookup table has been exhausted, we loop around to the front of it (only matters for people with long names). When we are out of characters, we compare the calculated code with the portion of the code entered before the dash. If they match, continue, if not, we lose.

0049F3D7 xor eax, eax ; prepare
0049F3D9 xor ebx, ebx
0049F3DB mov edx, 3 ; edx gets 3
0049F3E0 mov ecx, [ebp+arg_0] ; same stuff as last time around
0049F3E3 add ecx, 3
0049F3E6 cmp edx, [ebp+var_C] ; compare edx with length.
0049F3E9 jge short loc_49F40E ; compare calculated code with the entered one.

0049F3EB movzx esi, byte ptr [ecx] ; esi gets value of a character
0049F3EE movzx edi, byte ptr [ecx-1] ; edi gets the previous character
0049F3F2 imul esi, edi ; esi gets the two multiplied together
0049F3F5 imul esi, dword_536370[eax*4] ; esi get itself times the value from the lookup table
0049F3FD add ebx, esi ; add that to a running total
0049F3FF inc eax ; increment how many characters we've done. if at or over 26, zero it out
0049F400 cmp eax, 26h
0049F403 jle short loc_49F407 ; increment the character
0049F405 xor eax, eax

0049F407 inc edx ; increment the character
0049F408 inc ecx ; and the address
0049F409 cmp edx, [ebp+var_C] ; check to see if at last character
0049F40C jl short loc_49F3EB ; if not, repeat

0049F40E cmp ebx, [ebp+var_8] ; compare calculated code with the entered one.
0049F411 jz short loc_49F417 ; SET eax (we win) and return
0049F413 xor eax, eax
0049F415 jmp short loc_49F41C

OK, this is very easy to understand in light of the previous section. We reset the name index to the fourth character, and reset ebx (our calculated code) to zero, along with the lookup table index. The only difference here is that before we multiply our character by the value from the lookup table, we actually multiply it by the value of the previous character. Then that total is multiplied by the value from the lookup table. The result is added into ebx. The only other difference here is that we are of course comparing the final result with the second half of the code (the part after the dash), rather than the first half which we previously checked.

Part 3 : The KeyGen

My source code follows. The only practical difference between my code and the way the program does it is that I don't bother to have whitespace in my lookup table. I copied over the table from the program, removed the whitespace (but left it in hex, that is what the 0x prefix is in C/C++) and so I also removed the *4 when picking a character from the lookup table. Without much further ado, here is the code. It is written in C++ with some C artifacts. It is not elegant code, I wrote it at 6 am… after not having been to bed. Feel free to play with it, or to rewrite it in a different language. If you do the latter, please email me a copy of your source at rith@rith.cjb.net so that I may add it to the bottom of this tutorial. I will give full credit for its authorship.

#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main(){

const char lookup_table[]={0xB, 0x6 ,0x11 ,0xC ,0xC ,0xE ,0x5 ,0xC ,0x10 
                          ,0xA ,0xB ,0x6 ,0xE ,0xE ,0x4 ,0xB ,0x6 ,0xE 
                          ,0xE ,0x4 ,0xB ,0x9 ,0xC ,0xB ,0xA ,0x8 ,0xA
                          ,0xA ,0x10 ,0x8 ,0x4 ,0x6 ,0xA ,0xC ,0x10 ,0x8
                          ,0xA, 0x4, 0x10, 00};

cout << "KeyGen for mIRC 5.82t (32-bit version) by Rith.\n\n";
cout << "Please enter your name exactly as you wish it to appear in mIRC.\n"
<< "Due to a program requirement, the name must be between 5 and 80 characters long.\n" 
<< flush;

char name[81];
long int code1=0, code2=0;

int lookup_index=0;
cin.getline(name,80,'\n');
if(strlen(name)<5){
cout << "Error: name must be at least 5 characters long." << endl << flush;
}

for(int name_index=3; name_index<strlen(name); name_index++){
code1+=name[name_index]*lookup_table[lookup_index];
lookup_index++;
if(lookup_index>38) //hex 26
lookup_index=0;
}
lookup_index=0;

for(name_index=3; name_index<strlen(name); name_index++){
code2+=name[name_index]*name[name_index-1]*lookup_table[lookup_index];
lookup_index++;
if(lookup_index>38) //hex 26
lookup_index=0;
}

printf("Your code is: %i-%i\n",code1,code2);
cout << "\nEnjoy yet another fine release from Rith!\n" << flush;

return 0;

}

I truly must beg you, even more strongly than usual, to pay for this program if you love it. It is truly a wonderful work, and the choice IRC client for countless thousands. Khaled Mardam-Bey did a truly beautiful job on it… he just needs to learn a few better tricks for protecting it, if he seriously wants to do so. (Which is slightly in doubt in my mind, since he doesn't limit the length of usage or functionality in the unregistered version).

v5.71

"Rith provides here a well written tutorial introducing basic reverse engineering to the newest of newbie, just stay away if you know what a disassembler is :-). However, with mIRC being such a widely used program I hope at least someone will convert from the mindless quest for warez to the holy grail that is reverse engineering". "Slightly edited by CrackZ, and yes I do apologise for the holier than thou tone of this introduction :-)".

http://www.mirc.co.uk

Introduction

Before I begin, I want to acknowledge the fact that there are already two or three keygens for mIRC floating around. With that being the case, some might criticize the fact that I am only teaching here how to patch it, not to keygen it. The reason I am doing so is that I was amazed by how simple a program mIRC was to patch, and I thought it would make an extremely easy target for a newbie to learn with. Since keygenning is a somewhat more involved process, I decided to target this at the true beginner, who should have no trouble with this crack at all. I also wanted to show that a deadlisting approach can be even faster than a live approach, depending on the target. It took me about 5 minutes (!) to do mIRC 5.71 from a deadlisting.

What I Assume

I assume a basic knowledge of assembly language, as well as a basic familiarity with the "tools of the trade". I will not teach you how to work these tools, beyond a basic explanation to the process going on. This is one of the few tutorials that will NOT list SoftICE as a required tool. Truly, SoftICE is a reverser's best friend, but it is not needed in this case. The things that you will need are a hex editor (I personally prefer HIEW, but to each his own) and a disassembler. I use W32Dasm with this tutorial because it is the easier to learn tool for the newbie. There are many advantages to IDA, but they are not even needed in this case.

Notation & Tools

Where I have put comments beside code, I have used a standard ; to indicate the beginning of my comments on that line. Anything after the ; is my comments, and not the code. (So don't ask me why your copy doesn't say what does what :-))

A Hex Editor & Disassembler.

The Crack

OK, download and install mIRC. Before doing anything else, make a backup copy of mIRC so that if you screw up the original, you don't have to reinstall. (The other reason for an original backup is for automated patch generation, but that is not likely to be a concern for the average newbie.)

First, run the program, and try to register with any bogus info. We are not using SoftICE or setting breakpoints or anything else. We just want to see what it says if we enter invalid information. Here it is: "Sorry, your registration name and number don't match! Please...", yada, yada. The thing is that now we know exactly what to look for.

I was sure that since mIRC was such a widely used program, some time would have been invested to make sure that it was well protected against cracking. Nope. It is not even packed, which I was almost sure it would be (there is nothing more annoying than trying to disassemble something that is still packed). So, fire up your disassembler and disassemble the program file. (All snippets will be from W32Dasm, feel free to use IDA if you want.) What is the first thing you always check when you finish disassembling a target? Do I hear you saying the string references? Right! Open up the string references box, and start looking for any text that looks like what we saw when we entered invalid registration information. We find it pretty readily, and we double-click this text to be taken to where it is referenced.

Explanation for the extreme newbie

We double-click text in the string references dialog box of W32Dasm in order to be taken to any reference to the string in the program (if there is more than one use, clicking multiple times will move through them). The reason we do this is that a reference would only be made to the string if it was needed, i.e. we would only use a "bad reg info" message if we had determined that bad reg info was used. (There is an exception to this rule, but don't worry about it at this point. Sometimes a "bad reg info" message is moved into the location of the message to be used, and if it turns out to be good, the location of the "good reg info" message is written over it, but that doesn't apply in this case.) So, by going to the reference to the message, we are almost certain to find a point in code right after it has been determined that the reg info was bad. From that point it should be easy to figure out how one got there, and how *not* to get there.

Anyway, double-clicking the reference lands us here :-

:004A349E PUSH 00000000

* Possible Reference to String Resource ID=01913: "Sorry, your registration name and number don't match!"

:004A34A0 PUSH 00000779
:004A34A5 CALL 00424324
:004A34AA PUSH EAX
:004A34AB MOV EAX, D, [EBP+08]
:004A34AE PUSH EAX

* Reference To: USER32.MessageBoxA, Ord:0000h

:004A34AF CALL 005172D9

This is as straightforward as it gets. The code creates a message box with the message saying the reg info was bad. Now what do we do?. We find out how we got here. What brought us to this point of knowing that the reg info was bad?. So, we scroll up. We don't have to scroll up far before we hit this :-

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004A33B9(C)

:004A345A PUSH 00000000

The only thing we care about is the "Referenced by..." info. This tells us that a conditional (meaning that some test was performed to know whether or not to make the jump) jump brings us here, fortunately, only one address brings us here. That means that at the address 004A33B9 is a conditonal jump that decides we aren't a valid registered user. So, now we go to that code location. Here is what we find ourselves looking at :-

:004A33A8 PUSH 00531B23 ; pass some info relevant to registration. Name perhaps.
:004A33AD PUSH 0053173C ; more of the above, this could be the serial we entered.
:004A33B2 CALL 004A2F9C ; check them out, see if they are valid.
:004A33B7 TEST EAX, EAX ; well, are they?.
:004A33B9 JE 004A345A ; if not, tell 'em to go.

Now, I am guessing with the comments I put as to what the push's are. But I do know almost certainly that the function call there is the main protection scheme function. How? Immediately after that call, eax is tested. All test eax, eax does is to see if eax is zero. If it is, the zero register flag is set. So, the next instruction, which is a je (jump if equal, also the same as jz, jump IF ZERO), depends on whether eax was zero or not after that function. We can bet our bottom dollar that that function sets eax based on a good bad reg code, and since a value of zero makes us take the bad jump, we know that eax must be non-zero (the function probably returns 1 for a valid code) if a good code is entered. If we scroll down a little, we find the following :-

* Possible Reference to String Resource ID=01911: "Your registration has been entered successfully. Thanks for"

:004A343C PUSH 00000777
:004A3441 CALL 00424324
:004A3446 PUSH EAX
:004A3447 MOV ECX, D, [EBP+08]
:004A344A PUSH ECX

* Reference To: USER32.MessageBoxA, Ord:0000h

:004A344B CALL 005172D9

Since this is obviously a "valid reg info" message, What this tells us is that we want to proceed on to this code, and we are confirmed in our belief that we do not want the jump to be made. So, we must patch the code so that the jump is never taken. How do we do this? Simply by eliminating the conditional jump statement (if it isn't there, it can't be taken, now can it?). We can see from the line :-

:004A33B9 JE 004A345A

That the conditional jump statement is six bytes long: 0F 84 9B 00 00 00. There is a one-byte command for doing nothing. The mnemonic (instruction name) for this is "nop" (or No Operation). If you only memorize one hex opcode, memorize this one. The hex opcode for nop is 90. So we need to replace 0F 84 9B 00 00 00 with 90 90 90 90 90 90. (The reason we have to use six nop instructions is that we MUST keep the same "size" for that call intact. Since there was one six byte call, we must fill it in with six of our one byte instructions.) Open up your hex editor. If you used HIEW, you can use the virtual offset (004A33B9). To go here, press F5, then a . (period) followed by 4A33B9 (the 0's are redundant).

If you are using any other hex editor, you will likely have to search for the byte sequence (0F 84 9B 00 00 00) and get there that way, since most hex editors deal only with real file offsets and not the virtual offsets. Now, we must edit they bytes. In HIEW press F3, then fill in your space with six 90's and press F9 for update (save) and F10 to exit. Optionally, you could have pressed F2 after you pressed F3 and been allowed to actually type nop six times, rather than 90 (this is one MAJOR advantage of HIEW, along with virtual file offsets). In any other hex editor, once you are at the right place, fill in with six 90's however that is done in your editor, then save and exit.

So... expecting a "good job, congratulations" message from me here? Too bad. You aren't done. Unlike some tutorials where a program is picked that only has to be patched in one place, I wanted to show a program that had to be patched in more places, but that was still easy to crack. If you were to run it now, you *should* receive the message that your registration worked... but if you exited and came back in it would not still be regged.

The issue is that we just fooled it into taking the info... but, when it starts, it loads and RE-CHECKS the info. That means that there is at least one more place where it will check the code, and we need to patch them too. (Actually, there are two choices, we could patch each location where the check is done, or we could patch the check routine to always return a valid value. If there are many places that the check is done, then it makes plenty of sense to do this. In this case, there are only a total of three (as we will see in a moment, one of them already patched), and the check routine does a lot with the stack. Since we don't wanna trash esp (the stack pointer) we will just leave the check routine alone and patch the other locations, which is probably easier in this case.)

So, now we need to find the other locations. How do we do that? Simple. When we saw the check, the main thing was a call to the function at 004A2F9C. So, let's go there now and let W32Dasm tell us where *else* this is used from. We find this :-

* Referenced by a CALL at Addresses:
|:004A30F7 , :004A31CA , :004A33B2

:004A2F9C PUSH EBP

This is the beginning of the calculation/checking routine. If you really wanna know how it does things, wade through this... but since this is a newbie patching tutorial, we are only gonna concern ourselves with how to defeat it, not how it works. We immediately notice that 004A33B2 is the last of the three places this routine is called from. (Which makes sense, usually code at the beginning of a program comes before code that is run upon a command, i.e. an attempt to register). But, right next to it are two other places that this routine is called from. So, we hunt each of them down. Head over to 004A30F7 and we find this :-

:004A30F7 CALL 004A2F9C
:004A30FC TEST EAX, EAX
:004A30FE JE 004A3118

Hmm... looks familiar, doesn't it?. Well, since we already know that the return value is non-zero for a valid serial, we know that we do NOT want to take any jump that would result from the call returning zero (and hence a "equal" test result). So, we will patch this location as well, again ridding ourselves of the je instruction. Don't jump over to HIEW just yet though, because we have another location to do. For now, just note the address (004A30FE) and that we need to nop two bytes.

On to the other location, 004A31CA :-

:004A31CA CALL 004A2F9C
:004A31CF TEST EAX, EAX
:004A31D1 JE 004A3212

Damn... this really gets repetitive. :-). We can assume exactly the same as we did at the last location. We note down the address, and that the JE instruction is two bytes long.

Now go over into HIEW (or your alternate hex editor) and nop (90) out two bytes at each of the noted locations. Save, then exit. Run the program and enter any name (of at least 5 characters, because we didn't patch the length check, and I didn't deal with it here) and any serial. There is a check for a - (dash) in the serial, and I don't remember whether this was inside or out of the routine we patched, so if it doesn't take without it, just put a dash somewhere in the serial you use.

Congratulations! You just patched a program that checked the reg info in multiple places, and made it take your serial! (That wasn't so hard, now was it?).

Contact Info & Greets

Find this tutorial helpful? Too long? Too little info explained? Find a mistake? Feel free to drop me a line at Rith@rith.cjb.net. Note that I will not crack requests nor will I send cracks. Only contact me about tutorials I have written. Greetings go out to all those who have helped me along my way over the past couple of years. You know who you are, and I don't want to embarass myself (or upset anyone) by forgetting to mention someone, so I will not try to list you all here. Thank you for taking the time to help.

Serious Remonstration

mIRC is a beautiful work of art. I love it and Khaled Mardam-Bey (the author, whom I have communicated with personally about bugs/updates) has done a very good job on it, and put in a LOT of work. If you like the program you should BUY it.

-Rith.
July 15th, 2000.


Newbies


© 1998, 1999, 2000 CrackZ. 15th July 2000.