-------Addition and Subtraction 1------
A 4am crack 2014-05-10
---------------------------------------
"Blast Off: Addition and Subtraction 1"
is a 1985 educational program authored
by Thomas Hartsig. It is part of the
"Mathematics Courseware Series"
distributed by Scott, Foresman and
Company.
COPYA copies the disk without incident,
but the copy does not work. After a
fair amount of disk reading, it
displays the following message:
THIS IS AN ILLEGAL COPY AND WILL NOT
RUN. PRESS ANY KEY TO LEAVE.
I have decided to ignore this advice.
The program appears to be written in
Apple Pascal. The boot process starts
with several sequential disk reads,
then fills the screen with null bytes,
then clears the screen and displaying a
block cursor in the upper-left corner.
Anyone who has ever played Wizardry or
GATO will recognize the Apple Pascal
boot sequence.
Pulling out my trusty Copy ][+ sector
editor, I search for the text "ILLEGAL"
and find nothing. But searching for
the hex sequence "49 4C 4C 45" ("ILLE"
without the high bit set) does find the
error message on T22,S09.
--v--
SECTOR EDITOR DRIVE 1
00- B9 F4 CD 1C 01 04 A5 03 9tM...%.
08- 00 01 00 00 9E 05 B6 01 ......6.
10- 03 0C 00 CD 00 11 9E 00 ...M....
18- 00 00 CE 02 00 C3 A1 F6 ..N..C!v
20- 00 0A CD 00 1D B6 01 03 ..M..6..
28- D7 A6 24 54 48 49 53 20 W&$THIS
30- 49 53 20 41 4E 20 49 4C IS AN IL
38- 4C 45 47 41 4C 20 43 4F LEGAL CO
40- 50 59 20 41 4E 44 20 57 PY AND W
48- 49 4C 4C 20 4E 4F 54 00 ILL NOT.
50- CD 00 13 9E 00 B6 01 03 M....6..
58- CD 00 16 9E 00 B6 01 03 M....6..
60- 20 00 CD 00 11 9E 00 B6 .M....6
68- 01 03 CD 00 16 9E 00 B6 ..M....6
70- 01 03 D7 A6 1D 52 55 4E ..W&.RUN
78- 2E 20 20 50 52 45 53 53 . PRESS
80- 20 41 4E 59 20 4B 45 59 ANY KEY
TRACK $22 SECTOR $09 DOS 3.3
--^--
Unfortunately, the disassembly of the
surrounding code makes no sense --
because it's not assembly language!
It's p-code.
"What is p-code?" I hear you cry. I'm
glad you asked. Pascal was not limited
to the Apple II. Apple Pascal was an
implementation of USCD Pascal, which
defined a cross-platform representation
of "compiled" Pascal code ("p-code").
Quoting from
--v--
What is the UCSD P-System?
It is a portable operating system that
was popular in the early days of
personal computers, in the late 1970s
and early 1980s.
Like today's [note: this article was
written in 2004 -4am] Java, it was
based on a "virtual machine" with a
standard set of low-level, machine-
language-like "p-code" instructions
that were emulated on different
hardware, including the 6502, the 8080,
the Z-80, and the PDP-11. In this way,
a Pascal compiler that emitted p-code
executables could produce a program
that could be run under the P-System on
an Apple II, a Xerox 820, or a DEC
PDP-11.
The most popular language for the
P-System was UCSD Pascal. In fact, the
P-System operating system itself was
written in UCSD Pascal, making the
entire operating system relatively easy
to port between platforms.
By writing a p-code interpreter in the
platform's native assembly language,
and a few minimal hooks to operating
system functions for the file system
and interacting with the user, you
could move a p-code executable from
another system and run it on the new
platform. In this way, the p-code
generated on one computer could be used
to bootstrap the port of the P-System
to another computer.
--^--
Here endeth the history lesson and
starteth the hacking.
The first thing I'll need is an Apple
Pascal work disk, instead of my usual
(DOS 3.3-compatible) Diversi-DOS disk.
The second thing is a p-code decompiler
from
[S6D1=my work disk]
[S6D2=original disk]
]PR#6
...
Command: F(ile, E(dit, R(un, C(omp,
L(ink, X(ecute, A(ssem, ? [1.3]
--> "F" [runs the Apple Pascal file
management utility]
Filer: L(dir E(dir R(em T(rans C(hng
D(ate P(refix K(rnch Z(ero V(ols Q(uit?
--> "V" [shows which disks are online]
[Confirmed: the original disk is
written in Apple Pascal, and it's
readable by the standard Apple Pascal
filer. Apple Pascal volumes can be
referenced by name, like ProDOS
volumes. This disk's name is D99AS1:]
Volume # - Volume Name - # Blocks
1 CONSOLE:
2 SYSTERM:
4 AS1WORK: 280
5 D99AS1: 280
11 280
System volume is - AS1WORK:
Prefix volume is - AS1WORK:
--> "L" [lists a volume]
Directory listing of what volume ?
--> "D99AS1:" [name of original disk]
D99AS1:
RTSTRP.APPLE 24 25-Dec-83
SYSTEM.PASCAL 28 25-Dec-83
SYSTEM.LIBRARY 39 25-Dec-83
SYSTEM.MISCINFO 1 25-Dec-83
MFILE.DATA 128 14-Aug-80
AGI.CODE 47 16-Apr-84
SYSTEM.STARTUP 2 10-Apr-84
COLOR.DATA 1 29-Mar-84
8/8 files , 276 blocks
used, 4 unused, 3 in largest
[Under Apple Pascal, "SYSTEM.STARTUP"
is the program that runs automatically
on boot, like HELLO under DOS 3.3. I'm
hoping that's where the copy protection
routine lives.]
--> "Q" [return to main menu]
Command: F(ile, E(dit, R(un, C(omp,
L(ink, X(ecute, A(ssem, ? [1.3]
--> "X" [execute a program]
Execute what file ( to exit) ?
--> "DISASSM" [this is the program I
downloaded from callapple.org]
APPLE PASCAL/6502 DIS-ASSEMBLER
VERSION: July 27, 1982
Name of OUTPUT file (default is
CONSOLE:):
--> "AS1WORK:AS1"
Name of code file:
--> "D99AS1:SYSTEM.STARTUP"
Decode COPIED machine code?
[Hmm, that's interesting. Not sure what
it means, but yes, let's do that.]
--> Y
...a surprisingly short time later...
Success! Here is the complete output
file, AS1.TEXT:
--v--
Code file = D99AS1:SYSTEM.STARTUP
The following library bytes are non-
zero:
COPIED is a linked segment (6502
asmb vers.2), length = 396 bytes
These SYSTEM.LIBRARY intrinsics are
used:
28 (Chainstuff)
** # PROCS = 2, SEGMENT # = 1
Disassembly for procedure # 2; Lex
level = 0
Code length = 117 bytes with 0 .INTERP
fixups,
0 .REF fixups, 0 host fixups, and 0
internal fixups.
0000: 68 PLA
0001: 85 00 STA 0000
0003: 68 PLA
0004: 85 01 STA 0001
0006: 68 PLA
0007: 68 PLA
0008: 68 PLA
0009: 68 PLA
000A: 2C E9 C0 BIT 0C0E9
000D: A9 51 LDA #81.
000F: 85 04 STA 0004
0011: C6 06 DEC 0006
0013: D0 1A BNE R002F
0015: C6 04 DEC 0004
0017: D0 16 BNE R002F
0019: A9 00 LDA #0.
001B: F0 4C BEQ R0069
001D: AD EC C0 LDA 0C0EC
0020: 10 FB BPL R001D
0022: C9 D5 CMP #213.
0024: D0 EB BNE R0011
0026: AD EC C0 LDA 0C0EC
0029: 10 FB BPL R0026
002B: C9 AA CMP #170.
002D: D0 E2 BNE R0011
002F: AD EC C0 LDA 0C0EC
0032: 10 FB BPL R002F
0034: C9 96 CMP #150.
0036: D0 D9 BNE R0011
0038: A0 0A LDY #10.
003A: AD EC C0 LDA 0C0EC
003D: 10 FB BPL R003A
003F: C9 FF CMP #255.
0041: D0 CE BNE R0011
0043: 48 PHA
0044: 68 PLA
0045: AD EC C0 LDA 0C0EC
0048: C9 08 CMP #8.
004A: B0 C5 BCS R0011
004C: AD EC C0 LDA 0C0EC
004F: 10 FB BPL R004C
0051: 85 02 STA 0002
0053: 88 DEY
0054: F0 0B BEQ R0061
0056: AD EC C0 LDA 0C0EC
0059: 10 FB BPL R0056
005B: 45 02 EOR 0002
005D: D0 F2 BNE R0051
005F: F0 F0 BEQ R0051
0061: A5 02 LDA 0002
0063: 49 60 EOR #96.
0065: D0 AA BNE R0011
0067: A9 FF LDA #255.
0069: 2C E8 C0 BIT 0C0E8
006C: 48 PHA
006D: 48 PHA
006E: A5 01 LDA 0001
0070: 48 PHA
0071: A5 00 LDA 0000
0073: 48 PHA
0074: 60 RTS
0075: 00 BRK
Disassembly for procedure # 1; Lex
level = 0
P-code procedure 1
Code = 244, parameters = 4, data =
2 bytes; Jump table = 2 words
0: B9 F4 UJP -12
2: CD 1C 01 CXP 28,1
5: 04 SLDC 4
6: A5 03 LAO 3
8: 00 SLDC 0
9: 01 SLDC 1
10: 00 SLDC 0
11: 00 SLDC 0
12: 9E 05 CSP 5
14: B6 01 03 LOD 1,3
17: 0C SLDC 12
18: 00 SLDC 0
19: CD 00 11 CXP 0,17
22: 9E 00 CSP 0
24: 00 SLDC 0
25: 00 SLDC 0
26: CE 02 CLP 2
28: 00 SLDC 0
29: C3 EQUI
30: A1 F6 FJP -10
32: 00 SLDC 0
33: 0A SLDC 10
34: CD 00 1D CXP 0,29
37: B6 01 03 LOD 1,3
40: D7 NOP
41: A6 24 54 48 49 53 20 49 53 20 LSA
36,"THIS IS AN ILLEGAL COPY AND WIL
L NOT"
79: 00 SLDC 0
80: CD 00 13 CXP 0,19
83: 9E 00 CSP 0
85: B6 01 03 LOD 1,3
88: CD 00 16 CXP 0,22
91: 9E 00 CSP 0
93: B6 01 03 LOD 1,3
96: 20 SLDC 32
97: 00 SLDC 0
98: CD 00 11 CXP 0,17
101: 9E 00 CSP 0
103: B6 01 03 LOD 1,3
106: CD 00 16 CXP 0,22
109: 9E 00 CSP 0
111: B6 01 03 LOD 1,3
114: D7 NOP
115: A6 1D 52 55 4E 2E 20 20 50 52 LSA
29,"RUN. PRESS ANY KEY TO LEAVE."
146: 00 SLDC 0
147: CD 00 13 CXP 0,19
150: 9E 00 CSP 0
152: B6 01 03 LOD 1,3
155: CD 00 16 CXP 0,22
158: 9E 00 CSP 0
160: 00 SLDC 0
161: 16 SLDC 22
162: CD 00 1D CXP 0,29
165: B6 01 02 LOD 1,2
168: CD 00 15 CXP 0,21
171: 9E 00 CSP 0
173: 01 SLDC 1
174: 01 SLDC 1
175: 9E 04 CSP 4
177: A6 03 41 47 49 LSA 3,
"AGI"
182: D7 NOP
183: CD 1C 02 CXP 28,2
186: 19 SLDC 25
187: 16 SLDC 22
188: CD 00 1D CXP 0,29
191: B6 01 03 LOD 1,3
194: D7 NOP
195: A6 0C 50 4C 45 41 53 45 20 57 LSA
12,"PLEASE WAIT."
209: 00 SLDC 0
210: CD 00 13 CXP 0,19
213: 9E 00 CSP 0
215: B6 01 03 LOD 1,3
218: CD 00 16 CXP 0,22
221: 9E 00 CSP 0
223: 29 SLDC 41
224: 00 SLDC 0
225: CD 00 1D CXP 0,29
228: 01 SLDC 1
229: 01 SLDC 1
230: 9E 04 CSP 4
232: 1C SLDC 28
233: 9E 16 CSP 22
235: B9 05 UJP 5
237: 1C SLDC 28
238: 9E 15 CSP 21
240: B9 F2 UJP -14
242: C1 00 RBP 0
Jump table:
244: F2 00 .WORD R2 (Entry # -14)
246: 09 00 .WORD R237 (Entry # -12)
248: 47 00 .WORD R177 (Entry # -10)
--^--
Wow. That seems like a lot of useful
information, but what does it tell me?
I am by no means an expert here. This
is my first foray into Pascal hacking
and decompiling. (I do recognize the
nibble check in procedure #2, though. I
can smell those a mile away.)
Here are some references on Apple
Pascal hacking that I found helpful:
The "Apple Pascal Operating System
Reference Manual", currently available
at
Appendices A and B of that manual are
available in plain text at
"P-Source: A Guide to the Apple Pascal
System" by Randall Hyde, currently
available at
In 2012, "Tommy" wrote a series of
posts on comp.sys.apple2 (yes, the
Usenet newsgroup) called
"Re-engineered: Wizardry III, Legacy of
Llylgamyn", currently available at
In 2014, "Tommy" was encouraged to
write another series on comp.sys.apple2
called "Wizardry re-engineering",
currently available at
Armed with just enough information to
be dangerous, I set out on an adventure
of Pascal decompiling!
Under the "Disassembly of procedure 1",
the first instruction looks like this:
0: B9 F4 UJP -12
According to Appendix A in the Apple
Pascal Operating System Reference
Manual (hereafter known as "the f---ing
manual"), "UJP" is an unconditional
jump. Unlike 6502 assembly language,
all jumps are referenced by their
position in a "jump table" at the end
of the procedure. This is the jump
table for this procedure:
Jump table:
244: F2 00 .WORD R2 (Entry # -14)
246: 09 00 .WORD R237 (Entry # -12)
248: 47 00 .WORD R177 (Entry # -10)
So the instruction "UJP -12" jumps to
the address listed as "Entry # -12",
which is R237. "237" refers to the
instruction at byte offset 237, which
is listed near the end of the
procedure:
237: 1C SLDC 28
238: 9E 15 CSP 21
240: B9 F2 UJP -14
According to the f---ing manual, "SLDC
28" pushes the decimal value 28 ($1C)
to the stack. "CSP" stands for "call
standard procedure." Apple Pascal has a
number of built-in functions like "cos"
(calculate a cosine) or "readln" (read
a line of input). But what is standard
procedure #21? The f---ing manual does
not say. But Hyde's book does; page 331
has the entire CSP table, and the 21st
entry is "LDS" ("Load Segment").
Quoting Hyde: "[LDS] pops the segment
number off of the stack, gets it into
the 6502 accumulator, and then calls
the load segment subroutine. Upon
return from load segment, control is
returned to the main interpreter loop."
OK, but what segment are we loading?
#28, which was just pushed to the stack
in the previous instruction. But what
is segment #28? According to Appendix A
in the f---ing manual, segment 0 and
segments 2-6 are reserved for the
operating system. Segment 1 is the main
program that is currently executing
(SYSTEM.STARTUP in this case). Segments
7-21 are for segments and units defined
within the program. And segments 22-31
are predefined intrinsic units.
Apple Pascal includes several libraries
like TURTLEGRAPHICS for Logo-like
graphic commands and APPLESTUFF for
access to joystick buttons and other
hardware. But which one is #28? I
finally found the answer in one of
Tommy's posts in his "Wizardry re-
engineering" series. Using a tool
called LIBMAP, Tommy dug into the
SYSTEM.LIBRARY file (part of the Apple
Pascal operating system) and found the
internal segment numbers for each
intrinsic unit in Apple Pascal.
20 TURTLEGRAPHICS (Logo-like graphics)
22 APPLESTUFF (hardware access)
28 CHAINSTUFF (launching programs)
29 TRANSCEND (geometry functions)
30 LONGINTIO (long integer support)
31 PASCALIO (parsing decimals)
Thus, the p-code instruction "SLDC 28",
followed by "CSP 21", will load the
CHAINSTUFF intrinsic unit from
SYSTEM.LIBRARY.
"UJP -14" is an unconditional jump to
the address listed in the jump table
under Entry # -14, which is R2. So that
means we're jumping to the instruction
at byte offset 2, which is right after
the first unconditional jump.
2: CD 1C 01 CXP 28,1
"CXP" stands for "Call eXternal
Procedure", and the f---ing manual says
it takes two arguments, a segment #
and a procedure # (in that order).
According to Tommy, procedure #28 in
segment #1 is "uses chainstuff". That
makes sense, I suppose, since we just
got finished loading that library into
memory. Now we're signaling that we
want to use it.
That's it. Four hours of research, and
"uses chainstuff" is as far as I've
gotten. Did I mention I have no idea
what I'm doing? This is fun. Are you
having fun? I'm having fun.
Moving on.
5: 04 SLDC 4
6: A5 03 LAO 3
8: 00 SLDC 0
9: 01 SLDC 1
10: 00 SLDC 0
11: 00 SLDC 0
12: 9E 05 CSP 5
According to the f---ing manual, "SLDC
4" just pushes the value 4 to the
stack. (To conserve space, p-code has
lots of one-byte instructions that all
mean "push me to the stack.")
"LAO" is "load global address". The
f---ing manual has this to say about
that: "LAO B. Fetch the word with
offset 'B' in BASE activation record
and push it." Then more pushing, then
"CSP 5". According to Hyde, standard
procedure #5 is "UREAD" (unit
read).
According to Tommy, the unit read
procedure is a low-level disk read,
like calling the RWTS directly under
DOS 3.3. It takes 5 parameters. The
first parameter is a volume number. In
Apple Pascal, the volume number of the
disk you're booting from is always 4.
(I saw this earlier when I listed all
online volumes from the Filer.) That
explains the "SLDC 4" and "LAO 3"
instructions. It's getting a reference
to volume #4 and pushing it to the
stack, in preparation for the unit read
procedure to pull it off the stack and
use the value as its first parameter.
Damn, this is slow going.
Anyway, if I'm right that the embedded
assembly language procedure is a nibble
check, then this Pascal code makes
sense. It needs to position the drive
head properly before it can do the
nibble check, since the special nibble
sequence is usually only stored in one
place on the disk.
14: B6 01 03 LOD 1,3
17: 0C SLDC 12
18: 00 SLDC 0
19: CD 00 11 CXP 0,17
According to Tommy, procedure #17 in
segment 0 is "WRITE()". As in, write
things on the screen. I'm not sure what
it's writing, but I'm going to
optimistically skip over it and hope it
isn't important.
22: 9E 00 CSP 0
According to Hyde, standard procedure 0
is IOCHECK. It checks the result of the
last I/O operation and triggers a
run-time error if it's non-zero. I'm
assuming this is checking the result of
the unit read procedure that positioned
the drive head.
24: 00 SLDC 0
25: 00 SLDC 0
26: CE 02 CLP 2
The f---ing manual says that "CLP"
standards for "call local procedure."
From the decompiler listing I got,
local procedure #2 is the embedded
assembly language routine -- i.e. the
nibble check. This is it! This is where
it calls the nibble check!
28: 00 SLDC 0
29: C3 EQUI
"EQUI" is an "is-equals" test. It
compares the top value on the stack
with the next-to-top value on the
stack. The top value is 0, since the
instruction just before it (at offset
28) was "SLDC 0". The next-to-top value
is the return value from calling local
procedure #2. So I think this is saying
"if the nibble check returned 0, then."
Then what?
30: A1 F6 FJP -10
"FJP" stands for "false jump," or less
confusingly, "jump if false." If the
previous test evaluated to false, then
jump. The previous test was "did the
nibble check return 0?" So I had it
backwards in the previous paragraph.
The proper prose description of this
code is "if the nibble check did not
return 0, then jump." Jump to where? To
the offset listed in the jump table
under Entry #-10, which is R177.
Without decompiling every single
instruction between 30 and 177, it
seems pretty clear that this is the
branch we want to take (but don't,
because the nibble check fails and
returns 0). In between 30 and 177, I
see the exact error message about "THIS
IS AN ILLEGAL COPY" blah blah whatever.
This jump-if-false branches right over
all of that and goes on with the real
business of SYSTEM.STARTUP, which is
presumably to use the intrinsic library
CHAINSTUFF to launch the main program.
If I change that "EQUI" (is-equals)
instruction at offset 29 to a "NEQI"
(is-not-equals) instruction, that would
reverse the logic entirely. Instead of
saying, "if the nibble check failed,
display this message and hang," it
would say, "if the nibble check
succeeds, display an error message and
hang."
I like this solution for two reasons:
first, it's the simplest thing that
could possibly work. It's only a one-
byte change, so it doesn't require
inserting or removing any bytes of the
p-code (which is convoluted and
intimidating, to say the least). And
second, I enjoy the irony of a program
that does a nibble check but complains
about success instead of failure.
Whipping out my trusty Copy ][+ sector
editor, I go back to the sector where I
originally found the error message and
confirm that the surrounding bytes are
identical to the p-code that I've been
decompiling. With a mix of trepidation
and optimism, I make one small change:
T22,S09,$1D change "C3" to "CB"
It worked! It actually worked! The copy
boots and runs!
I have two other disks by the same
company that use the same copy
protection routine:
- Addition and Subtraction 2
T0C,S0B,$1D change "C3" to "CB"
- Frog Jump: Ordering Numbers
T0C,S0B,$1D change "C3" to "CB"
Quod erat liberandum.
---------------------------------------
A 4am crack No. 31
------------------EOF------------------