From e2c75bf718529ac9b4a67313120d4243eca7f70f Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Thu, 15 Jun 2023 10:41:18 +0700 Subject: [PATCH] rework ios-wrapper cli parsing --- .../wrapper/action/remove_classic_symbols.go | 2 +- .../internal/wrapper/action/remove_exports.go | 21 ++++++ .../internal/wrapper/action/save_imports.go | 71 ++++++++++++++++++ macho-go/internal/wrapper/cli.go | 71 +++++++++++++++++- macho-go/internal/wrapper/cli_parser.go | 31 +++++++- macho-go/internal/wrapper/program_context.go | 30 +++++--- macho-go/pkg/ios/macho/dyld_info.go | 4 + macho-go/pkg/ios/macho/edit.go | 75 ++++++++++--------- macho-go/pkg/ios/macho/fixups.c | 1 + macho-go/pkg/ios/macho/symtab.go | 2 +- macho-go/proto/macho_info.proto | 20 ++--- research/custom_loader/build.sh | 4 +- 12 files changed, 269 insertions(+), 63 deletions(-) create mode 100644 macho-go/internal/wrapper/action/remove_exports.go create mode 100644 macho-go/internal/wrapper/action/save_imports.go diff --git a/macho-go/internal/wrapper/action/remove_classic_symbols.go b/macho-go/internal/wrapper/action/remove_classic_symbols.go index 747397e..cda77fb 100644 --- a/macho-go/internal/wrapper/action/remove_classic_symbols.go +++ b/macho-go/internal/wrapper/action/remove_classic_symbols.go @@ -7,7 +7,7 @@ import ( type removeClassicSymbol struct{} func (action *removeClassicSymbol) withMacho(mf *MachoFile) error { - mf.Context().RemoveClassicSymbol() + mf.Context().RemoveSymbolTable() return nil } diff --git a/macho-go/internal/wrapper/action/remove_exports.go b/macho-go/internal/wrapper/action/remove_exports.go new file mode 100644 index 0000000..f4c257b --- /dev/null +++ b/macho-go/internal/wrapper/action/remove_exports.go @@ -0,0 +1,21 @@ +package action + +import ( + . "ios-wrapper/internal/wrapper/ofile" +) + +type removeExports struct{} + +func (action *removeExports) withMacho(mf *MachoFile) error { + mf.Context().RemoveExportTrie() + return nil +} + +func (action *removeExports) withFat(ff *FatFile) error { + return defaultWithFat(action, ff) +} + +func NewRemoveExportsAction() *removeExports { + return &removeExports{} +} + diff --git a/macho-go/internal/wrapper/action/save_imports.go b/macho-go/internal/wrapper/action/save_imports.go new file mode 100644 index 0000000..c24c30c --- /dev/null +++ b/macho-go/internal/wrapper/action/save_imports.go @@ -0,0 +1,71 @@ +package action + +import ( + "fmt" + // log "github.com/sirupsen/logrus" + + . "ios-wrapper/internal/wrapper/ofile" + "ios-wrapper/pkg/protomodel" +) + +type saveImports struct{} + +func (action *saveImports) withMacho(mf *MachoFile) error { + calculateHash := func(name string) uint32 { + var h uint32 = 0x811c9dc5 + for _, s := range name { + h ^= uint32(s) + h *= 0x01000193 + } + return h + } + + mc := mf.Context() + symbols := []*protomodel.MachoInfo_BindSymbol{} + fmt.Println("struct imported_symbol {const char* name; const char* lib; uint32_t hash; int segment_i; uint64_t offset;};") + fmt.Println("const char* lib_to_resolve = \"main\";") + fmt.Println("struct imported_symbol imported_table[] = {") + for _, symbol := range mc.CollectBindSymbols() { + if symbol.Type() != "lazy" { + continue + } + dylib_hash := calculateHash(symbol.Dylib()) + seg := mc.Segments()[symbol.Segment()] + + var offset uint64 + + if symbol.Address() >= seg.Vmaddr() { + // this is virtual address + offset = symbol.Address() - seg.Vmaddr() + } else { + // this is file address + offset = symbol.Address() - seg.Fileoff() + } + + fmt.Printf("{\"%s\", \"%s\", 0x%x, 0x%x, 0x%x},\n", + symbol.Name(), symbol.Dylib(), dylib_hash, symbol.Segment(), offset); + + symbols = append(symbols, + &protomodel.MachoInfo_BindSymbol{ + Name: symbol.Name(), + Libname: symbol.Dylib(), + Libhash: dylib_hash, + Segment: symbol.Segment(), + Offset: offset, + }) + + } + fmt.Println("};") + fmt.Printf("uint32_t nimports = %d;\n", len(symbols)); + mf.Info().Symbols = symbols + return nil +} + +func (action *saveImports) withFat(ff *FatFile) error { + return defaultWithFat(action, ff) +} + +func NewSaveImportsAction() *saveImports { + return &saveImports{} +} + diff --git a/macho-go/internal/wrapper/cli.go b/macho-go/internal/wrapper/cli.go index 3c531c3..4771aa7 100644 --- a/macho-go/internal/wrapper/cli.go +++ b/macho-go/internal/wrapper/cli.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "github.com/alecthomas/kong" log "github.com/sirupsen/logrus" @@ -63,7 +64,11 @@ func Cli() { arg := cli.Lipo.Join fat.FatJoin(arg.Macho, arg.Out) return - } + } else if command == "bcell2header" { + arg := cli.BcellToHeader + bcell2header(arg.Bcell, arg.Out) + return + } var pc ProgramContext var ofile OFile = nil @@ -73,7 +78,7 @@ func Cli() { arg := cli.Wrap ofile = NewOFile(arg.OFile) pc.remove_codesign = true - pc.strip_init_pointers = true + pc.remove_inits = true pc.dylib_to_add = []string{"@rpath/bcell.framework/bcell"} pc.rpath_to_add = []string{"@executable_path/Frameworks"} pc.outfile = arg.Out @@ -99,6 +104,29 @@ func Cli() { ofile = NewOFile(arg.OFile) pc.remove_imports = true pc.outfile = arg.Out + pc.bcellfile = arg.Bcell + + case "pepe": + arg := cli.Pepe + ofile = NewOFile(arg.OFile) + if arg.FullRemoval { + pc.remove_exports = true + pc.remove_symbol_table = true + pc.remove_imports = true + pc.remove_inits = true + pc.remove_codesign = true + pc.remove_others = true + } + pc.remove_imports = arg.RemoveBindSymbols + pc.remove_codesign = arg.RemoveCodeSign + pc.remove_inits = arg.RemoveInitFunctions + pc.remove_others = arg.RemoveOthers + pc.remove_exports = arg.RemoveExports + pc.remove_symbol_table = arg.RemoveSymbolTable + pc.dylib_to_add = arg.Dylibs + pc.rpath_to_add = arg.Rpath + pc.outfile = arg.Out + pc.bcellfile = arg.Bcell default: return @@ -165,6 +193,12 @@ func displayBcell(bfile string) { init_ptr.Value, ) } + fmt.Printf(" | Bind Symbols:\n") + for _, symbol := range info.Symbols { + lib := strings.Replace(symbol.Libname, "/System/Library/Frameworks/", "", 1) + fmt.Printf(" | %s offset=0x%x segmentID=0x%x\n", symbol.Name, symbol.Offset, symbol.Segment) + fmt.Printf(" | from=%s\n", lib) + } } } @@ -194,3 +228,36 @@ func resolveAddresses(dwarf string, load string, addresses []string) { } } } + +func bcell2header(bfile string, header string) { + raw_data, err := ioutil.ReadFile(bfile) + if err != nil { + log.Panic("Invalid Protobuf bcell.dat (1)") + } + data := &protomodel.BcellFile{} + err = proto.Unmarshal(raw_data, data) + if err != nil { + log.Panic("Invalid Protobuf bcell.dat (2)") + } + + fmt.Printf("[+] User Config: %+v\n", data.BcellConfig) + for arch, info := range data.MachoInfos { + fmt.Printf("[+] Arch %s:\n", arch) + fmt.Printf(" | PointerSize : %+v\n", info.PointerSize) + fmt.Printf(" | Image Base : 0x%x\n", info.ImageBase) + fmt.Printf(" | Init Pointers:\n") + for _, init_ptr := range info.InitPointers { + fmt.Printf( + " | offset 0x%x => addr 0x%x\n", + init_ptr.Offset, + init_ptr.Value, + ) + } + fmt.Printf(" | Bind Symbols:\n") + for _, symbol := range info.Symbols { + lib := strings.Replace(symbol.Libname, "/System/Library/Frameworks/", "", 1) + fmt.Printf(" | %s offset=0x%x segmentID=0x%x\n", symbol.Name, symbol.Offset, symbol.Segment) + fmt.Printf(" | from=%s\n", lib) + } + } +} diff --git a/macho-go/internal/wrapper/cli_parser.go b/macho-go/internal/wrapper/cli_parser.go index 503bd24..9179b13 100644 --- a/macho-go/internal/wrapper/cli_parser.go +++ b/macho-go/internal/wrapper/cli_parser.go @@ -26,6 +26,7 @@ type RemoveCodesignArgument struct { type RemoveImportsArgument struct { Out string `short:"o" required name:"outfile" help:"Modified Mach-O/Fat binary output file" type:"path"` + Bcell string `short:"b" required help:"bcell.dat output file" type:"path"` OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"` } @@ -55,7 +56,26 @@ type LipoArgument struct { } type PepeArgument struct { + Out string `short:"o" required name:"outfile" help:"Output file after transformation" type:"path"` + Bcell string `short:"b" required help:"bcell.dat output file" type:"path"` OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"` + Dylibs []string `short:"l" help:"Add more LC_DYLIB"` + Rpath []string `short:"r" help:"Add more LC_RPATH"` + RemoveCodeSign bool `default:"false" negatable:"" help:"Remove LC_CODE_SIGNATURE"` + RemoveExports bool `default:"false" negatable:"" help:"Clear the export table/trie"` + RemoveSymbolTable bool `default:"true" negatable:"" help:"Remove LC_SYMTAB and LC_DYSYMTAB"` + RemoveOthers bool `default:"false" negatable:"" help:"Remove LC_FUNCTION_STARTS, LC_DATA_IN_CODE, ..."` + RemoveID bool `default:"false" negatable:"" help:"(TODO) Remove LC_ID_DYLIB"` + RemoveInitFunctions bool `default:"false" name:"remove-inits" negatable:"" help:"Clear MOD_INIT_FUNC section"` + RemoveBindSymbols bool `default:"false" name:"remove-imports" negatable:"" help:"Remove all bind symbols information"` + 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"` +} + +type BcellToHeaderArgument struct { + Out string `short:"o" required name:"outfile" help:"Header file name to output to" type:"path"` + Bcell string `short:"b" required help:"Input bcell.dat file" type:"existingfile"` } type Argument struct { @@ -68,5 +88,14 @@ type Argument struct { DisplayBcell DisplayBcellArgument `cmd name:"display-bcell" help:"Display Protobuf content"` Addr2Line Addr2LineArgument `cmd name:"addr2line" help:"Resolve crash address from DWARF file"` Lipo LipoArgument `cmd help:"split Fat binary or join Mach-O binares"` - Pepe PepeArgument `cmd help:"split Fat binary or join Mach-O binares"` + Pepe PepeArgument `cmd help:"custom command"` + BcellToHeader BcellToHeaderArgument `cmd name:"bcell2header" help:"Build C header file from bcell file"` + AddSection struct { + Out string `short:"o" required name:"outfile" help:"Output file after transformation"` + Data string `arg help:"Input data file to fill in this section, or null if not provided" type:"existingfile"` + OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"` + Segment string `required help:"Segment name"` + Section string `required help:"Section name"` + Size int `required help:"Size of segment/section"` + } `cmd:"" default:"false" help:"(TODO) Add a new segment with 1 section, segment starts at the end of last segment"` } diff --git a/macho-go/internal/wrapper/program_context.go b/macho-go/internal/wrapper/program_context.go index 73b32d1..7f3b242 100644 --- a/macho-go/internal/wrapper/program_context.go +++ b/macho-go/internal/wrapper/program_context.go @@ -38,9 +38,12 @@ func (uc *UserConfig) Protomodel() *protomodel.Config { } type ProgramContext struct { - strip_init_pointers bool + remove_inits bool remove_codesign bool remove_imports bool + remove_others bool + remove_exports bool + remove_symbol_table bool dylib_to_add []string rpath_to_add []string @@ -84,24 +87,27 @@ func (pc *ProgramContext) dispatchActions(ofile OFile) { } func (pc *ProgramContext) Process(ofile OFile) { - if pc.remove_imports { - pc.AddAction(NewRemoveImportsAction()) - pc.AddAction(NewWriteFileAction(pc.outfile)) - pc.dispatchActions(ofile) - return - } if pc.remove_codesign { pc.AddAction(NewRemoveCodeSignatureAction()) } - if pc.strip_init_pointers { + if pc.remove_inits { pc.AddAction(NewSaveInitPointerAction()) pc.AddAction(NewRemoveInitPointerAction()) } - ExperimentalFeature("Remove Unnecessary Info", func() { - pc.AddAction(NewRemoveUnnecessaryInfoAction()) - }) - ExperimentalFeature("Remove Classic Symbol", func() { + if pc.remove_imports { + pc.AddAction(NewSaveImportsAction()) + pc.AddAction(NewRemoveImportsAction()) + } + if pc.remove_symbol_table { pc.AddAction(NewRemoveClassicSymbolAction()) + } + if pc.remove_exports { + pc.AddAction(NewRemoveExportsAction()) + } + ExperimentalFeature("Remove Unnecessary Info", func() { + if pc.remove_others { + pc.AddAction(NewRemoveUnnecessaryInfoAction()) + } }) pc.AddAction(NewAddRpathAction(pc.rpath_to_add)) pc.AddAction(NewAddDylibAction(pc.dylib_to_add)) diff --git a/macho-go/pkg/ios/macho/dyld_info.go b/macho-go/pkg/ios/macho/dyld_info.go index bf85924..494f4ba 100644 --- a/macho-go/pkg/ios/macho/dyld_info.go +++ b/macho-go/pkg/ios/macho/dyld_info.go @@ -57,6 +57,10 @@ func (sym *ImportSymbol) Stub() uint64 { return sym.stub } +func (sym *ImportSymbol) Segment() uint32 { + return sym.segment +} + func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol { if mc.dyldinfo == nil { return mc.CollectBindSymbolsModern() diff --git a/macho-go/pkg/ios/macho/edit.go b/macho-go/pkg/ios/macho/edit.go index cc47c73..7e84b6c 100644 --- a/macho-go/pkg/ios/macho/edit.go +++ b/macho-go/pkg/ios/macho/edit.go @@ -5,6 +5,7 @@ import ( "io" "math/rand" "time" + "bytes" log "github.com/sirupsen/logrus" @@ -190,10 +191,6 @@ func (mc *MachoContext) RemoveUnnecessaryInfo() bool { return false } -func (mc *MachoContext) RemoveClassicSymbol() bool { - return false -} - func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) { var offset uint64 payload := lcmd.Serialize(mc) @@ -285,42 +282,15 @@ func (mc *MachoContext) RemoveBindSymbols() { } else { mc.removeBindSymbolsLegacy() } - - calculateHash := func(name string) uint32 { - var h uint32 = 0x811c9dc5 - for _, s := range name { - h ^= uint32(s) - h *= 0x01000193 - } - return h - } + mc.RenameObjcClasslist() // due to some limitations when design this tool // we write the c code to stdout lol - fmt.Println("struct imported_symbol {const char* name; const char* lib; uint32_t hash; int segment_i; uint64_t offset;};") - fmt.Println("const char* lib_to_resolve = \"main\";") - fmt.Println("struct imported_symbol imported_table[] = {") - count := 0 for _, symbol := range mc.CollectBindSymbols() { if symbol.Type() != "lazy" { continue } - count += 1 - dylib_hash := calculateHash(symbol.Dylib()) - seg := mc.segments[symbol.segment] - var offset uint64 - - if symbol.address >= seg.Vmaddr() { - // this is virtual address - offset = symbol.address - seg.Vmaddr() - } else { - // this is file address - offset = symbol.address - seg.Fileoff() - } - - fmt.Printf("{\"%s\", \"%s\", 0x%x, 0x%x, 0x%x},\n", - symbol.Name(), symbol.Dylib(), dylib_hash, symbol.segment, offset); if mc.dyldinfo != nil { // for legacy resolve the opcodes can be rewritten as 0x00 mc.file.WriteAt(make([]byte, 8), int64(symbol.file_address)) @@ -344,8 +314,6 @@ func (mc *MachoContext) RemoveBindSymbols() { mc.file.WriteAt(v, int64(symbol.file_address)) } } - fmt.Println("};") - fmt.Printf("uint32_t nimports = %d;\n", count); } func (mc *MachoContext) removeBindSymbolsModern() { @@ -385,6 +353,45 @@ func (mc *MachoContext) removeBindSymbolsLegacy() { mc.file.WriteAt(make([]byte, size), int64(start)) } +func (mc *MachoContext) RenameObjcClasslist() { + ptr := int64(0) + if mc.Is64bit() { + ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart) + } else { + ptr, _ = mc.file.Seek(int64(Header_size), io.SeekStart) + } + + for _, cmd := range mc.commands { + if cmd.Cmd() != LC_SEGMENT_64 { + ptr += int64(cmd.Cmdsize()) + continue + } + var segment = cmd.(*Segment64) + + if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 { + section_ptr := ptr + 0x40 + 8 + for _, section := range segment.Sections() { + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classlist")) == 0 { + mc.file.WriteAt([]byte("__objc_classbruh"), section_ptr) + } + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 { + mc.file.WriteAt([]byte("__objc_nlclsbruh"), section_ptr) + } + section_ptr += 16 * 2 + 8 * 2 + 4 * 8 + } + } + // if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 { + // section_ptr := ptr + 0x40 + 8 + // for _, section := range segment.Sections() { + // section_ptr += 16 * 2 + 8 * 2 + 4 * 8 + // } + // } + ptr += int64(cmd.Cmdsize()) + } + mc.file.Seek(0, io.SeekStart) +} + + 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 c363e85..8d53c51 100644 --- a/macho-go/pkg/ios/macho/fixups.c +++ b/macho-go/pkg/ios/macho/fixups.c @@ -97,6 +97,7 @@ int ParseFixValue(int format, uint64_t value, int* bind, uint64_t* ret1, uint64_ uint64_t MakeRebaseFixupOpcode(int next, uint64_t target, uint64_t high8) { uint64_t value; struct dyld_chained_ptr_64_rebase* b = (struct dyld_chained_ptr_64_rebase*)&value; + b->bind = 0; b->next = next; b->target = target; b->high8 = high8; diff --git a/macho-go/pkg/ios/macho/symtab.go b/macho-go/pkg/ios/macho/symtab.go index a18c9d8..04e96b0 100644 --- a/macho-go/pkg/ios/macho/symtab.go +++ b/macho-go/pkg/ios/macho/symtab.go @@ -72,7 +72,6 @@ func (mc *MachoContext) CollectSymbols() []*Symbol { var flags uint8 var sect uint8 var desc uint16 - var value32 uint32 var value64 uint64 binary.Read(symtab_buffer, mc.byteorder, &strx) @@ -83,6 +82,7 @@ func (mc *MachoContext) CollectSymbols() []*Symbol { binary.Read(symtab_buffer, mc.byteorder, &value64) } else { // always use value64 + var value32 uint32 binary.Read(symtab_buffer, mc.byteorder, &value32) value64 = uint64(value32) } diff --git a/macho-go/proto/macho_info.proto b/macho-go/proto/macho_info.proto index 45b1f1f..f46e33c 100644 --- a/macho-go/proto/macho_info.proto +++ b/macho-go/proto/macho_info.proto @@ -17,18 +17,18 @@ message MachoInfo { uint64 value = 2; // address of the init function } - // iOS library rewrite these as opcodes and dyld processes - // message LazySymbol { - // string name = 1; - // int32 dylib_ordinal = 2; // could be -1 -2 - // uint32 segment = 3; - // uint32 segment_offset = 4; - // } + // right now we waste memory to store name/hash for all symbols + // should consider compress them, dyld stores the index in list of LC_DYLIB + message BindSymbol { + string name = 1; + string libname = 2; + uint32 libhash = 3; + uint32 segment = 4; // segment index + uint64 offset = 5; // offset in segment + } PointerSize pointer_size = 1; uint64 image_base = 2; repeated InitPointer init_pointers = 3; - // saved or read from header -> dyld_info -> lazyoff - // uint64 lazy_symbol_address = 4; - // repeated LazySymbol lazy_symbols = 5; + repeated BindSymbol symbols = 4; } diff --git a/research/custom_loader/build.sh b/research/custom_loader/build.sh index dd4018d..d06069c 100755 --- a/research/custom_loader/build.sh +++ b/research/custom_loader/build.sh @@ -61,11 +61,11 @@ 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 remove-imports $OUT/a -o $OUT/a > $OUT/b.h +../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table $OUT/a > $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 -out/a +out/a-fixed else