diff --git a/src/native/NativeMachoInspection.ts b/src/native/NativeMachoInspection.ts index 2f790af74..5df50ef3c 100644 --- a/src/native/NativeMachoInspection.ts +++ b/src/native/NativeMachoInspection.ts @@ -35,7 +35,7 @@ interface NativeMachoInspectionContext { const REQUIRED_COMMANDS = [ ["file", ["-b"]], ["lipo", ["-detailed_info"]], - ["otool", ["-l"]], + ["otool", ["-h", "-l"]], ["nm", ["-gjU"]], ["dyld_info", ["-imports"]], ["dyld_info", ["-exports"]], diff --git a/src/native/parsers/otool.ts b/src/native/parsers/otool.ts index aa3b932a1..61a50f258 100644 --- a/src/native/parsers/otool.ts +++ b/src/native/parsers/otool.ts @@ -98,7 +98,6 @@ const parseLoadCommand = (block: string, state: ParsedLoadCommands): void => { collectBuild(kind, block, fields, state); collectDependency(kind, fields, state); collectSegment(kind, block, fields, state); - collectFlags(fields, state.flags); }; const collectUuid = ( @@ -159,16 +158,6 @@ const collectSegment = ( state.segments.push(parseSegment(block, fields)); }; -const collectFlags = ( - fields: Readonly>, - flags: Set, -): void => { - const rawFlags = stringField(fields, "flags"); - if (rawFlags === null) return; - for (const flag of rawFlags.split(/\s+/u)) - if (flag.length > 0) flags.add(flag); -}; - const parseFields = (block: string): Record => { const fields: Record = {}; for (const rawLine of block.split(/\r?\n/u).slice(1)) { diff --git a/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts b/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts new file mode 100644 index 000000000..8582e9934 --- /dev/null +++ b/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts @@ -0,0 +1,44 @@ +import { expect, it } from "vitest"; + +import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js"; +import { parseOtoolLoadCommands } from "../../../../src/native/parsers/otool.js"; +import { + NativeFixtureRunner, + nativeFixture, + nativeMachoTarget, +} from "../../../fixtures/nativeCommands.js"; + +it("collects the actual Mach header alongside the native load commands", async () => { + // The provider always invokes `otool -h -l`, which the runner resolves + // via the "otool:-h" key; no bare `otool` override is needed. + const runner = new NativeFixtureRunner({ + "otool:-h": await nativeFixture( + "native-macho-header/otool-header-load.txt", + ), + }); + const client = new NativeMacOSProvider(runner, "darwin").createClient( + nativeMachoTarget("/owned/fixture"), + ); + const result = await client.execute("inspect_macho", {}); + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.value.result).toMatchObject({ + file_type: "2", + flags: ["0x00200085"], + segments: { + items: expect.arrayContaining([ + expect.objectContaining({ name: "__TEXT" }), + ]), + }, + }); +}); + +it("does not reinterpret segment and section flags as Mach header flags", async () => { + const load = parseOtoolLoadCommands( + await nativeFixture("native-macho-header/otool-header-load.txt"), + ); + expect(load.flags).toEqual(["0x00200085"]); + expect(load.commands.some((command) => command.fields.flags === 0)).toBe( + true, + ); +}); diff --git a/tests/fixtures/native-macos/native-macho-header/README.md b/tests/fixtures/native-macos/native-macho-header/README.md new file mode 100644 index 000000000..23cb8e549 --- /dev/null +++ b/tests/fixtures/native-macos/native-macho-header/README.md @@ -0,0 +1,14 @@ +# Source-built Mach-O header capture + +Captured on macOS arm64 with the host Xcode tools from the adjacent `fixture.c`: + +```sh +xcrun clang fixture.c -o fixture +xcrun otool -l fixture > otool-load.txt +{ xcrun otool -h fixture; xcrun otool -l fixture; } > otool-header-load.txt +``` + +Only the temporary fixture path was replaced with `/owned/fixture`; tool fields +are unchanged. The first output contains load commands without a Mach header. +The second contains the real numeric header followed by those same commands. +These are format regression captures, not Hopper or Ghidra runtime proof. diff --git a/tests/fixtures/native-macos/native-macho-header/fixture.c b/tests/fixtures/native-macos/native-macho-header/fixture.c new file mode 100644 index 000000000..94730dd5c --- /dev/null +++ b/tests/fixtures/native-macos/native-macho-header/fixture.c @@ -0,0 +1,2 @@ +int owned_value(void) { return 7; } +int main(void) { return owned_value(); } diff --git a/tests/fixtures/native-macos/native-macho-header/otool-header-load.txt b/tests/fixtures/native-macos/native-macho-header/otool-header-load.txt new file mode 100644 index 000000000..819e9e3a0 --- /dev/null +++ b/tests/fixtures/native-macos/native-macho-header/otool-header-load.txt @@ -0,0 +1,152 @@ +/owned/fixture: +Mach header + magic cputype cpusubtype caps filetype ncmds sizeofcmds flags + 0xfeedfacf 16777228 0 0x00 2 16 744 0x00200085 + +/owned/fixture: +Load command 0 + cmd LC_SEGMENT_64 + cmdsize 72 + segname __PAGEZERO + vmaddr 0x0000000000000000 + vmsize 0x0000000100000000 + fileoff 0 + filesize 0 + maxprot 0x00000000 + initprot 0x00000000 + nsects 0 + flags 0x0 +Load command 1 + cmd LC_SEGMENT_64 + cmdsize 232 + segname __TEXT + vmaddr 0x0000000100000000 + vmsize 0x0000000000004000 + fileoff 0 + filesize 16384 + maxprot 0x00000005 + initprot 0x00000005 + nsects 2 + flags 0x0 +Section + sectname __text + segname __TEXT + addr 0x0000000100000328 + size 0x0000000000000028 + offset 808 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x80000400 + reserved1 0 + reserved2 0 +Section + sectname __unwind_info + segname __TEXT + addr 0x0000000100000350 + size 0x0000000000000060 + offset 848 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x00000000 + reserved1 0 + reserved2 0 +Load command 2 + cmd LC_SEGMENT_64 + cmdsize 72 + segname __LINKEDIT + vmaddr 0x0000000100004000 + vmsize 0x0000000000004000 + fileoff 16384 + filesize 504 + maxprot 0x00000001 + initprot 0x00000001 + nsects 0 + flags 0x0 +Load command 3 + cmd LC_DYLD_CHAINED_FIXUPS + cmdsize 16 + dataoff 16384 + datasize 56 +Load command 4 + cmd LC_DYLD_EXPORTS_TRIE + cmdsize 16 + dataoff 16440 + datasize 64 +Load command 5 + cmd LC_SYMTAB + cmdsize 24 + symoff 16512 + nsyms 3 + stroff 16560 + strsize 48 +Load command 6 + cmd LC_DYSYMTAB + cmdsize 80 + ilocalsym 0 + nlocalsym 0 + iextdefsym 0 + nextdefsym 3 + iundefsym 3 + nundefsym 0 + tocoff 0 + ntoc 0 + modtaboff 0 + nmodtab 0 + extrefsymoff 0 + nextrefsyms 0 + indirectsymoff 0 + nindirectsyms 0 + extreloff 0 + nextrel 0 + locreloff 0 + nlocrel 0 +Load command 7 + cmd LC_LOAD_DYLINKER + cmdsize 32 + name /usr/lib/dyld (offset 12) +Load command 8 + cmd LC_UUID + cmdsize 24 + uuid 633B21A4-D14A-3312-A5AD-B3CB919DAC72 +Load command 9 + cmd LC_BUILD_VERSION + cmdsize 32 + platform 1 + minos 26.0 + sdk 26.4 + ntools 1 + tool 3 + version 1266.8 +Load command 10 + cmd LC_SOURCE_VERSION + cmdsize 16 + version 0.0 +Load command 11 + cmd LC_MAIN + cmdsize 24 + entryoff 816 + stacksize 0 +Load command 12 + cmd LC_LOAD_DYLIB + cmdsize 56 + name /usr/lib/libSystem.B.dylib (offset 24) + time stamp 2 Wed Dec 31 19:00:02 1969 + current version 1356.0.0 +compatibility version 1.0.0 +Load command 13 + cmd LC_FUNCTION_STARTS + cmdsize 16 + dataoff 16504 + datasize 8 +Load command 14 + cmd LC_DATA_IN_CODE + cmdsize 16 + dataoff 16512 + datasize 0 +Load command 15 + cmd LC_CODE_SIGNATURE + cmdsize 16 + dataoff 16608 + datasize 280 diff --git a/tests/fixtures/native-macos/native-macho-header/otool-load.txt b/tests/fixtures/native-macos/native-macho-header/otool-load.txt new file mode 100644 index 000000000..ca1551c66 --- /dev/null +++ b/tests/fixtures/native-macos/native-macho-header/otool-load.txt @@ -0,0 +1,147 @@ +/owned/fixture: +Load command 0 + cmd LC_SEGMENT_64 + cmdsize 72 + segname __PAGEZERO + vmaddr 0x0000000000000000 + vmsize 0x0000000100000000 + fileoff 0 + filesize 0 + maxprot 0x00000000 + initprot 0x00000000 + nsects 0 + flags 0x0 +Load command 1 + cmd LC_SEGMENT_64 + cmdsize 232 + segname __TEXT + vmaddr 0x0000000100000000 + vmsize 0x0000000000004000 + fileoff 0 + filesize 16384 + maxprot 0x00000005 + initprot 0x00000005 + nsects 2 + flags 0x0 +Section + sectname __text + segname __TEXT + addr 0x0000000100000328 + size 0x0000000000000028 + offset 808 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x80000400 + reserved1 0 + reserved2 0 +Section + sectname __unwind_info + segname __TEXT + addr 0x0000000100000350 + size 0x0000000000000060 + offset 848 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x00000000 + reserved1 0 + reserved2 0 +Load command 2 + cmd LC_SEGMENT_64 + cmdsize 72 + segname __LINKEDIT + vmaddr 0x0000000100004000 + vmsize 0x0000000000004000 + fileoff 16384 + filesize 504 + maxprot 0x00000001 + initprot 0x00000001 + nsects 0 + flags 0x0 +Load command 3 + cmd LC_DYLD_CHAINED_FIXUPS + cmdsize 16 + dataoff 16384 + datasize 56 +Load command 4 + cmd LC_DYLD_EXPORTS_TRIE + cmdsize 16 + dataoff 16440 + datasize 64 +Load command 5 + cmd LC_SYMTAB + cmdsize 24 + symoff 16512 + nsyms 3 + stroff 16560 + strsize 48 +Load command 6 + cmd LC_DYSYMTAB + cmdsize 80 + ilocalsym 0 + nlocalsym 0 + iextdefsym 0 + nextdefsym 3 + iundefsym 3 + nundefsym 0 + tocoff 0 + ntoc 0 + modtaboff 0 + nmodtab 0 + extrefsymoff 0 + nextrefsyms 0 + indirectsymoff 0 + nindirectsyms 0 + extreloff 0 + nextrel 0 + locreloff 0 + nlocrel 0 +Load command 7 + cmd LC_LOAD_DYLINKER + cmdsize 32 + name /usr/lib/dyld (offset 12) +Load command 8 + cmd LC_UUID + cmdsize 24 + uuid 633B21A4-D14A-3312-A5AD-B3CB919DAC72 +Load command 9 + cmd LC_BUILD_VERSION + cmdsize 32 + platform 1 + minos 26.0 + sdk 26.4 + ntools 1 + tool 3 + version 1266.8 +Load command 10 + cmd LC_SOURCE_VERSION + cmdsize 16 + version 0.0 +Load command 11 + cmd LC_MAIN + cmdsize 24 + entryoff 816 + stacksize 0 +Load command 12 + cmd LC_LOAD_DYLIB + cmdsize 56 + name /usr/lib/libSystem.B.dylib (offset 24) + time stamp 2 Wed Dec 31 19:00:02 1969 + current version 1356.0.0 +compatibility version 1.0.0 +Load command 13 + cmd LC_FUNCTION_STARTS + cmdsize 16 + dataoff 16504 + datasize 8 +Load command 14 + cmd LC_DATA_IN_CODE + cmdsize 16 + dataoff 16512 + datasize 0 +Load command 15 + cmd LC_CODE_SIGNATURE + cmdsize 16 + dataoff 16608 + datasize 280