²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²² ²² ____ __ __ ²²ßÛ ²² / _/_ _ __ _ ___ ____/ /____ _/ / ²² ÛßÛ ²² _/ // ' \/ ' \/ _ \/ __/ __/ _ `/ / ²² Û Û ²² /___/_/_/_/_/_/_/\___/_/ \__/\_,_/_/ ²² Û Û ²² ____ __ __ ²² Û Û ²² / __ \___ ___ _______ ___ ___/ /__ ____ / /____²² Û Û ²² / /_/ / -_|_-</ __/ -_) _ \/ _ / _ `/ _ \/ __(_-<²² Û Û ²²/_____/\__/___/\__/\__/_//_/\_,_/\_,_/_//_/\__/___/²² Û Û ²² ²² Û Û ²² Web: http://www.ImmortalDescendants.com ²² Û Û ²² Author: Lord Soth ²² Û Û ²² Date: 11/03/1999 (mm/dd/yy) ²² Û Û ²² Topic: A DOS paper protection, some basics ²² Û Û ²² Level: Basic (as I said :) ²² Û Û ²² ²² Û Û ²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²² Û Û ÛÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÛ Û ÛÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÛ A DOS based paper protection, a trip down memory lane :) ========================================================= Introduction =----------= Ok, first of all, hello to everyone reading this text, the time is about 20 mins after midnight here, and yours truly is pretty beat, so I'll be brief :) The old famed DOS paper protection is one of the oldest protection primitives that ever graced the computer/software world. This protection, back in the early days posed a real threat to non-crackers because those not possessing RCE skills couldn't usually obtain cracks for these simple protections because of the internet wasn't that developed or whatever reason you can think of. In the end, this protection was very potent just because of that, and it was pretty annoying, I remember it myself (before I got into cracking). So, we said this is a primitive, but as +ORC stated in his excellent essays on paper protections, a lot of today's protections still rely heavily on this simple protection. What we'll try to accomplish in this short essay is a comparison between a DOS paper protection and today's normal serial protection. Also, this will give us a good chance to review some differences in approach when cracking in DOS as opposed to the linear memory map of Winblows. The target selected is not hard to reverse or crack so this will just be a demo crack, and most of you will not even want to follow it with the program running. If any of you still want to try going over it, please contact me and I'll give you a copy of the game. Target : Empire, the WarGame of the century by Mark Baldwin, around 300KB.. Tools : SI 2.8 (my choice for DOS), Hiew (or any other hexeditor) Ok, some approachs to this whole thing =------------------------------------= Ok, so first lets see what we're up against, after we unzip the game to our destination folder lets do a simple test run and see whats going on. Some beeping and we finally arrive to a screen asking us to enter word number X from line number Y or something similar to that. We can enter a word, and when it is incorrect the program will launch in a demo mode to show us what this game is all about. We can of course choose Exit, but that will not get us anywhere :) After a few tries it also tells us that we have a pirated copy of the game and it gives us a chance to redeem our conscience. We'll soon see who redeems whom from what :) Step 1 - finding the text string we enter in the box, in memory =-------------------------------------------------------------= Ok folks, I don't have to repeat why exactly this step is a VERY important step in cracking any password/serial protection do I ? Even so, I'll give a good and solid reason to do so, and it is because we want to see how the program manipulates our input and then determine if to proceed or not. That of course is our final goal, to see how it determines if to proceed and force it to proceed always. So, if this was a Winblows program, a typical move would be to place some BPs on APIs that retrieve text from dialog box controls (assuming we're sitting infront the registration dialog). This is all neat, but in old DOS such important stuff did not exist! Even though M$'s Win32 APIs is an overbloated interface, it does support important features quite easily, at least that hehe In our program we don't have that luxury, so how are we gonna go about finding our text string ? There are more than one way to do this. If in Winblows we knew the address of where the string is about to 'show', here we will have to work harder to get it. Now since DOS doesn't support things like GetDlgItemTextA, programmers had to write their own retrieving routines. This of course involved a routine to scan the keyboard for keys pressed (so they could be displayed on screen and then taken to memory). So one might think if we put a BP on INT 16 (there are actually several services that can do the same job), we'll find where a char is taken and stored in memory. This however my friends might not be so trivial as you might think. This is because the char can only be used for display purposed and does not get stored untill we click the OK button, or because the programmers incorporated their KB routine into the normal interrupt routines that gets called a lot of times per second :) This last method is mostly used in games to get keyboard input. So if the programmers coded such a routine, why not use it for this too ? This makes our lives somewhat more annoying because we'll have to trace some more code to determine where the string chars are stored (probably somewhere in the caller's data segment). The last tracing and sniffing can sometimes be very annoying/difficult, depending on the program involved. This is a viable option, but lets not overlook more simple ways of doing the same exact job! If we were to enter a string that wouldn't have a chance to appear in the computer's memory we'll be able to search it, plain and simple! This also works in Winblows too, and preferably after a call to GetWindowText and equivalents. An immediate problem arises, since DOS does not have a FLAT memory module, like Winblows has, we are left with the task of guessing in which segment this string is at. Lets type something unusual into the box, and then pop SI, and inside start the search. A good place to start would definately be the current DS (Data Segment) Why you ask ? this is because 16 bit routines *usually* don't require much code to accomplish what they need to do (especially not something as simple as this routine's job). So if we'll search our string in SI : s ds:0 l ffff 'STRING'. This line above looks in the current Data Segment from address 0 to FFFFh, which is the whole segment, for the STRING string. If any results are found, and here we will find only one (jee what a surprise!) , the debugger will list their addresses. Now lets display it so we can look at it with our own eyes : D xxxx:yyyy Notice here that we are using segment addressing, this is of course because we are in 16 bit mode (DOS). SI though has the ability to display memory in any segment we want, so even if we are in a different segment, we can always view data in any segment we like. Ain't it nice of NuMega ?? :) Step 2 - figure out what the program is doing with our input =----------------------------------------------------------= Alrighty then, we have a few options here. We can try tracing code and figure out whats going on, and this option is pretty good if the code that manipulates our input is nearby, OR in the case that there is a certain check on it. For example, when cracking the serial registration dialog of a Winblows application you might put BPs on the input you put in the dialog but only to discover that the only thing the program is doing is check the length of the string! Suddenly it pops a message box saying the registration information is invalid! How come ? I'll tell you, maybe there has to be a minimum or maximum length for the input, and if that condition is not met, there is no need to start calculating the serial out of it! In this case tracing code after we break on the first reading of the input will probably be good to discover traps such as this. However, back in our game, this is not the case, and we can set the BP: bpm xxxx:yyyy r Setting a BPM on that address for read access (the first char is needed, nothing more actually). Lets continue execution and we'll immediately land in SI. What we're looking at is a REPZ SCASB which is a loop to measure a strings length, I'm sure you've all met it before. If not, trace it a bit and see how the registers are handled. We'll continue some tracing only to find out that this string length value is placed somewhere and then we go back with a RETF. Ok, now what ? after the RETF we don't see anything suspicious, so lets continue the execution and see what SI will break on next :) F5 and viola, we're back in SI. This time, we're stuck on a REPZ MOVSW. This as you all know will move a string word (MOVSW) from DS:SI to ES:DI, and then change SI and DI in incrememnts of 2 (because its a Word), according to the direction flag's status. Just some basic Asm for you. Lets quickly display the area pointed to by DS:SI, and we'll see our string copied to there, and the remaining bytes. Afterwards it will remove any spaces from the end of the string, but that is totally irrelevant to us. So, now that the string has changed position in memory, it would be a good idea to disabled the previous memory BP and place a new BP on the new location. Lets continue and see what else SI will break on (this is starting to be fun!) hehe Immediately we land in something that looks like this : MOV AL,[BX] ;get char, this pops the BP OR AL,AL JZ 0C19 307D:0C08 SUB AL,61 ;reduce ASCII of 'a' 307D:0C0A CMP AL,1A ;check we are not above 'z' 307D:0C0C JAE 0C12 ;if we are, no good, bug out 307D:0C0E ADD AL,41 ;add 'A', making letter a capital letter 307D:0C10 MOV [BX],AL ;store back 307D:0C12 INC BX ;next plz :) 307D:0C13 MOV AL,[BX] ;get next char 307D:0C15 OR AL,AL ;is it the end ? 307D:0C17 JNZ 0C08 ;no ? go back and convert this char too Alright, this looks very suspicious right ?? WRONG! This if you don't recognize is just a routine to convert a string from lowercase to uppercase. It does it byte by byte too, see how cool it is :) So , we gather this is not the right place to be at, and thus we'll continue in hopes of finding what we want (hope has nothing to do with it really hehe) So, ANOTHER F5, and SI breaks once more!! How amazing, it even looks the same way the above routine looks, well, loo!, it IS the same, so annoyed at the disturbance, we'll press F5 once again, and now comes a good part, one we've been waiting for :-) Now I'd like to take this opportunity to mention that since this is a text string, it has nowhere to hide, and thus we can ALWAYS find it in memory before any manipulation is done on it, and so far, nothing special has happened except it got converted to uppercase. However, now this is about to change. Ok, lets back track a little to the paper protection scheme. We know that the program has to somehow compare our input with the right word from the manual. However, is the manual is stored in the program somewhere ? are the right words stored in the program somewhere too ? Yes, you can SEARCH for meaningfull words, but I guarantee you won't find any. In most cases this is done so that you won't be able to see the right codes. In addition it would be easier to find the right 'codes' just trying to reverse the comparison routine, we'll see this soon. Ok, how does the programmers hide the codes from us ? they can encrypt them! Or they can manipulate the ASCII string somehow (kinda like encrypt tho hehe) In the following snippet of code (our latest BP), we'll see just that : 1AA1:0716 LES AX,[BP+06] ;load address of string 1AA1:0719 INC WORD PTR [BP+06] 1AA1:071C MOV AL,ES:[BX] ;<<< This triggered the BP (get char) 1AA1:071F CBW ;<<< and here WE ARE 1AA1:0720 NOT AX ;NOT the ASCII char 1AA1:0722 SUB AH,AH ;zero out high byte of value 1AA1:0724 ADD [BP-02],AX ;add the result to 'somewhere' 1AA1:0727 LES BX,[BP+06] ;next char's address 1AA1:072A CMP BYTE PTR ES:[BX],00 ;check if end of string 1AA1:072E JNZ 0716 ;repeat So in this little calculation routine we see the loop takes a char, NOT its value and add the lower byte to a variable (BP-02 in this case). This is indeed worthy of our attention, it is a manipulation of some sort. We'll keep that in mind and lets also write the result in BP-02 when the loop is over (finished processing all the letters). Well, a little tracing and we encounter a RETF (or was it RET ? hehe). In any case, we go back to where we were called from. Now this looks like this : 11F7:28B0 CALL 1AA1:06FB ;call the manipulation routine 11F7:28B5 ADD ESP,+04 ;another DWORD on the stack, we are here!! 11F7:28B8 MOV CX,AX ;put something in CX 11F7:28BA MOV AX,6 ;6 in AX 11F7:28BD IMUL WORD PTR [BP-10] ;multiply the contents of BP-10 by 6 11F7:28C0 MOV BX,AX ;also in BX 11F7:28C2 CMP [BX+0CAA],CX ;!!!AHHA!!!, this is it!, notice the value? 11F7:28C6 JZ 28CB ;good manual user 11F7:28C8 JMP 270B ;bad punk cracker As you can see there is a little calcualtion here, something that depends on the values returned from the previous call. Then at 28C2 we see our CMP which will compare whats in memory and CX. Notice that CX holds the value from the previous CALL that I asked you to remember. This value came from our input and is checked again a value in memory! Lets look at that memory location for a sec : d bx+0caa This will show us something that looks like a table of Word values. It doesn't seem to be just random data. In fact, it is most suspicious because we saw a little calculation above the compare, and this might be an index into a table of preserved values!! Imagine they took every code we could enter and do that manipulation on them. (I.E, the NOT and stuff from the previous snippet). And walla!! We have found the way the programmers found to hide their codes! They hide them by turning them into integers and when the user inputs a word it will turn it in the same way. Now since the program knows where the 'good' code's integer is in memory it will compare it to our manipulated string's value and if they are equal that means everything is OK. This eliminates the plain text code cracking some games are vulnerable to. So, how will we go about patching this ? We must remember to 'fix' the offensive conditions, IE, don't let the program do the JMP 270B at line 28C8. Now lets imagine what would happen if the right word would have been entered by a legit user. Then CX would hold the RIGHT value it is supposed to be. So why not we take the right value from our table of integers and put it in CX just in case there is another check somewhere ? This is good practice, although most protectionists are too lame to backcheck the input. So if we change the CMP [BX+0CAA],CX to MOV CX,[BX+0CAA], this will ensure that the right value is in CX. Also this is just changing of the first byte of the instruction (it is 4 bytes in memory, only need to change 1 hehe). Now, we will also take care to that JZ so that we will always jump instead of jump when equal. Lets do the changes, use the A command, and let it run, and if you did it right you should see a dialog saying that the word is verified! Continuing the game seems to go ok, doesn't it ? well it will :) Now something has occured to me in all of this. I traced through the code, (err more like stepped over) and I saw that I stepped over a CALL and all of a sudden that verification dialog was up. I pressed ENTER and it went away, and I went back to SI (JUST like modeless dialog boxes in Win!!) This gave me another idea : since these are all "dialogs", I figured maybe we can let the program think it asked the code and got a positive reply. This is exactly nag removal from winblows. Now, just a thought, in order for us to get back to the original caller of the code dialog and be positive we're in the right place (returning after a successful input), we will need to do a patch or change on the fly and then return to the caller. Maybe the program does not return to the same place on a bad input ?? So we'll do an on the fly patch and continue stepping till we get to a RETF. (or use F12!! hehe) Now we should see this : 0A96:0211 MOV ES,[7BDE] 0A96:0215 MOV WORD PTR ES:[636E],0001 ;flag, should not be set 0A96:021C CALL 11F7:268E ;call the input dialog 0A96:0221 OR AX,AX ;check return value, we are HERE!! 0A96:0223 JZ 0255 ;if good, DONT jump, if not, JUMP So, we see that if the function got a good input, AX equals 1 in return, so this is something we'll need to make a permanent thing. Also, if we let the program set the flag at 636E, the program will start as demo, so lets skip over it. I'll do that in the following manner : 0A96:0215 33C0 XOR AX,AX ;zero AX 0A96:0217 40 INC AX ;make it 1 0A96:0218 EB07 JMP 0225 ;(after the JZ at 0223) 0A96:021A NOP 0A96:021B NOP As you can see the original line at 0215 will take up 7 bytes (Code on will show this), so I replaced it with 7 bytes as well. Now its time to make the crack and check it. Backup the original file and patch in all the places I've mentioned. This is done exactly like in Winblows. For DOS programs I suggest using Hiew, it can search even by Asm instructions (it translates to op-codes). Patch and run and the nag SHOULD be no more!! :) Note: Its a good idea to patch the code check because maybe sometime in the middle of the game there is another check of a code (seen it too often), so its a good practice to make that check pass always (unless of course there is another section coded in a different location). To make a long story short, we have reversed this little game. The main things to remember are that cracking in DOS might be much more challenging. It all depends how the programmers hid the protection. They can spread it over segments and you'll have a hell of a time coordinating between all the segments and the data they contain! Because of this DOS was always potentially better for protections than Winblows. I hope you enjoyed this little lesson For comments : LordSoth@ImmortalDescendants.com lordsoth8@hotmail.com or ICQ 5178515 Greetings goes to everyone I know, and those I don't, and since it is so late I hope you guys will forgive me if I don't specify them all this time :) Later boys and girls, and happy reversing!! Lord Soth