The QBNews Page 18 Volume 1, Number 3 May 22, 1990 ---------------------------------------------------------------------- P o w e r P r o g r a m m i n g ---------------------------------------------------------------------- How to Make a Self-Cloning Exe in QuickBASIC by Larry Stone [EDITOR'S NOTE] All extended ASCII codes have been replaced in the following article. Have you ever had the need to create a program that holds a password away from prying eyes of others? Or, maybe you have discovered that writing shareware rewards your ego by making your name familiar to the PC world, but doesn't reward your pocket-book because most people will register their programs when they get around to it, which, often-times, is never. Or, maybe you just wish to write a program that holds it's configuration without having to create a configuration file. One of the easiest methods to accomplish the above listed tasks is to create an EXE file that "clones" information to itself. The trick is to create a "recognizable" area inside of the EXE itself that can be quickly read and modified. What makes this such an easy trick? Well, have you ever used Vernon D. Buerg's LIST utility to list your EXE's? If you do, you will notice that towards the end of the program, every string that you have defined within your program is CLEARLY VISIBLE! What you are viewing is the EXE's token definition area. When you compile and link your programs, a token is defined for every string used. For example, your program might have the code, Strike$ = "Strike any key". When you list the EXE, it may show something like, 0TStrike any key. The symbols 0T would be the programs marker to the definition, "Strike any key". Under no circumstance do you want to change this marker because really weird results could ensue. However, you can create a string that contains a marker that is exclusive to your use, i.e., Special$ = "<*!@#%>This is my special string" In the above example, <*!@#%> then becomes your special marker to the data that immediately follows. Then, all your program has to do is to look for your special marker and modify the next 25 characters as needed! CAUTION! Never, never, never try to reserve a data area for cloning by defining a string as: Special$ = "<*!@#%>" + SPACE$(25) Special$ = "<*!@#%> " Both of the above examples will *NOT* create the 25 character data area desired because BC will optimize the SPACE$(25) as a two byte token! Let's assume that we need to build a program that needs to hold a The QBNews Page 19 Volume 1, Number 3 May 22, 1990 user-defined, sixteen character password and a token that determines whether the EXE is shareware (limited in scope) or registered. Let's further say that when a registration is received by you, the author, you then mail that person a key which redefines the definition of a token so that features not available to the shareware user now become available. Let's further state that your program is going to hold configuration information within a 13 byte string. The first thing to do is to create a unique string inside of the program itself, as well as, two or three variables shared within the program. At the top of the program, do something like the following: DIM SHARED StoredData$, BytesToData& Now, if your program does not "clone" configuration information or passwords, then BytesToData& does not need to be SHARED. Rather, in this case, only the information itself (Shareware/registration key) needs to be shared. However, for this discussion, we're going for the entire pie. Next, someplace within your initialization subprogram (or in your main module), you should place code such as the following: SpotForKey$ = "<%*@#!>123456789012345678901234567890" Now, because your program needs to read itself, modify itself and, at the same time, hold part of the original string as your special marker, we need to do the following: tempKey$ = LEFT$(SpotForKey$, 6) In this way, no matter what we do to the following 30 character spaces, <%*@#!> will always remain our special marker that the program looks for. Let's build the routine that reads in this data. '--------------------- Get Special Data Routine --------------------- DIM SHARED StoredData$, BytesToData& SpotForKey$ = "<%*@#!>123456789012345678901234567890" tempKey$ = LEFT$(SpotForKey$, 6) 'Our special marker. 'The Bytes% variable is the number of bytes to read.If your program is 'less than 16000 bytes then this routine adjusts accordingly. Also, if 'a 16K byte "GET" cuts the marker field in two then you need to change 'it to another value, such as 15550. Bytes% = 16000 BytesToData& = 0 portion& = 1 countToKey% = 0 StoredDataLen% = 30 The QBNews Page 20 Volume 1, Number 3 May 22, 1990 OPEN MyProg$ FOR BINARY AS #1 FiSize& = LOF(1) 'Get the size of the file DO WHILE NOT EOF(1) IF Bytes% > FiSize& THEN Bytes% = FiSize& IF Bytes% + portion& > FiSize& THEN Bytes% = FiSize& - portion& A$ = INPUT$(Bytes%, 1) 'Read Bytes% number of characters. countToKey% = INSTR(A$, tempKey$) 'Look for our special marker. IF countToKey% THEN 'If we found it then process it. BytesToData& = portion& + countToKey% + 5 'Get past our marker. SEEK #1, BytesToData& 'Get the data from the file. StoredData$ = INPUT$(StoredDataLen%, 1) EXIT DO 'We found it so out of the DO LOOP END IF portion& = Bytes% + portion& 'Determine where the next SEEK is. 'If we're within 800 bytes of the end of the EXE then we are past the 'token definition area of the QB program. In this case, we're done. IF portion& >= FiSize& - 800 THEN EXIT DO 'Move pointer to the next 16000 byte block to read from the file. SEEK #1, portion& LOOP CLOSE #1 '--------------------- End Special Data Routine --------------------- Now, whenever our program needs to look for a password, a registration key value, or it's configuration information, it need only do the following: Password$ = LEFT$(StoredData$, 16) RegisValue = VAL(MID$(StoredData$, 17, 1)) IF RegisValue = 7 THEN PRINT "Shareware Edition" ELSE PRINT "Registered Edition" END IF ConfigData$ = MID$(StoredData$, 18) To prove this works, snip out the special routine and add the following statement at the end of the routine: PRINT StoredData$ Next, compile and link it, then run it. You will see the above string printed (you will also notice just how fast QB's INSTR function really is! - It's blazingly fast! - Couple this with BINARY access and you'll discover that load time is not appreciably degredated). Don't forget to define MyProg$ as something or you'll get a nasty error message! Okay, okay, so how do we write new information to our special The QBNews Page 21 Volume 1, Number 3 May 22, 1990 data area? Simple - just do the following: Password$ = "Lawrence Stone " RegisValue$ = "0" ConfigData$ = "Config Area 1" OPEN MyProg$ FOR BINARY AS #1 PUT #1, BytesToData&, Password$ 'To write the new password. RegisValue$ = "0" PUT #1, BytesToData& + 16, RegisValue$'To create a registered version. PUT #1, BytesToData& + 17, ConfigData$ 'To clone configuration data. CLOSE #1 Now, re-run your compiled program and have these values print to the monitor. Notice how easy it was to change the data? If you are going to have an external "key" program that "turns on" the registered version then it needs to simply read in the data using the same special marker that we created as a marker to search for. Also, you might wish to make another small program that converts your pre- defined password and configuration space to spaces. Otherwise, you need to run your program before you distribute it so that you can change the password (which is equal to "1234567890123456") to something like SPACE$(16). In other words, nullify the temporary string used by our program that forced BC to give us the data space we needed in the first place. Now, for demonstration purposes, we have used the default "7" for indicating that the program is shareware and "0" for registered. I would recommend that you reserve at least 8 character spaces for this field because then you can create unique codes for every key and every user. In this way, your program can look for the key in both itself and within the key program as well. This would also offer one more level of safeguards for you. One final word: Any casual hacker can use LIST to find your password if you leave it in it's native ASCII. You should consider a routine that converts the appearance of your data so that it looks like the rest of the binary code. Routines can be as simple as taking each character in the strings and adding 100 to their ASCII value for writing, then, subtracting 100 from their ASCII value for reading, to some truely cryptive procedure, depending on how sensitive you want the information contained therein to remain. ********************************************************************** Larry Stone is President of LSRGroup and is involved in writing software for marine and aquatic research. He can be reached at LSRGroup, P.O. Box 5715, Charleston, OR 97420, or in care of this newsletter. ********************************************************************** [EDITOR'S NOTE] The file CLONE.BAS contains a slightly modified version of Larry's code above. I have modified it to make it easier to add to your program. It is contained in CLONE.ZIP.