A Better Way to Protect Your AppleSoft BASIC Programs with AppleLock by Sam Ismail January 5, 1997 :Introduction: I devised this method of protecting AppleSoft BASIC program listings some years back during my prime Apple programming days. It involves a combination of several different techniques for keeping your program unlistable by the casual to intermediate user. This system is no match for advanced users who understand the internal structure of BASIC, for they could no doubt blast through this protection with little difficulty. Hence, this program is suitable for protecting programs which you wish to keep hidden from users who are less than expert Apple ][ hackers. The following is a technical explanation of the AppleLock protection scheme. If you don't care to read it, you may want to skip to the end of this document to get the source code to the AppleLock program. This discussion will not stop to explain in detail the advanced AppleSoft BASIC and machine language techniques that are employed in this scheme as there are plenty of FAQs around to explain the finer details of the tricks involved. :AppleSoft BASIC Internal Program Storage: AppleSoft BASIC stores your program in memory by tokenizing the keywords in your program into one byte values. So for instance, when you type the follwing line: 10 PRINT "HELLO WORLD!" the BASIC interpreter converts this into the following bunch of numbers in memory (shown in hexadecimal): 15 08 Pointer to program data for next line 0A 00 Line number (in this case, 10) BA The token for PRINT 22 The opening quotation mark 48 45 4C 4C 4F 20 57 4F 52 4C 44 21 The string HELLO WORLD! 22 The closing quotation mark 00 The end of line terminator If this was the first line of the program, it would be stored starting at memory location $0801 (or decimal 2049). As shown above in the first line of bytes, the next program line data would be stored at memory location $0815 (addresses are usually stored backwards in memory, which is the way the 6502 CPU reads addresses, so in this case 15 08 is $0815). :Some Simple Protection Schemes: One of the simplest and most widely known tricks for protecting your program is to POKE a 1 at memory location $0801 (POKE 2049,1). This will trick AppleSoft into thinking that the next line of the program is at $0801 instead of $0815. This will have the effect of listing the first line of your program over and over again, indefinitely, until the user presses Control-C to stop the display. Unfortunately, this trick is not permanent. If you SAVE your program in this condition and then LOAD it into memory again, AppleSoft will "fix" the pointer and make it point to the next line again. So this trick only keeps your program secret as long as the user always runs your program before doing anything else with it (ie. LISTing it) and as long as you perform this POKE as the first command inside your program. Not a very secure method. Another well known trick for keeping your programs secret is to set a flag in AppleSoft which has the effect of ignoring all AppleSoft BASIC commands at the command line and running your program instead, regardless of what the user types (with the exception of DOS commands which ignore the RUN-only flag and execute regardless of its status). This flag is turned on by POKE-ing any value greater than 127 at memory location 214 (eg. POKE 214,255). The flag is turned off by poking any value less than 128 at the same location (eg. POKE 214,0). Again, this method is not effective because your program, or some other program that runs before your program, must set this flag before the user has a chance to list your program. Otherwise, the user could simply load and list your program before it has a chance to set this flag. So what are we to do? Well, unfortunately our options are limited and ineffective unless we know some machine language and a little bit about the AppleSoft BASIC architecture. That's where AppleLock comes in. :Technical Overview of AppleLock: The key to this scheme is adding a special line to the beginning of your program which acts as a gateway to your program listing. This first line will block the rest of your program from being viewed. It seems benign enough to the naked eye. Yet it is performing some special magic to enable your program to run normally and be copied to another disk, but never listed. This scheme uses a machine language subroutine to change the pointers of your program to where your program really is. This machine code is stored right inside the first line, but it is hidden from the user by taking advantage of some features of AppleSoft BASIC. AppleSoft allows you to embed control characters in your program listings, for instance, in REM statements. When your program is LISTed, these control characters are interpreted and output to the display just like in any other circumstance. For instance, if you stuff a Control-H (or backspace) character in a REM statement, it will be interpreted as a backspace when the line is listed, and the character previous will be over-written by the character following the backspace. There are several programs available to allow you to create fancy listings by embedding backspace characters into your REM statements in this manner. The following will demonstrate the effect an embedded backspace will have when your program is listed: LIST 10 REM A CHARACER WILL BE MISSING ^ The backspace occurred here. The 'T' was printed, but it was followed by a backspace character, so the 'E' over- wrote the 'T'. Going back to our protection scheme, we must be able to CALL our machine language subroutine which unlocks the rest of the program, but we don't want the user to know what we are doing to unlock the program. So to hide the CALL command, we follow it with a REM statement with enough backspace characters to over-write the CALL command. As a side benefit, we can now put whatever message we want as the first line of the program, such as "THIS PROGRAM CANNOT BE LISTED". Or, we can get real tricky by displaying what looks like a program line, but in actuality is some dummy text which is hiding the real program line underneath, such as "10 END " (we must put sufficient spaces to over-write both the CALL and REM commands). To illustrate this more effectively, the following is an example of a raw line before we embed the backspace characters: 10 CALL 2100: REM *******************[ CAN'T LIST THIS! ] Now we would replace all the asteriks with a backspace character (ASCII 8). The easiest way to do this is from the monitor. In this case, we would find the first asterik at memory location $080C. We could then replace the asteriks with backspace characters by doing the following: CALL -151 80C: 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 This now changes the 19 asteriks into backspace characters. Nineteen backspace characters is enough to backspace all the way back to the line number at the beginning of the line. If our line number was 1, we would only need 18 backspaces. If our line number was 100, we would need 20 backspaces. If we had other commands after the CALL command, we would need enough backspace characters to backspace over these commands as well, including the formatting spaces AppleSoft prints when it displays your program listing. We have now completed the first step. We have created a line that when listed will only show our message. Now we must create some room for our machine language code which unlocks the program when it is CALLed by the hidden command. To do this, we will add extra asteriks after our message so that we can replace these asteriks with machine code and have the code stored right inside the BASIC program. We must store the code right inside the BASIC program so that it automatically gets loaded when we load our program. Otherwise, if the code was stored in a separate file, the user would be able to stop the program before it had a chance to unlock itself. This would enable the user to possibly figure out what was going on, and we don't want that. In order to do all this, we must hide our machine code from BASIC. Otherwise, BASIC will try to interpret our machine code as BASIC tokens when we list our program and will throw garbage all over the screen. First, let's create the program line we will need to hide our CALL command as well as reserve enough bytes to store our machine language unlock code. We will also add another program line for demonstration purposes later in the tutorial. 10 CALL 2100: REM *******************[ CAN'T LIST THIS! ]********* 20 PRINT "HELLO WORLD!" We have added some asteriks after our message to create the space we will use to store our machine code. There are two extra bytes that will be used to hide this machine code from BASIC as well as to fool BASIC into thinking our program is shorter than it really is, thus hiding the rest of the program. We must now jump back into the monitor to embed our backspace characters and then insert our machine code: CALL -151 80C: 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 Embedded backspaces 834: A9 3D 85 67 4C 65 d5 Our machine language code The machine language code disassembles to the following instructions: 834: A9 3D LDA #$3D ; Get the true start of our program 836: 85 67 STA $67 ; and tell AppleSoft where it really is 838: 4C 65 D5 JMP $D565 ; Now run the real program Basically, this code tells BASIC to jump over our first decoy line and then start running our real program. As you may have guessed, the CALL command that is at the beginning of our decoy line calls this code. Of course, the user doesn't see this CALL command because it is over-written by our message. Now we must hide our machine code from BASIC, or else when we list our program we will see a bunch of garbage. The machine code actually started at the second asterik after our message. We will replace the first asterik with a zero byte. A zero byte indicates to BASIC that it has reached the end of the program line, and should go on to list the next line. When we input a line, BASIC always puts a zero byte at the end of our line before starting a new line. So after the last asterik in our line is a zero byte. But we will also replace the last asterik in our line with another zero byte, resulting in two consecutive zero bytes. Two consecutive zero bytes tells BASIC that it has reached the end of the program, and it should stop listing. We are now going to change the internal pointers of our decoy line to point to these zero bytes so that we can fool BASIC into thinking our program is only one line long! 833: 0 Replace the first asterik with a 0 byte 83B: 0 Replace the last asterik with a 0 byte ($083C has another zero byte already) 801: 3B Change the internal pointer to point to the two zero bytes, fooling BASIC into thinking it has reached the end of the program Now when we list our program, we will see the follwing: [ CAN'T LIST THIS! ] But when we run it, we will see the following: HELLO WORLD! Cool, eh? Now, before we end our program, we must make sure we lock it back up so that we don't defeat our protection scheme by leaving the door open. You can add this line to your program to have it change the pointer to the start of our program back to our decoy line: 30 POKE 103,1 Now this program will run and then re-lock itself when it's done. And now hopefully you understand the basic premise of AppleLock. :Additional Security Requirements: As the programmer, you must also take several steps within your program to ensure nobody can break out of it by hitting Control-C. And we also have to contend with that darn reset button. First though, let's take care of Control-C. To prevent the user from trying to break out of the program and at the same time trap any unexpected errors in your program, make sure the FIRST line of your program is an ONERR GOTO command. ONERR GOTO allows you to tell BASIC to jump to any line in your program whenever an error occurs, and Control-C is considered an error (its error code is 255). The simplest way to handle errors and Control-C attempts is with the following lines: 10 ONERR GOTO 63999 63999 RESUME These lines will in effect cause the program to ignore any errors, including attempts to break out of the program by the user. There is still a very minor chance that the user could time a quick Control-C right when the machine language unlock program transfers control to your unlocked program, but before you issue the ONERR GOTO command or possibly set the AppleSoft RUN-only flag (POKE 214,255), thus allowing the user to break into your program. The AppleLock program at the end of this tutorial addresses this problem. Now let's take care of the reset button. The simplest way to prevent a person from using RESET to gain access to your program is by adding a simple POKE command at the beginning of your program, as follows: POKE 1010,0 This will cause the computer to re-boot whenever the reset button is pressed. We could get fancy and re-hook the reset vector so that when the user presses RESET we can restore our program lock and then return them to BASIC. This is exactly what the AppleLock program at the end of this tutorial does. Before your program terminates and returns to the command prompt, it must undo the RUN-only flag (if you have set it) and then re-lock your program. The following program line should be the last thing your program does before it exits: 63999 POKE 103,1:POKE 214,0 The first POKE re-locks your program. The second POKE turns off the AppleSoft RUN-only flag. You should also return the reset vector to its original setting if you have modified it. Otherwise, the next time the user presses RESET, your program will run again. If another program was loaded and then RESET was pressed, the system will most assuredly crash. AppleLock includes a routine which you can call at the end of your program which will restore the reset vector as well as the AppleSoft RUN-only flag and then re-lock your program before exiting. :AppleLock Effectiveness Analysis: Unfortunately, the weakest point of this scheme is that it takes only one well-placed POKE to unlock your program. As an aside, the user can find out what you are trying to hide by changing the character output speed with a SPEED= command. For instance, by setting SPEED=0, and then LIST-ing the program, the user would be able to see that there is a CALL command hidden behind your message. The best and most secure way to protect your program from prying eyes remains the use of a BASIC compiler. However, this is not always a feasible solution. There are a few compilers for DOS 3.3 that will convert your program to machine language. And there is the Beagle Compiler for ProDOS, but this compiler does not create a stand-alone machine language program, but rather requires an interpreter that replaces BASIC.SYSTEM. :Conclusion: Hopefully this tutorial has given you enough information to help you protect your AppleSoft BASIC programs for whatever reason you may have. Enjoy. :The AppleLock Program: This program is very rudimentary and not very pretty. A nice interface can be built around this program to make it more user friendly. The important thing is that it is functional. What it does is creates the machine language unlock code by POKE-ing it into memory. It then asks you to enter the name of the program you wish to lock, as well as the line number you wish to use as your decoy line. This must be the first line in your program! After that, it allows you to enter a message up to 160 characters in length (the length of the message is fairly arbitrary, but this length was chosen to keep the program simple). Once you get good at this technique, you will want to use other programs to create your message (or perhaps use the monitor like I do) so that you can have fancy banners show up when you try to list the program. It then builds a text file with all the commands necessary to apply AppleLock to your program. This file will then be EXEC-ed so that the AppleLock procedure will be performed automatically. Once the process is complete, your program will be loaded in memory and in a locked state (if you try to LIST it you will only get the message you typed in). At this point, you should SAVE your program to disk to make the process permanent. The program will give you instructions on how to lock/unlock your program so that you, as the author, can list and modify it as you please. Keep in mind that you must unlock your program before you make any changes to it, otherwise you stand the chance of royally screwing your program and possibly crashing your machine. Weird things can happen when you start to mess with AppleSoft program pointers. If this happens, simply reboot your computer and re-load your program, and all will be well again. If you ever want to remove AppleLock from your program, simply unlock it and then save it to your disk (be sure to restore the BASIC start-of-program marker with a POKE 103,1 after you are done saving). When you load your program again, it will be back to normal. The AppleLock program includes an enhanced locking mechanism which will modify the reset vector and also sets the AppleSoft RUN-only flag by POKE-ing a 255 at location 214 before it transfers control to your program. This will prevent users from listing your program should it fail and drop to the command prompt at some point, or the user presses Control-C and you haven't added an ONERR GOTO command to your program. If the reset button is pressed, the locking mechanism will re-lock the program, reset the AppleSoft RUN-only flag, restore the reset vector to its original value and then return to the command prompt. :Disclaimer: This program may be freely modified, distributed, sauteed, fricasseed, lambasted, spindled, mutilated, desecrated, tormented and forced to cry "uncle". It may not, however, be recruited into sado-masochistic rituals unless it has consented to participate in such activities. Niether myself, my immediate family, nor the code (it's just code, it doesn't know any better) can be held responsible for any havoc it wreaks on your AppleSoft programs. This program has been run and tested several times and has been deemed sound by the author. If you do somehow get yourself in a bind, I can be reached via email (dastar@crl.com) and I will try to help you reclaim your lost code. On that note, I leave you with these words to live by: Always Keep a Backup! ------- cut here ------- 0 REM APPLELOCK SCHEME (C) 1997 BY SAM ISMAIL 10 TEXT : HOME 20 DOS$ = CHR$ (4) 30 INPUT "WHAT IS THE NAME OF YOUR PROGRAM? ";P$ 40 INPUT "WHAT IS THE FIRST LINE OF YOUR PROGRAM? ";LINE 50 AS = 17 + LEN ( STR$ (LINE)) 60 PRINT : PRINT "ON THE LINE BELOW, TYPE WHAT YOU WOULD" 70 PRINT "LIKE TO BE DISPLAYED WHEN SOMEONE TRIES" 80 PRINT "TO LIST YOUR PROGRAM (IT MUST BE AT" 90 PRINT "LEAST ";AS;" CHARACTERS LONG FOR BEST" 100 PRINT "RESULTS)" 110 PRINT : INPUT ":";T$ 120 IF LEN (T$) < AS THEN PRINT : PRINT "TOO SHORT, TRY AGAIN...": GOTO 60 130 IF LEN (T$) > 160 THEN PRINT : PRINT "UNFORTUNATELY, THAT IS TOO LONG.": PRINT "TRY AGAIN...": GOTO 60 140 PRINT : PRINT "HANG ON A SECOND..." 150 FOR X = 1 TO 60: READ BYTE: POKE 767 + X,BYTE: NEXT X 160 L = LEN (T$) 170 BASE = 2059 + AS + L + 1:S = BASE + 1:E = BASE + 60 180 L1 = BASE + 5:H1 = BASE + 11 190 L2 = BASE + 14:H2 = BASE + 19 200 P = BASE + 31:R = BASE + 37 210 A1 = R + 1:A2 = R + 6 220 PRINT DOS$;"OPEN APPLELOCK.CMD" 230 PRINT DOS$;"CLOSE APPLELOCK.CMD" 240 PRINT DOS$;"DELETE APPLELOCK.CMD" 250 PRINT DOS$;"OPEN APPLELOCK.CMD" 260 PRINT DOS$;"WRITE APPLELOCK.CMD" 270 PRINT "LOAD ";P$ 280 PRINT STR$ (LINE);" CALL 0000:REM"; 290 FOR X = 1 TO AS: PRINT "*";: NEXT X 300 PRINT T$; 310 FOR X = 1 TO 62: PRINT "*";: NEXT X: PRINT 320 PRINT "FOR X=1 TO 4:POKE 2053+X,ASC(MID$("; CHR$ (34);S; CHR$ (34);",X,1)):NEXT X" 330 PRINT "FOR X=1 TO "; STR$ (AS);":POKE 2059+X,8:NEXT X" 340 PRINT "POKE "; STR$ (BASE);",0" 350 PRINT "FOR X=0 TO 59:POKE "; STR$ (S);"+X,PEEK(768+X):NEXT X" 360 PRINT "POKE "; STR$ (L1);","; STR$ (A1 - 2048) 370 PRINT "POKE "; STR$ (H1);","; STR$ (A2 - 2048) 380 H = INT (R / 256):L = R - (H * 256) 390 PRINT "POKE "; STR$ (L2);","; STR$ (L) 400 PRINT "POKE "; STR$ (H2);","; STR$ (H) 410 PRINT "POKE "; STR$ (P);","; STR$ (E - 2047 + 2) 420 PRINT "POKE "; STR$ (E + 1);",0" 430 PRINT "POKE 2049,"; STR$ (E - 2047) 440 PRINT "?"; CHR$ (34);"YOU ARE NOW READY TO ROCK!"; CHR$ (34) 450 PRINT DOS$;"CLOSE APPLELOCK.CMD" 460 HOME 470 PRINT "YOUR PROGRAM WILL NOW GO THROUGH" 480 PRINT "THE PROTECTION PROCESS. WHEN IT" 490 PRINT "IS DONE, MAKE SURE YOU SAVE YOUR" 500 PRINT "PROGRAM BEFORE YOU DO ANYTHING!!" 510 PRINT : PRINT "TO UNLOCK YOUR PROGRAM, TYPE:" 520 PRINT : PRINT "POKE 103,"; STR$ (E - 2047 + 2) 530 PRINT : PRINT "TO LOCK IT AGAIN, TYPE:" 540 PRINT : PRINT "POKE 103,1" 550 PRINT : PRINT "ADD THIS COMMAND TO THE END OF" 560 PRINT "YOUR PROGRAM TO AUTOMATICALLY" 570 PRINT "RE-LOCK YOUR PROGRAM:" 580 PRINT : PRINT "CALL "; STR$ (R) 590 PRINT : PRINT "MAKE SURE YOU SAVE YOUR PROGRAM" 600 PRINT "ONLY WHEN IT IS LOCKED!" 610 PRINT : PRINT "NOW PRESS A KEY TO DO IT..." 620 GET A$: PRINT 630 PRINT DOS$;"EXEC APPLELOCK.CMD" 1000 DATA 173,242,3,141,0,8,173,243 1010 DATA 3,141,0,8,169,0,141,242 1020 DATA 3,169,0,141,243,3,32,111 1030 DATA 251,169,255,133,214,169,0,133 1040 DATA 103,76,102,213,169,0,141,242 1050 DATA 3,169,0,141,243,3,32,111 1060 DATA 251,169,0,133,214,169,1,133 1070 DATA 103,76,208,3