erase objc method names

This commit is contained in:
2024-01-10 14:19:59 +07:00
parent 07f361d8ac
commit a68bbf2b8f
9 changed files with 212 additions and 13 deletions

View File

@ -42,6 +42,10 @@ func (sym *ImportSymbol) Type() string {
return sym.typ
}
func (sym *ImportSymbol) SafeForRemoval() bool {
return sym.typ == "lazy" || sym.typ == "fixups"
}
func (sym *ImportSymbol) Dylib() string {
return sym.dylib
}
@ -150,7 +154,7 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
sym.address = uint64(address)
sym.name = name
sym.dylib = dylib
sym.typ = "lazy"
sym.typ = "fixups"
sym.lib_ordinal = uint32(s.lib_ordinal)
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))

View File

@ -280,7 +280,10 @@ func (mc *MachoContext) RemoveBindSymbols() {
rand.Seed(time.Now().UnixNano())
if mc.dyldinfo == nil {
isModernSymbol := mc.dyldinfo == nil
isLegacySymbol := !isModernSymbol
if isModernSymbol {
mc.removeBindSymbolsModern()
} else {
mc.removeBindSymbolsLegacy()
@ -290,14 +293,12 @@ func (mc *MachoContext) RemoveBindSymbols() {
mc.ReworkForObjc()
}
// due to some limitations when design this tool
// we write the c code to stdout lol
for _, symbol := range mc.CollectBindSymbols() {
if symbol.Type() != "lazy" {
if !symbol.SafeForRemoval() {
continue
}
if mc.dyldinfo != nil {
if isLegacySymbol {
// for legacy resolve the opcodes can be rewritten as 0x00
mc.file.WriteAt(make([]byte, 8), int64(symbol.file_address))
} else {
@ -394,6 +395,16 @@ func (mc *MachoContext) ReworkForObjc() {
// edit flags to not S_MOD_INIT_FUNC
mc.file.WriteAt([]byte{0, 0, 0, 0}, section_ptr+0x40)
}
// erases all objc method names
// this should still works because the cache inserts the pointer value not string
// but some symbols relies on pre-defined implementations, such as **load** method
// load method is the same across all classes and so objc define an implementation
// selector should points to this load selector to make objc thinks that it's "load"
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
// mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr)
mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset()))
}
section_ptr += 16*2 + 8*2 + 4*8
}
}
@ -420,6 +431,15 @@ func (mc *MachoContext) ReworkForObjc() {
sections := segment.Sections()
last := sections[len(sections)-1]
data_end = int(last.Addr() - segment.Vmaddr() + segment.Fileoff() + last.Size())
// do not register selector and see what happens
section_ptr := ptr + 0x40 + 8
for _, section := range segment.Sections() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 {
// mc.file.WriteAt([]byte("__objc_selbruh"), section_ptr)
}
section_ptr += 16*2 + 8*2 + 4*8
}
}
ptr += int64(cmd.Cmdsize())
}

View File

@ -0,0 +1,95 @@
package macho
import (
"bytes"
"encoding/binary"
"strings"
. "ios-wrapper/pkg/ios"
)
type SpecialSelector struct {
idx uint
name string
}
func (sel *SpecialSelector) Idx() uint {
return sel.idx
}
func (sel *SpecialSelector) Name() string {
return sel.name
}
// collect the index and the name in selector list of special method names
// these names are resolved by the dyld objc cache
// through __dyld_get_objc_selector
//
// we currently have the following symbols guaranteed to be in this list:
// - load
// - retain
func (mc *MachoContext) CollectSpecialSelectors() []*SpecialSelector {
var special_selectors []*SpecialSelector
var methods []byte
var methname_offset uint32
for _, cmd := range mc.commands {
if cmd.Cmd() == LC_MAIN {
continue
}
if cmd.Cmd() != LC_SEGMENT_64 {
continue
}
var segment = cmd.(*Segment64)
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
for _, section := range segment.Sections() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
methname_offset = section.Offset()
methods = make([]byte, section.Size())
mc.file.ReadAt(methods, int64(section.Offset()))
}
}
}
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 {
for _, section := range segment.Sections() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 {
selectors_buffer := make([]byte, section.Size())
mc.file.ReadAt(selectors_buffer, int64(section.Offset()))
buffer := bytes.NewReader(selectors_buffer)
for i := uint(0); i < uint(section.Size()) / 8; i++ {
// this field is actually a Rebase
// we assume that no rebase is needed
// so everything sticks to its file offset
var offset uint32
binary.Read(buffer, mc.byteorder, &offset) // first 4 bytes is offset
var name_builder strings.Builder
for j := uint32(0); ; j++ {
c := methods[offset - methname_offset + j]
if c == 0 {
break
}
name_builder.WriteByte(c)
}
name := name_builder.String()
if name == "load" {
special_selectors = append(special_selectors, &SpecialSelector{
idx: i,
name: name,
})
}
binary.Read(buffer, mc.byteorder, &offset) // ignore rebase arguments
}
}
}
}
}
return special_selectors
}