Skip to content

Instantly share code, notes, and snippets.

@coreh
Last active August 27, 2026 04:45
Show Gist options
  • Select an option

  • Save coreh/e6e14ccafa89cd95a02723ed794385df to your computer and use it in GitHub Desktop.

Select an option

Save coreh/e6e14ccafa89cd95a02723ed794385df to your computer and use it in GitHub Desktop.
Mac OS X Server 1.0 and current macOS AppKit titlebar-button binary analysis

Mac OS X Server 1.0 titlebar-button investigation

TL;DR

Confirmed: both Mac OS X Server 1.0's PowerPC AppKit and current ARM64e AppKit create and add the titlebar controls in the same semantic order: close, zoom, then miniaturize. Because later-added sibling views are in front, the miniaturize button is above both the close and zoom buttons when they overlap. The positioning code is also in AppKit in both releases: Server 1.0 uses NSTitledFrame origin methods plus _tileTitlebar; current macOS uses NSThemeFrame origin methods plus _updateButtonPositions.

The binaries establish matching behavior across the two releases; they do not by themselves prove uninterrupted source-code lineage from 1999 to 2026.

Reproduce the setup

From this directory, install the read-only filesystem and disassembly tools:

./install_deps.sh

Then download or resume the exact preserved ISO and verify its size and SHA-1:

./download_iso.sh

The dependency script installs hfsutils, sleuthkit, and capstone with Homebrew. The download script writes the ISO into the directory containing download_iso.sh, resumes partial downloads, and rejects a file whose size or SHA-1 does not match.

To inspect the current, loaded AppKit methods and run the geometry probe:

clang -fobjc-arc -framework AppKit -framework Foundation \
    inspect_current_methods.m -o inspect_current_methods
./inspect_current_methods --window

The current-build offsets reported by that helper are reproducible only on the same AppKit build; the Server 1.0 addresses below belong to the extracted fixed address PowerPC image.

Media and filesystem

  • Source image: Mac OS X Server 1.0.iso
  • Size: 678,135,808 bytes
  • SHA-1: 7f01415dc96093acc77edbfe8da0d003c0b56da7
  • Container: raw optical-disc image with an Apple Partition Map, not UDIF/DMG
  • Startup volume: classic HFS, 142,606,336 bytes
  • System volume: big-endian UFS1 inside Apple_Rhapsody_UFS
  • APM UFS partition offset: 296,388 512-byte sectors
  • NeXT dlV3 front porch: 160 2,048-byte sectors
  • Actual UFS offset for Sleuth Kit: 297,028 512-byte sectors

The helper inspect_next_label.py parses the nested NeXT label and locates the big-endian UFS magic values.

Extracted PowerPC frameworks

The files were recovered directly from allocated UFS1 inodes with icat.

Installed path Inode Size SHA-256
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit 15899 5,268,704 6897a7fa932ba92cfe92a4a1af36db9300d63c1dcc81eb66ceba5963710a9240
/System/Library/PrivateFrameworks/appkit.framework/Versions/A/appkit 31813 3,524,488 519d1bdf22fa47c738e03ae38952c5c8d28418fb7e1c056b8e9a0dea4ad3dd29

The relevant implementation is the public PowerPC AppKit dylib:

  • Mach-O type: 32-bit PowerPC dynamically linked shared library
  • compatibility version: 45.0.0
  • current version: 380.3.0
  • __TEXT base: 0x43300000

Server 1.0 creation order

Symbol: -[NSTitledFrame _updateButtons]

  • address: 0x4336e0ec
  • file offset: 0x0006e0ec

The relevant PowerPC instructions, with selector references resolved from the extracted AppKit, Foundation, and System frameworks, are:

; close
4336e12c  addis r4,r31,0x2f
4336e130  mr    r3,r30
4336e134  lwz   r4,0x5054(r4) ; newCloseButton
4336e138  bl    0x43642004    ; objc_msgSend PIC stub
4336e140  stw   r5,0x5c(r30)  ; close-button ivar
4336e148  mr    r3,r30
4336e14c  lwz   r4,-0x7e2c(r4) ; addSubview:
4336e150  bl    0x43642004

; zoom
4336e1bc  addis r4,r31,0x2f
4336e1c0  mr    r3,r30
4336e1c4  lwz   r4,0x5050(r4) ; newZoomButton
4336e1c8  bl    0x43642004
4336e1d0  stw   r5,0x60(r30)  ; zoom-button ivar
4336e1d8  mr    r3,r30
4336e1dc  lwz   r4,-0x7e2c(r4) ; addSubview:
4336e1e0  bl    0x43642004

; miniaturize/collapse
4336e220  addis r4,r31,0x2f
4336e224  mr    r3,r30
4336e228  lwz   r4,0x504c(r4) ; newMiniaturizeButton
4336e22c  bl    0x43642004
4336e234  stw   r5,0x64(r30)  ; miniaturize-button ivar
4336e23c  mr    r3,r30
4336e240  lwz   r4,-0x7e2c(r4) ; addSubview:
4336e244  bl    0x43642004

The factory symbols themselves are:

  • -[NSTitledFrame newCloseButton] at 0x43373d24
  • -[NSTitledFrame newZoomButton] at 0x4338c944
  • -[NSTitledFrame newMiniaturizeButton] at 0x4338adc4

Server 1.0 positioning

Positioning is also implemented in the public AppKit binary, not in Window Server. -[NSTitledFrame _tileTitlebar] at 0x43346f78 asks the frame for each origin and sends setFrameOrigin: to the corresponding button:

; closeButton ivar at self + 0x5c
43346fa8  lwz r5,...       ; _closeButtonOrigin
43346fac  bl  0x43642094   ; struct-return objc_msgSend stub
43346fb4  lwz r3,0x5c(r30)
43346fb8  lwz r4,...       ; setFrameOrigin:
43346fc4  bl  0x43642004

; miniaturize/collapseButton ivar at self + 0x64
43346fe0  lwz r5,...       ; _collapseButtonOrigin
43346fe4  bl  0x43642094
43346fec  lwz r3,0x64(r30)
43346ffc  bl  0x43642004   ; setFrameOrigin:

; zoomButton ivar at self + 0x60
43347018  lwz r5,...       ; _zoomButtonOrigin
4334701c  bl  0x43642094
43347024  lwz r3,0x60(r30)
43347034  bl  0x43642004   ; setFrameOrigin:

The three origin methods are:

Symbol Address Recovered position
-[NSTitledFrame _closeButtonOrigin] 0x43346844 x = titlebarRect.x; vertically centered
-[NSTitledFrame _collapseButtonOrigin] 0x43355c8c x = maxX(titlebarRect) - buttonWidth; vertically centered
-[NSTitledFrame _zoomButtonOrigin] 0x433568e8 x = maxX(titlebarRect) - _maxXTitlebarButtonsWidth; vertically centered

For example, the collapse method resolves the titlebarRect and sizeOfTitlebarButtons selectors, then performs the right-edge calculation:

43355cb4  bl    0x43642094 ; [self titlebarRect]
43355cc8  bl    0x43642094 ; [self sizeOfTitlebarButtons]
43355cec  lfs   f0,0x50(r1) ; titlebar x
43355cf0  lfs   f13,0x58(r1); titlebar width
43355cf4  fadds f0,f0,f13
43355cf8  lfs   f12,0x48(r1); button width
43355cfc  fsubs f0,f0,f12   ; maxX(titlebar) - button width

The Y path in all three methods computes half the difference between titlebar height and button height, passes it through the binary's pixel-alignment helper, and adds titlebarRect.y. Server 1.0 therefore does not place the controls in a traffic-light row: close is on the left; collapse is at the right edge; zoom is immediately to the left of that right-side group.

Server 1.0 z-order semantics

The same binary supplies the ordering proof:

  1. -[NSView addSubview:] calls the child's _setSuperview:.
  2. -[NSView _setSuperview:] calls the parent's _addSubview:.
  3. -[NSView _addSubview:] lazily creates an NSMutableArray with allocWithZone: and initWithCapacity:, then calls addObject:.
  4. -[NSView hitTest:] starts at count - 1, fetches each child with objectAtIndex:, and walks downward until a hit is found.

Therefore later-added siblings are in front. Server 1.0 creates and appends the buttons in close, zoom, miniaturize order, so the miniaturize/collapse button is frontmost.

Current ARM64e comparison

On macOS 27.0 beta 6, build 26A5416b, AppKit UUID 6E2B8742-E130-3414-9402-57F0C396B9A7 has:

  • binary: /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
  • dyld-cache __TEXT base: 0x184ec2000
  • symbol: -[NSThemeFrame _updateButtons]
  • image offset: 0x5d3f4
  • preferred address: 0x184f1f3f4

Its creation/add sequence is likewise:

; red / close
184f1f580  mov x0,x19
184f1f584  bl  1880944a0 ; newCloseButton
184f1f58c  str x0,[x19,x28]
184f1f594  bl  1880738a0 ; addTitlebarSubview:

; green / zoom
184f1f624  mov x0,x19
184f1f628  bl  1880946f0 ; newZoomButton
184f1f630  str x0,[x19,x22]
184f1f638  bl  1880738a0 ; addTitlebarSubview:

; yellow / miniaturize
184f1f6c0  mov x0,x19
184f1f6c4  bl  188094610 ; newMiniaturizeButton
184f1f6cc  str x0,[x19,x22]
184f1f6d4  bl  1880738a0 ; addTitlebarSubview:

Current ARM64e positioning

Current positioning remains in AppKit, now on NSThemeFrame. On the inspected build the main methods are:

Symbol Image offset Preferred address Role
-[NSThemeFrame _updateButtonPositions] 0x00068e8c 0x184f2ae8c Obtains and applies all button origins
-[NSThemeFrame _closeButtonOrigin] 0x00069098 0x184f2b098 Computes the red button's anchor, including centering/RTL/titlebar cases
-[NSThemeFrame _setButton:frameOrigin:] 0x000698b8 0x184f2b8b8 Converts/applies an origin to a button
-[NSThemeFrame _collapseButtonOrigin] 0x0006a3ac 0x184f2c3ac Derives yellow from red
-[NSThemeFrame _windowTitlebarButtonSpacingWidth] 0x0006a47c 0x184f2c47c Supplies inter-button spacing
-[NSThemeFrame _zoomButtonOrigin] 0x0006a4d0 0x184f2c4d0 Derives green from yellow

Two related policy helpers are _shouldCenterTrafficLights at image offset 0x00069480 and _shouldFlipTrafficLightsForRTL at 0x00069528.

The compact part of the ARM64e positioning logic is the chain from close to miniaturize to zoom. Selector-specific dispatch stubs are annotated here:

; -[NSThemeFrame _collapseButtonOrigin]
184f2c3c8  bl    18803d810 ; _closeButtonOrigin -> d0=x, d1=y
184f2c3d8  bl    1880b1010 ; sizeOfTitlebarButtons -> d0=width
184f2c3e4  bl    1880706c0 ; _windowTitlebarButtonSpacingWidth -> d0=spacing
184f2c3e8  fadd  d10,d10,d0
184f2c3f0  bl    188064700 ; _shouldFlipTrafficLightsForRTL
184f2c3f4  fneg  d0,d10
184f2c3fc  fcsel d0,d0,d10,ne ; choose -step for RTL, +step otherwise
184f2c400  fadd  d0,d8,d0     ; collapse.x = close.x +/- step

; -[NSThemeFrame _zoomButtonOrigin]
184f2c4ec  bl    18803d9d0 ; _collapseButtonOrigin -> d0=x, d1=y
184f2c4fc  bl    1880b1010 ; sizeOfTitlebarButtons -> d0=width
184f2c508  bl    1880706c0 ; _windowTitlebarButtonSpacingWidth -> d0=spacing
184f2c50c  fadd  d10,d10,d0
184f2c514  bl    188064700 ; _shouldFlipTrafficLightsForRTL
184f2c51c  fcsel d0,d0,d10,ne
184f2c524  fadd  d0,d8,d0 ; zoom.x = collapse.x +/- step

Why chain one button from another?

The binary shows how AppKit positions the row, but not Apple's original rationale. A likely reason for the relative chain is that close is the only button that needs the complicated global anchoring policy: titlebar geometry, centering, toolbar mode, custom offsets, and RTL can all affect that first origin. Once close is placed, the other controls only need to preserve the local invariant “one button width plus the current spacing from the previous button.” Deriving collapse from close and zoom from collapse therefore keeps the group contiguous while automatically inheriting changes to the anchor, control metrics, spacing, and layout direction. It also avoids duplicating the same global-position calculation three times. This is an inference from the structure of the implementation, not a claim recovered from source comments.

In a normal 640-by-480 titled test window on this build, the runtime probe reports a 14-point button width, 9-point spacing, and origins {9,489}, {32,489}, and {55,489} for close, miniaturize, and zoom. The 23-point delta is exactly 14 + 9. The displayed button frames have the same X coordinates; their Y coordinates are converted from titlebar-view space by _setButton:frameOrigin:.

This is positioning logic, distinct from sibling z-order. Stretching changes the button frames enough to expose their overlap; creation/add order determines which overlapping sibling is on top.

Cross-architecture and cross-generation comparison

Question Same? Server 1.0 PowerPC Current ARM64e
Is the logic in AppKit? Yes Public AppKit framework Public AppKit framework, delivered through the dyld shared cache
Same frame class name? No NSTitledFrame NSThemeFrame
Same three factory selector names? Yes newCloseButton, newZoomButton, newMiniaturizeButton The same three selectors
Same creation/add order? Yes Close, zoom, miniaturize Close, zoom, miniaturize
Same high-level operation per button? Yes Create, store in an ivar, add as a child Create, store in an ivar, add as a child
Same add-to-hierarchy selector? No addSubview: addTitlebarSubview:
Same individual origin selector names? Yes _closeButtonOrigin, _collapseButtonOrigin, _zoomButtonOrigin The same three selectors
Same overall positioning entry point? No _tileTitlebar _updateButtonPositions
Same spatial arrangement? No Close at left; zoom and collapse at right Close, miniaturize, zoom in one traffic-light row
Same positioning strategy? No Each origin is calculated from titlebar geometry Close is the anchor; collapse derives from close; zoom derives from collapse
Explicit RTL-aware row direction? No evidence No equivalent branch in these origin methods _shouldFlipTrafficLightsForRTL reverses each relative step
Same frontmost button when all three overlap? Yes Miniaturize/collapse, because it is added last Miniaturize, because it is added last
Opcode-for-opcode translation? No 32-bit PowerPC ABI and common Objective-C dispatch stubs ARM64e ABI, pointer authentication, and selector-specific stubs
Does this prove continuous source-code lineage? No Matching binary behavior is evidence, not provenance Matching binary behavior is evidence, not provenance

Cross-architecture instruction mapping

The creation groups correspond closely at the semantic level, but they are not opcode-for-opcode translations:

PowerPC (1999) ARM64e (2026) Meaning
mr r3,r30 mov x0,x19 Put self in the first argument register
lwz r4,… Selector-specific call stub Select the Objective-C method
bl objc_msgSend bl msgSend$new… Call the button factory
stw r5,offset(r30) str x0,[x19,…] Store the returned button in an ivar
bl objc_msgSend bl …addTitlebarSubview: Add the button to the view hierarchy

PowerPC loads the selector into r4, returns the object in r3, and repeatedly calls a common objc_msgSend PIC stub. ARM64e returns the object in x0 and uses selector-specific message-send stubs. Current AppKit also uses the specialized addTitlebarSubview: rather than ordinary addSubview:.

Despite those ABI and implementation differences, both binaries have the same high-level sequence for each control: create, store, add—first close, then zoom, then miniaturize.

Conclusion

The observable claim is confirmed for both binaries: the Server 1.0 PowerPC AppKit and the current ARM64e AppKit create/add the controls in the same semantic order—close, zoom, miniaturize—even though Aqua placed miniaturize between close and zoom spatially. Because sibling order is back-to-front, miniaturize is above both other controls when stretched into overlap.

This proves matching implementation behavior across the two releases. It does not, by binary evidence alone, prove that Apple preserved the identical source code continuously between 1999 and 2026.

#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly ISO_NAME="Mac OS X Server 1.0.iso"
readonly ISO_PATH="${SCRIPT_DIR}/${ISO_NAME}"
readonly ISO_URL="https://archive.org/download/mac-os-x-server-1.0/Mac%20OS%20X%20Server%201.0.iso"
readonly EXPECTED_SIZE="678135808"
readonly EXPECTED_SHA1="7f01415dc96093acc77edbfe8da0d003c0b56da7"
if ! command -v curl >/dev/null 2>&1; then
echo "error: curl is required" >&2
exit 1
fi
if [[ -f "${ISO_PATH}" ]]; then
existing_size="$(stat -f '%z' "${ISO_PATH}")"
if (( existing_size > EXPECTED_SIZE )); then
echo "error: existing ISO is larger than expected (${existing_size} bytes)" >&2
echo "Move it aside and rerun this script." >&2
exit 1
fi
if [[ "${existing_size}" == "${EXPECTED_SIZE}" ]]; then
existing_sha1="$(shasum -a 1 "${ISO_PATH}" | awk '{print $1}')"
if [[ "${existing_sha1}" == "${EXPECTED_SHA1}" ]]; then
echo "Already downloaded and verified: ${ISO_PATH}"
echo "Size: ${existing_size} bytes"
echo "SHA-1: ${existing_sha1}"
exit 0
fi
echo "error: existing complete-size ISO has the wrong SHA-1" >&2
echo "Move it aside and rerun this script." >&2
echo "expected: ${EXPECTED_SHA1}" >&2
echo "actual: ${existing_sha1}" >&2
exit 1
fi
echo "Resuming partial file (${existing_size} of ${EXPECTED_SIZE} bytes)"
fi
echo "Downloading ${ISO_NAME}"
echo "Destination: ${ISO_PATH}"
curl --location \
--fail \
--continue-at - \
--retry 5 \
--retry-delay 2 \
--output "${ISO_PATH}" \
"${ISO_URL}"
actual_size="$(stat -f '%z' "${ISO_PATH}")"
if [[ "${actual_size}" != "${EXPECTED_SIZE}" ]]; then
echo "error: size mismatch: expected ${EXPECTED_SIZE}, got ${actual_size}" >&2
exit 1
fi
actual_sha1="$(shasum -a 1 "${ISO_PATH}" | awk '{print $1}')"
if [[ "${actual_sha1}" != "${EXPECTED_SHA1}" ]]; then
echo "error: SHA-1 mismatch" >&2
echo "expected: ${EXPECTED_SHA1}" >&2
echo "actual: ${actual_sha1}" >&2
exit 1
fi
echo "Verified ${ISO_NAME}"
echo "Size: ${actual_size} bytes"
echo "SHA-1: ${actual_sha1}"
#!/usr/bin/env python3
"""Resolve selected PowerPC AppKit virtual-address references."""
from __future__ import annotations
import struct
from pathlib import Path
class MachOImage:
def __init__(self, path: Path) -> None:
self.path = path
self.data = path.read_bytes()
magic, _, _, _, command_count = struct.unpack_from(">IIIII", self.data, 0)
if magic != 0xFEEDFACE:
raise ValueError(f"{path} is not a big-endian 32-bit Mach-O image")
self.segments: list[tuple[int, int, int, int]] = []
command_offset = 28
for _ in range(command_count):
command, command_size = struct.unpack_from(">II", self.data, command_offset)
if command == 1: # LC_SEGMENT
vm_address, vm_size, file_offset, file_size = struct.unpack_from(
">IIII", self.data, command_offset + 24
)
self.segments.append((vm_address, vm_size, file_offset, file_size))
command_offset += command_size
def file_offset(self, address: int) -> int:
for vm_address, vm_size, file_offset, file_size in self.segments:
relative = address - vm_address
if 0 <= relative < vm_size and relative < file_size:
return file_offset + relative
raise ValueError(f"0x{address:08x} is not backed by {self.path.name}")
def c_string(self, address: int) -> str:
offset = self.file_offset(address)
end = self.data.index(b"\0", offset)
return self.data[offset:end].decode("ascii", errors="replace")
def read_u32(self, address: int) -> int:
return struct.unpack_from(">I", self.data, self.file_offset(address))[0]
def main() -> None:
binaries = Path(__file__).with_name("binaries")
images = [MachOImage(binaries / name) for name in ("AppKit", "Foundation", "System")]
appkit = images[0]
def resolve_c_string(address: int) -> tuple[str, str]:
for image in images:
try:
return image.path.name, image.c_string(address)
except ValueError:
pass
raise ValueError(f"0x{address:08x} is not a C string in a loaded image")
constant_strings = [0x4365B630, 0x4365B63C, 0x4365B654, 0x4365B660]
pointer_slots = {
"selector@59a0": 0x436662B8,
"selector@5b68": 0x43666480,
"selector@3584": 0x43663E9C,
"argument@3588": 0x43663EA0,
"argument@3580": 0x43663E98,
"selector@530c": 0x43665C24,
"selector@3b5c": 0x43664474,
"class-ref@6f54": 0x4366786C,
"class-ref@710c": 0x43667A24,
"NSTitledFrame style mask": 0x43666170,
"NSTitledFrame factory #1": 0x43663140,
"NSTitledFrame factory #2": 0x4366313C,
"NSTitledFrame factory #3": 0x43663138,
"NSTitledFrame file-button predicate": 0x43663134,
"NSTitledFrame factory #4": 0x43663150,
"NSTitledFrame add subview": 0x436662C0,
"NSTitledFrame owner style mask": 0x43664460,
"NSTitledFrame final retile": 0x43663168,
"NSView addSubview selector 1224": 0x43666310,
"NSView addSubview selector 138c": 0x43666478,
"NSView addSubview selector 110c": 0x436661F8,
"NSView addSubview selector 1108": 0x436661F4,
"NSView addSubview selector 1104": 0x436661F0,
"NSView addSubview selector -20f0": 0x43662FFC,
"NSView addSubview selector 02b4": 0x436653A0,
"NSView addSubview selector 07e0": 0x436658CC,
"NSView addSubview selector 0230": 0x4366531C,
"NSView addSubview selector -0c6c": 0x43664480,
"NSView addSubview selector -20f4": 0x43662FF8,
"NSView addSubview selector -20f8": 0x43662FF4,
"NSView addSubview selector 0250": 0x4366533C,
"NSView addSubview selector 07d8": 0x436658C4,
"NSView addSubview selector -20fc": 0x43662FF0,
"NSView addSubview selector -00d8": 0x43665014,
"NSView addSubview selector -20dc": 0x43663010,
"NSView addSubview selector -00dc": 0x43665010,
"NSView _setSuperview selector 478c": 0x43662FEC,
"NSView _setSuperview selector 4788": 0x43662FE8,
"NSView _setSuperview selector 4784": 0x43662FE4,
"NSView _addSubview selector 0aec": 0x43666338,
"NSView _addSubview class-ref 229c": 0x43667AE8,
"NSView _addSubview selector 0ba8": 0x436663F4,
"NSView _addSubview selector 06e8": 0x43665F34,
"NSView _addSubview selector 06e0": 0x43665F2C,
"NSView _addSubview selector -286c": 0x43662FE0,
"NSView _addSubview selector 0a54": 0x436662A0,
"NSView hitTest subview count": 0x43666274,
"NSView hitTest object at index": 0x4366626C,
"NSView hitTest recurse": 0x43666064,
"NSTitledFrame origin: frame/bounds #1": 0x43663298,
"NSTitledFrame origin: frame/bounds #2": 0x4366321C,
"NSTitledFrame zoom: button width": 0x4366306C,
"NSTitledFrame titlebar: close origin": 0x43663164,
"NSTitledFrame titlebar: set frame origin": 0x43665C18,
"NSTitledFrame titlebar: collapse origin": 0x43663160,
"NSTitledFrame titlebar: zoom origin": 0x4366315C,
"NSTitledFrame titlebar: extra origin": 0x43663158,
"NSTitledFrame titlebar: super dispatch": 0x43664474,
"NSTitledFrame titlebar: title rect": 0x43665C28,
"NSTitledFrame tile: super tile": 0x43665B94,
"NSTitledFrame tile: tile titlebar": 0x43663168,
"NSTitledFrame tile: display flag": 0x436662A0,
}
for address in constant_strings:
offset = appkit.file_offset(address)
_, characters, length = struct.unpack_from(">III", appkit.data, offset)
character_offset = appkit.file_offset(characters)
text = appkit.data[character_offset : character_offset + length]
print(
f"constant-string@0x{address:08x}: chars=0x{characters:08x} "
f"length={length} value={text.decode('ascii', errors='replace')!r}"
)
for label, slot in pointer_slots.items():
pointer = appkit.read_u32(slot)
try:
source, string = resolve_c_string(pointer)
value = f"{string!r} ({source})"
except (ValueError, IndexError):
value = "not a C string"
print(f"{label}: slot=0x{slot:08x} pointer=0x{pointer:08x} value={value}")
if __name__ == "__main__":
main()
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <dlfcn.h>
#import <objc/runtime.h>
static BOOL isRelevantSelector(const char *name) {
static const char *needles[] = {
"button", "Button", "tile", "Tile", "layout", "Layout",
"origin", "Origin", "frame", "Frame", "traffic", "Traffic",
};
for (size_t index = 0; index < sizeof(needles) / sizeof(needles[0]); index++) {
if (strstr(name, needles[index]) != NULL) {
return YES;
}
}
return NO;
}
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (argc == 2 && strcmp(argv[1], "--window") == 0) {
[NSApplication sharedApplication];
NSWindowStyleMask style = NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskResizable;
NSWindow *window = [[NSWindow alloc]
initWithContentRect:NSMakeRect(0, 0, 640, 480)
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
id frame = window.contentView.superview;
printf("frame class: %s\n", object_getClassName(frame));
typedef NSPoint (*PointMethod)(id, SEL);
const char *selectors[] = {
"_closeButtonOrigin",
"_collapseButtonOrigin",
"_zoomButtonOrigin",
};
for (size_t index = 0;
index < sizeof(selectors) / sizeof(selectors[0]);
index++) {
SEL selector = sel_registerName(selectors[index]);
PointMethod function = (PointMethod)[frame methodForSelector:selector];
NSPoint point = function(frame, selector);
printf("%s = {%g, %g}\n", selectors[index], point.x, point.y);
}
SEL sizeSelector = sel_registerName("sizeOfTitlebarButtons");
typedef NSSize (*SizeMethod)(id, SEL);
SizeMethod sizeFunction =
(SizeMethod)[frame methodForSelector:sizeSelector];
NSSize buttonSize = sizeFunction(frame, sizeSelector);
SEL spacingSelector =
sel_registerName("_windowTitlebarButtonSpacingWidth");
typedef CGFloat (*ScalarMethod)(id, SEL);
ScalarMethod spacingFunction =
(ScalarMethod)[frame methodForSelector:spacingSelector];
CGFloat spacing = spacingFunction(frame, spacingSelector);
printf(
"sizeOfTitlebarButtons = {%g, %g}; spacing = %g\n",
buttonSize.width,
buttonSize.height,
spacing
);
const struct {
NSWindowButton kind;
const char *name;
} buttons[] = {
{NSWindowCloseButton, "close"},
{NSWindowMiniaturizeButton, "miniaturize"},
{NSWindowZoomButton, "zoom"},
};
for (size_t index = 0;
index < sizeof(buttons) / sizeof(buttons[0]);
index++) {
NSButton *button = [window standardWindowButton:buttons[index].kind];
NSRect buttonFrame = button.frame;
printf(
"%s frame = {{%g, %g}, {%g, %g}}\n",
buttons[index].name,
buttonFrame.origin.x,
buttonFrame.origin.y,
buttonFrame.size.width,
buttonFrame.size.height
);
}
return 0;
}
if (argc == 3 && strcmp(argv[1], "--read32") == 0) {
char *end = NULL;
unsigned long long address = strtoull(argv[2], &end, 0);
if (argv[2][0] == '\0' || end == NULL || *end != '\0') {
fprintf(stderr, "invalid address: %s\n", argv[2]);
return 1;
}
printf(
"0x%016llx: 0x%08x (%d)\n",
address,
*(const uint32_t *)(uintptr_t)address,
*(const int32_t *)(uintptr_t)address
);
return 0;
}
if (argc == 2 && strcmp(argv[1], "--ivars") == 0) {
for (Class cls = NSClassFromString(@"NSThemeFrame");
cls != Nil;
cls = class_getSuperclass(cls)) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
printf("class %s\n", class_getName(cls));
for (unsigned int index = 0; index < count; index++) {
printf(
" +0x%04lx %s %s\n",
(unsigned long)ivar_getOffset(ivars[index]),
ivar_getName(ivars[index]),
ivar_getTypeEncoding(ivars[index])
);
}
free(ivars);
}
return 0;
}
if (argc == 4 && strcmp(argv[1], "--calls") == 0) {
Class cls = NSClassFromString(@"NSThemeFrame");
SEL selector = sel_registerName(argv[2]);
Method method = class_getInstanceMethod(cls, selector);
if (method == NULL) {
fprintf(stderr, "method not found: -[NSThemeFrame %s]\n", argv[2]);
return 1;
}
char *end = NULL;
unsigned long byteCount = strtoul(argv[3], &end, 0);
if (argv[3][0] == '\0' || end == NULL || *end != '\0') {
fprintf(stderr, "invalid byte count: %s\n", argv[3]);
return 1;
}
const unsigned char *code =
(const unsigned char *)method_getImplementation(method);
Dl_info imageInfo = {0};
dladdr(code, &imageInfo);
uintptr_t imageBase = (uintptr_t)imageInfo.dli_fbase;
for (unsigned long index = 0; index + 4 <= byteCount; index += 4) {
uint32_t instruction = 0;
memcpy(&instruction, code + index, sizeof(instruction));
if ((instruction & 0xfc000000) != 0x94000000) {
continue;
}
int32_t immediate = (int32_t)(instruction & 0x03ffffff);
if ((immediate & 0x02000000) != 0) {
immediate |= (int32_t)0xfc000000;
}
uintptr_t pc = (uintptr_t)(code + index);
uintptr_t target = pc + ((intptr_t)immediate << 2);
Dl_info targetInfo = {0};
dladdr((const void *)target, &targetInfo);
printf(
"+0x%04lx (+0x%08lx) -> 0x%016lx %s\n",
index,
(unsigned long)(pc - imageBase),
(unsigned long)target,
targetInfo.dli_sname != NULL ? targetInfo.dli_sname : "?"
);
}
return 0;
}
if (argc == 4 && strcmp(argv[1], "--hex") == 0) {
Class cls = NSClassFromString(@"NSThemeFrame");
SEL selector = sel_registerName(argv[2]);
Method method = class_getInstanceMethod(cls, selector);
if (method == NULL) {
fprintf(stderr, "method not found: -[NSThemeFrame %s]\n", argv[2]);
return 1;
}
char *end = NULL;
unsigned long byteCount = strtoul(argv[3], &end, 0);
if (argv[3][0] == '\0' || end == NULL || *end != '\0') {
fprintf(stderr, "invalid byte count: %s\n", argv[3]);
return 1;
}
const unsigned char *code =
(const unsigned char *)method_getImplementation(method);
for (unsigned long index = 0; index < byteCount; index++) {
printf("%02x", code[index]);
}
putchar('\n');
return 0;
}
for (Class cls = NSClassFromString(@"NSThemeFrame");
cls != Nil;
cls = class_getSuperclass(cls)) {
printf("class %s\n", class_getName(cls));
unsigned int count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (unsigned int index = 0; index < count; index++) {
SEL selector = method_getName(methods[index]);
const char *name = sel_getName(selector);
if (!isRelevantSelector(name)) {
continue;
}
IMP implementation = method_getImplementation(methods[index]);
Dl_info info = {0};
dladdr((const void *)implementation, &info);
uintptr_t address = (uintptr_t)implementation;
uintptr_t base = (uintptr_t)info.dli_fbase;
printf(
" 0x%016lx +0x%08lx -[%s %s] %s\n",
(unsigned long)address,
(unsigned long)(address - base),
class_getName(cls),
name,
method_getTypeEncoding(methods[index])
);
}
free(methods);
}
}
return 0;
}
#!/usr/bin/env python3
"""Read the NeXT/Rhapsody dlV3 label and locate UFS magic values."""
from __future__ import annotations
import argparse
import mmap
import struct
from pathlib import Path
APM_BLOCK_SIZE = 512
NEXT_LABEL_PARTITION_BLOCK = 296_388
PARTITION_COUNT = 8
PARTITION_TABLE_OFFSET = 190
PARTITION_ENTRY_SIZE = 46
def c_string(data: bytes) -> str:
return data.split(b"\0", 1)[0].decode("mac_roman", errors="replace")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("image", type=Path)
args = parser.parse_args()
label_offset = NEXT_LABEL_PARTITION_BLOCK * APM_BLOCK_SIZE
with args.image.open("rb") as stream:
label = stream.read(0)
stream.seek(label_offset)
label = stream.read(8192)
magic, label_block, media_sectors = struct.unpack_from(">III", label, 0)
sector_size = struct.unpack_from(">I", label, 92)[0]
root_partition = chr(label[188])
rw_partition = chr(label[189])
print(f"label_offset={label_offset} (0x{label_offset:x})")
print(f"magic=0x{magic:08x} ({magic.to_bytes(4, 'big').decode('ascii')})")
print(f"label_block={label_block}")
print(f"media_sectors={media_sectors}")
print(f"label={c_string(label[12:36])}")
print(f"sector_size={sector_size}")
print(f"root_partition={root_partition}")
print(f"rw_partition={rw_partition}")
for index in range(PARTITION_COUNT):
offset = PARTITION_TABLE_OFFSET + index * PARTITION_ENTRY_SIZE
start, size, block_size, fragment_size = struct.unpack_from(
">IIHH", label, offset
)
if start == 0xFFFFFFFF:
continue
mount_point = c_string(label[offset + 20 : offset + 36])
auto_mount = label[offset + 36]
fs_type = c_string(label[offset + 37 : offset + 45])
print(
f"partition={chr(ord('a') + index)} start={start} size={size} "
f"block_size={block_size} fragment_size={fragment_size} "
f"mount={mount_point!r} automount={auto_mount} type={fs_type!r}"
)
with mmap.mmap(stream.fileno(), 0, access=mmap.ACCESS_READ) as image:
patterns = {
"UFS1 big-endian": bytes.fromhex("00011954"),
"UFS1 little-endian": bytes.fromhex("54190100"),
"UFS2 big-endian": bytes.fromhex("19540119"),
"UFS2 little-endian": bytes.fromhex("19015419"),
}
for name, pattern in patterns.items():
locations = []
position = label_offset
while len(locations) < 32:
position = image.find(pattern, position)
if position < 0:
break
locations.append(position)
position += 1
relative = [location - label_offset for location in locations]
print(f"{name}: absolute={locations} relative={relative}")
if __name__ == "__main__":
main()
#!/usr/bin/env bash
set -euo pipefail
readonly FORMULAE=(
hfsutils
sleuthkit
capstone
)
if ! command -v brew >/dev/null 2>&1; then
echo "error: Homebrew is required: https://brew.sh" >&2
exit 1
fi
missing=()
for formula in "${FORMULAE[@]}"; do
if brew list --formula "${formula}" >/dev/null 2>&1; then
echo "Already installed: ${formula}"
else
missing+=("${formula}")
fi
done
if (( ${#missing[@]} == 0 )); then
echo "All required dependencies are installed."
exit 0
fi
echo "Installing: ${missing[*]}"
brew install "${missing[@]}"
echo "Required dependencies are installed: ${FORMULAE[*]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment