Skip to content

Commit 1724e0c

Browse files
committed
Added in-memory parsing and exports for Mach-O.
1 parent 1b03ff5 commit 1724e0c

File tree

3 files changed

+143
-32
lines changed

3 files changed

+143
-32
lines changed

macho/exports.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package macho
2+
3+
const N_SECT = 0x0E
4+
const N_PEXT = 0x10
5+
const N_EXT = 0x01
6+
7+
// Export - describes a single export entry (similar to PE version for future refactoring)
8+
type Export struct {
9+
//Ordinal uint32 // no ordinals for Mach-O
10+
Name string
11+
VirtualAddress uint64
12+
}
13+
14+
// Exports - gets exports, including private exports
15+
func (f *File) Exports() []Export {
16+
var exports []Export
17+
for _, symbol := range f.Symtab.Syms {
18+
if (symbol.Type&N_PEXT == N_PEXT ||
19+
symbol.Type&N_EXT == N_EXT) && symbol.Value != 0 {
20+
var export Export
21+
export.Name = symbol.Name
22+
export.VirtualAddress = symbol.Value
23+
exports = append(exports, export)
24+
}
25+
}
26+
return exports
27+
}

macho/file.go

+105-28
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ func (s *Section) Data() ([]byte, error) {
180180
// Open returns a new ReadSeeker reading the Mach-O section.
181181
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
182182

183+
// A Dylinker represents a Mach-O load dynamic library command.
184+
type Dylinker struct {
185+
LoadBytes
186+
Name string
187+
}
188+
183189
// A Dylib represents a Mach-O load dynamic library command.
184190
type Dylib struct {
185191
LoadBytes
@@ -269,9 +275,19 @@ func (f *File) Close() error {
269275
return err
270276
}
271277

272-
// NewFile creates a new File for accessing a Mach-O binary in an underlying reader.
273-
// The Mach-O binary is expected to start at position 0 in the ReaderAt.
278+
// NewFile creates a new macho.File for accessing a Mach-o binary file in an underlying reader.
274279
func NewFile(r io.ReaderAt) (*File, error) {
280+
return newFileInternal(r, false)
281+
}
282+
283+
// NewFileFromMemory creates a new macho.File for accessing a Mach-O binary in-memory image in an underlying reader.
284+
func NewFileFromMemory(r io.ReaderAt) (*File, error) {
285+
return newFileInternal(r, true)
286+
}
287+
288+
// NewFile creates a new File for accessing a PE binary in an underlying reader.
289+
func newFileInternal(r io.ReaderAt, memoryMode bool) (*File, error) {
290+
275291
f := new(File)
276292
sr := io.NewSectionReader(r, 0, 1<<63-1)
277293

@@ -342,6 +358,20 @@ func NewFile(r io.ReaderAt) (*File, error) {
342358
l.LoadBytes = LoadBytes(cmddat)
343359
f.Loads[i] = l
344360

361+
case LoadCmdDylinker:
362+
var hdr DylinkerCmd
363+
b := bytes.NewReader(cmddat)
364+
if err := binary.Read(b, bo, &hdr); err != nil {
365+
return nil, err
366+
}
367+
l := new(Dylinker)
368+
if hdr.Name >= uint32(len(cmddat)) {
369+
return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
370+
}
371+
l.Name = cstring(cmddat[hdr.Name:])
372+
l.LoadBytes = LoadBytes(cmddat)
373+
f.Loads[i] = l
374+
345375
case LoadCmdDylib:
346376
var hdr DylibCmd
347377
b := bytes.NewReader(cmddat)
@@ -366,19 +396,52 @@ func NewFile(r io.ReaderAt) (*File, error) {
366396
return nil, err
367397
}
368398
strtab := make([]byte, hdr.Strsize)
369-
if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
370-
return nil, err
399+
400+
var linkeditAddr, textAddr, linkeditOffset int64
401+
if !memoryMode {
402+
if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
403+
return nil, err
404+
}
405+
} else {
406+
// in memory, we have to translate the file offsets for strtab/symtab into offsets into LINKEDIT segment
407+
for _, load := range f.Loads {
408+
switch segment := load.(type) {
409+
case *Segment:
410+
if segment == nil {
411+
continue
412+
}
413+
if segment.Name == "__LINKEDIT" {
414+
linkeditAddr = int64(segment.Addr)
415+
linkeditOffset = int64(segment.Offset)
416+
} else if segment.Name == "__TEXT" {
417+
textAddr = int64(segment.Addr)
418+
}
419+
}
420+
}
421+
strtabAddr := (linkeditAddr - textAddr) + (int64(hdr.Stroff) - linkeditOffset)
422+
if _, err := r.ReadAt(strtab, strtabAddr); err != nil {
423+
return nil, err
424+
}
371425
}
426+
372427
var symsz int
373428
if f.Magic == Magic64 {
374429
symsz = 16
375430
} else {
376431
symsz = 12
377432
}
378433
symdat := make([]byte, int(hdr.Nsyms)*symsz)
379-
if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
380-
return nil, err
434+
435+
if !memoryMode {
436+
if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
437+
return nil, err
438+
}
439+
} else {
440+
if _, err := r.ReadAt(symdat, (linkeditAddr-textAddr)+(int64(hdr.Symoff)-linkeditOffset)); err != nil {
441+
return nil, err
442+
}
381443
}
444+
382445
st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
383446
if err != nil {
384447
return nil, err
@@ -453,53 +516,63 @@ func NewFile(r io.ReaderAt) (*File, error) {
453516
var dylinkInfo DylinkInfo
454517
// Rebase deets
455518
if dylinkInfoCmd.Rebasesize > 0 {
456-
rebase := make([]byte, dylinkInfoCmd.Rebasesize)
457-
if _, err := r.ReadAt(rebase, int64(dylinkInfoCmd.Rebaseoff)); err != nil {
458-
return nil, err
519+
if !memoryMode { // this data is in LINKEDIT already
520+
rebase := make([]byte, dylinkInfoCmd.Rebasesize)
521+
if _, err := r.ReadAt(rebase, int64(dylinkInfoCmd.Rebaseoff)); err != nil {
522+
return nil, err
523+
}
524+
dylinkInfo.RebaseDat = rebase
459525
}
460526
dylinkInfo.RebaseLen = dylinkInfoCmd.Rebasesize
461527
dylinkInfo.RebaseOffset = uint64(dylinkInfoCmd.Rebaseoff)
462-
dylinkInfo.RebaseDat = rebase
463528
}
464529
// BindingInfo deets
465530
if dylinkInfoCmd.Bindinginfosize > 0 {
466-
binding := make([]byte, dylinkInfoCmd.Bindinginfosize)
467-
if _, err := r.ReadAt(binding, int64(dylinkInfoCmd.Bindinginfooff)); err != nil {
468-
return nil, err
531+
if !memoryMode { // this data is in LINKEDIT already
532+
binding := make([]byte, dylinkInfoCmd.Bindinginfosize)
533+
if _, err := r.ReadAt(binding, int64(dylinkInfoCmd.Bindinginfooff)); err != nil {
534+
return nil, err
535+
}
536+
dylinkInfo.BindingInfoDat = binding
469537
}
470538
dylinkInfo.BindingInfoLen = dylinkInfoCmd.Bindinginfosize
471539
dylinkInfo.BindingInfoOffset = uint64(dylinkInfoCmd.Bindinginfooff)
472-
dylinkInfo.BindingInfoDat = binding
473540
}
474541
// Weak deets
475542
if dylinkInfoCmd.Weakbindingsize > 0 {
476-
weak := make([]byte, dylinkInfoCmd.Weakbindingsize)
477-
if _, err := r.ReadAt(weak, int64(dylinkInfoCmd.Weakbindingoff)); err != nil {
478-
return nil, err
543+
if !memoryMode { // this data is in LINKEDIT already
544+
weak := make([]byte, dylinkInfoCmd.Weakbindingsize)
545+
if _, err := r.ReadAt(weak, int64(dylinkInfoCmd.Weakbindingoff)); err != nil {
546+
return nil, err
547+
}
548+
dylinkInfo.WeakBindingDat = weak
479549
}
480550
dylinkInfo.WeakBindingLen = dylinkInfoCmd.Weakbindingsize
481551
dylinkInfo.WeakBindingOffset = uint64(dylinkInfoCmd.Weakbindingoff)
482-
dylinkInfo.WeakBindingDat = weak
483552
}
484553
// Lazy deets
485554
if dylinkInfoCmd.Lazybindingsize > 0 {
486-
lazy := make([]byte, dylinkInfoCmd.Lazybindingsize)
487-
if _, err := r.ReadAt(lazy, int64(dylinkInfoCmd.Lazybindingoff)); err != nil {
488-
return nil, err
555+
if !memoryMode { // this data is in LINKEDIT already
556+
lazy := make([]byte, dylinkInfoCmd.Lazybindingsize)
557+
if _, err := r.ReadAt(lazy, int64(dylinkInfoCmd.Lazybindingoff)); err != nil {
558+
return nil, err
559+
}
560+
dylinkInfo.LazyBindingDat = lazy
489561
}
490562
dylinkInfo.LazyBindingLen = dylinkInfoCmd.Lazybindingsize
491563
dylinkInfo.LazyBindingOffset = uint64(dylinkInfoCmd.Lazybindingoff)
492-
dylinkInfo.LazyBindingDat = lazy
493564
}
494565
// ExportInfo deets
495566
if dylinkInfoCmd.Exportinfosize > 0 {
496-
export := make([]byte, dylinkInfoCmd.Exportinfosize)
497-
if _, err := r.ReadAt(export, int64(dylinkInfoCmd.Exportinfooff)); err != nil {
498-
return nil, err
567+
if !memoryMode { // this data is in LINKEDIT already
568+
export := make([]byte, dylinkInfoCmd.Exportinfosize)
569+
if _, err := r.ReadAt(export, int64(dylinkInfoCmd.Exportinfooff)); err != nil {
570+
return nil, err
571+
}
572+
dylinkInfo.ExportInfoDat = export
499573
}
500574
dylinkInfo.ExportInfoLen = dylinkInfoCmd.Exportinfosize
501575
dylinkInfo.ExportInfoOffset = uint64(dylinkInfoCmd.Exportinfooff)
502-
dylinkInfo.ExportInfoDat = export
503576
}
504577
// Finalize the object
505578
f.DylinkInfo = &dylinkInfo
@@ -623,10 +696,14 @@ func NewFile(r io.ReaderAt) (*File, error) {
623696
if err := binary.Read(b, bo, &entryPoint); err != nil {
624697
return nil, err
625698
}
626-
f.EntryPoint = entryPoint.entryoff
699+
f.EntryPoint = entryPoint.EntryOff
627700
}
628701
if s != nil {
629-
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
702+
if !memoryMode {
703+
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
704+
} else {
705+
s.sr = io.NewSectionReader(r, int64(s.Addr), int64(s.Filesz))
706+
}
630707
s.ReaderAt = s.sr
631708
}
632709
}

macho/macho.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ type (
200200
Nlocrel uint32
201201
}
202202

203+
// DylinkerCmd is a load command to identify the dynamic linker
204+
DylinkerCmd struct {
205+
Cmd LoadCmd
206+
Len uint32
207+
Name uint32
208+
}
209+
203210
// A DylibCmd is a Mach-O load dynamic library command.
204211
DylibCmd struct {
205212
Cmd LoadCmd
@@ -259,10 +266,10 @@ type (
259266

260267
// A EntryPointCmd is a Mach-O entry point command.
261268
EntryPointCmd struct {
262-
cmd uint32 /* LC_MAIN only used in MH_EXECUTE filetypes */
263-
cmdsize uint32 /* 24 */
264-
entryoff uint64 /* file (__TEXT) offset of main() */
265-
stacksize uint64 /* if not zero, initial stack size */
269+
Cmd uint32 /* LC_MAIN only used in MH_EXECUTE filetypes */
270+
CmdSize uint32 /* 24 */
271+
EntryOff uint64 /* file (__TEXT) offset of main() */
272+
StackSize uint64 /* if not zero, initial stack size */
266273
}
267274
)
268275

0 commit comments

Comments
 (0)