Go back

PART 4 - DECRYPTERS

By this point, you should be familiar with headerless loaders, which take up the bulk of most protection systems these days. However, there is one other important aspect of protection that you need to know about if you are to crack the more complex protection systems. It's encryption.

Encryption works like a secret code. You start off with somthing unintelligable, then you use the "secret rules" to change it into something that makes sense. So, IBDLFST doesn't make much sense, but if you take the previous letter in the alphabet each time, you get HACKERS, which makes perfect sense. Encryption works in roughly the same way. We've already seen that a loading system occupies a small area of memory. An encrypted loading system will appear as a block of code which makes absolutely no sense whatsoever. There will also be a short program which changes all this nonsense into workable code so the loading system can be run. Some really tough loading systems, have more than one decrypter, such as the Alkatraz loading system which has 250 of the damn things; in practice I've got through about 25 before going mad and hacking something else (don't worry, there's another way of hacking them which I'll tell you about later.)

To make it easier for you to understand decryption, its best to have a look at a real loading system. As an example, I've chosen Impossaball, which was on the YS#75 covertape. Load up STK at address 50000 (it's a safe address), and see what the BASIC has to say for itself by BLOADing it......

10 CLEAR 64530: LOAD "" CODE:RANDOMIZE USR 64531
20 LOAD "" CODE 16384
21 FOR i=1 TO 40 STEP - RANDOMIZE USR 50000

Fair enough - so enter CLEAR 64530:LOAD "" CODE and start the tape. The first code block loads in, and then - it crashes! What's going on? Don't worry, you have just fallen victim to the first type of encryption - BASIC encryption.

In BASIC, any numerical constant (technotwaddle for "number") between 0 and 65,535 is stored in the memory as follows. First, there is the number in it's ASCII form, which takes between 1 and 5 bytes. So, the number 1234 will appear as #30,#31#,#32,#33 here. Then, come the numbers #0D,#00 and 00 (this is always the case). Then, there is the number stored in it's two byte form (as in machine code). What happens it that the computer prints the ASCII form (which is what you get when you LIST a program), but uses the two-byte form in calculations and expressions. The upshot of all this is that what you see isn't always what you get! As an example, type in this:

1 PRINT 1

Now type POKE 23760,50 (which changes the ASCII value of the number 1 from "1" to "2"). Now if you LIST the program, you will see 1 PRINT 2, but if you RUN the program, the computer will print the number 1 instead!

Needless to say, protection systems use the same idea. Sometimes, the numbers listed are obviously fake (if you list a program and you get something like 0 CLEAR 0:RANDOMIZE USR 0 it's obviously got encrypted BASIC), but some programs, like the Impossaball are not obvious at all until you try executing what you see.

There was a program called *List printed ages ago in YS, but if you haven't got that, I've reprinted it here. So type it in, SAVE it to tape, RUN it and reload the Impossaball BASIC loader.

loading LINE 10 LEN 81

10 CLEAR 25599: LOAD "" CODE: RANDOMIZE USR 64512
20 LOAD "" CODE 16384
21 FOR i=1 TO 40 STEP ????....

Now that's what you really get! Type CLEAR 25599:LOAD "" CODE and load in the first block of code. When that's done, load up your disassembler, and disassemble address 64512 (FC00 hex) to have a look at the decrypter.

FC00 01 99 02   LD BC,#0299
FC03 21 13 FC   LD HL,#FC13
FC06 11 14 FC   LD DE,#FC14

This part of the program sets up all the initial values for the decrypters in some of the registers.

FC09 1A         LD A,(DE)

We've come across brackets before, but briefly what happens here is that the contents of the address with the value of the DE register (which starts of as #FC14) are put into the A register. So now the A register could contain any byte.

FC0A AE         XOR (HL)

We haven't seen XOR before, so I'll explain what it does. It is in technical terms a Boolean Operation. You may have seen XOR gates if you studied (or are studying!) Physics, Electronics or Computer Science at school. An XOR gate has two inputs, which can each either be 0 or 1, and one output, which can be either 0 or 1 as well. If the two inputs are the same (0 and 0, or 1 and 1), the output is 0, otherwise it is 1. In machine code, you XOR the A register with a number or contents of a register, and what happens is that each bit in the A register is XORed with the same bit in the number or register contents, and the result is stored in the A register. If you're confused, look at the example below.


Contents of the A register Number to be XORed with Result
00100101 01010011 01110110

(Notice that all the numbers are in binary - see your Spectrum manual for more information about this).

If you still don't understand, just remember that an XOR will change the contents of the A register. That's all you need to remember for now.

Continuing the disassembly...

FC0B 77       LD (HL),A

This puts the value of A (which has just been changed) into the bytes at the address with the value of the HL register (which starts off as #FC13).

So, the routine has basically taken a byte out of a memory location, changed it a bit, and put the altered value back again. This is decryption in its most obvious form - the changed values make up a working machine code program.

FC0C 23       INC HL
FC0D 13       INC DE
FC0E 0B       DEC BC

I don't think I've mentioned INC before, but it's basically the opposite of DEC in that it increases the value in whatever register. So the values in the HL and DE registers are incremented, and the value in the BC register is decremented.

FC0F 78       LD A,B
FC10 B1       OR C
FC11 20 F6    JR NZ,#FC09

This is a standard piece of code, and it essentially means "If BC isn't 0, then jump to #FC09". In other words, another byte will be decrypted until the value of BC is 0 (which happens when everything has been decrypted), and the decrypter ends.

Continuing the disassembly...

FC13 5C       LD E,H
FC14 9F       SBC A,A
FC15 A5       AND L
FC16 5B       LD E,E
FC17 13       INC DE
FC18 5B       LD E,E
.... 

Hang on - this code doesn't make sense! It has no relation to the code above, and the instruction LD E,E is pointless anyway. Well, you'll remember that the initial value of decryption is #FC13, which means that all the code from there onwards has to be decrypted. So we'll have to crack the decrypter to go any further.

Sometimes, it is possible to put an EI/RET instruction directly after the decrypter, but this is not possible here, as you will see. So instead, we'll have to move the decrypter somewhere else in memory, and put the EI/RET on the end (in actual fact we don't need the EI because there is no DI command in the decrypter). This is easily done by using the LDIR command. Type in the following program:

1 FOR N=23296 TO 23310:INPUT A:POKE N,A:NEXT N

Now RUN it and enter the following numbers:

33,0,252,17,128,91,1,1,18,0,237,176,54,201,201

The program you have just typed in is this:

LD HL,FC00
LD DE,5B80
LD BC,0012
LDIR
LD (HL),C9
RET

Now RANDOMIZE USR 23296 and the decrypter will be copied to 5B80 and a RET will be stuck on the end. Just RANDOMIZE USR 23424 (5B80 in decimal) to run the decrypter. When the OK message comes up, restart the disassembler and look at FC13 again:

FC13 C3 3A FE   JP #FE3A

This jumps to #FE3A, to start the loading.

FE3A F3         DI
FE3B 21 00 58   LD HL,#5800
FE3E 11 01 58   LD DE,#5801
FE41 01 FF 02   LD BC,#02FF
FE44 36 00      LDIR

As you already know, this makes the screen black.

FE48 CD 81 FE   CALL #FE81
FE4B CD 00 80   CALL #8000

If you look at the code at #FE81, you'll see it's a headerless loader. The routine at #8000 prints the loading screen.

FE4E CD 81 FE   CALL #FE81
FE51 CD 00 64   CALL #6400

This loads another block (the main game), and prints another screen (the border for the game).

FE54 F3         DI
FE55 31 FF FF   LD SP,#FFFF

This disables interrupts and changes the stack pointer, so we'll have to change that in the final hack.

FE58 21 00 6D   LD HL,#6000
FE5B 11 00 5B   LD DE,#5B00
FE5E 01 00 8F   LD BC,#8F00
FE61 ED BO      LDIR

This is another LDIR command, but if you know how the Spectrum's memory is organised, you will see that the BASIC system variables are overwritten, which means we can't return to BASIC when the game has loaded. Fortunately, there's a short routine to get round this, which I'll explain in a mo.

FE63 21 71 FE   LD HL,#FE71
FE66 11 00 F0   LD DE,#F000
FE69 01 10 00   LD BC,#0010
FE6C ED B0      LDIR
FE6E C3 00 F0   JP F000

This moves the code from FE71 to F000, and jumps to F000. Obviously, then, the code at FE71 is important.

FE71 21 00 FC   LD HL,#FC00
FE74 11 01 FC   LD DE,#FC01
FE77 01 FF 01   LD BC,#01FF
FE7A 36 00      LD (HL),0
FE7C ED B0      LDIR
FE7E C3 00 80   JP #8000

This routine wipes out all the memory from #FC00 to #FC01, but in actual fact you don't need to do this. Then it jumps to #8000, which is the start of the game. So, we can put a machine code routine to put a POKE in, and then jump to #8000.

However, we've got to find the POKE first, and there's the problem that we can't use BASIC because it is overwritten. Luckily, there is a short routine you can use which will cause a NEW to a certain address. Put it into the above program by typing in the following:

1 FOR N=65137 TO 65144:INPUT A:POKE N,A:NEXT N

Then RUN it, and enter these numbers in turn:

243,175,17,0,95,195,203,17

The program you've just typed in is the following:

DI
XOR A
LD DE,#5F00
JP #11CB

DI disables interrupts, XOR A loads A with 0 (think about what would happen if you XORed the value of the A register with itself, LD DE,#5F00 means we want to NEW up to #5F00 (this value can be changed from about #5D00 to #FFFF), and JP #11CB starts the NEW.

Now RANDOMIZE USR 65082 (#FE3A in decimal), and restart the game tape. When the computer resets, you can load STK into address 24320 to find POKEs.

Phew! And that's about all of that protection system cleared up. If you can get your way through that lot, I think you're probably ready to have a go at a "commercial" protection system. First, though, we'll write a complete hack for the game.

10 CLEAR 25599:LOAD "" CODE

This is from the BASIC loader, and it loads in the first machine code block

20 FOR N=23296 TO 23310:READ A:POKE N,A:NEXT N

This line POKEs in the machine code program to move the decrypter.

30 DATA 33,0,252,17,128,91,1,19,0,237,176,54,201,201

And here's the actual machine code itself

40 RANDOMIZE USR 23296

This line calls the decrypter and returns to BASIC

40 FOR N=65137 TO 65143: READ A:POKE N,A:NEXT N

This line POKEs in our hacking program

50 DATA 175,50,??,??,195,0,128

And here's the hacking program, which loads ???? with 0 (which is the infinite lives POKE), and jumps to #8000.

60 RANDOMIZE USR 65082

This starts the whole loading system off, with the POKE firmly in place.

And that's about it. A bit of a long piece of work, but it was worth it!



[ Valid HTML4.01 ]
Free Web Hosting