Skip to content

Commit

Permalink
fix: fixup fixups to fake loading (adjustChainedFixups)
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop committed Sep 23, 2020
1 parent 7b680e6 commit 3336458
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 56 deletions.
19 changes: 17 additions & 2 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -1369,8 +1369,23 @@ func (f *File) HasFixups() bool {
// DyldChainedFixups returns the dyld chained fixups.
func (f *File) DyldChainedFixups() (*fixupchains.DyldChainedFixups, error) {
for _, l := range f.Loads {
if dcf, ok := l.(*DyldChainedFixups); ok {
return fixupchains.Parse(bytes.NewReader(dcf.Data), f.sr, f.ByteOrder)
if dcfLC, ok := l.(*DyldChainedFixups); ok {
dcf := fixupchains.NewChainedFixups(bytes.NewReader(dcfLC.Data), f.sr, f.ByteOrder)
if err := dcf.ParseStarts(); err != nil {
return nil, err
}
segs := f.Segments()
for idx, start := range dcf.Starts {
if start.PageStarts != nil {
// Replacing SegmentOffset(vmaddr) with FileOffset
// (for static analysis of binaries with split segs
// since we aren't actually loading the MachO
// ref: void Adjustor<P>::adjustChainedFixups() in
// dyld-750.6/dyld3/shared-cache/AdjustDylibSegments.cpp
dcf.Starts[idx].SegmentOffset = segs[idx].Offset
}
}
return dcf.Parse()
}
}
return nil, fmt.Errorf("macho does not contain dyld chained fixups")
Expand Down
135 changes: 81 additions & 54 deletions pkg/fixupchains/fixupchains.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,32 @@ import (
"strings"
)

// Parse parses a LC_DYLD_CHAINED_FIXUPS load command
func Parse(lcdat *bytes.Reader, sr *io.SectionReader, bo binary.ByteOrder) (*DyldChainedFixups, error) {

dcf := &DyldChainedFixups{}

if err := binary.Read(lcdat, bo, &dcf.DyldChainedFixupsHeader); err != nil {
return nil, err
// NewChainedFixups creates a new DyldChainedFixups instance
func NewChainedFixups(lcdat *bytes.Reader, sr *io.SectionReader, bo binary.ByteOrder) *DyldChainedFixups {
return &DyldChainedFixups{
r: lcdat,
sr: sr,
bo: bo,
}
}

lcdat.Seek(int64(dcf.DyldChainedFixupsHeader.StartsOffset), io.SeekStart)
// Parse parses a LC_DYLD_CHAINED_FIXUPS load command
func (dcf *DyldChainedFixups) Parse() (*DyldChainedFixups, error) {

var segCount uint32
if err := binary.Read(lcdat, bo, &segCount); err != nil {
return nil, err
if dcf.Starts == nil {
if err := dcf.ParseStarts(); err != nil {
return nil, err
}
}

dcf.Starts = make([]DyldChainedStarts, segCount)
segInfoOffsets := make([]uint32, segCount)
if err := binary.Read(lcdat, bo, &segInfoOffsets); err != nil {
return nil, err
}
for segIdx, start := range dcf.Starts {

for segIdx, segInfoOffset := range segInfoOffsets {
if segInfoOffset == 0 {
if start.PageStarts == nil {
continue
}

lcdat.Seek(int64(dcf.DyldChainedFixupsHeader.StartsOffset+segInfoOffset), io.SeekStart)
if err := binary.Read(lcdat, bo, &dcf.Starts[segIdx].DyldChainedStartsInSegment); err != nil {
return nil, err
}

dcf.Starts[segIdx].PageStarts = make([]DCPtrStart, dcf.Starts[segIdx].DyldChainedStartsInSegment.PageCount)
if err := binary.Read(lcdat, bo, &dcf.Starts[segIdx].PageStarts); err != nil {
return nil, err
}

for pageIndex := uint16(0); pageIndex < dcf.Starts[segIdx].DyldChainedStartsInSegment.PageCount; pageIndex++ {
offsetInPage := dcf.Starts[segIdx].PageStarts[pageIndex]
for pageIndex := uint16(0); pageIndex < start.DyldChainedStartsInSegment.PageCount; pageIndex++ {
offsetInPage := start.PageStarts[pageIndex]

if offsetInPage == DYLD_CHAINED_PTR_START_NONE {
continue
Expand All @@ -58,45 +45,85 @@ func Parse(lcdat *bytes.Reader, sr *io.SectionReader, bo binary.ByteOrder) (*Dyl
overflowIndex := offsetInPage & ^DYLD_CHAINED_PTR_START_MULTI
chainEnd := false
for !chainEnd {
chainEnd = (dcf.Starts[segIdx].PageStarts[overflowIndex]&DYLD_CHAINED_PTR_START_LAST != 0)
offsetInPage = (dcf.Starts[segIdx].PageStarts[overflowIndex] & ^DYLD_CHAINED_PTR_START_LAST)
if err := dcf.walkDcFixupChain(sr, bo, segIdx, pageIndex, offsetInPage); err != nil {
chainEnd = (start.PageStarts[overflowIndex]&DYLD_CHAINED_PTR_START_LAST != 0)
offsetInPage = (start.PageStarts[overflowIndex] & ^DYLD_CHAINED_PTR_START_LAST)
if err := dcf.walkDcFixupChain(segIdx, pageIndex, offsetInPage); err != nil {
return nil, err
}
overflowIndex++
}

} else {
// one chain per page
if err := dcf.walkDcFixupChain(sr, bo, segIdx, pageIndex, offsetInPage); err != nil {
if err := dcf.walkDcFixupChain(segIdx, pageIndex, offsetInPage); err != nil {
return nil, err
}
}
}
}

// Parse Imports
dcf.parseImports(lcdat, bo)
dcf.parseImports()

return dcf, nil
}

func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.ByteOrder, segIdx int, pageIndex uint16, offsetInPage DCPtrStart) error {
// ParseStarts parses the DyldChainedStartsInSegment(s)
func (dcf *DyldChainedFixups) ParseStarts() error {

if err := binary.Read(dcf.r, dcf.bo, &dcf.DyldChainedFixupsHeader); err != nil {
return err
}

dcf.r.Seek(int64(dcf.DyldChainedFixupsHeader.StartsOffset), io.SeekStart)

var segCount uint32
if err := binary.Read(dcf.r, dcf.bo, &segCount); err != nil {
return err
}

dcf.Starts = make([]DyldChainedStarts, segCount)
segInfoOffsets := make([]uint32, segCount)
if err := binary.Read(dcf.r, dcf.bo, &segInfoOffsets); err != nil {
return err
}

for segIdx, segInfoOffset := range segInfoOffsets {
if segInfoOffset == 0 {
continue
}

dcf.r.Seek(int64(dcf.DyldChainedFixupsHeader.StartsOffset+segInfoOffset), io.SeekStart)
if err := binary.Read(dcf.r, dcf.bo, &dcf.Starts[segIdx].DyldChainedStartsInSegment); err != nil {
return err
}

dcf.Starts[segIdx].PageStarts = make([]DCPtrStart, dcf.Starts[segIdx].DyldChainedStartsInSegment.PageCount)
if err := binary.Read(dcf.r, dcf.bo, &dcf.Starts[segIdx].PageStarts); err != nil {
return err
}
}

return nil
}

func (dcf *DyldChainedFixups) walkDcFixupChain(segIdx int, pageIndex uint16, offsetInPage DCPtrStart) error {

var dcPtr uint32
var dcPtr64 uint64
var next uint64

chainEnd := false

pageContentStart := dcf.Starts[segIdx].DyldChainedStartsInSegment.SegmentOffset + uint64(pageIndex*dcf.Starts[segIdx].DyldChainedStartsInSegment.PageSize)

for !chainEnd {
fixupLocation := pageContentStart + uint64(offsetInPage) + next
sr.Seek(int64(fixupLocation), io.SeekStart)
dcf.sr.Seek(int64(fixupLocation), io.SeekStart)

switch dcf.Starts[segIdx].DyldChainedStartsInSegment.PointerFormat {
case DYLD_CHAINED_PTR_32:
if err := binary.Read(sr, bo, &dcPtr); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr); err != nil {
return err
}
if Generic32IsBind(dcPtr) {
Expand All @@ -115,7 +142,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic32Next(dcPtr) * 4
case DYLD_CHAINED_PTR_32_CACHE:
if err := binary.Read(sr, bo, &dcPtr); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr); err != nil {
return err
}
dcf.Starts[segIdx].Rebases = append(dcf.Starts[segIdx].Rebases, DyldChainedPtr32CacheRebase{
Expand All @@ -127,7 +154,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic32Next(dcPtr) * 4
case DYLD_CHAINED_PTR_32_FIRMWARE:
if err := binary.Read(sr, bo, &dcPtr); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr); err != nil {
return err
}
dcf.Starts[segIdx].Rebases = append(dcf.Starts[segIdx].Rebases, DyldChainedPtr32FirmwareRebase{
Expand All @@ -139,7 +166,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic32Next(dcPtr) * 4
case DYLD_CHAINED_PTR_64: // target is vmaddr
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
if Generic64IsBind(dcPtr64) {
Expand All @@ -158,7 +185,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic64Next(dcPtr64) * 4
case DYLD_CHAINED_PTR_64_OFFSET: // target is vm offset
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
dcf.Starts[segIdx].Rebases = append(dcf.Starts[segIdx].Rebases, DyldChainedPtr64RebaseOffset{
Expand All @@ -170,7 +197,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic64Next(dcPtr64) * 4
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
dcf.Starts[segIdx].Rebases = append(dcf.Starts[segIdx].Rebases, DyldChainedPtr64KernelCacheRebase{
Expand All @@ -182,7 +209,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic64Next(dcPtr64) * 4
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: // stride 1, x86_64 kernel caches
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
dcf.Starts[segIdx].Rebases = append(dcf.Starts[segIdx].Rebases, DyldChainedPtr64KernelCacheRebase{
Expand All @@ -194,7 +221,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += Generic64Next(dcPtr64)
case DYLD_CHAINED_PTR_ARM64E_KERNEL: // stride 4, unauth target is vm offset
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
if !DcpArm64eIsBind(dcPtr64) && !DcpArm64eIsAuth(dcPtr64) {
Expand Down Expand Up @@ -223,7 +250,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += DcpArm64eNext(dcPtr64) * 4
case DYLD_CHAINED_PTR_ARM64E_FIRMWARE: // stride 4, unauth target is vmaddr
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
if !DcpArm64eIsBind(dcPtr64) && !DcpArm64eIsAuth(dcPtr64) {
Expand Down Expand Up @@ -254,7 +281,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
case DYLD_CHAINED_PTR_ARM64E: // stride 8, unauth target is vmaddr
fallthrough
case DYLD_CHAINED_PTR_ARM64E_USERLAND: // stride 8, unauth target is vm offset
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
if !DcpArm64eIsBind(dcPtr64) && !DcpArm64eIsAuth(dcPtr64) {
Expand Down Expand Up @@ -283,7 +310,7 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
}
next += DcpArm64eNext(dcPtr64) * 8
case DYLD_CHAINED_PTR_ARM64E_USERLAND24: // stride 8, unauth target is vm offset, 24-bit bind
if err := binary.Read(sr, bo, &dcPtr64); err != nil {
if err := binary.Read(dcf.sr, dcf.bo, &dcPtr64); err != nil {
return err
}
if DcpArm64eIsBind(dcPtr64) && DcpArm64eIsAuth(dcPtr64) {
Expand Down Expand Up @@ -311,40 +338,40 @@ func (dcf *DyldChainedFixups) walkDcFixupChain(sr *io.SectionReader, bo binary.B
return nil
}

func (dcf *DyldChainedFixups) parseImports(r *bytes.Reader, bo binary.ByteOrder) error {
func (dcf *DyldChainedFixups) parseImports() error {

var imports []Import

r.Seek(int64(dcf.ImportsOffset), io.SeekStart)
dcf.r.Seek(int64(dcf.ImportsOffset), io.SeekStart)

switch dcf.DyldChainedFixupsHeader.ImportsFormat {
case DC_IMPORT:
ii := make([]DyldChainedImport, dcf.ImportsCount)
if err := binary.Read(r, bo, &ii); err != nil {
if err := binary.Read(dcf.r, dcf.bo, &ii); err != nil {
return err
}
for _, i := range ii {
imports = append(imports, i)
}
case DC_IMPORT_ADDEND:
ii := make([]DyldChainedImportAddend, dcf.ImportsCount)
if err := binary.Read(r, bo, &ii); err != nil {
if err := binary.Read(dcf.r, dcf.bo, &ii); err != nil {
return err
}
for _, i := range ii {
imports = append(imports, i)
}
case DC_IMPORT_ADDEND64:
ii := make([]DyldChainedImportAddend64, dcf.ImportsCount)
if err := binary.Read(r, bo, &ii); err != nil {
if err := binary.Read(dcf.r, dcf.bo, &ii); err != nil {
return err
}
for _, i := range ii {
imports = append(imports, i)
}
}

symbolsPool := io.NewSectionReader(r, int64(dcf.SymbolsOffset), r.Size()-int64(dcf.SymbolsOffset))
symbolsPool := io.NewSectionReader(dcf.r, int64(dcf.SymbolsOffset), dcf.r.Size()-int64(dcf.SymbolsOffset))
for _, i := range imports {
symbolsPool.Seek(int64(i.NameOffset()), io.SeekStart)
s, err := bufio.NewReader(symbolsPool).ReadString('\x00')
Expand Down
6 changes: 6 additions & 0 deletions pkg/fixupchains/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package fixupchains

import (
"bytes"
"encoding/binary"
"fmt"
"io"

"github.com/blacktop/go-macho/types"
)
Expand All @@ -10,6 +13,9 @@ type DyldChainedFixups struct {
DyldChainedFixupsHeader
Starts []DyldChainedStarts
Imports []DcfImport
r *bytes.Reader
sr *io.SectionReader
bo binary.ByteOrder
}

type Rebase interface {
Expand Down

0 comments on commit 3336458

Please sign in to comment.