💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › action › Ikari%20Warrior… captured on 2023-05-24 at 23:03:08.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

-------------Ikari Warriors------------
A 4am crack                  2015-08-02
---------------------------------------

Name: Ikari Warriors
Genre: arcade
Year: 1987
Authors: Quicksilver Software, Inc.
Publisher: Data East USA, Inc.
Media: single-sided 5.25-inch floppy
OS: Quick-DOS
Other versions:
  - The Shiek / Digital Gang
  - The Blade

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  no errors, but copy displays the
  Quick-DOS title screen then hangs

Copy ][+ nibble editor
  all tracks use standard prologues
  (address: D5 AA 96, data: D5 AA AD)
  but modified epilogues
  (address: FF FF EB, data: FF FF EB)

Disk Fixer
  ["O" -> "Input/Output Control"]
    set Address Epilogue to "FF FF EB"
    set Data Epilogue to "FF FF EB"
  Success! All tracks readable!
  T00 -> Quick-DOS bootloader (loads in
    language card)
  T11 -> DOS 3.3 disk catalog

Why didn't COPYA work?
  modified epilogue bytes (every track)

Why didn't Locksmith FDB work?
  modified epilogue bytes (every track)

Why didn't my EDD copy work?
  probably a nibble check during boot

Next steps:

  1. Super Demuffin
  2. Patch RWTS
  3. Find nibble check and bypass it

                   ~

               Chapter 1
          In Which We Choose
      The Right Tool For The Job


I'm going to use Super Demuffin here
(instead of my usual go-to conversion
tool, Advanced Demuffin). The disk is
uses a custom bootloader called "Quick-
DOS". It's different enough from the
standard DOS 3.3 bootloader that my
automated tools can't capture the RWTS.
But luckily, the RWTS modifications are
minor -- custom epilogue bytes, same on
every track -- so Super Demuffin will
work just fine.

When you first run Super Demuffin, it
asks for the parameters of the original
disk. In this case, the prologue bytes
are the same, but the epilogues are "FF
FF EB" instead of "DE AA EB".

                 --v--

      SUPER-DEMUFFIN AND FAST COPY
Modified by: The Saltine/Coast to Coast


   Address prologue: D5 AA 96

   Address epilogue: FF FF EB    DISK
                     ^^^^^     ORIGINAL
change from DE AA ---+++++

      Data prologue: D5 AA AD

      Data epilogue: FF FF EB
                     ^^^^^
change from DE AA ---+++++


 Ignore write errors while demuffining!


  D - Edit parameters
      <SPACE> - Advance to next parm
      <RETURN> - Exit edit mode
  R - Restore DOS 3.3 parameters
  O - Edit Original disk's parameters
  C - Edit Copy disk's parameters
  G - Begin demuffin process

                 --^--

Pressing "G" switches to the Locksmith
Fast Disk Copy UI. It assumes that both
disks are in slot 6, and that drive 1
is the original and drive 2 is the
copy.

[S6,D1=original disk]
[S6,D2=blank disk]

                 --v--

     LOCKSMITH 7.0  FAST DISK BACKUP


   R...................................
   W***********************************
HEX 00000000000000001111111111111111222
TRK 0123456789ABCDEF0123456789ABCDEF012
   0...................................
   1...................................
   2...................................
   3...................................
   4...................................
   5...................................
   6...................................
   7...................................
   8...................................
   9...................................
   A...................................
   B...................................
   C...................................
   D...................................
12 E...................................
   F...................................
[               ] PRESS [RESET] TO EXIT

                 --^--

]PR#6
...displays Quick-DOS title screen
   then hangs...

                   ~

               Chapter 2
        In Which We Learn That
     Hard Work Pays Off Over Time,
    But Laziness Pays Off Right Now


My EDD bit copy didn't work. Now my
demuffin'd copy doesn't work either.
Where's that nibble check?

[Disk Fixer]
  --> "F"ind
    --> "H"ex
      --> "BD 89 C0"

One thing that virtually all nibble
checks have in common is turning on the
drive motor by accessing a specific
address in the $C0xx range. For slot 6,
it's $C0E9, but to allow disks to boot
from any slot, developers usually use
code like this:

  LDX <slot number x 16>
  LDA $C089,X

There's nothing that says where the
slot number has to be, although the
disk controller ROM routine uses zero
page $2B and lots of disks just reuse
that. There's also nothing that says
you have to use the X-register as the
index, or that you must use the
accumulator as the load register. But
most RWTS code does, out of convention
I suppose (or possibly fear of messing
up such low-level code in subtle ways).

Here's the irony: depending on when the
nibble check is invoked, turning on the
drive motor is completely superfluous,
because it's already guaranteed to be
on. So you've provided a lazy way for
attackers(*) to find the very code that
you don't want me to find.

(*) me

Always search for "BD 89 C0". Always
always always.

Anyway, I found it on T00,S06. It looks
like this in Disk Fixer's disassembler:

T00,S06
----------- DISASSEMBLY MODE ----------
; save zero page
0000:A0 00          LDY   #$00
0002:B9 00 00       LDA   $0000,Y
0005:91 36          STA   ($36),Y
0007:88             DEY
0008:D0 F8          BNE   $0002
000A:A9 0A          LDA   #$0A
000C:85 50          STA   $50

; turn on drive motor manually
000E:A6 2B          LDX   $2B
0010:BD 89 C0       LDA   $C089,X
0013:BD 8E C0       LDA   $C08E,X

; an address (assuming this page is
; loaded at $D800)
0016:A9 9F          LDA   #$9F
0018:85 48          STA   $48
001A:A9 D8          LDA   #$D8
001C:85 49          STA   $49

; set up Death Counter
001E:A9 80          LDA   #$80
0020:85 51          STA   $51
0022:C6 51          DEC   $51

; if Death Counter hits 0, give up
0024:F0 66          BEQ   $008C

; find next address field
0026:20 A7 D8       JSR   $D8A7

; if that failed, give up
0029:B0 61          BCS   $008C

; check if sector is $0D
002B:A5 2E          LDA   $2E
002D:C9 0D          CMP   #$0D

; if not, loop back until we find it
002F:D0 F1          BNE   $0022

; look for $D5 nibble
0031:A0 00          LDY   #$00
0033:BD 8C C0       LDA   $C08C,X
0036:10 FB          BPL   $0033
0038:88             DEY
0039:F0 51          BEQ   $008C
003B:C9 D5          CMP   #$D5
003D:D0 F4          BNE   $0033

; find $E7 $E7 $E7 nibble sequence
003F:A0 00          LDY   #$00
0041:BD 8C C0       LDA   $C08C,X
0044:10 FB          BPL   $0041
0046:88             DEY

; fail if we don't find it in time
0047:F0 43          BEQ   $008C
0049:C9 E7          CMP   #$E7
004B:D0 F4          BNE   $0041
004D:BD 8C C0       LDA   $C08C,X
0050:10 FB          BPL   $004D
0052:C9 E7          CMP   #$E7
0054:D0 36          BNE   $008C  ; fail
0056:BD 8C C0       LDA   $C08C,X
0059:10 FB          BPL   $0056
005B:C9 E7          CMP   #$E7
005D:D0 2D          BNE   $008C  ; fail

; kill some time to get out of sync
; with the "proper" start of nibbles
; (see below)
005F:BD 8D C0       LDA   $C08D,X
0062:A0 10          LDY   #$10
0064:24 06          BIT   $06

; now start looking for nibbles that
; don't really exist (except they do,
; because we're out of sync and reading
; timing bits as data)
0066:BD 8C C0       LDA   $C08C,X
0069:10 FB          BPL   $0066
006B:88             DEY
006C:F0 1E          BEQ   $008C  ; fail
006E:C9 EE          CMP   #$EE
0070:D0 F4          BNE   $0066

; check for nibble sequence stored
; in reverse order at $D89F
0072:A0 07          LDY   #$07
0074:BD 8C C0       LDA   $C08C,X
0077:10 FB          BPL   $0074
0079:D1 48          CMP   ($48),Y
007B:D0 0F          BNE   $008C
007D:88             DEY
007E:10 F4          BPL   $0074

A short digression here into some super
low-level disk stuff, because this
wasn't low-level enough already...

$E7 $E7 $E7 $E7. What would that nibble
sequence look like on disk? The answer
is, "It depends." $E7 in hexadecimal is
11100111 in binary, so here is the
simplest possible answer:

   |--E7--||--E7--||--E7--||--E7--|
   11100111111001111110011111100111

But wait. Every nibble read from disk
must have its high bit set. In theory,
you could insert one or two "0" bits
after any of those nibbles. (Two is the
maximum, due to hardware limitations.)
These extra "0" bits would be swallowed
by the standard "wait for data latch to
have its high bit set" loop, which you
see over and over in any RWTS code:

  :1   LDA $C08C,X
       BPL :1

Now consider the following bitstream:

  |--E7--| |--E7--|  |--E7--||--E7--|
  11100111011100111001110011111100111
          ^        ^^
       (extra)   (extra)

The first $E7 has one extra "0" bit
after it, and the second $E7 has two
extra "0" bits after it. Totally legal,
works on any Apple II computer and any
floppy drive. A "LDA $C08C,X; BPL" loop
would still interpret this bitstream as
a sequence of four $E7 nibbles. Each of
the extra "0" bits appear after we've
just read a nibble and we're waiting
for the high bit to be set again.

Now, what if we miss the first few bits
of this bitstream, then start looking?
The disk is always spinning, whether
we're reading from it or not. If we
waste too much time doing something
other than reading, we'll literally
miss some bits as the disk spins by.
This is why the timing of low-level
RWTS code is so critical.

Let's say we waste 12 CPU cycles before
we start reading this bitstream. Each
bit takes 4 CPU cycles to go by, so
after 12 cycles, we would have missed
the first 3 bits (marked with an X).

            (normal start)

  |--E7--| |--E7--|  |--E7--||--E7--|
  11100111011100111001110011111100111
  XXX  |--EE--| |--E7--|  |--FC--|

           (delayed start)

Ah! It's interpreted as a completely
different nibble sequence if you delay
just a few CPU cycles before you start
reading. Also note that some of those
"extra" bits are no longer being
ignored; now they're being interpreted
as data, as part of the nibbles that
are being returned to the higher level
code. Meanwhile, other bits that were
part of the $E7 nibbles are now being
swallowed.

Now, let's go back to the first stream,
which had no extra bits between the
nibbles, and see what happens when we
waste those same 12 CPU cycles.

           (normal start)

   |--E7--||--E7--||--E7--||--E7--|
   11100111111001111110011111100111
   XXX  |--FC--||--FC--||--FC--|

          (delayed start)

After skipping the first three bits,
the stream is interpreted as a series
of $FC $FC $FC repeating endlessly --
not $EE $E7 $FC like the other stream.

Here's the kicker: generic bit copiers
didn't preserve these extra "0" bits
between nibbles. By "desynchronizing"
(wasting just the right number of CPU
cycles at just the right time), then
interpreting the bits on the disk in
mid-stream, developers could determine
at runtime whether you had an original
disk. Which is precisely the code we
just saw.

Here is the complete "E7 bitstream,"
annotated to show both the synchronized
and desynchronized nibble sequences.
($0265 wastes the right amount of time;
$0274 checks for $EE; $027F checks for
the rest of the nibbles, stored in
reverse order at $02C7.)

 |--E7--| |--E7--|  |--E7--||--E7--|
 111001110111001110011100111111001110
 XXX  |--EE--| |--E7--|  |--FC--||--E

 |--E7--|  |--E7--||--E7--| |--E7--|
 111001110011100111111001110111001110
 E--| |--E7--|  |--FC--||--EE--| |--E

 |--E7--||--E7--|
 1110011111100111
 E--| |--FC--|

We now return you to the actual code...

                   ~

               Chapter 3
      In Which It All Comes Down
              To One Bit


; (nibble check passed)
; restore zero page
0080:A0 00          LDY   #$00
0082:B1 36          LDA   ($36),Y
0084:99 00 00       STA   $0000,Y
0087:88             DEY
0088:D0 F8          BNE   $0082

; clear carry and exit
008A:18             CLC
008B:60             RTS

; all failures end up here -- try a few
; more times then give up completely
008C:C6 50          DEC   $50
008E:F0 03          BEQ   $0093
0090:4C 1E D8       JMP   $D81E

; (nibble check failed)
; restore zero page
0093:A0 00          LDY   #$00
0095:B1 36          LDA   ($36),Y
0097:99 00 00       STA   $0000,Y
009A:88             DEY
009B:D0 F8          BNE   $0095

; set carry and exit
009D:38             SEC
009E:60             RTS

So it's a nibble check that clears the
carry on success and sets it on
failure. There are no other side
effects. Just one bit of information
stands between me and a working copy.
The quickest way to bypass this is to
change the start of the routine to
clear the carry unconditionally and
exit.

T00,S06,$00 change "A0 00" to "18 60"

]PR#6
...works...

The RWTS is flexible enough to read
disks in a standard format. It accepts
accept any nibble between $DE and $FF
as the first epilogue, and any nibble
between $AA and $FF as the second.

Side B uses the same modified epilogue
sequence as side A, and it converts to
a standard format with Super Demuffin
in the same way.

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 385
------------------EOF------------------