Amiga cracking – A look at basic TVD’s

[0] Introduction
[1] A first look at a real TVD
[2] So, what now?
[3] Final words

.-=[ [0] Introduction ]=-.

TVD is an acronym for “Trace-Vector Decoder”, which is a commonly-used
method of protecting code/data as part of a copy-protection routine. It
takes its name from the fact that it uses the 68000 TRACE exception vector
to decode
something (typically the next instruction). A TRACE exception occurs when
the trace bit in the status register is set to 1.
[ pic: 68000 STATUS REGISTER]

When the trace bit is set, execution jumps to the address stored @ 00000024
(the offset in the 68000 vector table for the TRACE vector). As with other
exceptions, the current SR value (16 bits) and the return address (32
bits) are
pushed onto the stack. When the exception finishes (with an RTE instruction),
these values are used to continue execution as before – obviously this
means that unless something else changes the trace bit in the status register,
the trace vector will be called after every single instruction (since
the trace bit had to be set to get into the trace vector routine, and
it’s restored when
we leave it!). And that’s the key to TVD routines – they typically decode
the next instruction to be executed, and once that decoded instruction
has completed, they decode the next one, and so on…So, now that you
(hopefully) have some idea what this is all about, let’s dive in and have…
.-=[ [1] A first look at a real TVD ]=-.

This example was found in the game: ALTERED BEAST (C) ACTIVISION/SEGA.
You should have the original disk or a copy of the CAPS .ipf file(s) if
you want to take a look at it for yourself. It’s an early Rob Northen
“Copylock” protection that returns the “magic number” in D0 and the trace
vector (take a look at the contents of 00000024 after the drive-grinding
Track 0 check!).

The TVD in this game (as in most/all of Rob’s protections) is used to
protect the disk-protection check proper (Track 0 has a different sync,
data is read and a simple checksum routine is performed on the returned
data to calculate the “magic number” for the game). Right, power up the
Amiga+AR (or your personal choice of monitor cart/software (or even WinUAE!)),
and let’s have a look at it!
(All code is in UPPERCASE, comments are after the “;” character)
First, we have the setup:

0171E0 : LEA 17260,A0 ; 17260 is written into the
vector table @ 00000010, this is the ILLEGAL;INSTRUCTION vector, which
is jumped to if the 68000 can’t make sense of the;current opcode, or if
it’s deliberately called with the ‘ILLEGAL’ instruction;when we reach
this code, we are inside an ILLEGAL EXCEPTION
0171E4 : MOVE.L A0, 10
017260 : MOVEM.L D0/A0-A1,-(A7)
017264 : LEA 1729A,A0 ;TRACE VECTOR = 1729A (this is the decoder)
017268 : MOVE.L A0, 24
01726E : LEA 176C4,A0 ;PRIVV VECTOR = 176C4(called when a PRIVILEGE
VIOLATIONoccurs;when the cpu tries to use an instruction in user mode
which isn’t allowed.;In this game 176C4 contains an RTS instruction,
and is used to exit the;protection check and return to the game code…
017272 : MOVE.L A0, 20
017278 : ADDI.L #2, E(A7) ;Here is the “clever” bit… the ADDI
instruction adds 2 to the return address;stored on the stack, the ORI
instruction sets the interrupt mask in the saved;SR value on the stack
(changes it from 2000 to 2700, turning off all irqs;except level 7),
the BCHG instruction flips bit 7 in the saved SR high-byte;value. This
will set the trace bit when this value is retrieved (when the next;RTE
instruction is executed), thereby jumping to the TVD!
017280 : ORI.B #7, C(A7)
017286 : BCHG #7, C(A7)
01728C : LEA 171A2,A1 ;if the BCHG instruction @ 017286 set
the zero flag, we jump into the TVD;halfway through (used to turn OFF
the TVD later)
017290 : BEQ.S 172AC
017292 : MOVEA.L (A1),A0
017294 : MOVE.L 4(A1),(A0)
017298 : BRA.S 172C0 ;jumps to the end of the TVD, which does an RTE, and
starts the TVD 🙂
;*** trace vector decoder
begins ***
01729A : ANDI # F8FF,SR ;turn on interrupts by setting irq mask to 000
01729E : MOVEM.L D0/A0-A1,-(A7) ;save registers before use
0172A2 : LEA 171A2,A1
0172A6 : MOVEA.L (A1),A0 ;A0 now = the address of the instruction
decoded in the previous TVD loop;this writes the previously used encrypted
value over the old instruction.;This means every time the TVD runs, it
encrypts the last instruction executed,;which of course means you can’t
let the TVD loop through a certain number of;times and dump the decrypted
code from memory (Rob’s not that stupid!)
0172A8 : MOVE.L 4(A1),(A0)
0172AC : MOVEA.L E(A7),A0 ;A0 now = the next instruction to be decoded
(the instruction we return to when;the next RTE instruction occurs.
0172B0 : MOVE.L A0,(A1) ;save the address in memory for next loop;moves
the encrypted value into memory for next loop (used to overwrite the;decoded
instruction once it’s been executed).
0172B2 : MOVE.L (A0),4(A1)
0172B6 : MOVE.L -4(A0),D0 ;get the encrypted dword from memory (4
bytes before the instruction we’re about;to decode) into D0
0172BA : NOT.L D0 ;the extremely simple decryption routine.
0172BC : SWAP D0 ;the EOR writes the decoded instruction
into (A0), which we are about to reach;when the RTE below is executed…
0172BE : EOR.L D0,(A0)
0172C0 : MOVEM.L (A7)+,D0/A0-A1 ;restore the registers
0172C4 : RTE ;return from exception.;remember that
RTE has set the status register back to the saved value, which;means that
the trace bit will still be set (unless cleared by another routine);therefore
after executing the instruction that has just been decoded we’ll be;back
into the TVD!

Phew! A lot of commentary for such a little piece of
code, but I hope it was useful… right about now, you’re probably asking
.-=[ [2] So, what now? ]=-.
Well, we obviously need to get to the unencrypted code somehow in order
to see exactly what the protection is doing.
Let’s have another quick look at the original TVD:
01729A : 027C F8FF ANDI # F8FF,SR
01729E : 48E7 80C0 MOVEM.L D0/A0-A1,-(A7)
0172A2 : 43FA FEFE LEA 171A2,A1
0172A6 : 2051 MOVEA.L (A1),A0
0172A8 : 20A9 0004 MOVE.L 4(A1),(A0)
0172AC : 206F 000E MOVEA.L E(A7),A0
0172B0 : 2288 MOVE.L A0,(A1)
0172B2 : 2350 0004 MOVE.L (A0),4(A1)
0172B6 : 2028 FFFC MOVE.L -4(A0),D0
0172BA : 4680 NOT.L D0
0172BC : 4840 SWAP D0
0172BE : B190 EOR.L D0,(A0)
0172C0 : 4CDF 0301 MOVEM.L (A7)+,D0/A0-A1
0172C4 : 4E73 RTE

Now, originally I replaced the last 2 instructions of the TVD with:
0172C0 : 4EF9 0000 00C4 JMP 000000C4

so that it would jump to my routine, however…after a few crashes it
became obvious that something was going horribly wrong 🙂 I suspected
a checksum, but what I found (by a LOT of breakpoints/tracing, I won’t
bore you with all that) was quite clever… Rob uses the last 4 bytes
of the TVD (half of the MOVEM and the 2 bytes for the RTE instruction)
as one of his decryption key values – this way there’s no need for a
separate checksum routine, since if you modify them the instruction
it decodes is going to be incorrect, and… well, you know the rest!
This is not good, but now that we know that, we can move our hook up
into the routine, and avoid altering the bytes that cause the crash:
0172B6 : 2028 FFFC MOVE.L -4(A0),D0
0172BA : 4680 NOT.L D0

now becomes:
0172B6 : 4EF9 0000 00C4 JMP 000000C4
We don’t alter any other bytes in the original TVD. Now we need to insert
the following code @ 000000C4:
0000C4 : 2028 FFFC MOVE.L -4(A0),D0
0000C8 : 4680 NOT.L D0
0000CA : 4840 SWAP D0
0000CC : B190 EOR.L D0,(A0)
0000CE : 2250 MOVEA.L (A0),A1
0000D0 : 2008 MOVE.L A0,D0
0000D2 : 0080 0060 0000 ORI.L # 600000,D0
0000D8 : 2040 MOVEA.L D0,A0
0000DA : 2089 MOVE.L A1,(A0)
0000DC : 06B9 0000 0001 0000 00C0 ADDI.L #1, C0
0000E6 : 4CDF 0301 MOVEM.L (A7)+,D0/A0-A1
0000EA : 4E73 RTE

I’ve written this here without comments so you can type the bytes into
memory if you’re following along step-by-step… here’s the commented

0000C4 : 2028 FFFC MOVE.L -4(A0),D0 ;these are the remaining instructions
from the original TVD, that we need to;execute before doing anything else.;Now
we can use registers D0, A0 and A1 for our purposes, since we restore
them ;at the end…
0000C8 : 4680 NOT.L D0
0000CA : 4840 SWAP D0
0000CC : B190 EOR.L D0,(A0)
0000CE : 2250 MOVEA.L (A0),A1 ;get decrypted instruction into A1
0000D0 : 2008 MOVE.L A0,D0 ;get address of instruction into D0
0000D2 : 0080 0060 0000 ORI.L # 600000,D0
;***Please note, this value may differ on your Amiga, change this if you
do not ;have memory @ 600000 (in my case I have 1mb Fastram located here)
0000D8 : 2040 MOVEA.L D0,A0 ;A0 = new address to write opcode to
0000DA : 2089 MOVE.L A1,(A0) ;write decrypted opcode into our new ‘high’ address
0000DC : 06B9 0000 0001 0000 00C0 ADDI.L #1, C0 ;keep count of how many traces we’ve run
0000E6 : 4CDF 0301 MOVEM.L (A7)+,D0/A0-A1 ;these are the final 2 instructions
0000EA : 4E73 RTE from the original TVD that we must execute.

And that’s it! Now when this has all executed the game
should start, and when you look in memory @ 6171EC onwards you’ll see
the decrypted Rob Northen Copylock routine just sitting there, waiting
for you to save out to disk and then load it into your favourite disassembler
(ReSource, IRA, or even IDA on PC if you’re not an Amiga purist!). Also,
take a look at the trace counter we stored @ 000000C0, and think how
long that would’ve taken to trace by hand 🙂

.-=[ [3] Final words ]=-.

Now, I know there was no point in decoding the protection routines in
this particular game, since it performs a simple check and gives you
the magic number in the trace vector handler address @ 00000024…
however, it does illustrate the principles you’ll need if you’re going
to crack harder TVD protections in future (be prepared for cartridge
detection, checksums everywhere and worse!), so I hope you’ll agree
it was a worthwhile tutorial.
If there are any errors in the tutorial, forgive me, it’s 05:40am and
I’m starting to feel slightly tired! Thanks for reading, see you in
a future tutorial (possibly!)…Zzzzz!
BTW: De La Soul obviously never cracked Altered Beast. The magic number
isn’t 3, it’s 664E90EA.

-Wayne Kerr, May 2004 (Yes, I know, 15 years late!)


Publication author

offline 17 hours


Comments: 1985Publics: 20Registration: 15-05-2004

Notify of

Inline Feedbacks
View all comments
George K
George K
2 years ago

Great explanation. I remember the hard sweat to discover pretty much the same approach when I first cracked a RN protected game (I think it was Dragon Ninja) on my A500 with 1MB of RAM. Great times.

7 years ago

I was wondering whether the last 4 bytes of the TVD are always used as a decryption key?
Or is it dependent on the game/copylock generation?


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


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.

Would love your thoughts, please comment.x