docmaker | ||
iigs | ||
src | ||
.gitignore | ||
2mg.c | ||
65816.h | ||
addresses.h | ||
disasm.c | ||
disasm.h | ||
handle.h | ||
iigs.c | ||
iigs.dat | ||
LICENSE | ||
Makefile | ||
map.c | ||
map.h | ||
omf.c | ||
parser.c | ||
parser.h | ||
prodos8.h | ||
prodos16.h | ||
prodos_types.h | ||
README.md | ||
regs.c | ||
scan.c | ||
scan.h | ||
smartport.h | ||
tools.h |
What is this?
This is a set of command-line tools designed specifically to reverse engineer Apple IIgs software. It is comprised of 3 separate tools; 2mg
, omf
, and regs
.
2mg
2mg
extracts .2mg and .po prodos disk images. You can also just list the contents of the disk image with the -l
or --list
command line argument. Otherwise, it will create a folder with the name of the disk and extract all the files into that folder.
Listing out the files will also give you the metadata associated with each file. In particular, it will tell you the type and auxiliary type for the files.
omf
omf
is a rather complicated tool which is designed to extract relocatable segments from OMF files. Apple IIgs executables (.s16 files) and system tools (ex. SYSTEM/TOOLS/TOOL025) are in OMF format.
You first run this tool and pass it an OMF file and it will generate a .map file. This map file is a simple text file that you may edit. Each line is in the format:
segment:memory location
The segment
is the segment number from the OMF file, and memory location
is where in memory to relocate that segment.
omf
does its best to automatically pack all the relocatable segments into the smallest memory possible, starting at $2/0000
. You can change the starting memory address with the -o
or --org
argument. If you wish to manually specify where each segment should go in memory, feel free to edit the .map file however you wish.
The next step is to run omf
again, this time specifying the map file with -m
or --map
. This will apply the .map to the OMF and output each segment along with a corresponding .map file. omf
will throw an error if the segments cannot be mapped as the .map file dictates (for example, you modified the map file so that the segments accidentally overlap).
omf
will modify each segment, applying the proper relocations before outputting the segment. If you wish to change where a segment is in memory, you should modify the original .map file and re-run omf
. The resulting output files will be hardcoded for those specific memory locations.
The segment outputs will be named segX
and segX.map
, where X
is the segment number, in hex. If you wish to change the prefix from seg
use the -p
or --prefix
argument.
At this point, you can use the resulting segment files and map files as input to the regs
tool.
regs
regs
is my 65816 tracing disassembler. It can be used in conjunction with the .map files created by omf
or on its own. Since it is a tracing disassembler, it will start disassembly at a given location, and keep disassembling, following all possible paths. Everything not disassembled will be assumed to be data and shown in a hex view.
regs by itself
You can call regs
and simply pass it a binary file and it will attempt to disassemble it. You can specify where in memory it should load the file before disassembly with the -o
or --org
argument. You can also control whether or not the disassembler is in emulation mode or 16-bit mode with various command-line arguments. Emulation mode is also useful for disassembling 8-bit Apple II software. Use -e
to start in emulation mode (default native mode), -m
to start with an 8-bit accumulator (default 16-bit), and -x
to start with 8-bit index registers (default 16-bit).
regs with .map files
regs
with .map files is where the disassembler really shines. You can call regs
and pass it a .map file generated by omf
and have real control over the disassembly. The .map file is designed to be edited by hand. The format is as follows:
gMAP "seg1"
sORG $30000
$30000:
$30053:mx
$31066:e
$35440:d <myTable>
The first line specifies the segment file that this map applies to. The filename in the quotes should be relative to the current directory.
The next line specifies where the segment belongs in memory. Do not edit this if the segment was created by omf
, since it has also been hardcoded in the binary.
The next lines are a list of entry points to begin disassembly at. If, when analyzing the disassembly you find a switch case encoded as an indirect jump, you can take that list of jumps and add them to the map file and re-run regs
to disassemble the previously un-disassembled data. As you work through a disassembly, you may end up with a map file with hundreds of entry points, that's normal.
The flags after the colon are optional, and specify whether emulation mode should be enabled, or 16-bit or 8-bit accumulator and index registers should be used. It defaults to native mode with 16-bit registers.
After the flags, you may optionally give the address a symbol name. Whenever this memory location is referenced in the code, the symbol name will appear as a comment.
You can also use bank-separators if you wish. $3/1066
is the same as $31066
.
The d
flag is unique in that it identifies the address as a data location and
not an entry point. This is used to give variables symbol names.
Pascal folder
You'll notice a pascal folder in this repository. These are the original GS/OS pascal header files. This is to make it easier for you to look up the arguments and structures of various tool calls you'll come across when disassembling.
Examples
The flexibility of these tools makes their use a little complicated. So here are some examples of how to go about disassembling various things.
Disassembling an S16
I'll be using the S16 from Dream Zone as an example.
Generate a basic map of your S16:
$ omf dream.s16
This will create a file called dream.s16.map
, which we could edit if we choose. We'll leave it as it is. Extract the segments of the OMF with:
$ omf --map=dream.s16.map dream.s16 --prefix=dream
This will create files dream1
to dream5
as well as dream1.map
to dream5.map
.
The program's entry point is always the beginning of the first segment, so we'll start there.
$ regs dream1.map > dream1.s
This will disassemble the entry point. We can then modify the map to further refine the disassembly if we wish.
Disassembling a Tool
This works the same as disassembling an S16, but with an important difference.
We'll start the same, generating a map and extracting it.
$ omf TOOL025
$ omf --map=TOOL025.map TOOL025
Now, we'll remove all disassembly instructions from the map. You'll see why in a second. So we edit the map file to look like the following:
gMAP "seg1"
sORG $20000
That's it.. no disassembly instructions. Now we run the disassembler:
$ regs seg1.map > seg1.s
This will just give us a hex dump of the segment. That's actually what we want. All tools start with a tool table. The first dword specifies the number of tools in this toolset. The next dwords all contain addresses (minus 1) of the various tool entry points.
Let's say I want to disassemble NoteOn. We check the pascal folder and discover that it's tool $0b inside the $19 toolset. Which is the TOOL025 file we're working on (the tool numbers in the filenames are in decimal). So we calculate the offset to that entry point.
$0b * 4 + $20000 = $2:002c
If we look at the hex dump at that location we'll discover the entry point of NoteOn: $2:02dd
. Well that's minus one, so we add the real entry point to the .map file:
gMAP "seg1"
sORG $20000
$2/02de:
And rerun the disassembler.
$ regs seg1.map > seg1.s
We have just disassembled the NoteOn function.
Disassembling a Specific Tool Call in ROM
Let's say I want to disassemble WriteRamBlock. We discover it's in the sound toolset $08. If you search, you'll discover that there isn't a TOOL008 anywhere, so we'll have to pull it from ROM. I'll be using an older 128k ROM just because it's convenient.
First thing I do, is actually hand make a rom.map
file for the ROM.
gMAP "APPLE2GS.ROM"
sORG $fe0000
$fe/0000:
and disassemble it.
$ regs rom.map > rom.s
This is actually the bootstrap that initializes the $e1/0000
tool call entrypoint. I notice it copies over a block of memory from $fe/0051
into $e1/0000
. So we add $fe/0051
to the disassembly list of the map file, and disassemble it again.
Following along with the disassembly, we discover that there's a toolset list starting at $fe/012f
. It starts with a dword with the number of toolsets in the ROM, followed by a list of offsets to the various toolsets. We want toolset 8 for the sound toolset.
$8 * 4 + $fe012f = $fe014f
Look up the dword in that location and I find that the toolset is located at $ff/3e00
. If you then jump to that location, you'll find this is in the exact same format as a tool on disk. It starts with a tool table. WriteRamBlock is tool 9.
$9 * 4 + $ff3e00 = $ff3e24
At that location, we discover the offset to the tool entry point is $ff/41a4
so we'll add $ff/41a5
to the map file and rerun the disassembly.
Boom, we have just disassembled a specific tool call from ram.
Disassembling a simple ProDOS executable
ProDOS binaries aren't relocatable and don't have anything inside them that specifies where in RAM they should be loaded. However, the filesystem itself does have that information.
Using 2mg
with the -l
or --list
argument will give a list of the
files along with metadata associated with the files. Let's use BASIC.SYSTEM
as an example.
You'll see that BASIC.SYSTEM
has a type of $ff
and auxtype of
$2000
, and 2mg
identifies it as a "sys/ProDOS System File". This is
indeed a simple executable.
The aux type specifies where in RAM to load this executable, in this
case, it's $2000
.
It is also important to note that these executables should start with 8-bit registers.
So we can use all of that information to disassemble this file.
$ regs --org=0x2000 -m -x BASIC.SYSTEM > basic.s
This tells regs to start with 8-bit accumulator and indices, and load the
file starting at $2000
before disassembling it.