Updated Mach-O format to use LC_MAIN
All checks were successful
/ test (push) Successful in 17s

This commit is contained in:
Eduard Urbach 2025-07-08 19:17:48 +02:00
parent 8b5cca921b
commit e3444b3626
Signed by: ed
GPG key ID: 49226B848C78F6C8
8 changed files with 119 additions and 41 deletions

View file

@ -2,7 +2,8 @@
## Notes ## Notes
MacOS requires including the headers in the __TEXT segment. - Headers must be included in the __TEXT segment.
- Load command size must be divisible by 8.
## Links ## Links

View file

@ -2,6 +2,6 @@ package macho
const ( const (
BaseAddress = 0x1000000 BaseAddress = 0x1000000
SizeCommands = Segment64Size*3 + ThreadSize SizeCommands = Segment64Size*4 + DyldInfoCommandSize + MainSize + DylinkerCommandSize + len(LinkerString) + DylibCommandSize + len(LibSystemString)
HeaderEnd = HeaderSize + SizeCommands HeaderEnd = HeaderSize + SizeCommands
) )

View file

@ -0,0 +1,19 @@
package macho
const DyldInfoCommandSize = 48
// DyldInfoCommand contains the file offsets and sizes that dyld needs to load the image.
type DyldInfoCommand struct {
LoadCommand
Length uint32
RebaseOffset uint32
RebaseSize uint32
BindOffset uint32
BindSize uint32
WeakBindOffset uint32
WeakBindSize uint32
LazyBindOffset uint32
LazyBindSize uint32
ExportOffset uint32
ExportSize uint32
}

16
src/macho/DylibCommand.go Normal file
View file

@ -0,0 +1,16 @@
package macho
const (
DylibCommandSize = 24
LibSystemString = "/usr/lib/libSystem.B.dylib\000\000\000\000\000\000"
)
// DylibCommand is added for each shared library.
type DylibCommand struct {
LoadCommand
Length uint32
Name uint32
TimeStamp uint32
CurrentVersion uint32
CompatibilityVersion uint32
}

View file

@ -0,0 +1,13 @@
package macho
const (
DylinkerCommandSize = 12
LinkerString = "/usr/lib/dyld\000\000\000\000\000\000\000"
)
// DylinkerCommand is needed if the program uses a dynamic linker.
type DylinkerCommand struct {
LoadCommand
Length uint32
Name uint32
}

View file

@ -8,7 +8,7 @@ const (
LcThread LoadCommand = 0x4 LcThread LoadCommand = 0x4
LcUnixthread LoadCommand = 0x5 LcUnixthread LoadCommand = 0x5
LcDysymtab LoadCommand = 0xB LcDysymtab LoadCommand = 0xB
LcDylib LoadCommand = 0xC LcLoadDylib LoadCommand = 0xC
LcIdDylib LoadCommand = 0xD LcIdDylib LoadCommand = 0xD
LcLoadDylinker LoadCommand = 0xE LcLoadDylinker LoadCommand = 0xE
LcIdDylinker LoadCommand = 0xF LcIdDylinker LoadCommand = 0xF

View file

@ -11,19 +11,23 @@ import (
// MachO is the executable format used on MacOS. // MachO is the executable format used on MacOS.
type MachO struct { type MachO struct {
Header Header
PageZero Segment64 PageZero Segment64
CodeHeader Segment64 CodeHeader Segment64
DataHeader Segment64 DataHeader Segment64
UnixThread Thread ImportsHeader Segment64
MainHeader Main
InfoHeader DyldInfoCommand
LinkerHeader DylinkerCommand
LibSystemHeader DylibCommand
} }
// Write writes the Mach-O format to the given writer. // Write writes the Mach-O format to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) { func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) {
x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), codeBytes, dataBytes) x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), codeBytes, dataBytes, nil)
code := x.Sections[0] code := x.Sections[0]
data := x.Sections[1] data := x.Sections[1]
imports := x.Sections[2]
arch, microArch := Arch(b.Arch) arch, microArch := Arch(b.Arch)
entryPoint := BaseAddress + code.MemoryOffset
m := &MachO{ m := &MachO{
Header: Header{ Header: Header{
@ -31,8 +35,8 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []
Architecture: arch, Architecture: arch,
MicroArchitecture: microArch, MicroArchitecture: microArch,
Type: TypeExecute, Type: TypeExecute,
NumCommands: 4, NumCommands: 8,
SizeCommands: SizeCommands, SizeCommands: uint32(SizeCommands),
Flags: FlagNoUndefs | FlagPIE | FlagNoHeapExecution, Flags: FlagNoUndefs | FlagPIE | FlagNoHeapExecution,
Reserved: 0, Reserved: 0,
}, },
@ -65,7 +69,7 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []
DataHeader: Segment64{ DataHeader: Segment64{
LoadCommand: LcSegment64, LoadCommand: LcSegment64,
Length: Segment64Size, Length: Segment64Size,
Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A', '_', 'C', 'O', 'N', 'S', 'T'},
Address: uint64(BaseAddress + data.MemoryOffset), Address: uint64(BaseAddress + data.MemoryOffset),
SizeInMemory: uint64(len(data.Bytes)), SizeInMemory: uint64(len(data.Bytes)),
Offset: uint64(data.FileOffset), Offset: uint64(data.FileOffset),
@ -75,34 +79,40 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []
MaxProt: ProtReadable, MaxProt: ProtReadable,
InitProt: ProtReadable, InitProt: ProtReadable,
}, },
UnixThread: Thread{ ImportsHeader: Segment64{
LoadCommand: LcUnixthread, LoadCommand: LcSegment64,
Len: ThreadSize, Length: Segment64Size,
Type: 0x4, Name: [16]byte{'_', '_', 'L', 'I', 'N', 'K', 'E', 'D', 'I', 'T'},
Data: [43]uint32{ Address: uint64(BaseAddress + imports.MemoryOffset),
42, SizeInMemory: uint64(len(imports.Bytes)),
0, 0, Offset: uint64(imports.FileOffset),
0, 0, SizeInFile: uint64(len(imports.Bytes)),
0, 0, NumSections: 0,
0, 0, Flag: 0,
0, 0, MaxProt: ProtReadable,
0, 0, InitProt: ProtReadable,
0, 0, },
0, 0, MainHeader: Main{
0, 0, LoadCommand: LcMain,
0, 0, Length: MainSize,
0, 0, EntryFileOffset: uint64(code.MemoryOffset),
0, 0, StackSize: 0,
0, 0, },
0, 0, InfoHeader: DyldInfoCommand{
0, 0, LoadCommand: LcDyldInfoOnly,
0, 0, Length: DyldInfoCommandSize,
uint32(entryPoint), 0, RebaseOffset: uint32(imports.FileOffset),
0, 0, RebaseSize: uint32(len(imports.Bytes)),
0, 0, },
0, 0, LinkerHeader: DylinkerCommand{
0, 0, LoadCommand: LcLoadDylinker,
}, Length: uint32(DylinkerCommandSize + len(LinkerString)),
Name: DylinkerCommandSize,
},
LibSystemHeader: DylibCommand{
LoadCommand: LcLoadDylib,
Length: uint32(DylibCommandSize + len(LibSystemString)),
Name: DylibCommandSize,
}, },
} }
@ -110,9 +120,17 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []
binary.Write(writer, binary.LittleEndian, &m.PageZero) binary.Write(writer, binary.LittleEndian, &m.PageZero)
binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.CodeHeader)
binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader)
binary.Write(writer, binary.LittleEndian, &m.UnixThread) binary.Write(writer, binary.LittleEndian, &m.ImportsHeader)
binary.Write(writer, binary.LittleEndian, &m.MainHeader)
binary.Write(writer, binary.LittleEndian, &m.InfoHeader)
binary.Write(writer, binary.LittleEndian, &m.LinkerHeader)
writer.Write([]byte(LinkerString))
binary.Write(writer, binary.LittleEndian, &m.LibSystemHeader)
writer.Write([]byte(LibSystemString))
writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Seek(int64(code.Padding), io.SeekCurrent)
writer.Write(code.Bytes) writer.Write(code.Bytes)
writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Seek(int64(data.Padding), io.SeekCurrent)
writer.Write(data.Bytes) writer.Write(data.Bytes)
writer.Seek(int64(imports.Padding), io.SeekCurrent)
writer.Write(imports.Bytes)
} }

11
src/macho/Main.go Normal file
View file

@ -0,0 +1,11 @@
package macho
const MainSize = 24
// Main is the structure of the LC_MAIN load command.
type Main struct {
LoadCommand
Length uint32
EntryFileOffset uint64
StackSize uint64
}