COPYLOCK ANALYSIS
—————–
After you have used CopylockDecoder, you will have a decoded file which you
can disassemble. Be aware that CopylockDecoder loads the file into memory
and all offsets are from the first byte of the file, not from the location
of the copylock code! This is important to remember for AmigaDOS files which
often have hunk header information at the top and must be offset when
working out the addresses (more information to follow).

Here are the main things to look at in the decoded file:

lbC000448 moveq #4,d0
moveq #0,d1
lbC00044C movem.l d2-d7/a0-a3,-(sp) ;Elite crackers used to wire
moveq #0,d2 ;the encrypted key in here
roxr.l #3,d0 ;and branch to lbC000496
roxr.l #1,d2
roxl.l #3,d0
move.b d0,d2
move.l d1,d3
moveq #3,d7
moveq #0,d6
bsr.w lbC00055C
beq.b lbC000474
moveq #1,d0
bsr.w lbC000726
bsr.w lbC000704
bsr.w lbC00049A
bsr.w lbC000584
tst.l d6
bne.b lbC00048A

lbC00048A bsr.w lbC000540
move.w d2,d1
move.l d6,d0 ;Move copylock key to d0
movem.l (sp)+,d2-d7/a0-a3
lbC000496 bra.w lbC00083A ;Post copylock routine

Many elite crackers back in the early 1990’s became aware that several
sneaky tricks were being done in more and more copylocks. Rather than
trying to emulate all the code they discovered that you could alter the
encrypted copylock and wire the key in. By doing this, any sneaky tricks
were still done because the game had the correct copylock key. The routine
at lbC00044C would be changed to do a move.l #$b590a736,d0 and then bra
lbC000496. In this tutorial we are not worried about this and will crack
the game another way.

The next part is how the game works out the copylock key:

lbC00049A subq.w #4,sp
lbC00049C move.w #$8914,d0
move.w #$32,d1
lea (lbL000802,pc),a0
bsr.w lbC0005C8
bmi.b lbC000524
move.w #7,d1
lbC0004B2 sub.l (a0)+,d0
dbra d1,lbC0004B2
cmp.l #$A573632C,d0 ;Compare magic number
bne.b lbC00049C
lea (lbL000802,pc),a0
lbC0004C4 move.w #$8911,d0
bsr.w lbC000602
beq.b lbC000524
cmpi.w #$2A91,(a0)
bne.b lbC0004C4
move.l d0,(sp)
lbC0004D6 move.w #$8912,d0
bsr.w lbC000602
beq.b lbC000524
cmpi.w #$AA92,(a0)
bne.b lbC0004D6
move.l d0,d1
move.l (sp),d0
bsr.w lbC000528
bmi.b lbC000524
lbC0004F0 move.w #$8914,d0
bsr.w lbC000602
beq.b lbC000524
cmpi.w #$AA94,(a0)
bne.b lbC0004F0
move.l (sp),d1
bsr.b lbC000528
bmi.b lbC000524
move.w #$8951,d0
bsr.w lbC000666
cmp.w #$3AC,d0
blt.b lbC000524
cmp.w #$53C,d0
bgt.b lbC000524
move.w #11,d1
lbC00051E sub.l (a0)+,d6 ;Key calculation routine
dbra d1,lbC00051E
lbC000524 addq.w #4,sp
rts

The critical section is lbC00051E which shows the routine that calculates
the key based on the MFM data. In this case it loops around 12 times (d1
is set to 11 and it’s a dbra loop hence one extra) subtracting the MFM
longword in a0 from the key d6.

Now for the main decryption, relocation and memory clearing code:

lbC00083A moveq #0,d1 ;Decryption code
lbC00083C lea (lbC00083C,pc),a6
adda.l #$158,a6
move.l #$6400,d6 ;Decryption length
add.l a6,d6
lbC00084E roxr.l #1,d0 ;Decryption loop
roxr.l #1,d1
roxr.l #1,d2
roxr.l #1,d3
roxr.l #1,d4
roxr.l #1,d5
eor.l d0,(a6)+
cmpa.l d6,a6
bne.b lbC00084E
lbC000860 lea (lbC000860,pc),a6 ;Relocation code
move.l a6,d6
subi.l #$838,d6
adda.l #$134,a6
movem.l d0-d2/a0-a2,-(sp)
cmpi.l #$3E9,(a6) ;Check hunk_code
bne.b lbC0008C8
move.l (4,a6),d0
lsl.l #2,d0
lea (8,a6),a1
lea (a1,d0.l),a2
cmpi.l #$3EC,(a2)+ ;Check hunk_reloc32
bne.b lbC0008C8
lbC000894 move.l (a2)+,d1
beq.b lbC0008C8
move.l (a2)+,d0
bsr.b lbC0008B2
move.l a0,d2
tst.l (-4,a2)
bne.b lbC0008A6
move.l d6,d2
lbC0008A6 move.l (a2)+,d0
add.l d2,(a1,d0.l)
subq.l #1,d1
bne.b lbC0008A6
bra.b lbC000894

lbC0008B2 movea.l d6,a0
subq.l #4,a0
tst.l d0
beq.b lbC0008C4
lbC0008BA movea.l (a0),a0
adda.l a0,a0
adda.l a0,a0
subq.l #1,d0
bne.b lbC0008BA
lbC0008C4 addq.l #4,a0
rts

lbC0008C8 movem.l (sp)+,d0-d2/a0-a2
lea lbC0008DE(pc),a6
move.l (-4,a6),d6
add.l (8).l,d6
ori.w #$A71F,sr
addi.l #$44,($24).l
adda.l #$50,sp
move.l #$7FFF4E73,-(sp)
move.l #$584F0257,-(sp)
move.l #$4CDF7FFF,-(sp)
move.l #$4FEE007C,-(sp)
move.l #$2D4C00BE,-(sp)
move.l #$598066FA,-(sp)
move.l #$67064299,-(sp)
move.l #$66FA200B,-(sp)
move.l #$22D85980,-(sp)
move.l #$66FA6006,-(sp)
move.l #$23205980,-(sp)
move.l #$D1C0D3C0,-(sp)
move.l #$B3C86F0C,-(sp)
move.l #$200A6720,-(sp)
move.l #$4E7B0002,-(sp)
move.l #$202E00B8,-(sp)
move.l #$4DFAFFEE,-(sp)
move.l #$10,-(sp)
move.l #$1423DF,-(sp)
move.l #$4487A,-(sp)
move.l #$BD96BDAE,-(sp)
lea (*+$30,pc),a0 ;Copy from
movea.l a0,a1
suba.l #$974,a1 ;Copy to
movea.l #$15E9C,a2 ;Bytes to copy
movea.l #$6E08,a3 ;Bytes to clear
movea.l a1,a4
adda.l #0,a4 ;Return address for game
move.l sp,($24).l
nop
nop
nop
nop
nop

Remember that I said that CopylockDecoder’s offsets are all relative to the
first byte of the file in memory? In this case the file “wcfm” has $28
bytes of header information before the code starts (the copylock):

dc.l $3F3 ;Hunk header
dc.l 0 ;Name length (longwords)
dc.l 3 ;Table size (3 hunks)
dc.l 0 ;First hunk (0)
dc.l 2 ;Last hunk (2)
dc.l $40007329 ;Hunk size (0)
dc.l $4000E267 ;Hunk size (1)
dc.l $400030B8 ;Hunk size (2)
dc.l $3E9 ;Hunk code
dc.l $7329 ;Hunk size (longwords)

lbC000028 pea (lbC000034,pc)
move.l (sp)+,($10).l
illegal
lbC000034 movem.l d0-d7/a0-a7,-(sp)
pea (lbC000054,pc)
move.l (sp)+,($10).l
movea.l sp,a0
movec cacr,d0
move.l d0,($3C,sp)
bclr #0,d0
movec d0,cacr
lbC000054 movea.l a0,sp
movem.l (lbC00005C,pc),d0-d7/a0-a6
lbC00005C move.l #$4E730000,-(sp)

It is critical that you remember this! All offsets are effectively $28
bytes too high relative to the file when AmigaDOS actually runs it. If
you do not take this into account, the decryption will fail and the
Amiga will crash!

Now let’s go through this in sections, starting with the decryption
routine:

lbC00083A moveq #0,d1 ;Decryption code
lbC00083C lea (lbC00083C,pc),a6
adda.l #$158,a6
move.l #$6400,d6 ;Decryption length
add.l a6,d6
lbC00084E roxr.l #1,d0 ;Decryption loop
roxr.l #1,d1
roxr.l #1,d2
roxr.l #1,d3
roxr.l #1,d4
roxr.l #1,d5
eor.l d0,(a6)+
cmpa.l d6,a6
bne.b lbC00084E

This is the guts of the decrypter. After the copylock has obtained the
key from the disk it loads lbC00083C into A6 and adds on $158. This is
the first bit of data that will be decrypted. The loop will run and
decrypt $6400 bytes of data by doing an eor.l d0,(a6)+ on each longword.
In this case, all the other roxr.l’s are dummy values because registers
d1 to d5 are not used. This is NOT normal however, and most games use a
series of registers to decode the game.

To emulate this decryption routine, we basically only need to change one
line, the one at lbC00083C so a6 is set to the right value.

Next up is the relocate routine. Copylock encrypts the header of the first
hunk of game code and because of this, AmigaDOS was not able to relocate
it. Hence the copylock routine has to do it itself:

lbC000860 lea (lbC000860,pc),a6 ;Relocation code
move.l a6,d6
subi.l #$838,d6
adda.l #$134,a6
movem.l d0-d2/a0-a2,-(sp)
cmpi.l #$3E9,(a6) ;Check hunk_code
bne.b lbC0008C8
move.l (4,a6),d0
lsl.l #2,d0
lea (8,a6),a1
lea (a1,d0.l),a2
cmpi.l #$3EC,(a2)+ ;Check hunk_reloc32
bne.b lbC0008C8

You don’t really have to understand what it’s doing, but basically after
the encrypted copylock is $6400 bytes of encrypted AmigaDOS file with
header information. The copylock looks for hunk_code ($3e9) and then a
hunk_reloc32 ($3ec) value. If found, it moves the file into place. To
emulate this code we just need to set the correct starting address (a6)
at lbC000860.

Most people do not understand the last part of a copylock but it can cause
games to crash if it is not done! The register a0 specifies a start address
a0 (usually the code just after the copylock) and a destination a1 (which
is usually the start of the file). Register a2 is used as a length, and a3
is a clear memory length:

lea (*+$30,pc),a0 ;Copy from
movea.l a0,a1
suba.l #$974,a1 ;Copy to
movea.l #$15E9C,a2 ;Bytes to copy
movea.l #$6E08,a3 ;Bytes to clear
movea.l a1,a4
adda.l #0,a4 ;Return address for game
move.l sp,($24).l

In this case, we have to copy the decrypted game code back $974 bytes
(effectively erasing the copylock – now you know why you can’t find it in
memory when you run this type of copylock!). It will copy $15e9c bytes
backwards and then clear the following $6e08 bytes of memory. Failure to do
all this can mean the game will crash! We can rip Rob’s own code for the
copy routine by pulling apart this code:

adda.l #$50,sp
move.l #$7FFF4E73,-(sp)
move.l #$584F0257,-(sp)
move.l #$4CDF7FFF,-(sp)
move.l #$4FEE007C,-(sp)
move.l #$2D4C00BE,-(sp)
move.l #$598066FA,-(sp)
move.l #$67064299,-(sp)
move.l #$66FA200B,-(sp)
move.l #$22D85980,-(sp)
move.l #$66FA6006,-(sp)
move.l #$23205980,-(sp)
move.l #$D1C0D3C0,-(sp)
move.l #$B3C86F0C,-(sp)
move.l #$200A6720,-(sp)
move.l #$4E7B0002,-(sp)
move.l #$202E00B8,-(sp)
move.l #$4DFAFFEE,-(sp)
move.l #$10,-(sp)
move.l #$1423DF,-(sp)
move.l #$4487A,-(sp)
move.l #$BD96BDAE,-(sp)

If you run the code then have a look at the stack, you will see the
following routine (which I have commmented a little bit):

move.l a2,d0 ;Check anything to move
beq.b .NothingToDo
cmpa.l a0,a1
ble.b .CopyAscending
adda.l d0,a0
adda.l d0,a1
.CopyDescending move.l -(a0),-(a1)
subq.l #4,d0
bne.b .CopyDescending
bra.b .CheckClearMem

.CopyAscending move.l (a0)+,(a1)+
subq.l #4,d0
bne.b .CopyAscending
.CheckClearMem move.l a3,d0
beq.b .NothingToDo
.ClearLoop clr.l (a1)+
subq.l #4,d0
bne.b .ClearLoop

And that is the analysis section complete! Now we have to write a new
loader for the game.

WRITING THE LOADER
——————
The way I will crack this game is the cleanest method possible – by writing
a new program which will load the copy protected file, emulate the
protection and then start it up. Most of the code will be lifted directly
from the copylock routine!

To start off we must open dos.library and LoadSeg the main file:

lea (_dosname,pc),a1 ;Open dos.library
move.l (4),a6
jsr (_LVOOldOpenLibrary,a6)
lea (_dosbase,pc),a0
move.l d0,(a0)
move.l d0,a6 ;A6 = dosbase

lea _program(pc),a0 ;Load exe
move.l a0,d1
jsr (_LVOLoadSeg,a6)
move.l d0,d7 ;D7 = segment
beq _failedtoload

bsr _DecryptRob ;Decrypt and relocate

The DecryptRob routine will use the d7 register to work out where it needs
to patch the code. LoadSeg returns a BPTR which is a longword number
pointer to the first segment (not the code). To convert from a BPTR to a
real Amiga address, you multiply by 4 and then add 4 if you want the code
address. Therefore:

add.l d7,d7
add.l d7,d7
add.l #4,d7
move.l d7,a3

A3 is now pointing to the first actual piece of code. We can check now that
we have found the correct code or abort:

cmp.l #$104afc,$8(a3)
bne _wrongver

If the value $104afc wasn’t found 8 bytes away, we have made a mistake or
the loader is loading the wrong file. Now just copy all the copylock code
into and assemble it. The code below stores all registers before starting
the game which is vital for some games which are compiled with MANX C. It
then cracks the game, restores registers, invokes the game as if started
from the CLI with no parameters (a0/d0), and will cause the screen to turn
a certain colour if anything goes wrong and hang rather than elegantly
quitting, but it is purely shown as an example. The full crack code is:

;*—————————————————————————
; Program: FootballManagerWCE.s
; Contents: Crack for “Football Manager: World Cup Edition” (c) 1990
; Addictive
; Author: Codetapper of Action
; History: 10.10.04 – v1.0
; – Rob Northen Copylock removed
; Requires: An Amiga 🙂
; Copyright: Public Domain
; Language: 68000 Assembler
; Translator: Barfly
;—————————————————————————*

INCDIR Include:
INCLUDE lvo/dos.i
INCLUDE lvo/exec.i

IFD BARFLY
OUTPUT “FootballManagerWCE”
BOPT O+ ;enable optimizing
BOPT OG+ ;enable optimizing
BOPT ODd- ;disable mul optimizing
BOPT ODe- ;disable mul optimizing
BOPT w4- ;disable 64k warnings
BOPT wo- ;disable warnings
SUPER ;disable supervisor warnings
ENDC

;======================================================================

bra _FootballManWCE

dc.b “Cracked by Codetapper/Action on 11/10/2004!”,0
EVEN

_FootballManWCE lea _saveregs(pc),a0
movem.l d1-d6/a2-a6,(a0)
move.l (a7)+,(44,a0)

lea (_dosname,pc),a1 ;Open dos.library
move.l (4),a6
jsr (_LVOOldOpenLibrary,a6)
lea (_dosbase,pc),a0
move.l d0,(a0)
move.l d0,a6 ;A6 = dosbase

lea _program(pc),a0 ;Load exe
move.l a0,d1
jsr (_LVOLoadSeg,a6)
move.l d0,d7 ;D7 = segment
beq _failedtoload ;bra .end

bsr _DecryptRob ;Decrypt and relocate

move.l d7,a1 ;Start game
add.l a1,a1
add.l a1,a1
moveq #_args_end-_args,d0
lea (_args,pc),a0
movem.l (_saveregs,pc),d1-d6/a2-a6
jsr (4,a1)

bra _ok

_saveregs ds.l 11
_saverts dc.l 0
_dosbase dc.l 0
_program dc.b “wcfm”,0
_args dc.b 10
_args_end dc.b 0
_dosname dc.b “dos.library”,0
EVEN

;======================================================================

_DecryptRob movem.l d0-d7/a0-a6,-(sp)

add.l d7,d7
add.l d7,d7
add.l #4,d7
move.l d7,a3 ;a3 = Spare register

cmp.l #$104afc,$8(a3)
bne _wrongver

move.l #$b590a736,d0
moveq #0,d1
move.l #$0c007ebc,d2
move.l #$00000c87,d3
move.l #$ffff0cd5,d4
move.l #$00000002,d5

lea $83c-$28(a3),a6
adda.l #$158,a6
move.l #$6400,d6
add.l a6,d6
.Decrypt roxr.l #1,d0
roxr.l #1,d1
roxr.l #1,d2
roxr.l #1,d3
roxr.l #1,d4
roxr.l #1,d5
eor.l d0,(a6)+
cmpa.l d6,a6
bne.b .Decrypt

lea $860-$28(a3),a6 ;Process hunks
move.l a6,d6
subi.l #$838,d6
adda.l #$134,a6
movem.l d0-d2/a0-a2,-(sp)
cmpi.l #$3E9,(a6) ;Check hunk_code
bne.b .HunkProcDone
move.l (4,a6),d0
lsl.l #2,d0
lea (8,a6),a1
lea (a1,d0.l),a2
cmpi.l #$3EC,(a2)+ ;Check hunk_reloc32
bne.b .HunkProcDone
.NextHunk move.l (a2)+,d1
beq.b .HunkProcDone
move.l (a2)+,d0
bsr.b .ProcessHunk
move.l a0,d2
tst.l (-4,a2)
bne.b .AddRelocValue
move.l d6,d2
.AddRelocValue move.l (a2)+,d0
add.l d2,(a1,d0.l)
subq.l #1,d1
bne.b .AddRelocValue
bra.b .NextHunk

.ProcessHunk movea.l d6,a0
subq.l #4,a0
tst.l d0
beq.b .HunkLoopDone
.SkipHunks movea.l (a0),a0
adda.l a0,a0
adda.l a0,a0
subq.l #1,d0
bne.b .SkipHunks
.HunkLoopDone addq.l #4,a0
rts

.HunkProcDone movem.l (sp)+,d0-d2/a0-a2

lea $99c-$28(a3),a0 ;Setup relocation parameters
movea.l a0,a1 ;(ripped from the copylock)
suba.l #$974,a1
movea.l #$15e9c,a2
movea.l #$6e08,a3
movea.l a1,a4

move.l a2,d0 ;Code from copylock
beq.b .NothingToDo
cmpa.l a0,a1
ble.b .CopyAscending
adda.l d0,a0
adda.l d0,a1
.CopyDescending move.l -(a0),-(a1)
subq.l #4,d0
bne.b .CopyDescending
bra.b .CheckClearMem

.CopyAscending move.l (a0)+,(a1)+
subq.l #4,d0
bne.b .CopyAscending
.CheckClearMem move.l a3,d0
beq.b .NothingToDo
.ClearLoop clr.l (a1)+
subq.l #4,d0
bne.b .ClearLoop

.NothingToDo movem.l (sp)+,d0-d7/a0-a6
rts

;======================================================================

_wrongver move.w #$fff,d0
bra _end
_failedtoload move.w #$f00,d0
bra _end
_ok move.w #$0f0,d0
_end move.w d0,$dff180
nop
nop
nop
nop
bra _end

Now all you need to do is copy the new loader onto the disk and alter the
startup-sequence to run your cracktro, then the file “FootballManagerWCE”
instead of “wcmf”!

0

Publication author

offline 20 years

Codetapper

0
Comments: 29Publics: 4Registration: 07-08-2004

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
Authorization
*
*

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Registration
*
*
*

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Password generation

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

0
Would love your thoughts, please comment.x
()
x