diff --git a/README.md b/README.md index a6a4508..a3ba079 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ We have forked the debug/ folder from the standard library, to take direct control of the debug/elf, debug/macho, and debug/pe binary format parsers. To these parsers, we have added the ability to also generate executable files from the parsed intermediate data structures. This lets us load a file with debug parsers, make changes by interacting with the parser structures, and then write those changes back out to a new file. ## Relocation editing -- `debug/elf`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `AddRelocationForSymbol`, `AddRelocationForAddr`, `AddRelocationsToRelocSection` -- `debug/macho`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `AddRelocationForSymbol`, `AddRelocationForSymbolWithDylibOrdinal`, `SetDylibOrdinalForSymbol` -- `debug/pe`: `AddBaseRelocation`, `ReplaceBaseRelocations`, `AddBaseReloc`, `AddSectionRelocation`, `ReplaceSectionRelocations` +- `debug/elf`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `RemoveRelocations`, `AddRelocationForSymbol`, `AddRelocationForAddr`, `AddRelocationsToRelocSection`, `RemoveRelocationsFromRelocSection` (alloc relocs can now grow via a new PT_LOAD when needed) +- `debug/macho`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `RemoveRelocations`, `AddScatteredRelocation`, `AddRelocationForSymbol`, `AddRelocationForSymbolWithDylibOrdinal`, `SetDylibOrdinalForSymbol`, `SetBindKindForSymbol` +- `debug/pe`: `AddBaseRelocation`, `ReplaceBaseRelocations`, `RemoveBaseRelocations`, `AddBaseReloc`, `AddSectionRelocation`, `AddSectionRelocationForSymbol`, `ReplaceSectionRelocations`, `RemoveSectionRelocations` ## Read more about the project here: diff --git a/elf/reloc_edit.go b/elf/reloc_edit.go index 280ddf7..c41b2ad 100644 --- a/elf/reloc_edit.go +++ b/elf/reloc_edit.go @@ -23,6 +23,30 @@ func (f *File) ReplaceRelocations(target *Section, rels interface{}) error { return f.addRelocations(target, rels, true, nil) } +// RemoveRelocations clears relocation entries for the target section. +func (f *File) RemoveRelocations(target *Section) error { + if target == nil { + return errors.New("target section is nil") + } + targetIndex, ok := f.sectionIndex(target) + if !ok { + return errors.New("target section not found in file") + } + relocSecRel, _ := f.relocationSection(targetIndex, SHT_REL) + relocSecRela, _ := f.relocationSection(targetIndex, SHT_RELA) + if relocSecRel == nil && relocSecRela == nil { + return nil + } + if relocSecRel != nil { + relocSecRel.Replace(bytes.NewReader(nil), 0) + } + if relocSecRela != nil { + relocSecRela.Replace(bytes.NewReader(nil), 0) + } + f.updateDynamicRelocTags() + return nil +} + // AddRelocationsToRelocSection appends relocation entries to a specific relocation section // (e.g. ".rela.dyn" or ".rel.plt"). func (f *File) AddRelocationsToRelocSection(sectionName string, rels interface{}) error { @@ -67,6 +91,20 @@ func (f *File) AddRelocationsToRelocSection(sectionName string, rels interface{} return nil } +// RemoveRelocationsFromRelocSection clears relocation entries for the named relocation section. +func (f *File) RemoveRelocationsFromRelocSection(sectionName string) error { + relocSec := f.Section(sectionName) + if relocSec == nil { + return fmt.Errorf("relocation section %q not found", sectionName) + } + if relocSec.Type != SHT_REL && relocSec.Type != SHT_RELA { + return fmt.Errorf("section %q is not a relocation section", sectionName) + } + relocSec.Replace(bytes.NewReader(nil), 0) + f.updateDynamicRelocTags() + return nil +} + // AddRelocationForSymbol builds and adds a relocation for the named symbol. // If addend is nil, a REL entry is created. Otherwise a RELA entry is used. func (f *File) AddRelocationForSymbol(sectionName, symbolName string, offset uint64, rType uint32, addend *int64) error { @@ -476,7 +514,7 @@ func (f *File) relayoutAllocRelocationSection(section *Section) error { newOff := alignUp(prog.Off+prog.Filesz, align) newEnd := newOff + section.FileSize if nextLoadOff != 0 && newEnd > nextLoadOff { - return fmt.Errorf("no room to grow %q within PT_LOAD", section.Name) + return f.relayoutAllocRelocationSectionNewLoad(section) } newAddr := prog.Vaddr + (newOff - prog.Off) section.Offset = newOff @@ -496,6 +534,67 @@ func (f *File) relayoutAllocRelocationSection(section *Section) error { return nil } +func (f *File) relayoutAllocRelocationSectionNewLoad(section *Section) error { + align := section.Addralign + if align == 0 { + align = relocationAlign(f.Class) + } + var lastLoad *Prog + for _, p := range f.Progs { + if p.Type != PT_LOAD { + continue + } + if lastLoad == nil || p.Off > lastLoad.Off { + lastLoad = p + } + } + if lastLoad == nil { + return fmt.Errorf("no PT_LOAD segments available") + } + baseOff := lastLoad.Off + lastLoad.Filesz + fileEnd := f.maxFileEnd() + if fileEnd > baseOff { + baseOff = fileEnd + } + newOff := alignUp(baseOff, align) + newAddr := lastLoad.Vaddr + (newOff - lastLoad.Off) + section.Offset = newOff + section.Addr = newAddr + end := newOff + section.FileSize + if end > lastLoad.Off+lastLoad.Filesz { + delta := end - (lastLoad.Off + lastLoad.Filesz) + lastLoad.Filesz += delta + lastLoad.Memsz += delta + } + + if end := section.Offset + section.FileSize; int64(end) > f.SHTOffset { + shtAlign := uint64(4) + if f.Class == ELFCLASS64 { + shtAlign = 8 + } + f.SHTOffset = int64(alignUp(end, shtAlign)) + } + return nil +} + +func (f *File) maxFileEnd() uint64 { + var maxEnd uint64 + for _, s := range f.Sections { + if s.Type == SHT_NOBITS || s.FileSize == 0 { + continue + } + end := s.Offset + s.FileSize + if end > maxEnd { + maxEnd = end + } + } + if f.SHTOffset > int64(maxEnd) { + maxEnd = uint64(f.SHTOffset) + } + return maxEnd +} + + func (f *File) updateDynamicRelocTags() { if len(f.DynTags) == 0 { return diff --git a/elf/reloc_edit_test.go b/elf/reloc_edit_test.go index 4a37728..824d3ff 100644 --- a/elf/reloc_edit_test.go +++ b/elf/reloc_edit_test.go @@ -3,7 +3,6 @@ package elf import ( "bytes" "path" - "strings" "testing" ) @@ -63,15 +62,13 @@ func TestAddDynamicRelocationUpdatesTags(t *testing.T) { if relDyn == nil { t.Fatalf("rela.dyn missing in test binary") } + origSize := relDyn.Size rel := Rela64{ Off: 0, Info: R_INFO(0, uint32(R_X86_64_RELATIVE)), Addend: 0, } if err := f.AddRelocationsToRelocSection(".rela.dyn", []Rela64{rel}); err != nil { - if strings.Contains(err.Error(), "no room to grow") { - t.Skip("test binary has no space to grow .rela.dyn") - } t.Fatalf("add to rela.dyn: %v", err) } @@ -85,7 +82,7 @@ func TestAddDynamicRelocationUpdatesTags(t *testing.T) { } relDyn2 := f2.Section(".rela.dyn") - if relDyn2 == nil || relDyn2.Size <= relDyn.Size { + if relDyn2 == nil || relDyn2.Size <= origSize { t.Fatalf("rela.dyn not grown") } @@ -130,3 +127,95 @@ func TestAddRelocationCreatesRelText(t *testing.T) { t.Fatalf("rel.text size invalid: size=%d entsize=%d", rel.Size, rel.Entsize) } } + +func TestPltRelocationUpdatesTags(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-amd64-linux-exec")) + if err != nil { + t.Fatalf("open elf: %v", err) + } + defer f.Close() + + relPlt := f.Section(".rela.plt") + if relPlt == nil { + t.Skip("no .rela.plt section in test binary") + } + origSize := relPlt.Size + rel := Rela64{ + Off: 0, + Info: R_INFO(0, uint32(R_X86_64_JMP_SLOT)), + Addend: 0, + } + if err := f.AddRelocationsToRelocSection(".rela.plt", []Rela64{rel}); err != nil { + t.Fatalf("add to rela.plt: %v", err) + } + + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + relPlt2 := f2.Section(".rela.plt") + if relPlt2 == nil || relPlt2.Size <= origSize { + t.Fatalf("rela.plt not grown") + } + dynTags := map[DynTag]uint64{} + for _, tag := range f2.DynTags { + dynTags[tag.Tag] = tag.Value + } + if dynTags[DT_JMPREL] != relPlt2.Addr { + t.Fatalf("DT_JMPREL not updated") + } + if dynTags[DT_PLTRELSZ] != relPlt2.Size { + t.Fatalf("DT_PLTRELSZ not updated") + } + if dynTags[DT_PLTREL] != uint64(DT_RELA) { + t.Fatalf("DT_PLTREL not updated") + } +} + +func TestRemoveRelocationsFromSection(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-amd64-linux-exec")) + if err != nil { + t.Fatalf("open elf: %v", err) + } + defer f.Close() + + addend := int64(0) + syms, err := f.Symbols() + if err != nil { + t.Fatalf("symbols: %v", err) + } + var symName string + for _, sym := range syms { + if sym.Name != "" && sym.Section != SHN_UNDEF { + symName = sym.Name + break + } + } + if symName == "" { + t.Skip("no suitable symbol found") + } + if err := f.AddRelocationForSymbol(".text", symName, 0, uint32(R_X86_64_64), &addend); err != nil { + t.Fatalf("add relocation: %v", err) + } + if err := f.RemoveRelocations(f.Section(".text")); err != nil { + t.Fatalf("remove relocations: %v", err) + } + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + if sec := f2.Section(".rela.text"); sec != nil && sec.Size != 0 { + t.Fatalf("expected .rela.text cleared") + } + if sec := f2.Section(".rel.text"); sec != nil && sec.Size != 0 { + t.Fatalf("expected .rel.text cleared") + } +} diff --git a/elf/write.go b/elf/write.go index a3ec757..d598020 100644 --- a/elf/write.go +++ b/elf/write.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "os" + "sort" ) // Bytes - returns the bytes of an Elf file @@ -152,8 +153,10 @@ func (elfFile *File) Bytes() ([]byte, error) { } } - sortedSections := elfFile.Sections[:] - //sort.Slice(sortedSections, func(a, b int) bool { return elfFile.Sections[a].Link < elfFile.Sections[b].Link }) + sortedSections := append([]*Section(nil), elfFile.Sections...) + sort.SliceStable(sortedSections, func(i, j int) bool { + return sortedSections[i].Offset < sortedSections[j].Offset + }) for _, s := range sortedSections { //log.Printf("Writing section: %s type: %+v\n", s.Name, s.Type) diff --git a/macho/file.go b/macho/file.go index cf823cb..10eab47 100644 --- a/macho/file.go +++ b/macho/file.go @@ -36,6 +36,7 @@ type File struct { Insertion []byte dylibOrdinalBySymbol map[uint32]uint8 + bindKindBySymbol map[uint32]BindKind closer io.Closer } diff --git a/macho/reloc_edit.go b/macho/reloc_edit.go index c2cc11c..c00cc8c 100644 --- a/macho/reloc_edit.go +++ b/macho/reloc_edit.go @@ -5,6 +5,15 @@ import ( "fmt" ) +// BindKind controls which dyld binding stream is used for an extern relocation. +type BindKind uint8 + +const ( + BindNormal BindKind = iota + BindWeak + BindLazy +) + // AddRelocation appends a relocation to the named section. func (f *File) AddRelocation(sectionName string, rel Reloc) error { section := f.Section(sectionName) @@ -15,6 +24,25 @@ func (f *File) AddRelocation(sectionName string, rel Reloc) error { return nil } +// AddScatteredRelocation appends a scattered relocation to the named section. +func (f *File) AddScatteredRelocation(sectionName string, addr uint32, value uint32, relType uint8, length uint8, pcrel bool) error { + section := f.Section(sectionName) + if section == nil { + return fmt.Errorf("section %q not found", sectionName) + } + rel := Reloc{ + Addr: addr, + Value: value, + Type: relType, + Len: length, + Pcrel: pcrel, + Extern: false, + Scattered: true, + } + section.Relocs = append(section.Relocs, rel) + return nil +} + // AddRelocations appends relocations to the named section. func (f *File) AddRelocations(sectionName string, rels []Reloc) error { section := f.Section(sectionName) @@ -35,6 +63,16 @@ func (f *File) ReplaceRelocations(sectionName string, rels []Reloc) error { return nil } +// RemoveRelocations clears relocations for the named section. +func (f *File) RemoveRelocations(sectionName string) error { + section := f.Section(sectionName) + if section == nil { + return fmt.Errorf("section %q not found", sectionName) + } + section.Relocs = section.Relocs[:0] + return nil +} + // AddRelocationForSymbol creates a non-scattered relocation that references a symbol. func (f *File) AddRelocationForSymbol(sectionName, symbolName string, addr uint32, relType uint8, length uint8, pcrel bool) error { if f.Symtab == nil { @@ -108,3 +146,25 @@ func (f *File) SetDylibOrdinalForSymbolIndex(index uint32, ordinal uint8) error f.dylibOrdinalBySymbol[index] = ordinal return nil } + +// SetBindKindForSymbol records a bind kind for the named symbol. +func (f *File) SetBindKindForSymbol(symbolName string, kind BindKind) error { + if f.Symtab == nil { + return errors.New("symbol table not available") + } + for i, sym := range f.Symtab.Syms { + if sym.Name == symbolName { + return f.SetBindKindForSymbolIndex(uint32(i), kind) + } + } + return fmt.Errorf("symbol %q not found", symbolName) +} + +// SetBindKindForSymbolIndex records a bind kind for the symbol index. +func (f *File) SetBindKindForSymbolIndex(index uint32, kind BindKind) error { + if f.bindKindBySymbol == nil { + f.bindKindBySymbol = map[uint32]BindKind{} + } + f.bindKindBySymbol[index] = kind + return nil +} diff --git a/macho/reloc_edit_test.go b/macho/reloc_edit_test.go index 1bc9f94..3de84d4 100644 --- a/macho/reloc_edit_test.go +++ b/macho/reloc_edit_test.go @@ -91,3 +91,136 @@ func TestDyldBindOrdinal(t *testing.T) { t.Fatalf("bind info missing ordinal opcode") } } + +func TestScatteredRelocationRoundTrip(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec")) + if err != nil { + t.Fatalf("open macho: %v", err) + } + defer f.Close() + + sec := f.Section("__text") + if sec == nil { + t.Fatalf("missing __text section") + } + origCount := len(sec.Relocs) + if err := f.AddScatteredRelocation("__text", 0, 0, 0, 2, false); err != nil { + t.Fatalf("add scattered relocation: %v", err) + } + + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + sec2 := f2.Section("__text") + if sec2 == nil || len(sec2.Relocs) != origCount+1 { + t.Fatalf("relocations not persisted") + } + found := false + for _, rel := range sec2.Relocs { + if rel.Scattered { + found = true + break + } + } + if !found { + t.Fatalf("scattered relocation not preserved") + } +} + +func TestRemoveRelocations(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec")) + if err != nil { + t.Fatalf("open macho: %v", err) + } + defer f.Close() + + sec := f.Section("__text") + if sec == nil { + t.Fatalf("missing __text section") + } + if err := f.AddRelocation("__text", Reloc{Addr: 0, Type: 0, Len: 3, Pcrel: false, Extern: false}); err != nil { + t.Fatalf("add relocation: %v", err) + } + if err := f.RemoveRelocations("__text"); err != nil { + t.Fatalf("remove relocation: %v", err) + } + + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + sec2 := f2.Section("__text") + if sec2 == nil { + t.Fatalf("missing __text section") + } + if sec2.Nreloc != 0 || len(sec2.Relocs) != 0 { + t.Fatalf("relocations not removed") + } +} + +func TestWeakAndLazyBindKinds(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec")) + if err != nil { + t.Fatalf("open macho: %v", err) + } + defer f.Close() + + if f.DylinkInfo == nil || f.Symtab == nil { + t.Skip("missing dyld info or symbol table") + } + sec := f.Section("__text") + if sec == nil { + t.Fatalf("missing __text section") + } + + var symName string + for _, sym := range f.Symtab.Syms { + if sym.Name != "" { + symName = sym.Name + break + } + } + if symName == "" { + t.Skip("no suitable symbol found") + } + + if err := f.AddRelocationForSymbol("__text", symName, 0, 0, 3, false); err != nil { + t.Fatalf("add relocation: %v", err) + } + if err := f.SetBindKindForSymbol(symName, BindWeak); err != nil { + t.Fatalf("set weak bind: %v", err) + } + if err := f.AddRelocationForSymbol("__text", symName, 4, 0, 3, false); err != nil { + t.Fatalf("add relocation: %v", err) + } + if err := f.SetBindKindForSymbol(symName, BindLazy); err != nil { + t.Fatalf("set lazy bind: %v", err) + } + + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + if f2.DylinkInfo == nil { + t.Fatalf("missing dyld info") + } + if len(f2.DylinkInfo.WeakBindingDat) == 0 { + t.Fatalf("weak binding info missing") + } + if len(f2.DylinkInfo.LazyBindingDat) == 0 { + t.Fatalf("lazy binding info missing") + } +} diff --git a/macho/reloc_write.go b/macho/reloc_write.go index 77f2edc..68dbab8 100644 --- a/macho/reloc_write.go +++ b/macho/reloc_write.go @@ -73,17 +73,17 @@ func (f *File) prepareDyldInfoFromRelocs() error { if f.DylinkInfo == nil { return nil } - rebaseDat, bindDat, err := f.encodeDyldInfoFromRelocs() + rebaseDat, bindDat, weakBindDat, lazyBindDat, err := f.encodeDyldInfoFromRelocs() if err != nil { return err } - if len(rebaseDat) == 0 && len(bindDat) == 0 { + if len(rebaseDat) == 0 && len(bindDat) == 0 && len(weakBindDat) == 0 && len(lazyBindDat) == 0 { return nil } start := alignUp64(f.endOfSections(), 4) limit := f.dyldInfoEndLimit() - total := uint64(len(rebaseDat) + len(bindDat)) + total := uint64(len(rebaseDat) + len(bindDat) + len(weakBindDat) + len(lazyBindDat)) if limit != 0 && start+total > limit { return fmt.Errorf("not enough room for dyld info") } @@ -97,6 +97,16 @@ func (f *File) prepareDyldInfoFromRelocs() error { f.DylinkInfo.BindingInfoDat = bindDat f.DylinkInfo.BindingInfoLen = uint32(len(bindDat)) f.DylinkInfo.BindingInfoOffset = offset + offset += uint64(len(bindDat)) + + f.DylinkInfo.WeakBindingDat = weakBindDat + f.DylinkInfo.WeakBindingLen = uint32(len(weakBindDat)) + f.DylinkInfo.WeakBindingOffset = offset + offset += uint64(len(weakBindDat)) + + f.DylinkInfo.LazyBindingDat = lazyBindDat + f.DylinkInfo.LazyBindingLen = uint32(len(lazyBindDat)) + f.DylinkInfo.LazyBindingOffset = offset return f.refreshDylinkInfoLoadBytes() } @@ -145,19 +155,28 @@ func (f *File) dyldInfoEndLimit() uint64 { return limit } -func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) { +func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, []byte, []byte, error) { segments := f.segmentOrdinals() if len(segments) == 0 { - return nil, nil, nil + return nil, nil, nil, nil, nil } var rebase bytes.Buffer var bind bytes.Buffer + var weak bytes.Buffer + var lazy bytes.Buffer rebase.WriteByte(rebaseOpcodeSetTypeImm | rebaseTypePointer) bind.WriteByte(bindOpcodeSetTypeImm | bindTypePointer) + weak.WriteByte(bindOpcodeSetTypeImm | bindTypePointer) + lazy.WriteByte(bindOpcodeSetTypeImm | bindTypePointer) + currentOrdinal := uint8(0) + weakOrdinal := uint8(0) + lazyOrdinal := uint8(0) bind.WriteByte(bindOpcodeSetDylibOrdinalImm | currentOrdinal) + weak.WriteByte(bindOpcodeSetDylibOrdinalImm | weakOrdinal) + lazy.WriteByte(bindOpcodeSetDylibOrdinalImm | lazyOrdinal) for _, s := range f.Sections { if len(s.Relocs) == 0 { @@ -165,35 +184,45 @@ func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) { } ordinal, ok := segments[s.Seg] if !ok { - return nil, nil, fmt.Errorf("unknown segment for section %q", s.Name) + return nil, nil, nil, nil, fmt.Errorf("unknown segment for section %q", s.Name) } seg := byte(ordinal & 0x0f) for _, rel := range s.Relocs { offset := uint64(s.Addr) + uint64(rel.Addr) segBase := f.segmentAddr(s.Seg) if offset < segBase { - return nil, nil, fmt.Errorf("relocation offset underflows segment %q", s.Seg) + return nil, nil, nil, nil, fmt.Errorf("relocation offset underflows segment %q", s.Seg) } segOffset := offset - segBase if rel.Extern { ordinal, err := f.dylibOrdinalForSymbol(rel.Value) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, err } - if ordinal != currentOrdinal { - bind.WriteByte(bindOpcodeSetDylibOrdinalImm | ordinal) - currentOrdinal = ordinal + stream := &bind + streamOrdinal := ¤tOrdinal + switch f.bindKindForSymbol(rel.Value) { + case BindWeak: + stream = &weak + streamOrdinal = &weakOrdinal + case BindLazy: + stream = &lazy + streamOrdinal = &lazyOrdinal + } + if ordinal != *streamOrdinal { + stream.WriteByte(bindOpcodeSetDylibOrdinalImm | ordinal) + *streamOrdinal = ordinal } name, err := f.symbolName(rel.Value) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, err } - bind.WriteByte(bindOpcodeSetSegmentAndOffsetULEB | seg) - bind.Write(encodeULEB128(segOffset)) - bind.WriteByte(bindOpcodeSetSymbolTrailingFlags | 0) - bind.WriteString(name) - bind.WriteByte(0) - bind.WriteByte(bindOpcodeDoBind) + stream.WriteByte(bindOpcodeSetSegmentAndOffsetULEB | seg) + stream.Write(encodeULEB128(segOffset)) + stream.WriteByte(bindOpcodeSetSymbolTrailingFlags | 0) + stream.WriteString(name) + stream.WriteByte(0) + stream.WriteByte(bindOpcodeDoBind) } else { rebase.WriteByte(rebaseOpcodeSetSegmentAndOffsetULEB | seg) rebase.Write(encodeULEB128(segOffset)) @@ -203,9 +232,17 @@ func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) { } rebase.WriteByte(rebaseOpcodeDone) - bind.WriteByte(bindOpcodeDone) + if bind.Len() > 1 { + bind.WriteByte(bindOpcodeDone) + } + if weak.Len() > 1 { + weak.WriteByte(bindOpcodeDone) + } + if lazy.Len() > 1 { + lazy.WriteByte(bindOpcodeDone) + } - return rebase.Bytes(), bind.Bytes(), nil + return rebase.Bytes(), bind.Bytes(), weak.Bytes(), lazy.Bytes(), nil } func (f *File) segmentOrdinals() map[string]int { @@ -250,6 +287,13 @@ func (f *File) dylibOrdinalForSymbol(index uint32) (uint8, error) { return ordinal, nil } +func (f *File) bindKindForSymbol(index uint32) BindKind { + if f.bindKindBySymbol == nil { + return BindNormal + } + return f.bindKindBySymbol[index] +} + func encodeULEB128(value uint64) []byte { var out []byte for { diff --git a/pe/reloc_edit.go b/pe/reloc_edit.go index 33a60ea..b2a6376 100644 --- a/pe/reloc_edit.go +++ b/pe/reloc_edit.go @@ -1,6 +1,8 @@ package pe -import "fmt" +import ( + "fmt" +) // AddBaseRelocation appends a relocation block to the base relocation table. func (f *File) AddBaseRelocation(block RelocationTableEntry) { @@ -20,6 +22,15 @@ func (f *File) ReplaceBaseRelocations(blocks []RelocationTableEntry) { *f.BaseRelocationTable = append((*f.BaseRelocationTable)[:0], blocks...) } +// RemoveBaseRelocations clears all base relocation entries. +func (f *File) RemoveBaseRelocations() { + if f.BaseRelocationTable == nil { + entries := []RelocationTableEntry{} + f.BaseRelocationTable = &entries + } + *f.BaseRelocationTable = (*f.BaseRelocationTable)[:0] +} + // AddBaseReloc adds a single base relocation to the block for the containing page. func (f *File) AddBaseReloc(rva uint32, typ byte) { page := rva &^ 0x0fff @@ -52,6 +63,33 @@ func (f *File) AddSectionRelocation(sectionName string, rel Reloc) error { return nil } +// AddSectionRelocationForSymbol appends a COFF relocation using a symbol name lookup. +func (f *File) AddSectionRelocationForSymbol(sectionName string, symbolName string, rel Reloc) error { + index, err := f.SymbolIndexByName(symbolName) + if err != nil { + return err + } + rel.SymbolTableIndex = index + return f.AddSectionRelocation(sectionName, rel) +} + +// SymbolIndexByName returns the COFF symbol table index for the given name. +func (f *File) SymbolIndexByName(symbolName string) (uint32, error) { + if len(f.COFFSymbols) == 0 { + return 0, fmt.Errorf("no COFF symbols available") + } + for i := range f.COFFSymbols { + name, err := f.COFFSymbols[i].FullName(f.StringTable) + if err != nil { + return 0, err + } + if name == symbolName { + return uint32(i), nil + } + } + return 0, fmt.Errorf("symbol %q not found", symbolName) +} + // ReplaceSectionRelocations replaces COFF relocations for a section. func (f *File) ReplaceSectionRelocations(sectionName string, rels []Reloc) error { section := f.Section(sectionName) @@ -61,3 +99,13 @@ func (f *File) ReplaceSectionRelocations(sectionName string, rels []Reloc) error section.Relocs = append(section.Relocs[:0], rels...) return nil } + +// RemoveSectionRelocations clears COFF relocations for a section. +func (f *File) RemoveSectionRelocations(sectionName string) error { + section := f.Section(sectionName) + if section == nil { + return fmt.Errorf("section %q not found", sectionName) + } + section.Relocs = section.Relocs[:0] + return nil +} diff --git a/pe/reloc_edit_test.go b/pe/reloc_edit_test.go index eb94839..5712c61 100644 --- a/pe/reloc_edit_test.go +++ b/pe/reloc_edit_test.go @@ -58,3 +58,101 @@ func TestAddBaseAndSectionRelocations(t *testing.T) { t.Fatalf("relocs stripped flag still set") } } + +func TestAddSectionRelocationForSymbol(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-386-mingw-exec")) + if err != nil { + t.Fatalf("open pe: %v", err) + } + defer f.Close() + + if len(f.COFFSymbols) == 0 { + t.Skip("no COFF symbols available") + } + var symName string + for i := range f.COFFSymbols { + name, err := f.COFFSymbols[i].FullName(f.StringTable) + if err != nil { + t.Fatalf("symbol name: %v", err) + } + if name != "" { + symName = name + break + } + } + if symName == "" { + t.Skip("no suitable symbol found") + } + + sec := f.Sections[0] + rel := Reloc{VirtualAddress: 0, Type: 0} + if err := f.AddSectionRelocationForSymbol(sec.Name, symName, rel); err != nil { + t.Fatalf("add relocation: %v", err) + } + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + sec2 := f2.Section(sec.Name) + if sec2 == nil || len(sec2.Relocs) == 0 { + t.Fatalf("relocations not written") + } + idx, err := f.SymbolIndexByName(symName) + if err != nil { + t.Fatalf("symbol index: %v", err) + } + found := false + for _, r := range sec2.Relocs { + if r.SymbolTableIndex == idx { + found = true + break + } + } + if !found { + t.Fatalf("relocation with symbol index not found") + } +} + +func TestRemoveRelocations(t *testing.T) { + f, err := Open(path.Join("testdata", "gcc-386-mingw-exec")) + if err != nil { + t.Fatalf("open pe: %v", err) + } + defer f.Close() + + if len(f.Sections) == 0 { + t.Fatalf("no sections") + } + sec := f.Sections[0] + f.AddBaseReloc(sec.VirtualAddress, IMAGE_REL_BASED_HIGHLOW) + if err := f.AddSectionRelocation(sec.Name, Reloc{VirtualAddress: 0, SymbolTableIndex: 0, Type: 0}); err != nil { + t.Fatalf("add relocation: %v", err) + } + f.RemoveBaseRelocations() + if err := f.RemoveSectionRelocations(sec.Name); err != nil { + t.Fatalf("remove relocation: %v", err) + } + + out, err := f.Bytes() + if err != nil { + t.Fatalf("write: %v", err) + } + f2, err := NewFile(bytes.NewReader(out)) + if err != nil { + t.Fatalf("reopen: %v", err) + } + if f2.BaseRelocationTable != nil && len(*f2.BaseRelocationTable) != 0 { + t.Fatalf("base relocations not removed") + } + sec2 := f2.Section(sec.Name) + if sec2 == nil { + t.Fatalf("missing section") + } + if len(sec2.Relocs) != 0 || sec2.NumberOfRelocations != 0 { + t.Fatalf("section relocations not removed") + } +}