Added Mach-O file format

This commit is contained in:
Eduard Urbach 2024-08-11 21:15:47 +02:00
parent cc215a27c7
commit 58f010b81a
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
10 changed files with 266 additions and 4 deletions

View File

@ -4,11 +4,13 @@ import (
"bufio" "bufio"
"io" "io"
"os" "os"
"runtime"
"git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/core"
"git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/elf"
"git.akyoto.dev/cli/q/src/macho"
"git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/cli/q/src/os/linux"
) )
@ -31,13 +33,22 @@ func (r *Result) finalize() ([]byte, []byte) {
Data: make(map[string][]byte, r.DataCount), Data: make(map[string][]byte, r.DataCount),
} }
sysExit := 0
switch runtime.GOOS {
case "linux":
sysExit = linux.Exit
case "darwin":
sysExit = 1 | 0x2000000
}
final.Call("main.main") final.Call("main.main")
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall() final.Syscall()
final.Label(asm.LABEL, "_crash") final.Label(asm.LABEL, "_crash")
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
final.Syscall() final.Syscall()
@ -117,7 +128,17 @@ func (r *Result) WriteFile(path string) error {
// write writes an executable file to the given writer. // write writes an executable file to the given writer.
func write(writer io.Writer, code []byte, data []byte) error { func write(writer io.Writer, code []byte, data []byte) error {
buffer := bufio.NewWriter(writer) buffer := bufio.NewWriter(writer)
executable := elf.New(code, data)
executable.Write(buffer) switch runtime.GOOS {
case "darwin":
exe := macho.New(code, data)
exe.Write(buffer)
case "linux":
exe := elf.New(code, data)
exe.Write(buffer)
default:
panic("unsupported platform")
}
return buffer.Flush() return buffer.Flush()
} }

10
src/macho/CPU.go Normal file
View File

@ -0,0 +1,10 @@
package macho
type CPU uint32
const (
CPU_X86 CPU = 7
CPU_X86_64 CPU = CPU_X86 | 0x01000000
CPU_ARM CPU = 12
CPU_ARM_64 CPU = CPU_ARM | 0x01000000
)

13
src/macho/Header.go Normal file
View File

@ -0,0 +1,13 @@
package macho
// Header contains general information.
type Header struct {
Magic uint32
Architecture CPU
MicroArchitecture uint32
Type HeaderType
NumCommands uint32
SizeCommands uint32
Flags HeaderFlags
Reserved uint32
}

32
src/macho/HeaderFlags.go Normal file
View File

@ -0,0 +1,32 @@
package macho
type HeaderFlags uint32
const (
FlagNoUndefs HeaderFlags = 0x1
FlagIncrLink HeaderFlags = 0x2
FlagDyldLink HeaderFlags = 0x4
FlagBindAtLoad HeaderFlags = 0x8
FlagPrebound HeaderFlags = 0x10
FlagSplitSegs HeaderFlags = 0x20
FlagLazyInit HeaderFlags = 0x40
FlagTwoLevel HeaderFlags = 0x80
FlagForceFlat HeaderFlags = 0x100
FlagNoMultiDefs HeaderFlags = 0x200
FlagNoFixPrebinding HeaderFlags = 0x400
FlagPrebindable HeaderFlags = 0x800
FlagAllModsBound HeaderFlags = 0x1000
FlagSubsectionsViaSymbols HeaderFlags = 0x2000
FlagCanonical HeaderFlags = 0x4000
FlagWeakDefines HeaderFlags = 0x8000
FlagBindsToWeak HeaderFlags = 0x10000
FlagAllowStackExecution HeaderFlags = 0x20000
FlagRootSafe HeaderFlags = 0x40000
FlagSetuidSafe HeaderFlags = 0x80000
FlagNoReexportedDylibs HeaderFlags = 0x100000
FlagPIE HeaderFlags = 0x200000
FlagDeadStrippableDylib HeaderFlags = 0x400000
FlagHasTLVDescriptors HeaderFlags = 0x800000
FlagNoHeapExecution HeaderFlags = 0x1000000
FlagAppExtensionSafe HeaderFlags = 0x2000000
)

12
src/macho/HeaderType.go Normal file
View File

@ -0,0 +1,12 @@
package macho
type HeaderType uint32
const (
TypeObject HeaderType = 0x1
TypeExecute HeaderType = 0x2
TypeCore HeaderType = 0x4
TypeDylib HeaderType = 0x6
TypeBundle HeaderType = 0x8
TypeDsym HeaderType = 0xA
)

34
src/macho/LoadCommand.go Normal file
View File

@ -0,0 +1,34 @@
package macho
type LoadCommand uint32
const (
LcSegment LoadCommand = 0x1
LcSymtab LoadCommand = 0x2
LcThread LoadCommand = 0x4
LcUnixthread LoadCommand = 0x5
LcDysymtab LoadCommand = 0xB
LcDylib LoadCommand = 0xC
LcIdDylib LoadCommand = 0xD
LcLoadDylinker LoadCommand = 0xE
LcIdDylinker LoadCommand = 0xF
LcSegment64 LoadCommand = 0x19
LcUuid LoadCommand = 0x1B
LcCodeSignature LoadCommand = 0x1D
LcSegmentSplitInfo LoadCommand = 0x1E
LcRpath LoadCommand = 0x8000001C
LcEncryptionInfo LoadCommand = 0x21
LcDyldInfo LoadCommand = 0x22
LcDyldInfoOnly LoadCommand = 0x80000022
LcVersionMinMacosx LoadCommand = 0x24
LcVersionMinIphoneos LoadCommand = 0x25
LcFunctionStarts LoadCommand = 0x26
LcDyldEnvironment LoadCommand = 0x27
LcMain LoadCommand = 0x80000028
LcDataInCode LoadCommand = 0x29
LcSourceVersion LoadCommand = 0x2A
LcDylibCodeSignDrs LoadCommand = 0x2B
LcEncryptionInfo64 LoadCommand = 0x2C
LcVersionMinTvos LoadCommand = 0x2F
LcVersionMinWatchos LoadCommand = 0x30
)

107
src/macho/MachO.go Normal file
View File

@ -0,0 +1,107 @@
package macho
import (
"bytes"
"encoding/binary"
"io"
"git.akyoto.dev/cli/q/src/config"
)
// MachO is the executable format used on MacOS.
type MachO struct {
Header
Code []byte
Data []byte
}
// New creates a new Mach-O binary.
func New(code []byte, data []byte) *MachO {
return &MachO{
Header: Header{
Magic: 0xFEEDFACF,
Architecture: CPU_X86_64,
MicroArchitecture: 0x80000003,
Type: TypeExecute,
NumCommands: 3,
SizeCommands: 0x48*2 + 184,
Flags: FlagNoUndefs,
Reserved: 0,
},
Code: code,
Data: data,
}
}
// Write writes the Mach-O format to the given writer.
func (m *MachO) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &m.Header)
binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64,
Length: 0x48,
Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'},
Address: 0,
SizeInMemory: config.BaseAddress,
Offset: 0,
SizeInFile: 0,
NumSections: 0,
Flag: 0,
MaxProt: 0,
InitProt: 0,
})
codeStart := 32 + m.Header.SizeCommands
totalSize := uint64(codeStart) + uint64(len(m.Code))
binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64,
Length: 0x48,
Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'},
Address: config.BaseAddress,
SizeInMemory: totalSize,
Offset: 0,
SizeInFile: totalSize,
NumSections: 0,
Flag: 0,
MaxProt: ProtReadable | ProtWritable | ProtExecutable,
InitProt: ProtReadable | ProtExecutable,
})
binary.Write(writer, binary.LittleEndian, &Thread{
LoadCommand: LcUnixthread,
Len: 184,
Type: 0x4,
})
binary.Write(writer, binary.LittleEndian, []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,
config.BaseAddress + uint32(codeStart), 0,
0, 0,
0, 0,
0, 0,
0, 0,
})
writer.Write(m.Code)
if totalSize < 4096 {
writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize)))
}
}

9
src/macho/Prot.go Normal file
View File

@ -0,0 +1,9 @@
package macho
type Prot uint32
const (
ProtReadable Prot = 0x1
ProtWritable Prot = 0x2
ProtExecutable Prot = 0x4
)

16
src/macho/Segment64.go Normal file
View File

@ -0,0 +1,16 @@
package macho
// Segment64 is a segment load command.
type Segment64 struct {
LoadCommand
Length uint32
Name [16]byte
Address uint64
SizeInMemory uint64
Offset uint64
SizeInFile uint64
MaxProt Prot
InitProt Prot
NumSections uint32
Flag uint32
}

8
src/macho/Thread.go Normal file
View File

@ -0,0 +1,8 @@
package macho
// Thread is a thread state load command.
type Thread struct {
LoadCommand
Len uint32
Type uint32
}