Once I had accepted the fact that copying of distribution disks is not preventable, I started trying to figure out what else can be done to prevent unauthorized use of the software. I eventually came up with a protocol that has evolved over the years for a system that not only provides protection against unauthorized use, but also gives me a great deal of flexibility and control over who uses my software and how it is used.
Now I encourage others to copy the distribution disks and pass the copies along to others!
Before going into detail, the statement needs to be made: These procedures and protocols work for me in my development niche. Feel free to use whatever ideas make sense to you for your situation, then build on them as appropriate. Believe me, I have no corner on “How things should be done.” I’m always amazed when I read articles in this bulletin, or Q & A in the dBASE newsgroups: I think to myself “Why didn't I think of that?” or “I didn't know you could do it that way!”
Every application I produce has a one record “startup” table. I usually call it Startup.dbf and it usually resides in the home/program folder, the one that contains the .exe for the application. (You could call it George.dbf and put it any place you wish — just be consistent.) I either put the data tables in a sub-folder or in a second level application folder. For example, I would put the program code in a folder called AppName and its data in a sub-folder called Tables or else the main AppName folder would contain nothing but two sub-folders: one containing the program code (called Prgs) and a second sub-folder used as data repository (called Tables).
The startup table is created on the first run of the application. It is paired with a startup form that queries the user for name, address, zip, and phone number (and other fields such as Company_Name, Club_Name, etc. as appropriate to the application). Some of this information is used to customize the Main Menu/Opening screen, as well as being available for use in appropriate reports and forms. This customization is a nice touch — it makes the user feel like the software was written for him/her.
What is not seen in the startup form, is that there are also a number of “hidden” fields in the startup table/record that will be used to prevent unauthorized use as well as controlling other functions of how the program works. They are “hidden” in the respect that they are not visible to the user. In addition, if you were to examine the startup table with a word processor or dBASE, you would see that these fields have nondescript names such as F46, F47, etc. and their contents are unreadable because they’ve been encrypted. (For consistency in this article, I’ll refer to these fields with names starting with “F46.” You can name the fields anything you want.)
When you start running the application, one of the first things the application does is to open the startup table and store each field as a public variable so that each will be available throughout the application.
Note: For what it’s worth, I also store today’s date twice (date() and mdy(date())) to public variables called Tday and cTday. That way they’re always available as needed throughout the program. Today is a reserved word in a number of languages including some dBASE clones, so I’ve stuck with Tday. I probably should be storing them as _app.Tday and _app.cTday, but I’m still somewhat stuck in DOS.
Initializing the Hidden Fields
In a typical setup table, the first hidden field (F46) would be a character field [char(4)] with an initial value of DEMO inserted when the table was created. The second (F47) would be a date field. Additional fields will be found depending on the application. For this article let’s use the following: F48 – char(28), F49 – char(5), F50 – date, F51 – char(8). Now let’s look at how these fields are initialized:
As long as F46 contains the DEMO value, the system checks the demo expiration date. From the Main application menu, usually as a sub-menu to Utilities, the user can access an “Upgrade” form. As the demo time grows short, the app advises the user of how many days remain for the demo use, and asks if they want to upgrade. A “yes” puts them directly in the Upgrade form. After the demo expiration date, the only thing the demo will do is advise that the demo time has expired and then open the Upgrade form. If they don’t upgrade, the application closes.
When the user upgrades from the demo to a full version, F46’s value is changed to FULL or some other value. The demo expiration field is ignored. If you aren’t dealing with demo software, you may want to leave the field blank until some trigger initializes it as a “full” version. That could involve emailing an upgrade key, or some other unique trigger that the user could input.
What keeps someone from deleting the setup table and starting the demo expiration time over again? Or passing on the startup disk along with a “full” version startup table to another user?
Excellent question! They provide the lead-in to the next level of the protocol:
First, to discourage the snooper from guessing the purpose of the “hidden” fields, the field names don’t tell you anything. Second, the data in those fields is encrypted. (We’ll look at field encryption at the end of this article.) Third, there are additional “hidden” values being checked by the application to assure that the user is authorized to use the software and to enforce version control.
The Hidden Memory Variable File
When the application opens — prior to opening the setup table — it first looks for a hidden memory (.mem) file that contains memory variables that are the equivalent of the values in the setup table’s hidden fields. Then, it opens the setup table and compares the values. If the values agree, the application continues normally.
If the hidden memo file doesn’t exist and key setup table values are empty, this is the first run and the application forces the user to a setup form where the user’s name, address and other key values are input into the setup table (and the hidden memory file).
If the hidden memory file doesn’t exist and the setup table has values, the system puts the values from the setup table into the correct memory variable names/format and creates the hidden memory file.
If the setup table’s hidden fields are empty, but values exist from the hidden memory file, this is either a legitimate reinstall or someone is trying to defeat the piracy protection — perhaps trying to start the demo period over again. Either way, the memory variables are written to the hidden fields. The application opens in the same state it was in before the setup table was deleted, the legitimate reinstall runs as expected and the attempted demo reset is still expired.
Hiding the Hidden Memory File
What we’ve done so far would probably suffice to protect the application from a high percentage of the potential abusers out there. But there are a significant number of folks who know a little programming and will snoop around in the application directories/folders.
So rather than leaving a .mem file in the main application directory, let’s hide it and disguise it. I give the application two locations to hide the memory file. The first (and preferred) location is in the C:\Windows folder. However, in case the C:\Windows folder doesn’t exist — this particular machine boots Windows off drive D: — give the app an alternate location that is sure to always exist; your choice. I usually opt for the root of the currently active drive.
Now, give the file a nondescriptive name that you can identify and give it a .dll extension. (We’ve disguised it as well as hiding it among all the other DLLs.) Also remember, the data being stored is encrypted. Anyone brave enough to try to read the DLL will be seeing what looks like machine code hash. Most of us who have ever snooped around at the program level have learned the hard way: don’t monkey with DLLs!!!!
So our code to retrieve the hidden/disguised memory
variables looks something like this:
IF FILE("C:\WINDOWS\Win.ini") // C:\Windows in its usual place
* -- Restore Hidden Variables
IF FILE("C:\WINDOWS\myhidden.dll") // First run the disguised .mem/.dll doesn’t exist
RESTORE FROM C:\WINDOWS\myhidden.dll additive
* -- Restore from alternate location.
One step that could be taken to further hide/disguise the memory file would be to change its time/date attribute so that it can’t be associated with your application installation date. I’ve never bothered taking that step.
Beyond Piracy Protection
The other hidden fields and their memory variable “check sums” can do much more than provide piracy protection. The following examples may give you ideas about what fits your particular application:
F48 & F49 – City and Zip: These were important to me before the days of updates from the web site. When the user moved, the change required them to let me know. Also, before I added the hidden memory file to the protocol, a change of address was an indicator that someone may have copied the entire program/data file to another (pirate) machine. (In the dBASE III/IV days I tended to put the programs and data all in the same directory.) You may find a use for this type of duplicate cross checking data.
F50 – Renewal Date: Some of my more recent software has taken a lesson from dBASE.com. Software that has a high probability of frequent revisions and/or updates (high maintenance) is now distributed on an annual subscription basis. When the software is updated to a “full” version, the annual renewal date is input so that the user starts getting reminders to update his/her subscription prior to the renewal date. After the renewal date, the application only goes to the Update/Renewal form, exiting if the software is not renewed.
F51 – Upgrade/Renewal Code: I have written a routine that generates an alphanumeric code for each day of the year. I have a version that generates and displays the code on the screen; the version in the application generates the code to memory variables. When the user inputs the code I give them (via phone or email) the code is checked against the memory variables. If they match, the code is accepted, F46 is changed to FULL, and/or the renewal date is updated to next year. This upgrade/update code is (obviously) date sensitive; I tell the user to do the upgrade/update immediately.
I have one application that contains three versions on the distribution (demo) disk. It’s a Barrel Racing Show Management application that defaults to the “Professional” version in the Demo. When the user upgrades from the demo, the F46 field is changed to DIST, PROF or SUPR which stand for the three versions: “District Directors” version (a version with limited use), “Professional” version which supports a wide variety of shows, formats and sanctioning organizations, and the “Super Show” version which also supports National Barrel Horse Association Super Shows (BIG Money!). The F51 alphacode generator has a provision (in the Barrel Racer version) that produces a unique code for the version as well as the date to ensure the correct version update. The initial subscription cost and annual renewal vary in price for each version of this high-maintenance system. (More details? Go to www.barreltime.com.)
Finally, Field Encryption
As mentioned earlier, the “hidden” field values in the setup table are encrypted, as are the memory variables in the hidden memory file. In grade school most of us played encryption games where we’d substitute letters of the alphabet by a certain offset. (With an offset of 4, A would become E, B would become F, etc.)
ASCII and ANSI give us a bigger playground.
By substituting the alphanumeric ASCII/ANSI values with numbers larger
than 122, we get all kinds of unintelligible symbols in the fields.
We can add to the illogic by making the substitution value change with
the position of the character in the field. (i.e., the first character
in the field does not have the same substitution value as the second, the
second is different from the third, etc.) . Pitfalls to watch out for:
ASCII and ANSI are not 100% equivalent. Substitution values higher
than 255 are going to get you in trouble. Try rolling your own. For
* Use : Variable1 = "abc"
* e = Encrypt(Variable1) // or: e = Encrypt("abc")
* ? e // "IKM"
if ArgCount() = 0
msgbox("This routine needs a string to be passed. "," No parameters...")
local c, n, Encrypted_Variable
Encrypted_Variable = ""
for n = 0 to (Param1.length - 1)
c = "".asc( Param1.substring(n, n + 1) ) - 24 + n
if c < 1
c = c + 255
Encrypted_Variable = Encrypted_Variable + "".chr(c)
There is no known way to prevent someone from copying your application distribution disks. But there are other ways to prevent unauthorized use of your software. The protocol described in this article provides not only protection from unauthorized use, but also can be used to “customize” and “personalize” the software, enforce demo time restrictions, enforce version control, and enforce renewals of subscription-based software.
Although the described protocol provides a high level of piracy protection, it is not 100%. But it may be approaching 99.9%. Ask yourself how much time and effort the abuser would be willing to spend to crack your security levels. (If your software protects National Security Codes, you need a whole different approach!!)
What is your time worth to protect against the remaining one tenth of one percent? What is your data worth? If you have a business applications where it may be desirable to encrypt certain fields in the data files, add that to your protocol.
Pick and choose those aspects that work for you, then go on to develop your own unique solutions for your software’s needs.