From 6815ea6556020c64410c6d54728faaa9f44effc2 Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Tue, 11 Jul 2023 10:06:59 +0700 Subject: [PATCH] add keep imports action --- .../wrapper/action/rewrite_imports.go | 26 +++++++ .../internal/wrapper/action/save_imports.go | 35 ++++++++- macho-go/internal/wrapper/cli.go | 1 + macho-go/internal/wrapper/cli_parser.go | 2 + macho-go/internal/wrapper/program_context.go | 4 +- macho-go/pkg/ios/macho/edit.go | 72 +++++++++++++++++++ macho-go/pkg/ios/macho/fixups.c | 22 ++++++ macho-go/pkg/ios/macho/fixups.h | 3 + macho-go/pkg/ios/macho/load_commands.go | 16 +++++ macho-go/pkg/ios/macho/macho.go | 8 +++ research/custom_loader/build.sh | 3 +- 11 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 macho-go/internal/wrapper/action/rewrite_imports.go diff --git a/macho-go/internal/wrapper/action/rewrite_imports.go b/macho-go/internal/wrapper/action/rewrite_imports.go new file mode 100644 index 0000000..4d17624 --- /dev/null +++ b/macho-go/internal/wrapper/action/rewrite_imports.go @@ -0,0 +1,26 @@ +package action + +import ( + . "ios-wrapper/internal/wrapper/ofile" +) + +type rewriteImports struct{ + symbols []string +} + +func (action *rewriteImports) withMacho(mf *MachoFile) error { + mc := mf.Context() + mc.RewriteImportsTable(action.symbols) + return nil +} + +func (action *rewriteImports) withFat(ff *FatFile) error { + return defaultWithFat(action, ff) +} + +func NewRewriteImportsWithKeepSymbolsAction(symbols []string) *rewriteImports { + return &rewriteImports{ + symbols, + } +} + diff --git a/macho-go/internal/wrapper/action/save_imports.go b/macho-go/internal/wrapper/action/save_imports.go index ae73ec9..454b4d3 100644 --- a/macho-go/internal/wrapper/action/save_imports.go +++ b/macho-go/internal/wrapper/action/save_imports.go @@ -3,13 +3,16 @@ package action import ( // "fmt" "sort" + "strings" // log "github.com/sirupsen/logrus" . "ios-wrapper/internal/wrapper/ofile" "ios-wrapper/pkg/protomodel" ) -type saveImports struct{} +type saveImports struct{ + keepSymbols []string +} func (action *saveImports) withMacho(mf *MachoFile) error { // calculateHash := func(name string) uint32 { @@ -51,6 +54,30 @@ func (action *saveImports) withMacho(mf *MachoFile) error { if symbol.Type() != "lazy" { continue } + + skip := false + for _, keep := range action.keepSymbols { + name := keep + lib := "" + parts := strings.Split(keep, ",") + if len(parts) == 2 { + name = parts[0] + lib = parts[1] + } + + if symbol.Name() != name { + continue + } + if lib == "" || lib == symbol.Dylib() { + skip = true + break + } + } + + if skip { + continue + } + // dylib_hash := calculateHash(symbol.Dylib()) seg := mc.Segments()[symbol.Segment()] @@ -113,6 +140,8 @@ func (action *saveImports) withFat(ff *FatFile) error { return defaultWithFat(action, ff) } -func NewSaveImportsAction() *saveImports { - return &saveImports{} +func NewSaveImportsAction(keepSymbols []string) *saveImports { + return &saveImports{ + keepSymbols, + } } diff --git a/macho-go/internal/wrapper/cli.go b/macho-go/internal/wrapper/cli.go index 6e16d40..78dc905 100644 --- a/macho-go/internal/wrapper/cli.go +++ b/macho-go/internal/wrapper/cli.go @@ -128,6 +128,7 @@ func Cli() { pc.rpath_to_add = arg.Rpath pc.outfile = arg.Out pc.bcellfile = arg.Bcell + pc.symbols_keep = arg.KeepImports default: return diff --git a/macho-go/internal/wrapper/cli_parser.go b/macho-go/internal/wrapper/cli_parser.go index 74b54f4..ce15c82 100644 --- a/macho-go/internal/wrapper/cli_parser.go +++ b/macho-go/internal/wrapper/cli_parser.go @@ -71,6 +71,8 @@ type PepeArgument struct { RemoveObjCString bool `default:"false" negatable:"" help:"(TODO) Remove references, string litteral to Objctive-C strings"` RebuildLinkEdit bool `default:"false" negatable:"" help:"(TODO) Rebuild the LINKEDIT section"` FullRemoval bool `default:"false" help:"Apply every removal possible"` + + KeepImports []string `short:"keep" help:"Do not remove import symbols and allow dyld resolve. If symbols is found with more than 1 occurrances, specify the library with a comma or else all occurrances will be kept. e.g., _symbol,libLIB.dylib"` } type BcellToHeaderArgument struct { diff --git a/macho-go/internal/wrapper/program_context.go b/macho-go/internal/wrapper/program_context.go index 5711a57..94e049d 100644 --- a/macho-go/internal/wrapper/program_context.go +++ b/macho-go/internal/wrapper/program_context.go @@ -46,6 +46,7 @@ type ProgramContext struct { remove_symbol_table bool dylib_to_add []string rpath_to_add []string + symbols_keep []string outfile string @@ -95,8 +96,9 @@ func (pc *ProgramContext) Process(ofile OFile) { pc.AddAction(NewRemoveInitPointerAction()) } if pc.remove_imports { - pc.AddAction(NewSaveImportsAction()) + pc.AddAction(NewSaveImportsAction(pc.symbols_keep)) pc.AddAction(NewRemoveImportsAction()) + pc.AddAction(NewRewriteImportsWithKeepSymbolsAction(pc.symbols_keep)) } if pc.remove_symbol_table { pc.AddAction(NewRemoveClassicSymbolAction()) diff --git a/macho-go/pkg/ios/macho/edit.go b/macho-go/pkg/ios/macho/edit.go index 4442e75..01e3868 100644 --- a/macho-go/pkg/ios/macho/edit.go +++ b/macho-go/pkg/ios/macho/edit.go @@ -6,6 +6,7 @@ import ( "io" "math/rand" "time" + "strings" log "github.com/sirupsen/logrus" @@ -524,6 +525,77 @@ func (mc *MachoContext) ReworkForObjc() { } } +func (mc *MachoContext) RewriteImportsTable(keepSymbols []string) { + allSymbols := mc.CollectBindSymbols() + fixups, fixupsOffset := mc.Fixups() + + importTable := fixups.ImportsOffset(uint32(fixupsOffset)) + symbolTable := fixups.SymbolsOffset(uint32(fixupsOffset)) + symbolTablePtr := symbolTable + + // in removeBindSymbolsModern, we erase these pointers in file + // but because we keep a few symbols, we need to rewrite the pointers + // as well as rebuild the import table and strings table, and bind values + + keepCount := uint32(0) + for _, symbol := range keepSymbols { + name := symbol + lib := "" + parts := strings.Split(symbol, ",") + if len(parts) == 2 { + name = parts[0] + lib = parts[1] + } + + symbolInfo := (*ImportSymbol)(nil) + for _, s := range allSymbols { + if s.Name() != name { + continue + } + if lib == "" || lib == s.Dylib() { + symbolInfo = s + break + } + } + if symbolInfo == nil { + // symbol not found + continue + } + + fmt.Printf("keep symbol %s\n", name) + fmt.Printf("importTable at 0x%x; stringTable at 0x%x\n", importTable, symbolTablePtr) + fmt.Printf("bind value at 0x%x\n", symbolInfo.file_address) + + // write string to string table + mc.file.WriteAt([]byte(name), int64(symbolTablePtr)) + // fix bind value + rebaseOpcodeBytes := make([]byte, 8) + mc.file.ReadAt(rebaseOpcodeBytes, int64(symbolInfo.file_address)) + rebaseOpcode := mc.byteorder.Uint64(rebaseOpcodeBytes) + bindOpcode := C.MakeBindFixupOpcodeFromRebase(C.uint64_t(rebaseOpcode), C.uint(keepCount)) + + { + v := make([]byte, 8) + mc.byteorder.PutUint64(v, uint64(bindOpcode)) + mc.file.WriteAt(v, int64(symbolInfo.file_address)) + } + // write import data to import table + entry := C.MakeImportTableEntry(C.uint(symbolInfo.LibOrdinal()), C.uint(symbolTablePtr - symbolTable)) + { + v := make([]byte, 4) + mc.byteorder.PutUint32(v, uint32(entry)) + mc.file.WriteAt(v, int64(importTable)) + } + + keepCount += 1 + importTable += 4 + symbolTablePtr += uint32(len(name)) + 1 + } + + fixups.imports_count = keepCount + mc.file.WriteAt(fixups.Serialize(mc), int64(mc.fixups.dataoff)) +} + func (mc *MachoContext) RemoveSymbolTable() { // try to remove symtab and dysymtab mc.removeSymtabCommand() diff --git a/macho-go/pkg/ios/macho/fixups.c b/macho-go/pkg/ios/macho/fixups.c index 8d53c51..dc4ab09 100644 --- a/macho-go/pkg/ios/macho/fixups.c +++ b/macho-go/pkg/ios/macho/fixups.c @@ -104,6 +104,28 @@ uint64_t MakeRebaseFixupOpcode(int next, uint64_t target, uint64_t high8) { return value; } +uint64_t MakeBindFixupOpcodeFromRebase(uint64_t rebaseOpcode, uint32_t ordinal) { + printf("fix bind value\n"); + uint64_t ret; + struct dyld_chained_ptr_64_bind* b = (struct dyld_chained_ptr_64_bind*)&ret; + b->next = ((struct dyld_chained_ptr_64_rebase*)&rebaseOpcode)->next; + b->bind = 1; + b->ordinal = ordinal; + b->addend = 0; + b->reserved = 0; + return ret; +} + +uint32_t MakeImportTableEntry(uint32_t lib_ordinal, uint32_t name_offset) { + printf("append import table\n"); + uint32_t ret; + struct dyld_chained_import* import = (struct dyld_chained_import*)&ret; + import->lib_ordinal = lib_ordinal; + import->name_offset = name_offset; + import->weak_import = 0; + return ret; +} + void ParseFixUps(uint8_t* buffer) { struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer; printf("starts=0x%x\n", header->starts_offset); diff --git a/macho-go/pkg/ios/macho/fixups.h b/macho-go/pkg/ios/macho/fixups.h index 4f9be7f..443a1c1 100644 --- a/macho-go/pkg/ios/macho/fixups.h +++ b/macho-go/pkg/ios/macho/fixups.h @@ -31,4 +31,7 @@ struct ImportSymbol GetImportsAt(struct ImportTable* table, int i); int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix); int ParseFixValue(int format, uint64_t value, int* bind, uint64_t* ret1, uint64_t* ret2); uint64_t MakeRebaseFixupOpcode(int next, uint64_t target, uint64_t high8); + +uint64_t MakeBindFixupOpcodeFromRebase(uint64_t rebaseOpcode, uint32_t ordinal); +uint32_t MakeImportTableEntry(uint32_t lib_ordinal, uint32_t name_offset); #endif diff --git a/macho-go/pkg/ios/macho/load_commands.go b/macho-go/pkg/ios/macho/load_commands.go index 799520b..5e36516 100644 --- a/macho-go/pkg/ios/macho/load_commands.go +++ b/macho-go/pkg/ios/macho/load_commands.go @@ -37,6 +37,14 @@ func (h *Header) Cpusubtype() uint32 { return h.cpusubtype } +func (h *Header) IsExecutable() bool { + return h.filetype == 0x2 +} + +func (h *Header) IsDylib() bool { + return h.filetype == 0x6 +} + func (h *Header) Serialize(mc *MachoContext) []byte { buf := new(bytes.Buffer) binary.Write(buf, mc.byteorder, h.magic) @@ -406,6 +414,14 @@ type Fixups struct { symbols_format uint32 } +func (lcmd *Fixups) ImportsOffset(fixups_offset uint32) uint32 { + return lcmd.imports_offset + fixups_offset +} + +func (lcmd *Fixups) SymbolsOffset(fixups_offset uint32) uint32 { + return lcmd.symbols_offset + fixups_offset +} + func (lcmd *Fixups) Serialize(mc *MachoContext) []byte { buf := new(bytes.Buffer) binary.Write(buf, mc.byteorder, lcmd.fixups_version) diff --git a/macho-go/pkg/ios/macho/macho.go b/macho-go/pkg/ios/macho/macho.go index f5d1122..6aefe5f 100644 --- a/macho-go/pkg/ios/macho/macho.go +++ b/macho-go/pkg/ios/macho/macho.go @@ -68,6 +68,14 @@ func (mc *MachoContext) Main() uint64 { return mc.entryoff } +func (mc *MachoContext) Fixups() (*Fixups, uint64) { + start := mc.fixups.dataoff + size := mc.fixups.datasize + fixups := new(Fixups) + fixups.Deserialize(mc, mc.buf[start:start+size]) + return fixups, uint64(mc.fixups.dataoff) +} + func (mc *MachoContext) WriteEnabled() bool { return mc.file != nil } diff --git a/research/custom_loader/build.sh b/research/custom_loader/build.sh index 4ef3bb6..b22c194 100755 --- a/research/custom_loader/build.sh +++ b/research/custom_loader/build.sh @@ -63,7 +63,8 @@ clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_l clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.mm # extract symbols from a -../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table $OUT/a +# ../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --keep-imports _printf $OUT/a +../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --keep-imports _printf $OUT/a ../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h # build libb with symbols extracted from a clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc