diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5d27f9c..bcc3001 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -106,6 +106,14 @@ jobs: file /tmp/hello.macos chmod +x /tmp/hello.macos + echo "=== verify PIE flag + LC_DYLD_INFO_ONLY ===" + otool -hv /tmp/hello.macos | head -5 + otool -l /tmp/hello.macos | grep -E "LC_DYLD_INFO_ONLY|LC_CODE_SIGNATURE|LC_UUID|LC_BUILD_VERSION" || true + + # 编译 time.pe(用户报告的被 kill 的程序) + $PAZE tests/time.pe --target macos $ARCH_FLAG -o /tmp/time.macos 2>&1 || true + chmod +x /tmp/time.macos 2>/dev/null || true + echo "=== codesign display (paze-generated) ===" codesign -dv --verbose=4 /tmp/hello.macos 2>&1 || true echo "=== codesign verify (paze-generated) ===" @@ -114,6 +122,9 @@ jobs: echo "=== try run (paze-generated signature) ===" /tmp/hello.macos 2>&1; echo "exit code: $?" + echo "=== try run time.macos ===" + /tmp/time.macos 2>&1; echo "time exit code: $?" + # 对照实验:用系统 codesign -s - 重新签名,排除 paze 签名生成器的问题 echo "=== re-sign with system codesign -s - ===" cp /tmp/hello.macos /tmp/hello_resigned.macos @@ -143,6 +154,17 @@ jobs: echo "=== dyld debug (hello.macos) ===" DYLD_PRINT_LIBRARIES=1 DYLD_PRINT_BINDINGS=1 /tmp/hello.macos 2>&1 | head -20; echo "dyld exit: $?" + # clang 对比测试:用 clang 编译同样逻辑的程序,对比 Mach-O 结构 + echo "=== clang reference build ===" + echo 'int main(void){return 0;}' > /tmp/ref.c + clang -o /tmp/ref.macos /tmp/ref.c + file /tmp/ref.macos + /tmp/ref.macos; echo "clang ref exit: $?" + echo "--- clang ref otool -l (segments) ---" + otool -l /tmp/ref.macos 2>&1 | grep -E "LC_SEGMENT|segname|vmaddr|vmsize|fileoff|filesize|cmd LC" | head -40 + echo "--- paze hello otool -l (segments) ---" + otool -l /tmp/hello.macos 2>&1 | grep -E "LC_SEGMENT|segname|vmaddr|vmsize|fileoff|filesize|cmd LC" | head -40 + exit 0 - name: Dump Mach-O load commands diff --git a/src/PazeE.Compiler/Binary/MachOWriter.cs b/src/PazeE.Compiler/Binary/MachOWriter.cs index fc0b04b..b6f0c73 100644 --- a/src/PazeE.Compiler/Binary/MachOWriter.cs +++ b/src/PazeE.Compiler/Binary/MachOWriter.cs @@ -31,6 +31,14 @@ public sealed class MachOWriter : IExecutableWriter private const uint LC_CODE_SIGNATURE = 0x1D; private const uint LC_BUILD_VERSION = 0x32; private const uint LC_UUID = 0x1B; + private const uint LC_DYLD_INFO_ONLY = 0x80000022; + private const int MH_PIE = 0x200000; + // Rebase opcodes(dyld 对 PIE 二进制中绝对地址加 ASLR slide) + private const byte REBASE_OPCODE_DONE = 0x00; + private const byte REBASE_OPCODE_SET_TYPE_IMM = 0x10; + private const byte REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 = 0x20; + private const byte REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50; + private const int REBASE_TYPE_POINTER = 1; private const int VM_PROT_READ = 1, VM_PROT_WRITE = 2, VM_PROT_EXECUTE = 4; private const int S_NON_LAZY_SYMBOL_POINTERS = 0x06; // nlist n_type @@ -164,8 +172,9 @@ public sealed class MachOWriter : IExecutableWriter int lcCodeSig = 16; // linkedit_data_command(cmd+cmdsize+dataoff+datasize) int lcBuildVer = 24; // build_version_command(cmd+cmdsize+platform+minos+sdk+ntools) int lcUuid = 24; // uuid_command(cmd+cmdsize+uuid[16]) - int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid; - int ncmds = 11; + int lcDyldInfo = 48; // dyld_info_command(rebase+bind+lazy_bind+export 各 off/size) + int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid + lcDyldInfo; + int ncmds = 12; int headerSize = 32; int textFileOff = headerSize + sizeofcmds; // __text 文件偏移 @@ -187,22 +196,12 @@ public sealed class MachOWriter : IExecutableWriter long dataSegFileEnd = dataFileOff + data.Count; long dataSegVmEnd = bssVmaddr + bssSize; - // __LINKEDIT 段 + // __LINKEDIT 段(偏移在 fixup 处理后计算,因 rebase 数据需先生成) long linkSegFileOff = Align(dataSegFileEnd, Page); long linkSegVmaddr = Align(dataSegVmEnd, Page); - - // LINKEDIT 内布局:bind, symtab, indirect, strtab —— 每个数据结构 8 字节对齐, - // 否则 dyld 报 "mis-aligned LINKEDIT content" 并拒绝加载(SIGKILL)。 - long bindOff = linkSegFileOff; - long symtabOff = Align(bindOff + bind.Count, 8); - long indirectOff = Align(symtabOff + symtab.Count, 8); - long strtabOff = Align(indirectOff + indirect.Count, 8); - // 代码签名(ad-hoc):16 字节对齐后附加到 __LINKEDIT 末尾 - long sigOff = Align(strtabOff + strtab.Count, 16); - int codeLimit = (int)sigOff; - int sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit); - long linkSegFileEnd = sigOff + sigBlobSize; - long linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff); + long rebaseOff = 0, bindOff = 0, symtabOff = 0, indirectOff = 0, strtabOff = 0, sigOff = 0; + int codeLimit = 0, sigBlobSize = 0; + long linkSegFileEnd = 0, linkSegVmEnd = 0; // ---- 填充 main 符号 n_value ---- long mainVmaddr = textVmaddr + mainOff; @@ -219,6 +218,7 @@ public sealed class MachOWriter : IExecutableWriter } // ---- 解析 fixup ---- + var rebaseFixups = new List<(int seg, long off)>(); foreach (var f in img.Fixups) { var list = f.Section == img.Data ? data : f.Section == img.RData ? cstring : text; @@ -251,9 +251,43 @@ public sealed class MachOWriter : IExecutableWriter { long target = SymVmaddr(f.Symbol, img, textVmaddr, cstringVmaddr, dataVmaddr, bssVmaddr, stubsVmaddr, externals); Write64At(list, f.Offset, target); + // PIE: 记录需要 dyld rebase 的绝对地址位置(segment index + 段内偏移) + if (f.Section == img.Data) + rebaseFixups.Add((1, got.Count + f.Offset)); // __DATA seg + else if (f.Section == img.Text) + rebaseFixups.Add((0, textFileOff + f.Offset)); // __TEXT seg + else if (f.Section == img.RData) + rebaseFixups.Add((0, textFileOff + text.Count + f.Offset)); // __TEXT seg (__cstring) } } + // ---- 生成 rebase opcodes(PIE: dyld 对绝对地址加 ASLR slide)---- + var rebase = new List(); + if (rebaseFixups.Count > 0) + { + rebaseFixups.Sort((a, b) => a.seg == b.seg ? a.off.CompareTo(b.off) : a.seg.CompareTo(b.seg)); + rebase.Add((byte)(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER)); + foreach (var (seg, off) in rebaseFixups) + { + rebase.Add((byte)(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 | seg)); + WriteUleb128(rebase, (ulong)off); + rebase.Add((byte)(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1)); + } + rebase.Add(REBASE_OPCODE_DONE); + } + + // ---- LINKEDIT 内布局:rebase, bind, symtab, indirect, strtab, sig ---- + rebaseOff = rebase.Count > 0 ? linkSegFileOff : 0; + bindOff = rebase.Count > 0 ? Align(rebaseOff + rebase.Count, 8) : linkSegFileOff; + symtabOff = Align(bindOff + bind.Count, 8); + indirectOff = Align(symtabOff + symtab.Count, 8); + strtabOff = Align(indirectOff + indirect.Count, 8); + sigOff = Align(strtabOff + strtab.Count, 16); + codeLimit = (int)sigOff; + sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit); + linkSegFileEnd = sigOff + sigBlobSize; + linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff); + // ============ 装配 Mach-O ============ var f2 = new List(); @@ -264,7 +298,7 @@ public sealed class MachOWriter : IExecutableWriter Write32At(f2, (uint)MH_EXECUTE); Write32At(f2, (uint)ncmds); Write32At(f2, (uint)sizeofcmds); - Write32At(f2, 0); // flags(非 PIE) + Write32At(f2, (uint)MH_PIE); // flags = MH_PIE(macOS 11+ arm64 强制要求 PIE,x64 也推荐) Write32At(f2, 0); // reserved // ---- LC_SEGMENT_64 __TEXT ---- @@ -289,6 +323,18 @@ public sealed class MachOWriter : IExecutableWriter WriteSegment64(f2, "__LINKEDIT", linkSegVmaddr, linkSegVmEnd - linkSegVmaddr, linkSegFileOff, linkSegFileEnd - linkSegFileOff, VM_PROT_READ, VM_PROT_READ, 0); + // ---- LC_DYLD_INFO_ONLY ----(rebase/bind 信息,dyld 通过此命令处理符号绑定和 PIE 地址修正) + Write32At(f2, LC_DYLD_INFO_ONLY); + Write32At(f2, 48); // cmdsize (dyld_info_command) + Write32At(f2, (uint)(rebase.Count > 0 ? rebaseOff : 0)); // rebase_off + Write32At(f2, (uint)rebase.Count); // rebase_size + Write32At(f2, (uint)bindOff); // bind_off + Write32At(f2, (uint)bind.Count); // bind_size + Write32At(f2, 0); // lazy_bind_off(非懒绑定,无 lazy stub) + Write32At(f2, 0); // lazy_bind_size + Write32At(f2, 0); // export_off(主可执行文件无需导出符号) + Write32At(f2, 0); // export_size + // ---- LC_LOAD_DYLINKER ---- Write32At(f2, LC_LOAD_DYLINKER); Write32At(f2, (uint)lcDylinker); @@ -370,6 +416,11 @@ public sealed class MachOWriter : IExecutableWriter // ---- __LINKEDIT 段数据 ----(各子表之间 8 字节对齐填充) while (f2.Count < linkSegFileOff) f2.Add(0); + if (rebase.Count > 0) + { + f2.AddRange(rebase); + while (f2.Count < bindOff) f2.Add(0); + } f2.AddRange(bind); while (f2.Count < symtabOff) f2.Add(0); f2.AddRange(symtab); diff --git a/src/PazeE.Compiler/Binary/MachOWriterArm64.cs b/src/PazeE.Compiler/Binary/MachOWriterArm64.cs index af2107a..d78e836 100644 --- a/src/PazeE.Compiler/Binary/MachOWriterArm64.cs +++ b/src/PazeE.Compiler/Binary/MachOWriterArm64.cs @@ -34,6 +34,14 @@ public sealed class MachOWriterArm64 : IExecutableWriter private const uint LC_CODE_SIGNATURE = 0x1D; private const uint LC_BUILD_VERSION = 0x32; private const uint LC_UUID = 0x1B; + private const uint LC_DYLD_INFO_ONLY = 0x80000022; + private const int MH_PIE = 0x200000; + // Rebase opcodes(dyld 对 PIE 二进制中绝对地址加 ASLR slide) + private const byte REBASE_OPCODE_DONE = 0x00; + private const byte REBASE_OPCODE_SET_TYPE_IMM = 0x10; + private const byte REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 = 0x20; + private const byte REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50; + private const int REBASE_TYPE_POINTER = 1; private const int VM_PROT_READ = 1, VM_PROT_WRITE = 2, VM_PROT_EXECUTE = 4; private const int S_NON_LAZY_SYMBOL_POINTERS = 0x06; private const byte N_EXT = 0x01, N_SECT = 0x0e; @@ -143,8 +151,9 @@ public sealed class MachOWriterArm64 : IExecutableWriter int lcCodeSig = 16; // linkedit_data_command(cmd+cmdsize+dataoff+datasize) int lcBuildVer = 24; // build_version_command(cmd+cmdsize+platform+minos+sdk+ntools) int lcUuid = 24; // uuid_command(cmd+cmdsize+uuid[16]) - int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid; - int ncmds = 11; + int lcDyldInfo = 48; // dyld_info_command(rebase+bind+lazy_bind+export 各 off/size) + int sizeofcmds = segTextCmd + segDataCmd + segLinkCmd + lcDylinker + lcDylib + lcMain + lcSymtab + lcDysymtab + lcCodeSig + lcBuildVer + lcUuid + lcDyldInfo; + int ncmds = 12; int headerSize = 32; int textFileOff = headerSize + sizeofcmds; @@ -166,22 +175,12 @@ public sealed class MachOWriterArm64 : IExecutableWriter long dataSegFileEnd = dataFileOff + data.Count; long dataSegVmEnd = bssVmaddr + bssSize; - // __LINKEDIT 段 + // __LINKEDIT 段(偏移在 fixup 处理后计算,因 rebase 数据需先生成) long linkSegFileOff = Align(dataSegFileEnd, Page); long linkSegVmaddr = Align(dataSegVmEnd, Page); - - // LINKEDIT 内布局:bind, symtab, indirect, strtab —— 每个数据结构 8 字节对齐, - // 否则 dyld 报 "mis-aligned LINKEDIT content" 并拒绝加载(SIGKILL)。 - long bindOff = linkSegFileOff; - long symtabOff = Align(bindOff + bind.Count, 8); - long indirectOff = Align(symtabOff + symtab.Count, 8); - long strtabOff = Align(indirectOff + indirect.Count, 8); - // 代码签名(ad-hoc):16 字节对齐后附加到 __LINKEDIT 末尾 - long sigOff = Align(strtabOff + strtab.Count, 16); - int codeLimit = (int)sigOff; - int sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit); - long linkSegFileEnd = sigOff + sigBlobSize; - long linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff); + long rebaseOff = 0, bindOff = 0, symtabOff = 0, indirectOff = 0, strtabOff = 0, sigOff = 0; + int codeLimit = 0, sigBlobSize = 0; + long linkSegFileEnd = 0, linkSegVmEnd = 0; // ---- 填充 main 符号 n_value ---- long mainVmaddr = textVmaddr + mainOff; @@ -205,6 +204,7 @@ public sealed class MachOWriterArm64 : IExecutableWriter } // ---- 解析 fixup ---- + var rebaseFixups = new List<(int seg, long off)>(); foreach (var f in img.Fixups) { var list = f.Section == img.Data ? data : f.Section == img.RData ? cstring : text; @@ -250,9 +250,43 @@ public sealed class MachOWriterArm64 : IExecutableWriter { long target = SymVmaddr(f.Symbol, img, textVmaddr, cstringVmaddr, dataVmaddr, bssVmaddr, stubsVmaddr, externals); Write64At(list, f.Offset, target); + // PIE: 记录需要 dyld rebase 的绝对地址位置(segment index + 段内偏移) + if (f.Section == img.Data) + rebaseFixups.Add((1, got.Count + f.Offset)); // __DATA seg + else if (f.Section == img.Text) + rebaseFixups.Add((0, textFileOff + f.Offset)); // __TEXT seg + else if (f.Section == img.RData) + rebaseFixups.Add((0, textFileOff + text.Count + f.Offset)); // __TEXT seg (__cstring) } } + // ---- 生成 rebase opcodes(PIE: dyld 对绝对地址加 ASLR slide)---- + var rebase = new List(); + if (rebaseFixups.Count > 0) + { + rebaseFixups.Sort((a, b) => a.seg == b.seg ? a.off.CompareTo(b.off) : a.seg.CompareTo(b.seg)); + rebase.Add((byte)(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER)); + foreach (var (seg, off) in rebaseFixups) + { + rebase.Add((byte)(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB128 | seg)); + WriteUleb128(rebase, (ulong)off); + rebase.Add((byte)(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1)); + } + rebase.Add(REBASE_OPCODE_DONE); + } + + // ---- LINKEDIT 内布局:rebase, bind, symtab, indirect, strtab, sig ---- + rebaseOff = rebase.Count > 0 ? linkSegFileOff : 0; + bindOff = rebase.Count > 0 ? Align(rebaseOff + rebase.Count, 8) : linkSegFileOff; + symtabOff = Align(bindOff + bind.Count, 8); + indirectOff = Align(symtabOff + symtab.Count, 8); + strtabOff = Align(indirectOff + indirect.Count, 8); + sigOff = Align(strtabOff + strtab.Count, 16); + codeLimit = (int)sigOff; + sigBlobSize = MachOCodeSignature.ComputeBlobSize(codeLimit); + linkSegFileEnd = sigOff + sigBlobSize; + linkSegVmEnd = linkSegVmaddr + (linkSegFileEnd - linkSegFileOff); + // ============ 装配 Mach-O ============ var f2 = new List(); @@ -263,7 +297,7 @@ public sealed class MachOWriterArm64 : IExecutableWriter Write32At(f2, (uint)MH_EXECUTE); Write32At(f2, (uint)ncmds); Write32At(f2, (uint)sizeofcmds); - Write32At(f2, 0); // flags(非 PIE) + Write32At(f2, (uint)MH_PIE); // flags = MH_PIE(macOS 11+ arm64 强制要求 PIE) Write32At(f2, 0); // reserved // ---- LC_SEGMENT_64 __TEXT ---- @@ -287,6 +321,18 @@ public sealed class MachOWriterArm64 : IExecutableWriter WriteSegment64(f2, "__LINKEDIT", linkSegVmaddr, linkSegVmEnd - linkSegVmaddr, linkSegFileOff, linkSegFileEnd - linkSegFileOff, VM_PROT_READ, VM_PROT_READ, 0); + // ---- LC_DYLD_INFO_ONLY ----(rebase/bind 信息,dyld 通过此命令处理符号绑定和 PIE 地址修正) + Write32At(f2, LC_DYLD_INFO_ONLY); + Write32At(f2, 48); // cmdsize (dyld_info_command) + Write32At(f2, (uint)(rebase.Count > 0 ? rebaseOff : 0)); // rebase_off + Write32At(f2, (uint)rebase.Count); // rebase_size + Write32At(f2, (uint)bindOff); // bind_off + Write32At(f2, (uint)bind.Count); // bind_size + Write32At(f2, 0); // lazy_bind_off(非懒绑定,无 lazy stub) + Write32At(f2, 0); // lazy_bind_size + Write32At(f2, 0); // export_off(主可执行文件无需导出符号) + Write32At(f2, 0); // export_size + // ---- LC_LOAD_DYLINKER ---- Write32At(f2, LC_LOAD_DYLINKER); Write32At(f2, (uint)lcDylinker); @@ -367,6 +413,11 @@ public sealed class MachOWriterArm64 : IExecutableWriter // ---- __LINKEDIT 段数据 ----(各子表之间 8 字节对齐填充) while (f2.Count < linkSegFileOff) f2.Add(0); + if (rebase.Count > 0) + { + f2.AddRange(rebase); + while (f2.Count < bindOff) f2.Add(0); + } f2.AddRange(bind); while (f2.Count < symtabOff) f2.Add(0); f2.AddRange(symtab);