mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
docs(amiga): complete AmigaOS 3.1/3.2 developer reference — 172 files across 17 sections
Comprehensive technical documentation covering: - Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives - Boot sequence: cold boot through startup-sequence - Binary format: HUNK executable spec, relocation, debug info - Linking & ABI: .fd files, LVO tables, register calling conventions - Exec kernel: tasks, interrupts, memory, signals, semaphores - AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting - Graphics: planar bitmaps, Copper programming, HAM/EHB modes - Intuition: screens, windows, IDCMP, BOOPSI - Devices: trackdisk, SCSI, serial, timer, audio, keyboard - Libraries: utility, expansion, IFFParse, locale, ARexx - Networking: bsdsocket API, SANA-II, TCP/IP stack comparison - Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging - Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies - CPU & MMU: 68040/060 emulation libs, PMMU, cache management - Driver development: SANA-II, Picasso96/RTG, AHI audio All files include breadcrumb navigation. No local paths or proprietary content.
This commit is contained in:
parent
f07a368bf1
commit
21751c0025
172 changed files with 19701 additions and 0 deletions
48
05_reversing/README.md
Normal file
48
05_reversing/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
[← Home](../README.md)
|
||||
|
||||
# Reverse Engineering AmigaOS Binaries
|
||||
|
||||
## Overview
|
||||
|
||||
This section provides a systematic methodology for reverse engineering AmigaOS executables and shared libraries using IDA Pro (or Ghidra with the Amiga plugin), with focus on:
|
||||
|
||||
- Reconstructing the library JMP table
|
||||
- Identifying compiler-specific code patterns
|
||||
- Understanding the exec/dos calling convention at the assembly level
|
||||
- Tracing library patches (SetFunction)
|
||||
- Case studies from real Amiga software
|
||||
|
||||
## Contents
|
||||
|
||||
| File | Topic |
|
||||
|---|---|
|
||||
| [methodology.md](methodology.md) | Step-by-step RE workflow for Amiga HUNK binaries |
|
||||
| [ida_setup.md](ida_setup.md) | IDA Pro configuration for 68k/Amiga analysis |
|
||||
| [compiler_fingerprints.md](compiler_fingerprints.md) | Compiler identification by code patterns |
|
||||
| [library_reconstruction.md](library_reconstruction.md) | Reconstructing unknown library JMP tables |
|
||||
| [patching_techniques.md](patching_techniques.md) | Surgical binary patching methods |
|
||||
| [case_studies/](case_studies/) | Real-world RE walkthroughs |
|
||||
| [case_studies/ramdrive_device.md](case_studies/ramdrive_device.md) | ramdrive.device RE walkthrough |
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Know the ABI first** — All library calls are `JSR LVO(A6)`. Before reversing any function, identify which library A6 holds using the `lib_Node.ln_Name` string at `base+$00`.
|
||||
2. **Use .fd files** — The NDK39 `.fd` files give you every function name and parameter mapping for free.
|
||||
3. **Relocations are your friend** — HUNK_RELOC32 entries tell you exactly which longwords are inter-hunk references, making it easy to distinguish code from data.
|
||||
4. **Compiler signatures reduce work** — SAS/C vs GCC produces distinct prologues. Identifying the compiler narrows the pattern space dramatically.
|
||||
|
||||
## Tool Setup
|
||||
|
||||
| Tool | Purpose |
|
||||
|---|---|
|
||||
| IDA Pro 7.x | Primary disassembler and decompiler (Hex-Rays) |
|
||||
| IDA Amiga plugin | HUNK loader, HUNK_SYMBOL import |
|
||||
| `hunkinfo` | Quick hunk/symbol/reloc dump |
|
||||
| Ghidra + AmigaOS plugin | Free alternative to IDA |
|
||||
| wack / MonAm | On-device debugger |
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `fd/`, `include/`
|
||||
- ADCD 2.1: complete library autodocs
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* and *Devices*
|
||||
129
05_reversing/case_studies/ramdrive_device.md
Normal file
129
05_reversing/case_studies/ramdrive_device.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Case Study — ramdrive.device Structure Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
`ramdrive.device` is the Amiga's built-in RAM disk device. It ships in Kickstart ROM and implements the `trackdisk.device`-compatible interface on top of allocated Chip/Fast RAM. Analysing it teaches exec device architecture, IORequest handling, and the device-as-library pattern.
|
||||
|
||||
---
|
||||
|
||||
## Locating ramdrive.device in ROM
|
||||
|
||||
```bash
|
||||
# Find the resident tag in Kickstart ROM dump:
|
||||
python3 - <<'EOF'
|
||||
import struct, sys
|
||||
|
||||
rom = open("kick31.rom", "rb").read()
|
||||
for i in range(0, len(rom)-4, 2):
|
||||
tag = struct.unpack_from(">H", rom, i)[0]
|
||||
if tag == 0x4AFC: # RomTag magic
|
||||
rt_matchword = struct.unpack_from(">H", rom, i)[0]
|
||||
rt_matchtag = struct.unpack_from(">I", rom, i+2)[0]
|
||||
rt_name = struct.unpack_from(">I", rom, i+14)[0]
|
||||
# print offset and map rt_name to string
|
||||
print(f"RomTag @ ROM+{i:#x}")
|
||||
EOF
|
||||
```
|
||||
|
||||
The RomTag for `ramdrive.device` has `RT_TYPE=NT_DEVICE` and `RT_NAME="ramdrive.device"`.
|
||||
|
||||
---
|
||||
|
||||
## Device Structure Layout
|
||||
|
||||
`ramdrive.device` extends `struct Device` (which extends `struct Library`):
|
||||
|
||||
```c
|
||||
struct RAMDriveBase {
|
||||
struct Device rd_Device; /* standard device base */
|
||||
/* private fields follow */
|
||||
APTR rd_RAMStart; /* pointer to allocated RAM block */
|
||||
ULONG rd_RAMSize; /* total size */
|
||||
ULONG rd_BlockSize; /* always 512 */
|
||||
ULONG rd_NumBlocks; /* RAMSize / BlockSize */
|
||||
struct MinList rd_Units; /* list of open units */
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Standard Device Vectors (LVO)
|
||||
|
||||
| Offset | Vector | Description |
|
||||
|---|---|---|
|
||||
| −6 | `Open` | Open a unit (unit number in io_Unit) |
|
||||
| −12 | `Close` | Close unit, decrement open count |
|
||||
| −18 | `Expunge` | Unload if no users |
|
||||
| −24 | `Reserved` | NULL |
|
||||
| −30 | `BeginIO` | Queue or execute an IORequest |
|
||||
| −36 | `AbortIO` | Cancel pending IORequest |
|
||||
|
||||
`BeginIO` is the heart of any device driver — it dispatches on `io_Command`.
|
||||
|
||||
---
|
||||
|
||||
## IORequest Command Handling
|
||||
|
||||
```c
|
||||
void BeginIO(struct IORequest *ior) {
|
||||
struct IOStdReq *io = (struct IOStdReq *)ior;
|
||||
switch (io->io_Command) {
|
||||
case CMD_READ: rd_Read(io); break;
|
||||
case CMD_WRITE: rd_Write(io); break;
|
||||
case CMD_CLEAR: rd_Clear(io); break;
|
||||
case TD_FORMAT: rd_Format(io); break;
|
||||
case TD_GETGEOMETRY: rd_Geometry(io); break;
|
||||
default:
|
||||
io->io_Error = IOERR_NOCMD;
|
||||
ReplyMsg(&io->io_Message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `CMD_READ` Implementation
|
||||
|
||||
```c
|
||||
void rd_Read(struct IOStdReq *io) {
|
||||
UBYTE *src = rdbase->rd_RAMStart + io->io_Offset;
|
||||
CopyMem(src, io->io_Data, io->io_Length);
|
||||
io->io_Actual = io->io_Length;
|
||||
io->io_Error = 0;
|
||||
ReplyMsg(&io->io_Message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Allocation Strategy
|
||||
|
||||
On initialisation, `ramdrive.device` uses `AllocMem`:
|
||||
|
||||
```c
|
||||
rdbase->rd_RAMStart = AllocMem(rdbase->rd_RAMSize,
|
||||
MEMF_PUBLIC | MEMF_CLEAR);
|
||||
```
|
||||
|
||||
Later requests can pass `MEMF_CHIP` to force chip RAM allocation (useful for audio/graphics DMA sources).
|
||||
|
||||
---
|
||||
|
||||
## Disassembly Landmarks in IDA
|
||||
|
||||
After loading Kickstart ROM in IDA with M68k + HUNK/ROM loader:
|
||||
|
||||
1. Search for string `"ramdrive.device"` → find RomTag
|
||||
2. `RT_INIT` pointer → initialization function
|
||||
3. `RT_INIT` calls `MakeLibrary` then `AddDevice`
|
||||
4. The device base is stored — follow to find `BeginIO` function
|
||||
5. `BeginIO` switch table → individual command handlers
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/devices.h`, `exec/io.h`, `devices/trackdisk.h`
|
||||
- `06_exec_os/io_requests.md` — IORequest structure and dispatch
|
||||
- `10_devices/trackdisk_device.md` — TD_* command codes
|
||||
- Kickstart 3.1 ROM dump (required for disassembly)
|
||||
183
05_reversing/compiler_fingerprints.md
Normal file
183
05_reversing/compiler_fingerprints.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
[← Home](../README.md) · [Reverse Engineering](README.md)
|
||||
|
||||
# Compiler Fingerprints — Identifying the Toolchain
|
||||
|
||||
## Overview
|
||||
|
||||
Knowing which compiler produced an Amiga binary dramatically reduces reverse engineering effort. Each compiler has distinctive code generation patterns at the function prologue, epilogue, calling sequence, and global variable access levels.
|
||||
|
||||
---
|
||||
|
||||
## SAS/C 6.x Fingerprints
|
||||
|
||||
### Function Prologue
|
||||
```asm
|
||||
; Classic SAS/C stack frame with A5 as frame pointer:
|
||||
LINK A5, #-N ; allocate N bytes on stack
|
||||
MOVEM.L D2-D7/A2-A4, -(SP) ; save callee-saved registers
|
||||
```
|
||||
|
||||
Alternatively (small functions):
|
||||
```asm
|
||||
LINK A5, #0 ; minimal frame (no locals)
|
||||
MOVE.L D2, -(SP) ; save only used regs
|
||||
```
|
||||
|
||||
### Function Epilogue
|
||||
```asm
|
||||
MOVEM.L (SP)+, D2-D7/A2-A4 ; restore registers (reverse order)
|
||||
UNLK A5 ; deallocate stack frame
|
||||
RTS ; return
|
||||
```
|
||||
|
||||
### Global Variable Access
|
||||
```asm
|
||||
; SAS/C: absolute addressing (with HUNK_RELOC32):
|
||||
MOVE.L $0000BEEF, D0 ; absolute address — gets relocated
|
||||
MOVEA.L _DOSBase, A6 ; load from global via absolute ref
|
||||
```
|
||||
|
||||
### Library Calls
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6 ; always load from named global
|
||||
JSR -48(A6) ; Write LVO
|
||||
```
|
||||
|
||||
### Identifying Strings
|
||||
Look for:
|
||||
- `"dos.library"` string in DATA hunk — opened by startup
|
||||
- `"SAS/C"` or `"SAS C"` in the ID string of any custom library written with SAS/C
|
||||
- The startup `_ReturnCode` variable name in HUNK_SYMBOL
|
||||
|
||||
---
|
||||
|
||||
## GCC (m68k-amigaos / bebbo) Fingerprints
|
||||
|
||||
### Function Prologue
|
||||
```asm
|
||||
; GCC: no frame pointer (default -fomit-frame-pointer)
|
||||
MOVEM.L D2/D3/A2, -(SP) ; save only actually-used regs
|
||||
; (no LINK instruction)
|
||||
```
|
||||
|
||||
Or with frame pointer (`-fno-omit-frame-pointer`):
|
||||
```asm
|
||||
LINK A6, #-N ; GCC uses A6 as frame pointer (not A5!)
|
||||
MOVEM.L D2/A2, -(SP)
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> GCC uses **A6** as frame pointer when frame pointers are enabled. SAS/C uses **A5**. This is the primary disambiguation between the two.
|
||||
|
||||
### Function Epilogue
|
||||
```asm
|
||||
MOVEM.L (SP)+, D2/D3/A2 ; restore
|
||||
RTS
|
||||
; (no UNLK — GCC prefers to adjust SP directly)
|
||||
```
|
||||
|
||||
### Global Variable Access (PC-relative)
|
||||
```asm
|
||||
; GCC -fpic: PC-relative access to globals:
|
||||
LEA _SysBase(PC), A0 ; PC-relative address of global
|
||||
MOVEA.L (A0), A6 ; dereference to get library base
|
||||
|
||||
; Alternative (without PIC):
|
||||
MOVEA.L (_DOSBase).L, A6 ; absolute with reloc (similar to SAS/C)
|
||||
```
|
||||
|
||||
### Library Calls
|
||||
```asm
|
||||
; GCC inline stubs emit 16-bit short JSR:
|
||||
JSR -198(A6) ; same visual result as SAS/C
|
||||
```
|
||||
|
||||
But the surrounding code differs:
|
||||
```asm
|
||||
; GCC: tighter register use, less stack traffic
|
||||
MOVEA.L (_SysBase).L, A6
|
||||
MOVE.L #$400, D0 ; byteSize
|
||||
MOVE.L #2, D1 ; MEMF_CHIP
|
||||
JSR -198(A6) ; AllocMem
|
||||
```
|
||||
|
||||
### Identifying Strings
|
||||
- `"libnix"` or `"clib2"` version strings in DATA hunk
|
||||
- GCC version string in `.comment` section (if present): `"GCC 6.5.0b ..."`
|
||||
- `__main`, `__exit`, `__parse_args` as function names from HUNK_SYMBOL
|
||||
|
||||
---
|
||||
|
||||
## VBCC Fingerprints
|
||||
|
||||
VBCC is the most common modern AmigaOS compiler and produces very clean, standards-compliant code.
|
||||
|
||||
### Function Prologue
|
||||
```asm
|
||||
; VBCC: highly similar to GCC, no LINK by default
|
||||
MOVEM.L D2-D5/A2-A3, -(SP) ; exact set of used regs
|
||||
```
|
||||
|
||||
### Distinguishing VBCC from GCC
|
||||
|
||||
| Pattern | GCC | VBCC |
|
||||
|---|---|---|
|
||||
| `__saveds` keyword | Yes (some stubs) | Yes |
|
||||
| Tail calls via JMP | Rare | Common |
|
||||
| Stack checking | Optional (`-stack-check`) | Optional |
|
||||
| `move.l #imm, An` | `movea.l #imm, An` | Same |
|
||||
| BRA to epilogue | Sometimes | Common |
|
||||
| register int a0 | Supported | Supported |
|
||||
|
||||
VBCC often generates more BRA→epilogue patterns where GCC inlines the epilogue code.
|
||||
|
||||
### Identifying Strings
|
||||
- `"vbcc"` in any metadata strings
|
||||
- VBCC version string: `"vbcc (c) in 1995-2020 by Volker Barthelmann"`
|
||||
|
||||
---
|
||||
|
||||
## Aztec C / Manx C
|
||||
|
||||
Rare but present in 1.x/2.x era software. Distinctive:
|
||||
|
||||
```asm
|
||||
; Aztec C: uses A4 as small-data register (AmigaOS __far model)
|
||||
MOVEA.L _DOSBase, A6 ; absolute refs
|
||||
MOVE.L A4, -(SP) ; A4 preserved (small-data base)
|
||||
```
|
||||
|
||||
Aztec C often uses a different calling convention for internal functions — examine carefully before assuming standard lib-call convention.
|
||||
|
||||
---
|
||||
|
||||
## Assembler-Only Code
|
||||
|
||||
Some core library routines and demos are pure assembly. Identifying features:
|
||||
- No compiler prologue pattern
|
||||
- `MOVEM.L` register lists tend to be maximally specified
|
||||
- Copper/blitter setup code appears directly
|
||||
- May use `SECTION` macros instead of implicit hunk ordering
|
||||
|
||||
---
|
||||
|
||||
## Quick Fingerprint Checklist
|
||||
|
||||
```
|
||||
□ Does function prologue use LINK A5? → SAS/C
|
||||
□ Does function prologue use LINK A6? → GCC with -fno-omit-frame-pointer
|
||||
□ No LINK at all, just MOVEM.L? → GCC/VBCC (check other patterns)
|
||||
□ PC-relative globals (LEA x(PC))? → GCC -fpic or VBCC
|
||||
□ Absolute globals + HUNK_RELOC32? → SAS/C or GCC without -fpic
|
||||
□ HUNK_SYMBOL has __main, __exit? → GCC/libnix
|
||||
□ HUNK_SYMBOL has _c_start, _main? → SAS/C
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- SAS/C 6.x Programmer's Guide — code generation appendix
|
||||
- GCC m68k-amigaos port (bebbo): https://github.com/bebbo/amiga-gcc
|
||||
- VBCC manual: http://www.ibaug.de/vbcc/doc/vbcc.html
|
||||
- Aztec C 68k manual (archive.org)
|
||||
110
05_reversing/dynamic/enforcer_mungwall.md
Normal file
110
05_reversing/dynamic/enforcer_mungwall.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Enforcer and MungWall — Memory Violation Tracing
|
||||
|
||||
## Overview
|
||||
|
||||
**Enforcer** (by Michael Sinz) and **MungWall** are the two canonical Amiga memory debugging tools. They catch illegal memory accesses and heap corruption at runtime, providing the equivalent of AddressSanitizer for AmigaOS.
|
||||
|
||||
---
|
||||
|
||||
## Enforcer
|
||||
|
||||
Enforcer uses the 68020+ MMU (or software patching on 68000) to trap accesses to:
|
||||
- Address `$0000–$07FF` (lower 2 KB — reserved vectors and exec structures)
|
||||
- Odd-addressed word/longword reads
|
||||
- Accesses above the installed RAM
|
||||
- Writes to ROM addresses
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
; AmigaOS Shell:
|
||||
run enforcer
|
||||
; or for logging:
|
||||
run enforcer QUIET LOG enforcer.log
|
||||
```
|
||||
|
||||
Enforcer patches the `BusError` exception vector (`$8`). Any illegal access causes a bus error, which Enforcer catches, logs, and (usually) continues.
|
||||
|
||||
### Output Format
|
||||
|
||||
```
|
||||
ENFORCER HIT: by Unknown (Task: "DPaint" at $001234AB)
|
||||
Program Counter: $0023AB12
|
||||
Address Accessed: $0000012C (read longword)
|
||||
Stack Dump: $001234C0 $0001A2B4 ...
|
||||
```
|
||||
|
||||
- **Program Counter** — instruction that caused the hit
|
||||
- **Address Accessed** — illegal address
|
||||
- Cross-reference PC against `HUNK_SYMBOL` names or IDA disassembly
|
||||
|
||||
### Common Causes
|
||||
|
||||
| Hit Pattern | Likely Cause |
|
||||
|---|---|
|
||||
| Access to `$0–$3FF` | NULL pointer dereference |
|
||||
| Access to `$4` (SysBase) without read | Null exec base |
|
||||
| Odd address read (word/long) | Misaligned pointer |
|
||||
| Access to `$B80000–$BFFFFF` | CIA access without correct alignment |
|
||||
| Write to ROM `$F80000+` | Write to Kickstart ROM |
|
||||
|
||||
---
|
||||
|
||||
## MungWall
|
||||
|
||||
MungWall fills `AllocMem()` allocations with a known pattern (`$ABADCAFE`) and adds guard longwords before and after each block (`$DEADBEEF`). On `FreeMem()`, it verifies the guards.
|
||||
|
||||
### What It Catches
|
||||
|
||||
- **Heap underrun** — write before the allocated block (guard before = corrupted)
|
||||
- **Heap overrun** — write past the end of block (guard after = corrupted)
|
||||
- **Use after free** — block is filled with `$DEADBEEF` on free; reads from it will fail if Enforcer is also running
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
run mungwall
|
||||
```
|
||||
|
||||
### Output on Corruption
|
||||
|
||||
```
|
||||
MUNGWALL: Block $001A2000 (size 128) has been overwritten!
|
||||
Header guard: OK
|
||||
Trailer guard: CORRUPTED at +132
|
||||
Caller: $0023BC44 (FreeMem called from here)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Combined Workflow
|
||||
|
||||
1. `run mungwall` first — patches AllocMem/FreeMem
|
||||
2. `run enforcer` — adds MMU-level illegal access detection
|
||||
3. Launch the suspect program
|
||||
4. Any crash produces Enforcer + MungWall output on the serial port / `enforcer.log`
|
||||
5. Cross-reference the PC value with `HUNK_SYMBOL` or IDA to find the exact line
|
||||
|
||||
---
|
||||
|
||||
## Serial Port Logging
|
||||
|
||||
Both tools output via `kprintf` to serial port (115200 8N1). Capture on host:
|
||||
|
||||
```bash
|
||||
# macOS / Linux:
|
||||
screen /dev/cu.usbserial-XXXX 115200
|
||||
# or
|
||||
minicom -D /dev/cu.usbserial-XXXX -b 115200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Enforcer: Michael Sinz — available on Aminet (`util/misc/Enforcer.lha`)
|
||||
- MungWall: original CBM debug tool, available on Aminet
|
||||
- `dynamic/serial_debug.md` — serial output setup
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — exec memory management
|
||||
132
05_reversing/dynamic/live_memory_probing.md
Normal file
132
05_reversing/dynamic/live_memory_probing.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Live Memory Probing
|
||||
|
||||
## Overview
|
||||
|
||||
Live memory probing on a running Amiga means directly reading exec structures — `SysBase`, `LibList`, `TaskReady`, `MemList` — to observe system state without a traditional debugger.
|
||||
|
||||
---
|
||||
|
||||
## SysBase: The Root of Everything
|
||||
|
||||
`SysBase` is always at absolute address `$4` (a pointer to the `ExecBase` structure):
|
||||
|
||||
```c
|
||||
struct ExecBase *SysBase = *((struct ExecBase **)4);
|
||||
printf("exec version: %d.%d\n",
|
||||
SysBase->LibNode.lib_Version,
|
||||
SysBase->LibNode.lib_Revision);
|
||||
```
|
||||
|
||||
In assembly:
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; A6 = SysBase (exec.library base)
|
||||
MOVE.W ($16,A6), D0 ; lib_Version
|
||||
MOVE.W ($18,A6), D1 ; lib_Revision
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Walking the Library List
|
||||
|
||||
```c
|
||||
struct Node *n = SysBase->LibList.lh_Head;
|
||||
while (n->ln_Succ != NULL) {
|
||||
struct Library *lib = (struct Library *)n;
|
||||
printf("%-30s v%d.%d opens=%d\n",
|
||||
lib->lib_Node.ln_Name,
|
||||
lib->lib_Version, lib->lib_Revision,
|
||||
lib->lib_OpenCnt);
|
||||
n = n->ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
This enumerates all currently loaded libraries. Useful for:
|
||||
- Finding if a target library is loaded
|
||||
- Reading `lib_OpenCnt` to detect if your hook is installed
|
||||
- Checking `lib_Flags & LIBF_DELEXP` (expunge pending)
|
||||
|
||||
---
|
||||
|
||||
## Reading `lib_OpenCnt` Live
|
||||
|
||||
```c
|
||||
/* Check if bsdsocket.library is loaded and its open count */
|
||||
struct Library *base = FindName(&SysBase->LibList, "bsdsocket.library");
|
||||
if (base) {
|
||||
printf("bsdsocket: OpenCnt=%d, Version=%d\n",
|
||||
base->lib_OpenCnt, base->lib_Version);
|
||||
}
|
||||
```
|
||||
|
||||
`FindName` scans `ln_Name` in a linked list — it is an exec function at LVO −276.
|
||||
|
||||
---
|
||||
|
||||
## Memory Region Map
|
||||
|
||||
`SysBase->MemList` lists all memory regions:
|
||||
|
||||
```c
|
||||
struct MemHeader *mh = (struct MemHeader *)SysBase->MemList.lh_Head;
|
||||
while (mh->mh_Node.ln_Succ) {
|
||||
printf("Region: %s %08lx–%08lx free=%ld\n",
|
||||
mh->mh_Node.ln_Name,
|
||||
(ULONG)mh->mh_Lower,
|
||||
(ULONG)mh->mh_Upper,
|
||||
mh->mh_Free);
|
||||
mh = (struct MemHeader *)mh->mh_Node.ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
Output example:
|
||||
```
|
||||
Region: chip memory $000000–$1FFFFF free=524288
|
||||
Region: fast memory $200000–$9FFFFF free=6291456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task List Inspection
|
||||
|
||||
```c
|
||||
/* Running tasks: */
|
||||
Forbid();
|
||||
struct Task *t = (struct Task *)SysBase->TaskReady.lh_Head;
|
||||
while (t->tc_Node.ln_Succ) {
|
||||
printf("Task: %-20s pri=%d state=%d\n",
|
||||
t->tc_Node.ln_Name,
|
||||
t->tc_Node.ln_Pri,
|
||||
t->tc_State);
|
||||
t = (struct Task *)t->tc_Node.ln_Succ;
|
||||
}
|
||||
Permit();
|
||||
```
|
||||
|
||||
`Forbid()` / `Permit()` are mandatory — the task list must not change while walking it.
|
||||
|
||||
---
|
||||
|
||||
## Patching Memory Live (Surgical Writes)
|
||||
|
||||
For RE/patching: direct longword write to an OS structure:
|
||||
|
||||
```c
|
||||
/* Example: force a library's version to 99 */
|
||||
Forbid();
|
||||
target_lib->lib_Version = 99;
|
||||
Permit();
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Direct memory writes to OS structures bypass all synchronization. Always use `Forbid()` at minimum; use `Disable()` if modifying interrupt-visible data.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`, `exec/memory.h`, `exec/tasks.h`
|
||||
- `06_exec_os/exec_base.md` — full ExecBase offset table
|
||||
- `06_exec_os/memory_management.md` — MemHeader structure
|
||||
- `05_reversing/dynamic/setfunction_patching.md` — Forbid/Permit patterns
|
||||
118
05_reversing/dynamic/serial_debug.md
Normal file
118
05_reversing/dynamic/serial_debug.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Serial Debugging — kprintf and Serial Output
|
||||
|
||||
## Overview
|
||||
|
||||
The Amiga's built-in serial port is the primary low-level debugging channel. `kprintf()` (kernel printf) and `RawPutChar()` write directly to the serial hardware, bypassing `dos.library` and working even from interrupt context or before OS initialization.
|
||||
|
||||
---
|
||||
|
||||
## `kprintf()` — Kernel Printf
|
||||
|
||||
`kprintf()` is a ROM debug function present in Kickstart 1.3 and later debug ROMs. It formats a string and outputs each character via `RawPutChar`.
|
||||
|
||||
```c
|
||||
/* Prototype (exec internal, not in NDK — declare manually): */
|
||||
void kprintf(const char *fmt, ...);
|
||||
/* Arguments in: D1=fmt, stack args (unlike standard AmigaOS register ABI) */
|
||||
```
|
||||
|
||||
### Calling `kprintf` from Assembly
|
||||
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; SysBase
|
||||
LEA _fmt_str(PC), A0 ; format string
|
||||
MOVE.L A0, -(SP) ; push as stack argument
|
||||
MOVE.L A0, D1 ; some implementations use D1
|
||||
JSR (-$F0,A6) ; RawDoFmt or debug rom entry
|
||||
; OR for ROM debug builds:
|
||||
JSR _kprintf
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `kprintf` is **not available** in standard Kickstart 3.1 release ROMs. Use `debug.lib` stubs (`dprintf`) or `RawDoFmt + RawPutChar` instead.
|
||||
|
||||
---
|
||||
|
||||
## `RawDoFmt` + `RawPutChar` — Universal Approach
|
||||
|
||||
This works on **all** Kickstart versions (1.2+):
|
||||
|
||||
```c
|
||||
/* Format into a buffer and output via RawPutChar */
|
||||
static void serial_putchar(UBYTE c, APTR dummy) {
|
||||
/* write directly to serial data register */
|
||||
volatile UWORD *SERDATR = (UWORD *)0xDFF018;
|
||||
volatile UWORD *SERDATW = (UWORD *)0xDFF030;
|
||||
volatile UWORD *SERDATSTAT;
|
||||
/* Wait for TBE (transmit buffer empty) */
|
||||
while (!(*SERDATR & 0x2000));
|
||||
*SERDATW = 0x0100 | c; /* 8 bits + start bit */
|
||||
}
|
||||
|
||||
void dbg_printf(const char *fmt, ...) {
|
||||
UBYTE buf[256];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
/* RawDoFmt(fmt, args, putChar, buf) */
|
||||
RawDoFmt((STRPTR)fmt, &args,
|
||||
(VOID (*)())serial_putchar, buf);
|
||||
va_end(args);
|
||||
}
|
||||
```
|
||||
|
||||
Or simpler — write to the serial hardware directly:
|
||||
|
||||
```c
|
||||
static void SerPutChar(UBYTE c) {
|
||||
while (!(*((volatile UWORD *)0xDFF018) & 0x2000)); /* wait TBE */
|
||||
*((volatile UWORD *)0xDFF030) = 0x0100 | c;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `debug.lib` (SAS/C)
|
||||
|
||||
SAS/C ships `debug.lib` providing `dprintf`:
|
||||
|
||||
```c
|
||||
#include <debug.h>
|
||||
dprintf("mylib: Open called, name=%s\n", name);
|
||||
```
|
||||
|
||||
Output goes to the serial port at the rate set by SERPER (default 9600 baud on startup, 115200 if set).
|
||||
|
||||
---
|
||||
|
||||
## Setting Baud Rate
|
||||
|
||||
```c
|
||||
/* Set serial to 115200 baud (PAL, 3.546895 MHz clock): */
|
||||
/* SERPER = (clock / (16 * baud)) - 1 */
|
||||
/* = (3546895 / (16 * 115200)) - 1 = 0 */
|
||||
volatile UWORD *SERPER = (UWORD *)0xDFF032;
|
||||
*SERPER = 0x0000; /* 115200 on PAL */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Host-Side Capture
|
||||
|
||||
```bash
|
||||
# macOS (USB-serial adapter):
|
||||
screen /dev/cu.usbserial-XXXX 115200
|
||||
# or:
|
||||
stty -f /dev/cu.usbserial-XXXX 115200 raw && cat /dev/cu.usbserial-XXXX
|
||||
```
|
||||
|
||||
MiSTer FPGA: the UART bridge is exposed on the MiSTer IO board or via the DE10-Nano UART.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h` — `RawDoFmt`, `RawPutChar` LVOs
|
||||
- `01_hardware/ocs_a500/paula_serial.md` — SERPER, SERDATR, SERDATW register details
|
||||
- Aminet: `debug/misc/dprintf.lha`
|
||||
130
05_reversing/dynamic/setfunction_patching.md
Normal file
130
05_reversing/dynamic/setfunction_patching.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# SetFunction — Hooking Library Vectors at Runtime
|
||||
|
||||
## Overview
|
||||
|
||||
`SetFunction()` is the official AmigaOS mechanism for **patching a library's JMP table** at runtime. It installs a custom function at a given LVO, replacing the original, and returns the old function pointer so a trampoline can be constructed.
|
||||
|
||||
---
|
||||
|
||||
## `SetFunction()` API
|
||||
|
||||
```c
|
||||
/* exec/execbase.h */
|
||||
APTR SetFunction(struct Library *library, LONG funcOffset, APTR newFunction);
|
||||
/* Returns: pointer to OLD function */
|
||||
```
|
||||
|
||||
- `library` — target library base (e.g., `DOSBase`)
|
||||
- `funcOffset` — negative LVO offset (e.g., `-30` for `dos.library Open`)
|
||||
- `newFunction` — your replacement function
|
||||
|
||||
---
|
||||
|
||||
## Installing a Hook
|
||||
|
||||
```asm
|
||||
; Example: hook dos.library Write() at LVO -48
|
||||
|
||||
MOVEA.L _SysBase, A6
|
||||
JSR (-120,A6) ; Forbid() — prevent preemption during patch
|
||||
|
||||
MOVEA.L _DOSBase, A1
|
||||
MOVE.L #-48, A0 ; LVO for Write
|
||||
LEA _my_write(PC), A2
|
||||
JSR (-420,A6) ; SetFunction(DOSBase, -48, &my_write)
|
||||
MOVE.L D0, _orig_write ; save original function pointer
|
||||
|
||||
JSR (-126,A6) ; Permit()
|
||||
```
|
||||
|
||||
### C equivalent:
|
||||
|
||||
```c
|
||||
static APTR orig_write;
|
||||
|
||||
void install_hook(void) {
|
||||
Forbid();
|
||||
orig_write = SetFunction((struct Library *)DOSBase, -48,
|
||||
(APTR)my_write_hook);
|
||||
Permit();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writing a Trampoline
|
||||
|
||||
The hook function must:
|
||||
1. Perform its instrumentation
|
||||
2. Call the original via the saved pointer
|
||||
3. Return with the original return value in D0
|
||||
|
||||
```asm
|
||||
_my_write:
|
||||
; D1 = file handle, D2 = buffer, D3 = length (Write args)
|
||||
MOVEM.L D0-D7/A0-A6, -(SP) ; save all (we may corrupt anything)
|
||||
|
||||
; ... instrumentation: log args, patch buffer, etc. ...
|
||||
|
||||
MOVEM.L (SP)+, D0-D7/A0-A6
|
||||
MOVEA.L _orig_write, A0
|
||||
JMP (A0) ; jump to original — not JSR; let original RTS
|
||||
```
|
||||
|
||||
In C (with `__asm` constraints):
|
||||
```c
|
||||
LONG __asm my_write_hook(register __d1 BPTR fh,
|
||||
register __d2 APTR buf,
|
||||
register __d3 LONG len) {
|
||||
/* instrumentation */
|
||||
return ((LONG (*)(BPTR,APTR,LONG))orig_write)(fh, buf, len);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Restoring on Exit
|
||||
|
||||
**Critical:** Always restore the original function before the program exits. Failure leaves a dangling pointer in the library JMP table, causing crashes for any subsequent users of the library.
|
||||
|
||||
```c
|
||||
void remove_hook(void) {
|
||||
Forbid();
|
||||
SetFunction((struct Library *)DOSBase, -48, orig_write);
|
||||
Permit();
|
||||
}
|
||||
|
||||
/* Register with atexit: */
|
||||
atexit(remove_hook);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety Considerations
|
||||
|
||||
- `Forbid()` / `Permit()` disable task switching — keep the window minimal
|
||||
- If the hook itself calls OS functions, use `Disable()` / `Enable()` instead only when interrupts must be excluded
|
||||
- Hooks are system-global — all tasks using the library will go through your hook
|
||||
|
||||
---
|
||||
|
||||
## Common Use Cases in RE
|
||||
|
||||
| Use | Hook | LVO |
|
||||
|---|---|---|
|
||||
| Trace file access | `dos.library Open` | −30 |
|
||||
| Intercept writes | `dos.library Write` | −48 |
|
||||
| Monitor memory allocation | `exec.library AllocMem` | −198 |
|
||||
| Log task creation | `exec.library AddTask` | −282 |
|
||||
| Spy on library opens | `exec.library OpenLibrary` | −552 |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`
|
||||
- ADCD 2.1: `SetFunction` autodoc
|
||||
- `05_reversing/dynamic/live_memory_probing.md` — SysBase structure access
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — SetFunction chapter
|
||||
190
05_reversing/ida_setup.md
Normal file
190
05_reversing/ida_setup.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
[← Home](../README.md) · [Reverse Engineering](README.md)
|
||||
|
||||
# IDA Pro Setup for Amiga 68k Binaries
|
||||
|
||||
## Requirements
|
||||
|
||||
| Component | Version / Notes |
|
||||
|---|---|
|
||||
| IDA Pro | 7.0+ (7.5+ recommended for Hex-Rays decompiler quality) |
|
||||
| Processor module | M68k — included in IDA standard install |
|
||||
| HUNK loader | Included in some IDA builds; community plugin if absent |
|
||||
| Hex-Rays decompiler | 68k decompiler license required for pseudocode |
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the HUNK Loader Plugin
|
||||
|
||||
IDA does not ship with an Amiga HUNK loader in all versions. The community loader is available at:
|
||||
|
||||
- https://github.com/wiemerc/ida-amiga (Python-based, IDA 7.x)
|
||||
- Alternative: load as raw binary at base address 0, then manually define hunk segments
|
||||
|
||||
### Installing the plugin:
|
||||
|
||||
```bash
|
||||
cp amiga_hunk.py ~/.idapro/plugins/ # macOS/Linux
|
||||
cp amiga_hunk.py %APPDATA%\Hex-Rays\IDA Pro\plugins\ # Windows
|
||||
```
|
||||
|
||||
Restart IDA. The loader appears in the format list when opening `.library`, `.device`, or executables with `$3F3` magic.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Processor Configuration
|
||||
|
||||
When loading, select:
|
||||
- **Processor**: Motorola 680x0
|
||||
- **Variant**: 68020 for A1200/A4000 targets, 68000 for A500 targets
|
||||
- **Endianness**: Big-endian (automatic for 68k)
|
||||
|
||||
Changing processor after load:
|
||||
`Edit → Plugins → Change processor type`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Segment Setup (Manual Load Fallback)
|
||||
|
||||
If not using the HUNK plugin, define segments manually after loading as raw binary:
|
||||
|
||||
```python
|
||||
# IDA Python: create segments for a 2-hunk binary
|
||||
# (code at 0, data at 0x1234)
|
||||
import idc, idaapi
|
||||
|
||||
CODE_BASE = 0x00000000
|
||||
DATA_BASE = 0x00001234
|
||||
|
||||
idc.add_segm(0, CODE_BASE, CODE_BASE + 0x1230, "CODE", "CODE")
|
||||
idc.add_segm(0, DATA_BASE, DATA_BASE + 0x200, "DATA", "DATA")
|
||||
idc.set_segm_class(idc.get_segm_attr(CODE_BASE, idc.SEGATTR_SEL), "CODE")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Define SysBase and Library Bases
|
||||
|
||||
Tell IDA about global library pointers so it can track A6:
|
||||
|
||||
```python
|
||||
# Mark $00000004 as SysBase (exec pointer)
|
||||
idc.create_dword(4)
|
||||
idc.set_name(4, "SysBase", idc.SN_NOWARN)
|
||||
idc.set_cmt(4, "exec.library base pointer (absolute address 4)", 0)
|
||||
```
|
||||
|
||||
For each library base found during analysis:
|
||||
```python
|
||||
idc.set_name(ea_of_libbase_var, "_DOSBase", idc.SN_NOWARN)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Apply FLIRT Signatures
|
||||
|
||||
FLIRT (Fast Library Identification and Recognition Technology) signatures identify known library startup and runtime functions. Amiga-specific signature files:
|
||||
|
||||
- `m68k_amiga_sasc6.sig` — SAS/C 6.x standard library
|
||||
- `m68k_amiga_gcc_libnix.sig` — GCC libnix
|
||||
- `m68k_amiga_vbcc.sig` — VBCC
|
||||
|
||||
Apply via: `File → Load file → FLIRT signature file`
|
||||
|
||||
If no prebuilt sigs are available, create them with IDA's PELF tool from known `.lib` files.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Import AmigaOS Types
|
||||
|
||||
Apply AmigaOS structure definitions:
|
||||
|
||||
### Option A: Type Library (.til)
|
||||
|
||||
If an AmigaOS `.til` is available:
|
||||
```
|
||||
View → Open Subviews → Type Libraries → Insert → select amigaos.til
|
||||
```
|
||||
|
||||
### Option B: Parse Headers Directly
|
||||
|
||||
```
|
||||
File → Load file → Parse C header file
|
||||
```
|
||||
|
||||
Load NDK39 headers (adjust path to your NDK location):
|
||||
```
|
||||
NDK39/include/exec/execbase.h
|
||||
NDK39/include/dos/dosextens.h
|
||||
NDK39/include/graphics/gfxbase.h
|
||||
```
|
||||
|
||||
Set pre-processor defines:
|
||||
```c
|
||||
#define __AMIGA__
|
||||
#define __mc68000__
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Annotate JMP Table Calls
|
||||
|
||||
Run the LVO annotation script:
|
||||
|
||||
```python
|
||||
import idautils, idc, idaapi, re
|
||||
|
||||
# Build LVO→name dict from NDK fd files (partial)
|
||||
EXEC_LVO = {
|
||||
-198: "AllocMem", -210: "FreeMem",
|
||||
-282: "AddTask", -288: "RemTask",
|
||||
-366: "PutMsg", -372: "GetMsg",
|
||||
-552: "OpenLibrary",-414: "CloseLibrary",
|
||||
-420: "SetFunction",-624: "CopyMem",
|
||||
# ... extend from lvo_table.md
|
||||
}
|
||||
|
||||
def annotate_lvos():
|
||||
for seg_ea in idautils.Segments():
|
||||
for func_ea in idautils.Functions(seg_ea, idc.get_segm_end(seg_ea)):
|
||||
for ea in idautils.FuncItems(func_ea):
|
||||
if idc.print_insn_mnem(ea).lower() == 'jsr':
|
||||
op = idc.print_operand(ea, 0)
|
||||
m = re.match(r'(-\d+)\(A6\)', op, re.IGNORECASE)
|
||||
if m:
|
||||
lvo = int(m.group(1))
|
||||
name = EXEC_LVO.get(lvo)
|
||||
if name:
|
||||
idc.set_cmt(ea, f"exec: {name}", 0)
|
||||
|
||||
annotate_lvos()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Hex-Rays Decompiler Tips for 68k
|
||||
|
||||
The Hex-Rays 68k decompiler needs type information to produce clean pseudocode:
|
||||
|
||||
1. **Set function types** — mark return type and argument registers for library call wrappers
|
||||
2. **Suppress spurious variables** — many D-register temps appear; use `Collapse variable` or retype
|
||||
3. **Add `__asm` register hints** for known argument registers
|
||||
|
||||
Example — marking a library function prototype:
|
||||
```c
|
||||
// In IDA Local Types:
|
||||
APTR __cdecl AllocMem_wrap(ULONG byteSize, ULONG requirements);
|
||||
```
|
||||
|
||||
Then apply to call sites via `Y` (set type) on the JSR instruction.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- IDA Pro 7.x documentation — processor modules, FLIRT
|
||||
- ida-amiga plugin: https://github.com/wiemerc/ida-amiga
|
||||
- Ghidra Amiga plugin: https://github.com/lab313ru/ghidra_amiga_ldr
|
||||
- Ghidra m68k fixer: https://github.com/lab313ru/m68k_fixer
|
||||
- BartmanAbyss Ghidra Amiga: https://github.com/BartmanAbyss/ghidra-amiga — Amiga HUNK loader + helpers for Ghidra
|
||||
- IDA Pro m68k extensions: https://github.com/LucienMP/idapro_m68k — GDB step-over, type info
|
||||
- NDK39: header files for type import
|
||||
217
05_reversing/methodology.md
Normal file
217
05_reversing/methodology.md
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
[← Home](../README.md) · [Reverse Engineering](README.md)
|
||||
|
||||
# RE Methodology — AmigaOS HUNK Binaries
|
||||
|
||||
## Phase 1: Initial Triage
|
||||
|
||||
### 1.1 Identify the File Type
|
||||
|
||||
```bash
|
||||
xxd target.library | head -4
|
||||
```
|
||||
|
||||
| First 4 bytes | Type |
|
||||
|---|---|
|
||||
| `00 00 03 F3` | Executable (HUNK_HEADER) |
|
||||
| `00 00 03 E7` | Object file (HUNK_UNIT) |
|
||||
| `70 FF 60 FA` | Resident tag (RomTag) |
|
||||
|
||||
### 1.2 Dump HUNK Structure
|
||||
|
||||
```bash
|
||||
hunkinfo target.library
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
Hunk 0: CODE size=$1234 offset=$00012000
|
||||
Hunk 1: DATA size=$0200 offset=$00013240
|
||||
Hunk 2: BSS size=$0400
|
||||
Symbols: 45
|
||||
Relocs: 127
|
||||
```
|
||||
|
||||
### 1.3 Verify Library Header
|
||||
|
||||
For a `.library` file, hunk 0 should start with a `RomTag` (resident module tag):
|
||||
|
||||
```
|
||||
$4AFC ; RT_MATCHWORD (hex: MOVEQ #0, D0 in context)
|
||||
```
|
||||
|
||||
Real `RomTag` starts at word `$4AFC`:
|
||||
```asm
|
||||
_romtag:
|
||||
DC.W RTC_MATCHWORD ; $4AFC
|
||||
DC.L _romtag ; RT_MATCHTAG (self-pointer)
|
||||
DC.L _endskip ; RT_ENDSKIP
|
||||
DC.B RTF_AUTOINIT ; RT_FLAGS
|
||||
DC.B VERSION ; RT_VERSION
|
||||
DC.B NT_LIBRARY ; RT_TYPE
|
||||
DC.B PRIORITY ; RT_PRI
|
||||
DC.L _libname ; RT_NAME
|
||||
DC.L _idstring ; RT_IDSTRING
|
||||
DC.L _inittable ; RT_INIT (or function)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Load in IDA Pro
|
||||
|
||||
### 2.1 Load the File
|
||||
|
||||
File → New → select the binary → select "Amiga HUNK" format (or m68k raw if plugin not available).
|
||||
|
||||
If using the HUNK plugin:
|
||||
- Hunks are mapped to segments: `CODE0`, `DATA0`, `BSS0`, etc.
|
||||
- HUNK_SYMBOL entries become IDA names automatically
|
||||
- HUNK_RELOC32 become IDA fixups
|
||||
|
||||
### 2.2 Set Processor
|
||||
|
||||
`Options → General → Processor type = Motorola 680x0`
|
||||
|
||||
For A1200/A4000 targets, enable `68020` or `68040` instruction sets.
|
||||
|
||||
### 2.3 Apply Library Type Information
|
||||
|
||||
Load the AmigaOS TIL (Type Information Library) if available:
|
||||
- `File → Load file → FLIRT signature file` — use Amiga-specific FLIRT sigs
|
||||
- Or manually: `View → Open Subviews → Type Libraries` → load `amigaos.til`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Locate the Library Base
|
||||
|
||||
For a `.library` file, the library base is created by `MakeLibrary()` at runtime. In the static binary, look for:
|
||||
|
||||
```asm
|
||||
; InitTable for the library:
|
||||
DC.L _libsize ; LIB_POSSIZE (struct size)
|
||||
DC.L _funcTable ; function array pointer
|
||||
DC.L _dataTable ; data init table (or NULL)
|
||||
DC.L _initCode ; init function
|
||||
```
|
||||
|
||||
The function table is an array of absolute function pointers terminated by `-1`:
|
||||
```asm
|
||||
_funcTable:
|
||||
DC.L _Open
|
||||
DC.L _Close
|
||||
DC.L _Expunge
|
||||
DC.L _Reserved
|
||||
DC.L _MyFunc1
|
||||
DC.L _MyFunc2
|
||||
...
|
||||
DC.L -1 ; terminator
|
||||
```
|
||||
|
||||
This is the easiest way to find all library functions from the static binary.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Identify Calling Conventions
|
||||
|
||||
### 4.1 Find Library Calls
|
||||
|
||||
Search for the pattern `JSR -N(A6)` or `JSR -N(A5)` (some code uses A5):
|
||||
|
||||
In IDA, search for instruction text: `jsr -`
|
||||
|
||||
Each hit is a library call. The register `A6` (or A5) holds the library base at that point.
|
||||
|
||||
### 4.2 Trace Library Base
|
||||
|
||||
Trace backwards from the JSR to find where A6 was loaded:
|
||||
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6 ; most common: global variable
|
||||
JSR -48(A6) ; Write
|
||||
|
||||
MOVEA.L D0, A6 ; from return value of OpenLibrary
|
||||
JSR -30(A6) ; Open
|
||||
|
||||
MOVEA.L 4.W, A6 ; SysBase directly
|
||||
JSR -198(A6) ; AllocMem
|
||||
```
|
||||
|
||||
### 4.3 Resolve the LVO
|
||||
|
||||
```python
|
||||
# IDA Python: resolve a JSR -N(A6) to a function name
|
||||
def resolve_lvo(ea):
|
||||
insn = idc.print_insn_mnem(ea)
|
||||
if insn not in ('jsr', 'JSR'):
|
||||
return
|
||||
op = idc.print_operand(ea, 0)
|
||||
# op looks like "-48(A6)" or "-552(a6)"
|
||||
import re
|
||||
m = re.match(r'(-?\d+)\(A6\)', op, re.IGNORECASE)
|
||||
if m:
|
||||
lvo = int(m.group(1))
|
||||
# Look up in your LVO table
|
||||
name = LVO_TABLE.get(lvo, f"unknown_lvo_{-lvo}")
|
||||
idc.set_cmt(ea, f"→ {name} LVO={lvo}", 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Annotate and Name
|
||||
|
||||
### 5.1 Apply Library Function Names
|
||||
|
||||
Using the LVO tables from [lvo_table.md](../04_linking_and_libraries/lvo_table.md), annotate each JSR:
|
||||
|
||||
```python
|
||||
# IDA script: annotate all JSR -N(A6) calls in exec context
|
||||
import idautils, idc, idaapi
|
||||
|
||||
EXEC_LVOS = {
|
||||
-198: "AllocMem",
|
||||
-210: "FreeMem",
|
||||
-282: "AddTask",
|
||||
-552: "OpenLibrary",
|
||||
# ... (full table from lvo_table.md)
|
||||
}
|
||||
|
||||
for ea in idautils.CodeRefsTo(idc.get_name_ea_simple("_SysBase"), 0):
|
||||
# For each reference to SysBase load, find subsequent JSR
|
||||
pass # implement full trace
|
||||
```
|
||||
|
||||
### 5.2 Name Functions
|
||||
|
||||
After resolving all library calls in a function, it becomes clear what the function does. Apply a meaningful name:
|
||||
|
||||
```
|
||||
IDA: Press N on a function → rename to descriptive identifier
|
||||
```
|
||||
|
||||
### 5.3 Define Structures
|
||||
|
||||
Apply AmigaOS structure types:
|
||||
- `View → Open Subviews → Local Types` → import from AmigaOS headers
|
||||
- Or manually define: `ExecBase`, `DosLibrary`, `MsgPort`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Patch Analysis
|
||||
|
||||
Look for evidence of `SetFunction` patching:
|
||||
1. JMP table entries pointing outside the library code segment
|
||||
2. `LIBF_CHANGED` bit set in `lib_Flags`
|
||||
3. Functions that immediately JSR to a stored old-function address
|
||||
|
||||
Look for timer/protection mechanisms:
|
||||
1. Calls to `dos.library CurrentTime()` or `timer.device`
|
||||
2. CIA timer reads (`$BFDE00` area)
|
||||
3. Comparison of tick counts with a threshold
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [ida_setup.md](ida_setup.md) — IDA configuration details
|
||||
- [compiler_fingerprints.md](compiler_fingerprints.md) — compiler identification
|
||||
- [lvo_table.md](../04_linking_and_libraries/lvo_table.md) — complete LVO tables
|
||||
- NDK39: all `.fd` and `include/` files
|
||||
228
05_reversing/patching_techniques.md
Normal file
228
05_reversing/patching_techniques.md
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
[← Home](../README.md) · [Reverse Engineering](README.md)
|
||||
|
||||
# Patching Techniques for Amiga Binaries
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers the methods used to surgically patch AmigaOS HUNK executables and libraries — without access to source code. All techniques operate on the binary file directly.
|
||||
|
||||
---
|
||||
|
||||
## Method 1: NOP Patching
|
||||
|
||||
Replace one or more instructions with `NOP` ($4E71) to eliminate a code path:
|
||||
|
||||
```python
|
||||
# Python: NOP out 6 bytes at offset $1234
|
||||
import struct
|
||||
|
||||
with open("target.library", "r+b") as f:
|
||||
f.seek(0x1234)
|
||||
f.write(b'\x4e\x71' * 3) # 3× NOP = 6 bytes
|
||||
```
|
||||
|
||||
**Use case:** Disable a conditional check:
|
||||
```asm
|
||||
; Before: jumps to expiry code if timer > limit
|
||||
CMPI.L #$12345678, D3
|
||||
BHI.S .expired
|
||||
|
||||
; After: NOP the BHI (2 bytes)
|
||||
CMPI.L #$12345678, D3
|
||||
NOP
|
||||
NOP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method 2: Branch Inversion
|
||||
|
||||
Flip the condition of a branch instruction to always take / never take a path:
|
||||
|
||||
| Original | Patched | Effect |
|
||||
|---|---|---|
|
||||
| `BEQ $4E43` | `BRA $4E43` | Always branch (was: branch if equal) |
|
||||
| `BNE $4E43` | `BRA $4E43` | Always branch (was: branch if not equal) |
|
||||
| `BEQ $4E43` | `NOP`×2 | Never branch (was: branch if equal) |
|
||||
| `BHI $4E43` | `BLS $4E43` | Invert condition |
|
||||
|
||||
Branch instruction bytes:
|
||||
```
|
||||
67 xx BEQ.S (offset xx)
|
||||
66 xx BNE.S
|
||||
6E xx BGT.S
|
||||
6F xx BLE.S
|
||||
60 xx BRA.S
|
||||
```
|
||||
|
||||
Change `BEQ.S` to `BRA.S`: replace first byte `$67` with `$60`.
|
||||
|
||||
---
|
||||
|
||||
## Method 3: Return Value Forcing
|
||||
|
||||
Force a function to always return a specific value:
|
||||
|
||||
```asm
|
||||
; Original: complex check, returns 0 on failure
|
||||
_CheckTimer:
|
||||
LINK A5, #-4
|
||||
... ; timer logic
|
||||
UNLK A5
|
||||
RTS
|
||||
|
||||
; Patched: always return 1 (success/valid)
|
||||
_CheckTimer:
|
||||
MOVEQ #1, D0 ; $7001 — MOVEQ #1, D0 (2 bytes)
|
||||
RTS ; $4E75 (2 bytes)
|
||||
```
|
||||
|
||||
`MOVEQ #1, D0` = `$70 $01` (2 bytes)
|
||||
`RTS` = `$4E $75` (2 bytes)
|
||||
|
||||
Write 4 bytes at the function entry point.
|
||||
|
||||
---
|
||||
|
||||
## Method 4: JMP Redirect
|
||||
|
||||
Redirect a function call to a completely different address:
|
||||
|
||||
```asm
|
||||
; Original call:
|
||||
JSR _CheckAuth ; $4EB9 XXXXXXXX
|
||||
|
||||
; Patched to call our stub instead:
|
||||
JSR _AlwaysTrue ; $4EB9 YYYYYYYY
|
||||
```
|
||||
|
||||
Requires updating the relocation table if the new address is in a different hunk — simpler if both are in the same hunk (no reloc change needed).
|
||||
|
||||
---
|
||||
|
||||
## Method 5: Constant Replacement
|
||||
|
||||
Replace a comparison constant (timer limit, version check value, etc.):
|
||||
|
||||
```asm
|
||||
; Original: 10 retry limit
|
||||
CMPI.L #$0000000A, D3 ; $0A = 10
|
||||
|
||||
; Patched: effectively unlimited retries
|
||||
CMPI.L #$7FFFFFFF, D3 ; max positive longword
|
||||
```
|
||||
|
||||
Find the constant in the binary: `xxd target.library | grep "00 00 00 0a"`
|
||||
Replace with new value at that offset.
|
||||
|
||||
---
|
||||
|
||||
## Method 6: Library JMP Table Patch (Runtime)
|
||||
|
||||
For patching a library's JMP table at runtime (not file-level):
|
||||
|
||||
```asm
|
||||
; Install patch at LVO -30 of DOSBase:
|
||||
MOVEA.L _DOSBase, A0 ; library base
|
||||
MOVE.L #_MyOpen, -28(A0) ; overwrite address bytes of JMP.L at -30
|
||||
; JMP.L = 4E F9 [AAAAAAAA]
|
||||
; address is at offset -30+2 = -28
|
||||
```
|
||||
|
||||
Note: this does not use `SetFunction()` — no `LIBF_CHANGED` flag. Used when you need silent patching.
|
||||
|
||||
---
|
||||
|
||||
## Updating Relocations After Patching
|
||||
|
||||
If a patched longword is in the HUNK_RELOC32 list, the loader will overwrite your patch at load time by adding the hunk base to it. You must either:
|
||||
|
||||
1. **Remove the reloc entry** for that offset from `HUNK_RELOC32`
|
||||
2. **Adjust the stored value** so that after relocation it becomes the desired value
|
||||
|
||||
**Finding reloc entries to remove:**
|
||||
|
||||
```python
|
||||
def remove_reloc_entry(data, hunk_offset, target_offset):
|
||||
"""Remove a specific offset from HUNK_RELOC32 records."""
|
||||
# Parse the file, find HUNK_RELOC32, remove the entry
|
||||
# This requires a full HUNK parser — see hunk_parser.py
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automated Patcher Template (Python)
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Amiga HUNK Binary Patcher
|
||||
"""
|
||||
import struct
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
class AmigaPatcher:
|
||||
def __init__(self, path):
|
||||
with open(path, 'rb') as f:
|
||||
self.data = bytearray(f.read())
|
||||
self.path = path
|
||||
|
||||
def find_pattern(self, pattern: bytes) -> list:
|
||||
"""Find all occurrences of a byte pattern."""
|
||||
results = []
|
||||
idx = 0
|
||||
while True:
|
||||
idx = self.data.find(pattern, idx)
|
||||
if idx == -1:
|
||||
break
|
||||
results.append(idx)
|
||||
idx += 1
|
||||
return results
|
||||
|
||||
def patch_bytes(self, offset: int, new_bytes: bytes, comment: str = ""):
|
||||
old = self.data[offset:offset+len(new_bytes)]
|
||||
print(f"[PATCH] {comment}")
|
||||
print(f" Offset: {offset:#010x}")
|
||||
print(f" Old: {old.hex()}")
|
||||
print(f" New: {new_bytes.hex()}")
|
||||
self.data[offset:offset+len(new_bytes)] = new_bytes
|
||||
|
||||
def nop(self, offset: int, count: int, comment: str = ""):
|
||||
self.patch_bytes(offset, b'\x4e\x71' * count, f"NOP×{count}: {comment}")
|
||||
|
||||
def save(self, out_path: str):
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(self.data)
|
||||
print(f"[SAVE] Written to {out_path}")
|
||||
|
||||
# Example usage:
|
||||
if __name__ == "__main__":
|
||||
p = AmigaPatcher("target.library")
|
||||
|
||||
# Patch 1: NOP out redundant range check
|
||||
p.nop(0x1234, 3, "skip bounds validation BHI branch")
|
||||
|
||||
# Patch 2: Force version check to pass
|
||||
p.patch_bytes(0x5678, bytes([0x70, 0x01, 0x4E, 0x75]),
|
||||
"always return 1 from CheckVersion")
|
||||
|
||||
p.save("target_patched.library")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification After Patching
|
||||
|
||||
1. **Checksum update**: Some libraries check `SumLibrary()` at init — may need to disable that check too
|
||||
2. **Test on hardware**: Use the MiSTer or UAE emulator to verify the patched binary
|
||||
3. **Regression test**: Ensure patched functions that are chained still work correctly
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [setfunction.md](../04_linking_and_libraries/setfunction.md) — runtime patching
|
||||
- [hunk_relocation.md](../03_loader_and_exec_format/hunk_relocation.md) — relocation interaction
|
||||
- [case_studies/ramdrive_device.md](case_studies/ramdrive_device.md) — complete real-world example
|
||||
296
05_reversing/static/api_call_identification.md
Normal file
296
05_reversing/static/api_call_identification.md
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Identifying OS API Calls in Disassembly
|
||||
|
||||
## Background: How AmigaOS Library Calls Work
|
||||
|
||||
Before diving into identification techniques, it helps to understand the mechanics from first principles.
|
||||
|
||||
### What is a Shared Library?
|
||||
|
||||
On AmigaOS, a **shared library** is a block of code loaded into RAM once and shared by every program that needs it. Programs don't link the OS code into their own executable — they call it indirectly at runtime. This keeps executables small and allows the OS to be upgraded without relinking every application.
|
||||
|
||||
Examples: `dos.library`, `graphics.library`, `intuition.library`.
|
||||
|
||||
### What is a Library Base?
|
||||
|
||||
When you open a library, exec returns a pointer to the **library base** — a `struct Library` that lives in RAM. Immediately *before* this pointer (at negative offsets) sits the **JMP table**: a sequence of `JMP <address>` instructions, one per library function.
|
||||
|
||||
```
|
||||
Memory layout:
|
||||
|
||||
lib_base - 30: JMP Open_impl ← first user function
|
||||
lib_base - 24: JMP Reserved
|
||||
lib_base - 18: JMP Expunge
|
||||
lib_base - 12: JMP Close
|
||||
lib_base - 6: JMP Open (standard)
|
||||
lib_base + 0: struct Library ← pointer returned by OpenLibrary()
|
||||
lib_base + N: private library data
|
||||
```
|
||||
|
||||
Every program that wants to call `dos.library Open()` stores the library base somewhere and calls `JSR -30(A6)`, where A6 holds the library base.
|
||||
|
||||
---
|
||||
|
||||
## What is an LVO?
|
||||
|
||||
**LVO** stands for **Library Vector Offset**. It is the negative byte offset from the library base to a specific function's JMP table slot.
|
||||
|
||||
The formula is:
|
||||
```
|
||||
LVO = −6 × (slot_index + 1)
|
||||
|
||||
slot 0 (Open standard): −6
|
||||
slot 1 (Close standard): −12
|
||||
slot 2 (Expunge): −18
|
||||
slot 3 (Reserved): −24
|
||||
slot 4 (first user fn): −30 ← dos.library Open()
|
||||
slot 5: −36 ← dos.library Close()
|
||||
...
|
||||
```
|
||||
|
||||
So `JSR -30(A6)` means "call the function at LVO −30 in the library whose base is in A6." Every unique LVO in every library maps to exactly one function.
|
||||
|
||||
### Why Negative Offsets?
|
||||
|
||||
The JMP table grows **downward** in memory from the library base. Using negative offsets means programs only need to store a single pointer (the library base) and derive all function entry points from it with a constant displacement. This is the same trick used by C++ vtables.
|
||||
|
||||
---
|
||||
|
||||
## What is an .fd File?
|
||||
|
||||
**`.fd` files** (Function Descriptor files) are part of the Amiga NDK (Native Developer Kit). They are simple text files that declare every public function in a library: its name, argument registers, and LVO (called the **bias** in `.fd` terminology).
|
||||
|
||||
### Example: `dos_lib.fd` (excerpt)
|
||||
|
||||
```
|
||||
##base _DOSBase
|
||||
##bias 30
|
||||
##public
|
||||
Open(name,accessMode)(d1,d2)
|
||||
##bias 36
|
||||
Close(file)(d1)
|
||||
##bias 42
|
||||
Read(file,buffer,length)(d1,d2,d3)
|
||||
##bias 48
|
||||
Write(file,buffer,length)(d1,d2,d3)
|
||||
##bias 54
|
||||
Input()(-)
|
||||
##bias 60
|
||||
Output()(-)
|
||||
##bias 138
|
||||
Delay(timeout)(d1)
|
||||
```
|
||||
|
||||
Reading this:
|
||||
- `##base _DOSBase` — the global variable that holds the library base
|
||||
- `##bias 30` — the **positive** bias; the actual call offset is `−30`
|
||||
- `Open(name,accessMode)(d1,d2)` — function name, argument names, and the registers each argument goes in
|
||||
|
||||
So `##bias 30` means LVO `−30`. When you see `JSR (-30,A6)` in disassembly and A6 holds `DOSBase`, that is `dos.library Open()`.
|
||||
|
||||
### Where are .fd files?
|
||||
|
||||
In the NDK39 distribution at:
|
||||
```
|
||||
NDK39/
|
||||
fd/
|
||||
dos_lib.fd
|
||||
exec_lib.fd
|
||||
graphics_lib.fd
|
||||
intuition_lib.fd
|
||||
...
|
||||
```
|
||||
|
||||
They are plain text — open any with a text editor.
|
||||
|
||||
---
|
||||
|
||||
## The Canonical Call Pattern
|
||||
|
||||
Every AmigaOS library call in disassembly looks like this:
|
||||
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6 ; (1) load the library base into A6
|
||||
JSR (-30,A6) ; (2) call function at LVO -30 = Open()
|
||||
; D0 now contains the return value
|
||||
```
|
||||
|
||||
Sometimes the base is loaded once and reused:
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (-30,A6) ; Open
|
||||
...
|
||||
; A6 still holds DOSBase — no reload needed
|
||||
JSR (-48,A6) ; Write
|
||||
```
|
||||
|
||||
And for `exec.library`, programs often use the fixed address `$4` directly:
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; exec.library base is always at $4
|
||||
MOVEQ #40, D0 ; minimum version
|
||||
LEA _str_dos(PC), A1 ; "dos.library"
|
||||
JSR (-552,A6) ; exec.library OpenLibrary(A1,D0)
|
||||
MOVE.L D0, _DOSBase ; save result for later
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step: Tracing OS Calls in IDA Pro
|
||||
|
||||
### Step 1 — Find OpenLibrary calls at startup
|
||||
|
||||
Search for `JSR (-552,A6)` — that is always `exec.library OpenLibrary`. The instruction immediately before it loads A1 with a library name string.
|
||||
|
||||
```asm
|
||||
LEA (_str_dos).L, A1 ; → xref this to see "dos.library"
|
||||
MOVEQ #40, D0
|
||||
MOVEA.L 4.W, A6
|
||||
JSR (-552,A6) ; OpenLibrary("dos.library", 40)
|
||||
MOVE.L D0, (_DOSBase).L ; ← label this global "_DOSBase"
|
||||
```
|
||||
|
||||
Press `N` in IDA on the `_DOSBase` write to name the variable.
|
||||
|
||||
### Step 2 — Find all reads of that library base
|
||||
|
||||
Press `X` on `_DOSBase` to show all cross-references. Each xref is either a write (the open) or a read (before a JSR).
|
||||
|
||||
### Step 3 — Resolve each JSR to a function name
|
||||
|
||||
For each `JSR (-N,A6)` where A6 holds `_DOSBase`:
|
||||
1. Look up `N` in `dos_lib.fd` under `##bias N`
|
||||
2. Read the function name
|
||||
3. Press `N` in IDA on the JSR instruction's displacement to annotate it
|
||||
|
||||
After annotation:
|
||||
```asm
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (Open,A6) ; was: JSR (-30,A6)
|
||||
```
|
||||
|
||||
### Step 4 — Note argument registers
|
||||
|
||||
From `dos_lib.fd`:
|
||||
```
|
||||
Open(name,accessMode)(d1,d2)
|
||||
```
|
||||
So immediately before the JSR:
|
||||
- `D1` is loaded with the filename pointer
|
||||
- `D2` is loaded with the access mode (`MODE_OLDFILE` = 1005, `MODE_NEWFILE` = 1006)
|
||||
|
||||
---
|
||||
|
||||
## Quick LVO Reference: dos.library
|
||||
|
||||
| LVO | Bias | Function | Args | Return |
|
||||
|---|---|---|---|---|
|
||||
| −30 | 30 | `Open` | D1=name, D2=mode | D0=BPTR handle (0=fail) |
|
||||
| −36 | 36 | `Close` | D1=handle | — |
|
||||
| −42 | 42 | `Read` | D1=handle, D2=buf, D3=len | D0=actual (−1=fail) |
|
||||
| −48 | 48 | `Write` | D1=handle, D2=buf, D3=len | D0=actual |
|
||||
| −54 | 54 | `Input` | — | D0=stdin handle |
|
||||
| −60 | 60 | `Output` | — | D0=stdout handle |
|
||||
| −66 | 66 | `IoErr` | — | D0=last error code |
|
||||
| −78 | 78 | `CreateDir` | D1=name | D0=lock |
|
||||
| −84 | 84 | `CurrentDir` | D1=lock | D0=old lock |
|
||||
| −90 | 90 | `Lock` | D1=name, D2=mode | D0=lock |
|
||||
| −96 | 96 | `UnLock` | D1=lock | — |
|
||||
| −102 | 102 | `DupLock` | D1=lock | D0=new lock |
|
||||
| −108 | 108 | `Examine` | D1=lock, D2=fib | D0=bool |
|
||||
| −120 | 120 | `ExNext` | D1=lock, D2=fib | D0=bool |
|
||||
| −126 | 126 | `Info` | D1=lock, D2=infoblock | D0=bool |
|
||||
| −132 | 132 | `Execute` | D1=string, D2=input, D3=output | D0=bool |
|
||||
| −138 | 138 | `Delay` | D1=ticks | — |
|
||||
| −144 | 144 | `DateStamp` | D1=datestamp | D0=datestamp |
|
||||
| −150 | 150 | `Exit` | D1=returnCode | — |
|
||||
| −156 | 156 | `LoadSeg` | D1=name | D0=seglist |
|
||||
| −162 | 162 | `UnLoadSeg` | D1=seglist | — |
|
||||
|
||||
## Quick LVO Reference: exec.library (selected)
|
||||
|
||||
| LVO | Bias | Function | Args | Return |
|
||||
|---|---|---|---|---|
|
||||
| −6 | 6 | `Supervisor` | A5=func | — |
|
||||
| −120 | 120 | `Forbid` | — | — |
|
||||
| −126 | 126 | `Permit` | — | — |
|
||||
| −132 | 132 | `Disable` | — | — |
|
||||
| −138 | 138 | `Enable` | — | — |
|
||||
| −168 | 168 | `FindTask` | A1=name | D0=task |
|
||||
| −174 | 174 | `SetTaskPri` | A1=task, D0=pri | D0=old |
|
||||
| −192 | 192 | `Signal` | A1=task, D0=signals | — |
|
||||
| −198 | 198 | `AllocMem` | D0=size, D1=attrs | D0=ptr |
|
||||
| −210 | 210 | `FreeMem` | A1=ptr, D0=size | — |
|
||||
| −234 | 234 | `Wait` | D0=signals | D0=set |
|
||||
| −270 | 270 | `AddPort` | A1=port | — |
|
||||
| −276 | 276 | `FindName` | A0=list, A1=name | D0=node |
|
||||
| −378 | 378 | `PutMsg` | A0=port, A1=msg | — |
|
||||
| −384 | 384 | `GetMsg` | A0=port | D0=msg |
|
||||
| −408 | 408 | `WaitPort` | A0=port | D0=msg |
|
||||
| −420 | 420 | `SetFunction` | A1=lib, A0=lvo, D0=func | D0=old |
|
||||
| −552 | 552 | `OpenLibrary` | A1=name, D0=ver | D0=base |
|
||||
| −558 | 558 | `CloseLibrary` | A1=lib | — |
|
||||
|
||||
Full tables: [`04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md)
|
||||
|
||||
---
|
||||
|
||||
## Automated IDA Script
|
||||
|
||||
```python
|
||||
# apply_dos_lvos.py — run from IDA's File → Script command
|
||||
import idaapi, idc, idautils
|
||||
|
||||
DOS_LVO = {
|
||||
-30: "Open", -36: "Close", -42: "Read", -48: "Write",
|
||||
-54: "Input", -60: "Output", -66: "IoErr", -132: "Execute",
|
||||
-138: "Delay", -156: "LoadSeg",-162: "UnLoadSeg",
|
||||
}
|
||||
|
||||
EXEC_LVO = {
|
||||
-120: "Forbid", -126: "Permit", -132: "Disable", -138: "Enable",
|
||||
-198: "AllocMem", -210: "FreeMem",-234: "Wait",
|
||||
-378: "PutMsg", -384: "GetMsg", -408: "WaitPort",
|
||||
-420: "SetFunction", -552: "OpenLibrary", -558: "CloseLibrary",
|
||||
}
|
||||
|
||||
def apply_lvos(lib_global_name, lvo_map):
|
||||
ea = idc.get_name_ea_simple(lib_global_name)
|
||||
if ea == idc.BADADDR:
|
||||
print(f"Global {lib_global_name} not found")
|
||||
return
|
||||
lib_ptr = idc.get_wide_dword(ea)
|
||||
for lvo, name in lvo_map.items():
|
||||
jmp_ea = lib_ptr + lvo
|
||||
# JMP ABS.L opcode: 4EF9, target at +2
|
||||
target = idc.get_wide_dword(jmp_ea + 2)
|
||||
if target != 0xFFFFFFFF:
|
||||
idc.set_name(target, f"{lib_global_name[1:]}_{name}",
|
||||
idaapi.SN_NOWARN)
|
||||
print(f" {lvo:+5d} → {name} @ {target:#010x}")
|
||||
|
||||
apply_lvos("_DOSBase", DOS_LVO)
|
||||
apply_lvos("_SysBase", EXEC_LVO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Identifying Unknown Library Calls
|
||||
|
||||
If you encounter `JSR (-N,A6)` and don't know which library A6 holds:
|
||||
|
||||
1. Trace A6 backward in IDA (`View → Register tracking`) to its last write
|
||||
2. The write is `MOVEA.L (some_global).L, A6` — name that global
|
||||
3. Trace *that* global backward to its `MOVE.L D0, ...` after an `OpenLibrary` call
|
||||
4. The string argument to OpenLibrary names the library
|
||||
5. Look up LVO `−N` in the matching `.fd` file
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `fd/` directory — all library `.fd` files (plain text, open in any editor)
|
||||
- `04_linking_and_libraries/lvo_table.md` — formatted LVO tables
|
||||
- `static/library_jmp_table.md` — JMP table layout and IDA scripting
|
||||
- `04_linking_and_libraries/fd_files.md` — `.fd` file format specification
|
||||
- ADCD 2.1 Autodocs online: http://amigadev.elowar.com/read/ADCD_2.1/
|
||||
123
05_reversing/static/hunk_reconstruction.md
Normal file
123
05_reversing/static/hunk_reconstruction.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Static Analysis — HUNK Reconstruction
|
||||
|
||||
## Overview
|
||||
|
||||
Manually parsing a HUNK binary from a hex dump is a foundational Amiga RE skill. It reveals segment boundaries, symbol tables, and relocation data before any tool processing.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Identify Magic and Header
|
||||
|
||||
```bash
|
||||
xxd mybinary | head -8
|
||||
```
|
||||
|
||||
```
|
||||
00000000: 0000 03f3 ← HUNK_HEADER magic
|
||||
00000004: 0000 0000 ← resident library list (always 0)
|
||||
00000008: 0000 0003 ← num_hunks = 3
|
||||
0000000c: 0000 0000 ← first_hunk = 0
|
||||
00000010: 0000 0002 ← last_hunk = 2
|
||||
00000014: 0000 0200 ← hunk 0: 0x200 longs = 0x800 bytes (code)
|
||||
00000018: 0000 0020 ← hunk 1: 0x20 longs = 0x80 bytes (data)
|
||||
0000001c: 0000 0010 ← hunk 2: 0x10 longs = 0x40 bytes (BSS)
|
||||
```
|
||||
|
||||
Each size longword: **bits 31–30** = memory type flag, **bits 29–0** = size in longs.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Walk the Hunk Stream
|
||||
|
||||
After the header, scan longword-by-longword:
|
||||
|
||||
```
|
||||
$000003E9 → HUNK_CODE: read next longword = size, then size*4 bytes
|
||||
$000003EA → HUNK_DATA: same
|
||||
$000003EB → HUNK_BSS: read size longword only (no data)
|
||||
$000003EC → HUNK_RELOC32: read pairs until terminator 0
|
||||
$000003F0 → HUNK_SYMBOL: read (name_len, name, value) until name_len=0
|
||||
$000003F1 → HUNK_DEBUG: read size longword, skip size*4 bytes
|
||||
$000003F2 → HUNK_END: advance to next hunk
|
||||
```
|
||||
|
||||
### Grep for hunk boundaries
|
||||
```bash
|
||||
xxd mybinary | grep -E "0003 (e9|ea|eb|ec|f0|f1|f2|f3)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Extract HUNK_SYMBOL Table
|
||||
|
||||
```bash
|
||||
# find HUNK_SYMBOL ($3F0)
|
||||
python3 - <<'EOF'
|
||||
import struct, sys
|
||||
|
||||
data = open("mybinary", "rb").read()
|
||||
i = 0
|
||||
while i < len(data) - 4:
|
||||
tag = struct.unpack_from(">I", data, i)[0]
|
||||
if tag == 0x3F0: # HUNK_SYMBOL
|
||||
print(f"HUNK_SYMBOL at offset {i:#x}")
|
||||
i += 4
|
||||
while True:
|
||||
nlen = struct.unpack_from(">I", data, i)[0]
|
||||
if nlen == 0: break
|
||||
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
|
||||
val = struct.unpack_from(">I", data, i+4+nlen*4)[0]
|
||||
print(f" {name} = {val:#x}")
|
||||
i += 4 + nlen*4 + 4
|
||||
else:
|
||||
i += 4
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Resolve HUNK_EXT Imports/Exports
|
||||
|
||||
In object files (HUNK_UNIT), `HUNK_EXT` carries import/export tables:
|
||||
|
||||
```python
|
||||
# Simplified HUNK_EXT parser
|
||||
elif tag == 0x3EF: # HUNK_EXT
|
||||
i += 4
|
||||
while True:
|
||||
word = struct.unpack_from(">I", data, i)[0]
|
||||
if word == 0: break
|
||||
ext_type = (word >> 24) & 0xFF
|
||||
nlen = word & 0x00FFFFFF
|
||||
name = data[i+4 : i+4+nlen*4].rstrip(b"\x00").decode("ascii","replace")
|
||||
i += 4 + nlen * 4
|
||||
if ext_type in (1, 2): # EXT_DEF / EXT_ABS
|
||||
val = struct.unpack_from(">I", data, i)[0]; i += 4
|
||||
print(f" EXPORT {name} = {val:#x}")
|
||||
elif ext_type == 0x81: # EXT_REF32
|
||||
nrefs = struct.unpack_from(">I", data, i)[0]; i += 4
|
||||
refs = struct.unpack_from(f">{nrefs}I", data, i); i += nrefs*4
|
||||
print(f" IMPORT {name} @ {[hex(r) for r in refs]}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Annotating Reloc Patches in IDA
|
||||
|
||||
After loading the HUNK file in IDA:
|
||||
1. `View → Open Subviews → Fixups` — lists all HUNK_RELOC32 patch sites
|
||||
2. Press `F5` on a relocated longword to see the computed address
|
||||
3. Use `Edit → Operand type → Offset (data segment)` to annotate as a pointer
|
||||
|
||||
IDA's Amiga loader applies relocations automatically, so all cross-hunk pointers show their final resolved addresses.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `dos/doshunks.h`
|
||||
- `hunk_format.md` — hunk type code reference
|
||||
- `hunk_relocation.md` — HUNK_RELOC32 mechanics
|
||||
- vlink documentation (HUNK appendix): http://sun.hasenbraten.de/vlink/
|
||||
142
05_reversing/static/library_jmp_table.md
Normal file
142
05_reversing/static/library_jmp_table.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Reconstructing Library JMP Tables
|
||||
|
||||
## Overview
|
||||
|
||||
Every AmigaOS library has a **JMP table** at negative offsets from its base pointer. Reconstructing this table maps LVOs to function names and is essential for identifying all OS calls made by a binary under analysis.
|
||||
|
||||
---
|
||||
|
||||
## JMP Table Layout
|
||||
|
||||
```
|
||||
lib_base - N*6: JFF xxxx xxxx ; JMP to function N (6 bytes)
|
||||
...
|
||||
lib_base - 24: JMP Reserved()
|
||||
lib_base - 18: JMP Expunge()
|
||||
lib_base - 12: JMP Close()
|
||||
lib_base - 6: JMP Open()
|
||||
lib_base + 0: struct Library ; lib_Node, lib_Version, ...
|
||||
```
|
||||
|
||||
Each entry is a 68k `JMP (abs.l)` — opcode `4EF9` followed by a 4-byte absolute address, totalling 6 bytes. Hence LVO = `−6 × slot_index`.
|
||||
|
||||
---
|
||||
|
||||
## Finding the Library Base
|
||||
|
||||
### From SysBase LibList
|
||||
|
||||
The `exec.library` maintains a doubly-linked list at `SysBase→LibList`:
|
||||
|
||||
```c
|
||||
struct ExecBase {
|
||||
...
|
||||
struct List LibList; /* offset +378 — list of open libraries */
|
||||
...
|
||||
};
|
||||
|
||||
/* Walk the list: */
|
||||
struct Node *n = SysBase->LibList.lh_Head;
|
||||
while (n->ln_Succ) {
|
||||
struct Library *lib = (struct Library *)n;
|
||||
printf("%s v%d\n", lib->lib_Node.ln_Name, lib->lib_Version);
|
||||
n = n->ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
### In IDA Pro
|
||||
|
||||
After loading, `SysBase` is at `$4`. Use `Edit → Segments → Create Segment` pointed at `$4` with type `WORD` to follow the pointer to `ExecBase`. Then navigate to `LibList` at offset `+0x17A` and walk the linked list.
|
||||
|
||||
---
|
||||
|
||||
## Reading the JMP Table in IDA
|
||||
|
||||
1. Know the library base address (e.g., `DOSBase` from the `OpenLibrary` result)
|
||||
2. Navigate to `lib_base - 6` — first user function slot
|
||||
3. IDA shows `JMP sub_XXXXXX` — the target is the actual function implementation
|
||||
4. Rename each `sub_` with the function name from the LVO table
|
||||
|
||||
### Automated Script: `apply_lvo_names.py`
|
||||
|
||||
```python
|
||||
import idaapi, idc
|
||||
|
||||
LVO_DOS = {
|
||||
-30: "Open", # LVO -30 = Open(name, mode) d1/d2
|
||||
-36: "Close",
|
||||
-42: "Read",
|
||||
-48: "Write",
|
||||
-54: "Input",
|
||||
-60: "Output",
|
||||
-126: "WaitForChar",
|
||||
-138: "Delay",
|
||||
# ... extend from dos_lib.fd
|
||||
}
|
||||
|
||||
DOS_BASE = idc.get_name_ea_simple("_DOSBase")
|
||||
dos_ptr = idc.get_wide_dword(DOS_BASE)
|
||||
|
||||
for lvo, name in LVO_DOS.items():
|
||||
jmp_entry = dos_ptr + lvo
|
||||
# read the JMP target: opcode at jmp_entry is 4EF9, target at +2
|
||||
target = idc.get_wide_dword(jmp_entry + 2)
|
||||
idc.set_name(target, f"dos_{name}", idaapi.SN_NOWARN)
|
||||
print(f"LVO {lvo:+d}: {name} → {target:#010x}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapping LVO → Function via `.fd` Files
|
||||
|
||||
NDK39 `.fd` files define the exact register assignments and bias (LVO offset):
|
||||
|
||||
```
|
||||
## NDK39/fd/dos_lib.fd (excerpt)
|
||||
##base _DOSBase
|
||||
##bias 30
|
||||
##public
|
||||
Open(name,accessMode)(d1,d2)
|
||||
##bias 36
|
||||
Close(file)(d1)
|
||||
##bias 42
|
||||
Read(file,buffer,length)(d1,d2,d3)
|
||||
##bias 48
|
||||
Write(file,buffer,length)(d1,d2,d3)
|
||||
```
|
||||
|
||||
The `##bias` value **is** the positive LVO — the actual call offset is `−bias`.
|
||||
|
||||
---
|
||||
|
||||
## JSR −LVO(A6) Pattern in Disassembly
|
||||
|
||||
```asm
|
||||
; Typical OS call site in disassembly:
|
||||
MOVEA.L (_DOSBase).L, A6
|
||||
JSR (-30,A6) ; Open(d1=name, d2=mode)
|
||||
; D0 = file handle (BPTR) or 0 on error
|
||||
```
|
||||
|
||||
In IDA, this appears as `jsr ($fffffffe2,a6)` with displacement `-30` (`$FFFFFFE2` in two's complement 16-bit). Applying LVO names makes this `jsr (Open,a6)`.
|
||||
|
||||
---
|
||||
|
||||
## Common Library Bases and LVO Tables
|
||||
|
||||
See [`../../../04_linking_and_libraries/lvo_table.md`](../../../04_linking_and_libraries/lvo_table.md) for complete LVO offset tables for:
|
||||
- `exec.library`
|
||||
- `dos.library`
|
||||
- `graphics.library`
|
||||
- `intuition.library`
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `fd/` directory — all library `.fd` files
|
||||
- `04_linking_and_libraries/lvo_table.md`
|
||||
- ADCD 2.1: `Libraries_Manual_guide/`
|
||||
- IDA Pro scripting: `idc.py` reference
|
||||
156
05_reversing/static/m68k_codegen_patterns.md
Normal file
156
05_reversing/static/m68k_codegen_patterns.md
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Compiler-Specific Code Generation Patterns
|
||||
|
||||
## Overview
|
||||
|
||||
Different Amiga compilers produce distinct code signatures. Recognising these helps quickly identify compiler origin, locate `main()`, and distinguish OS glue from application logic.
|
||||
|
||||
---
|
||||
|
||||
## SAS/C 6.x Patterns
|
||||
|
||||
### Function Prologue / Epilogue
|
||||
|
||||
```asm
|
||||
; Non-leaf function with local vars:
|
||||
LINK A5, #-N ; allocate N bytes of locals on stack
|
||||
MOVEM.L D2-D7/A2-A3, -(SP) ; save preserved registers
|
||||
...
|
||||
MOVEM.L (SP)+, D2-D7/A2-A3
|
||||
UNLK A5
|
||||
RTS
|
||||
|
||||
; Leaf function (no locals, no preserved regs):
|
||||
; — no LINK, pure computation, ends in RTS
|
||||
```
|
||||
|
||||
### D0 Save Pattern
|
||||
|
||||
SAS/C saves D0 at the start of functions that need it later:
|
||||
|
||||
```asm
|
||||
MOVE.L D0, -(SP) ; save return value from previous call
|
||||
JSR another_func
|
||||
MOVE.L (SP)+, D0 ; restore
|
||||
```
|
||||
|
||||
### Register Argument Passing
|
||||
|
||||
SAS/C passes OS call args via `#pragma amicall` register placement. Inside application functions, SAS/C uses a **stack-based C ABI** (unlike OS calls):
|
||||
|
||||
```asm
|
||||
; C function call in SAS/C: push args right-to-left
|
||||
MOVE.L arg3, -(SP)
|
||||
MOVE.L arg2, -(SP)
|
||||
MOVE.L arg1, -(SP)
|
||||
JSR _myfunction
|
||||
ADDQ.L #12, SP ; clean args (caller cleanup)
|
||||
```
|
||||
|
||||
### String Constants
|
||||
|
||||
SAS/C places string literals in the **data hunk**, referenced via absolute addresses requiring `HUNK_RELOC32`:
|
||||
|
||||
```asm
|
||||
MOVE.L #_str_hello, D1 ; absolute address → RELOC32 entry
|
||||
MOVEA.L _DOSBase, A6
|
||||
JSR (-48,A6) ; Write(stdout, "hello", ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GCC (m68k-amigaos / bebbo) Patterns
|
||||
|
||||
### PC-Relative String Access
|
||||
|
||||
GCC uses PC-relative addressing by default, eliminating most HUNK_RELOC32 entries:
|
||||
|
||||
```asm
|
||||
LEA _str_hello(PC), A0 ; PC-relative — no reloc needed
|
||||
```
|
||||
|
||||
### No Frame Pointer (Default)
|
||||
|
||||
```asm
|
||||
; GCC -O2 leaf function:
|
||||
MOVEM.L D2/A2, -(SP) ; only save what's used
|
||||
...
|
||||
MOVEM.L (SP)+, D2/A2
|
||||
RTS
|
||||
; No LINK/UNLK — pure register allocation
|
||||
```
|
||||
|
||||
### GCC Function Prologues
|
||||
|
||||
```asm
|
||||
; Non-leaf with GCC -fno-omit-frame-pointer:
|
||||
LINK A6, #-N ; note: GCC uses A6 as frame pointer here
|
||||
; (conflicts with OS library base usage — rare)
|
||||
; More common with -O2:
|
||||
SUBQ.L #N, SP ; allocate locals without frame pointer
|
||||
```
|
||||
|
||||
### Integer Division / Modulo
|
||||
|
||||
GCC emits calls to `__divsi3`, `__modsi3` from `libgcc`:
|
||||
|
||||
```asm
|
||||
JSR ___divsi3 ; 32-bit signed divide (libgcc helper)
|
||||
; operands in D0:D1, result in D0
|
||||
```
|
||||
|
||||
SAS/C uses the 68k `DIVS.L` instruction directly (available on 020+) or `DIVS.W`.
|
||||
|
||||
---
|
||||
|
||||
## VBCC Patterns
|
||||
|
||||
VBCC generates very tight code with minimal function overhead:
|
||||
|
||||
```asm
|
||||
; VBCC typical function (no frame pointer, minimal saves):
|
||||
MOVEM.L D2-D4, -(SP)
|
||||
...
|
||||
MOVEM.L (SP)+, D2-D4
|
||||
RTS
|
||||
```
|
||||
|
||||
VBCC's OS call inline expansion looks identical to GCC's inline-asm stubs.
|
||||
|
||||
---
|
||||
|
||||
## Distinguishing Compiler Artefacts from Logic
|
||||
|
||||
| Pattern | Compiler | Meaning |
|
||||
|---|---|---|
|
||||
| `LINK A5, #-N` | SAS/C | Function with locals |
|
||||
| `LINK A6, #-N` | GCC (rare) | Frame pointer mode |
|
||||
| `JSR ___divsi3` | GCC | Software 32-bit division |
|
||||
| `DIVS.L D1, D0` | SAS/C (020+) | Hardware divide |
|
||||
| `MULS.L D1, D0` | SAS/C (020+) | Hardware multiply |
|
||||
| `LEA str(PC), A0` | GCC | PC-relative string ref |
|
||||
| `MOVE.L #_str, D1` | SAS/C | Absolute string ref (reloc'd) |
|
||||
| `JSR _main` | Startup | C main() entry point |
|
||||
| `MOVE.L 4.W, A6` | Startup | SysBase load |
|
||||
| `JSR -552(A6)` | Any | exec.library OpenLibrary |
|
||||
|
||||
---
|
||||
|
||||
## Locating `main()` via Startup Skip
|
||||
|
||||
After identifying the startup stub (`MOVE.L 4.W, A6` → `JSR _OpenLibraries`):
|
||||
|
||||
1. Find the first `JSR` or `BSR` after library opens
|
||||
2. That target is `__main` or directly `_main`
|
||||
3. If `__main`: follow its internal `JSR _main` call
|
||||
4. Label the target `main` in IDA
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- SAS/C 6.x manual — code generation chapter
|
||||
- GCC for m68k: https://github.com/bebbo/amiga-gcc
|
||||
- VBCC manual: http://www.compilers.de/vbcc.html
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions
|
||||
123
05_reversing/static/string_xref_analysis.md
Normal file
123
05_reversing/static/string_xref_analysis.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# String Cross-Reference Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
String references are the fastest entry point into a disassembled Amiga binary. Library name strings, error messages, and format strings immediately reveal program intent and identify OS API usage patterns.
|
||||
|
||||
---
|
||||
|
||||
## Finding Library Name Strings
|
||||
|
||||
Every `OpenLibrary` call is preceded by a string reference. Search for `".library"`:
|
||||
|
||||
```bash
|
||||
# Host: grep for library name strings in binary
|
||||
strings mybinary | grep -i library
|
||||
# → "dos.library", "graphics.library", "intuition.library", ...
|
||||
```
|
||||
|
||||
In IDA:
|
||||
1. `View → Open Subviews → Strings` (Shift+F12)
|
||||
2. Search for `.library`
|
||||
3. Press `X` on any result to see all cross-references
|
||||
4. Each xref leads to a `LEA str(PC), A1` or `MOVE.L #str, A1` before a `JSR -552(A6)` (OpenLibrary)
|
||||
|
||||
---
|
||||
|
||||
## Tracing OpenLibrary Calls to Their Targets
|
||||
|
||||
```asm
|
||||
; Pattern to find:
|
||||
LEA (_str_dos).L, A1 ; "dos.library"
|
||||
MOVEQ #36, D0 ; min version
|
||||
MOVEA.L 4.W, A6 ; exec.library
|
||||
JSR (-552,A6) ; OpenLibrary → D0 = DOSBase
|
||||
MOVE.L D0, (_DOSBase).L ; store for later use
|
||||
```
|
||||
|
||||
Xref `_str_dos` → find this block → identify the stored library base variable → label it `_DOSBase`.
|
||||
|
||||
---
|
||||
|
||||
## Using HUNK_SYMBOL Names as Seed Labels
|
||||
|
||||
If `HUNK_SYMBOL` is present (debug build), IDA auto-applies names. These seed labels help bootstrap analysis:
|
||||
|
||||
1. `View → Open Subviews → Names` → look for any `_` prefixed symbols
|
||||
2. Named functions often call unnamed helpers nearby — work outward
|
||||
3. String xrefs from named functions propagate names further
|
||||
|
||||
---
|
||||
|
||||
## Error Message Strings
|
||||
|
||||
Error/diagnostic strings reveal program flow:
|
||||
|
||||
```asm
|
||||
; Common pattern:
|
||||
LEA _err_nolib(PC), A0 ; "Can't open dos.library"
|
||||
MOVEA.L _DOSBase, A6
|
||||
JSR (-60,A6) ; Output() → D0 = stdout
|
||||
MOVE.L D0, D1
|
||||
LEA _err_nolib(PC), A2
|
||||
MOVE.L A2, D2
|
||||
MOVEQ #_err_nolib_end - _err_nolib, D3
|
||||
JSR (-48,A6) ; Write(stdout, msg, len)
|
||||
```
|
||||
|
||||
The error string tells you exactly what this code path handles.
|
||||
|
||||
---
|
||||
|
||||
## Format String Xref Analysis (printf)
|
||||
|
||||
SAS/C `printf` style calls via `dos.library VPrintf`:
|
||||
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6
|
||||
LEA _fmt_str(PC), A0 ; "Error: %ld\n"
|
||||
MOVE.L A0, D1
|
||||
MOVE.L A1, D2 ; varargs array
|
||||
JSR (-954,A6) ; VPrintf()
|
||||
```
|
||||
|
||||
Format strings like `"Error: %ld\n"` or `"Processing: %s"` reveal parameter types and function purpose.
|
||||
|
||||
---
|
||||
|
||||
## Workbench Title Strings
|
||||
|
||||
```asm
|
||||
; Typical NewScreen/OpenScreen call sequence:
|
||||
LEA _screen_title(PC), A0 ; "MyApp v1.0"
|
||||
MOVE.L A0, (NewScreen+ns_Title)
|
||||
```
|
||||
|
||||
Screen/window title strings appear in `intuition.library` `OpenScreen` / `OpenWindow` calls and give the product name.
|
||||
|
||||
---
|
||||
|
||||
## Automated String Map
|
||||
|
||||
Build a complete string inventory:
|
||||
|
||||
```python
|
||||
# IDA script: map all string xrefs
|
||||
for s in idautils.Strings():
|
||||
text = str(idc.get_strlit_contents(s.ea, s.length, s.strtype))
|
||||
refs = list(idautils.XrefsTo(s.ea))
|
||||
if refs:
|
||||
for ref in refs:
|
||||
func = idc.get_func_name(ref.frm)
|
||||
print(f"{s.ea:#x} [{text!r:40s}] ← {func or 'unknown'} @ {ref.frm:#x}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- IDA Pro: Strings subview (Shift+F12), Xrefs (X key)
|
||||
- `static/api_call_identification.md` — resolving library base from string xrefs
|
||||
- NDK39: `dos/dos.h` — `VPrintf`, `FPrintf`, error code strings
|
||||
137
05_reversing/static/struct_recovery.md
Normal file
137
05_reversing/static/struct_recovery.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Recovering Data Structures
|
||||
|
||||
## Overview
|
||||
|
||||
Amiga executables use OS structures extensively — `ExecBase`, `Node`, `Process`, `IORequest`, etc. This document describes how to recover and annotate these structures in disassembly by matching field access patterns against NDK39 header offsets.
|
||||
|
||||
---
|
||||
|
||||
## The MOVE.L offset(An),Dm Pattern
|
||||
|
||||
Structure field accesses appear as:
|
||||
|
||||
```asm
|
||||
MOVEA.L _DOSBase, A6
|
||||
MOVE.L ($17A,A6), A0 ; SysBase->LibList at offset +0x17A
|
||||
MOVE.L (A0), A1 ; lh_Head
|
||||
```
|
||||
|
||||
The key is the **base register** and **constant offset**. Match the offset against known structure definitions.
|
||||
|
||||
---
|
||||
|
||||
## Common Structures and Key Offsets
|
||||
|
||||
### `struct ExecBase` (at absolute address `$4`)
|
||||
|
||||
| Offset | Field | Type |
|
||||
|---|---|---|
|
||||
| +0 | `LibNode` | `struct Library` |
|
||||
| +0x128 | `TaskReady` | `struct List` |
|
||||
| +0x132 | `TaskWait` | `struct List` |
|
||||
| +0x17A | `LibList` | `struct List` |
|
||||
| +0x182 | `DeviceList` | `struct List` |
|
||||
| +0x21E | `ChipRevBits0` | `UWORD` |
|
||||
| +0x280 | `MemList` | `struct List` |
|
||||
|
||||
### `struct Node` (8 bytes)
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `ln_Succ` (next node) |
|
||||
| +4 | `ln_Pred` (prev node) |
|
||||
| +8 | `ln_Type` (UBYTE) |
|
||||
| +9 | `ln_Pri` (BYTE priority) |
|
||||
| +10 | `ln_Name` (STRPTR) |
|
||||
|
||||
List traversal:
|
||||
```asm
|
||||
MOVEA.L lh_Head, A0 ; first node
|
||||
.loop:
|
||||
TST.L (A0) ; ln_Succ == NULL?
|
||||
BEQ.S .done
|
||||
; process node at A0
|
||||
MOVEA.L (A0), A0 ; A0 = ln_Succ
|
||||
BRA.S .loop
|
||||
```
|
||||
|
||||
### `struct Process` (extends `struct Task`)
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `pr_Task` (struct Task) |
|
||||
| +92 | `pr_MsgPort` |
|
||||
| +128 | `pr_CLI` (BPTR, non-NULL if CLI) |
|
||||
| +172 | `pr_SegList` (BPTR to segment list) |
|
||||
|
||||
Detection in disassembly:
|
||||
```asm
|
||||
MOVE.L ($80,A4), D0 ; pr_CLI at offset +0x80
|
||||
BEQ.S .wb_launch ; NULL = Workbench
|
||||
```
|
||||
|
||||
### `struct IORequest` / `struct IOStdReq`
|
||||
|
||||
| Offset | Field |
|
||||
|---|---|
|
||||
| +0 | `io_Message` (struct Message) |
|
||||
| +20 | `io_Device` |
|
||||
| +24 | `io_Unit` |
|
||||
| +28 | `io_Command` (UWORD) |
|
||||
| +30 | `io_Flags` (UBYTE) |
|
||||
| +32 | `io_Error` (BYTE) |
|
||||
| +36 | `io_Length` (ULONG) |
|
||||
| +40 | `io_Actual` (ULONG) |
|
||||
| +44 | `io_Data` (APTR) |
|
||||
| +48 | `io_Offset` (ULONG) |
|
||||
|
||||
---
|
||||
|
||||
## Annotating Structures in IDA Pro
|
||||
|
||||
### Define a structure type:
|
||||
|
||||
1. `View → Open Subviews → Local Types` → `Insert` → paste C struct definition
|
||||
2. IDA parses the struct and creates a type entry
|
||||
3. Navigate to the base register in disassembly
|
||||
4. Press `T` (structure offset) on any `offset(An)` operand
|
||||
5. Select the struct type → all accesses auto-annotated
|
||||
|
||||
### Import NDK39 headers:
|
||||
|
||||
Use `File → Load file → Parse C header file` → select `exec/execbase.h`, `exec/tasks.h`, etc. from NDK39.
|
||||
|
||||
---
|
||||
|
||||
## Exec Node Traversal Loops
|
||||
|
||||
A recurring pattern: walking the `LibList` or `DeviceList`:
|
||||
|
||||
```asm
|
||||
; Annotated after struct recovery:
|
||||
MOVEA.L SysBase, A6
|
||||
LEA (LibList,A6), A0 ; &SysBase->LibList
|
||||
MOVEA.L (lh_Head,A0), A1 ; first lib node
|
||||
.scan:
|
||||
MOVEA.L (ln_Succ,A1), A2 ; peek next
|
||||
TST.L A2
|
||||
BEQ.S .not_found
|
||||
; compare ln_Name string
|
||||
MOVEA.L (ln_Name,A1), A0
|
||||
JSR ___strcmp
|
||||
TST.L D0
|
||||
BEQ.S .found
|
||||
MOVEA.L A2, A1
|
||||
BRA.S .scan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`, `exec/tasks.h`, `exec/nodes.h`, `exec/io.h`
|
||||
- `06_exec_os/exec_base.md` — full ExecBase field listing
|
||||
- `06_exec_os/lists_nodes.md` — MinList/List traversal
|
||||
- IDA Pro: Structure subview, Local Types, T hotkey for struct offset
|
||||
Loading…
Add table
Add a link
Reference in a new issue