diff --git a/docs/macho.md b/docs/macho.md index 0ef40c5..06dad05 100644 --- a/docs/macho.md +++ b/docs/macho.md @@ -2,7 +2,8 @@ ## 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 diff --git a/src/macho/Constants.go b/src/macho/Constants.go index eb9cc4a..be1a635 100644 --- a/src/macho/Constants.go +++ b/src/macho/Constants.go @@ -2,6 +2,6 @@ package macho const ( BaseAddress = 0x1000000 - SizeCommands = Segment64Size*3 + ThreadSize + SizeCommands = Segment64Size*4 + DyldInfoCommandSize + MainSize + DylinkerCommandSize + len(LinkerString) + DylibCommandSize + len(LibSystemString) HeaderEnd = HeaderSize + SizeCommands ) \ No newline at end of file diff --git a/src/macho/DyldInfoCommand.go b/src/macho/DyldInfoCommand.go new file mode 100644 index 0000000..c580c1f --- /dev/null +++ b/src/macho/DyldInfoCommand.go @@ -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 +} \ No newline at end of file diff --git a/src/macho/DylibCommand.go b/src/macho/DylibCommand.go new file mode 100644 index 0000000..c177393 --- /dev/null +++ b/src/macho/DylibCommand.go @@ -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 +} \ No newline at end of file diff --git a/src/macho/DylinkerCommand.go b/src/macho/DylinkerCommand.go new file mode 100644 index 0000000..6e55b14 --- /dev/null +++ b/src/macho/DylinkerCommand.go @@ -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 +} \ No newline at end of file diff --git a/src/macho/LoadCommand.go b/src/macho/LoadCommand.go index 2504ba2..6b2f806 100644 --- a/src/macho/LoadCommand.go +++ b/src/macho/LoadCommand.go @@ -8,7 +8,7 @@ const ( LcThread LoadCommand = 0x4 LcUnixthread LoadCommand = 0x5 LcDysymtab LoadCommand = 0xB - LcDylib LoadCommand = 0xC + LcLoadDylib LoadCommand = 0xC LcIdDylib LoadCommand = 0xD LcLoadDylinker LoadCommand = 0xE LcIdDylinker LoadCommand = 0xF diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 1d3f6e4..3b6f7af 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -11,19 +11,23 @@ import ( // MachO is the executable format used on MacOS. type MachO struct { Header - PageZero Segment64 - CodeHeader Segment64 - DataHeader Segment64 - UnixThread Thread + PageZero Segment64 + CodeHeader Segment64 + DataHeader Segment64 + ImportsHeader Segment64 + MainHeader Main + InfoHeader DyldInfoCommand + LinkerHeader DylinkerCommand + LibSystemHeader DylibCommand } // Write writes the Mach-O format to the given writer. 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] data := x.Sections[1] + imports := x.Sections[2] arch, microArch := Arch(b.Arch) - entryPoint := BaseAddress + code.MemoryOffset m := &MachO{ Header: Header{ @@ -31,8 +35,8 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes [] Architecture: arch, MicroArchitecture: microArch, Type: TypeExecute, - NumCommands: 4, - SizeCommands: SizeCommands, + NumCommands: 8, + SizeCommands: uint32(SizeCommands), Flags: FlagNoUndefs | FlagPIE | FlagNoHeapExecution, Reserved: 0, }, @@ -65,7 +69,7 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes [] DataHeader: Segment64{ LoadCommand: LcSegment64, 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), SizeInMemory: uint64(len(data.Bytes)), Offset: uint64(data.FileOffset), @@ -75,34 +79,40 @@ func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes [] MaxProt: ProtReadable, InitProt: ProtReadable, }, - UnixThread: Thread{ - LoadCommand: LcUnixthread, - Len: ThreadSize, - Type: 0x4, - Data: [43]uint32{ - 42, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - uint32(entryPoint), 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - }, + ImportsHeader: Segment64{ + LoadCommand: LcSegment64, + Length: Segment64Size, + Name: [16]byte{'_', '_', 'L', 'I', 'N', 'K', 'E', 'D', 'I', 'T'}, + Address: uint64(BaseAddress + imports.MemoryOffset), + SizeInMemory: uint64(len(imports.Bytes)), + Offset: uint64(imports.FileOffset), + SizeInFile: uint64(len(imports.Bytes)), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable, + InitProt: ProtReadable, + }, + MainHeader: Main{ + LoadCommand: LcMain, + Length: MainSize, + EntryFileOffset: uint64(code.MemoryOffset), + StackSize: 0, + }, + InfoHeader: DyldInfoCommand{ + LoadCommand: LcDyldInfoOnly, + Length: DyldInfoCommandSize, + RebaseOffset: uint32(imports.FileOffset), + RebaseSize: uint32(len(imports.Bytes)), + }, + LinkerHeader: DylinkerCommand{ + 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.CodeHeader) 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.Write(code.Bytes) writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) + writer.Seek(int64(imports.Padding), io.SeekCurrent) + writer.Write(imports.Bytes) } \ No newline at end of file diff --git a/src/macho/Main.go b/src/macho/Main.go new file mode 100644 index 0000000..3a534e8 --- /dev/null +++ b/src/macho/Main.go @@ -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 +} \ No newline at end of file