35 Commits

Author SHA1 Message Date
07f361d8ac add comment on libintl 2024-01-04 06:41:05 +07:00
263596b1a1 clean code and add comment 2024-01-04 06:34:07 +07:00
7a6a41b4d8 First big update b.cc (gnu coreutils) 2024-01-03 22:12:10 +07:00
0a070941b1 keep symbol table 2024-01-03 22:09:08 +07:00
4dea12dd9e save import libintl.8.dylib 2024-01-03 22:08:57 +07:00
011abfd8db Update shellcode 2024-01-03 22:08:29 +07:00
67157c91ef update: build.sh 2023-12-14 10:44:40 +07:00
26d002cdb1 Add: rpath resolve 2023-12-14 10:38:44 +07:00
c805fc56b3 fix: check cputype 2023-12-14 10:37:56 +07:00
1b3eb467a7 fix x86_64 shellcode 2023-11-08 22:36:28 +07:00
54f61f36ab Add x86_84 shellcode 2023-11-08 22:26:55 +07:00
f88861a87e format code 2023-07-12 13:37:54 +07:00
4016abf40d clean code 2023-07-12 13:34:30 +07:00
4ee62a2d93 add selfbind functionality 2023-07-12 13:34:02 +07:00
6815ea6556 add keep imports action 2023-07-11 10:06:59 +07:00
557eed0254 small changes to remove imports action 2023-07-11 10:05:58 +07:00
eccd0bf845 optimize shellcode and recover main address at runtime 2023-07-10 14:15:05 +07:00
ed2f09348e compress the extracted information 2023-07-10 14:14:03 +07:00
2eede8f9b2 format go code 2023-06-26 15:33:37 +07:00
b8d8343835 update test program for custom loader 2023-06-26 15:33:30 +07:00
e15d1e8d6f run initializers in the correct order
- Objective-C load methods must be called first
- Constructors are called after
- All constructors arguments are passed correctly
2023-06-26 15:33:24 +07:00
a2f9ca82e7 update shellcode
- shellcode correctly passes arguments to main
- shellcode deals with __bss section in __DATA
- remove hardcoded values
2023-06-26 15:33:15 +07:00
693c2b6c95 update build script for custom_loader 2023-06-26 15:33:07 +07:00
7eb43a35fb add full rebuild for Objective-C binaries 2023-06-26 15:32:54 +07:00
f5144fec4f add modifications for ObjC binaries 2023-06-26 15:31:54 +07:00
ebd52d9acb add docs/ 2023-06-15 10:48:07 +07:00
3aaa85520e add fix for objc binaries
TODO: Fix call to +load() for non-lazy class
2023-06-15 10:46:10 +07:00
ed793b1df6 add more utilities to custom_loader lib 2023-06-15 10:45:01 +07:00
9f54720e7b don't remap region to READONLY
TODO: Should remap to its original state before fix to READ|WRITE
2023-06-15 10:43:35 +07:00
fdccdca8a0 add objc4 symtab contents for reference 2023-06-15 10:42:19 +07:00
e2c75bf718 rework ios-wrapper cli parsing 2023-06-15 10:41:18 +07:00
a257286d2e add src link to objc dyld 2023-06-15 10:40:45 +07:00
91e5b1f6b3 fix parsing fixups chains
address was not incrementing correctly leads to wrong offset of symbol
2023-06-07 15:56:36 +07:00
887c53ed44 add test for objc 2023-06-07 10:49:59 +07:00
88bb0aa09d fix fixups chain rewrite stops at first entry 2023-06-07 10:49:05 +07:00
27 changed files with 3723 additions and 466 deletions

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "apple/dyld"]
path = apple/dyld
url = git@github.com:apple-oss-distributions/dyld.git
[submodule "apple/objc4"]
path = apple/objc4
url = git@github.com:apple-oss-distributions/objc4.git

1
apple/dyld Submodule

Submodule apple/dyld added at c8a445f88f

1
apple/objc4 Submodule

Submodule apple/objc4 added at 689525d556

29
docs/links.md Normal file
View File

@ -0,0 +1,29 @@
## Direct References in Apple
[dyld_info](https://github.com/apple-oss-distributions/dyld/blob/main/other-tools/dyld_info.cpp)
[macho layout](https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOLayout.cpp#L714)
[mod init func](https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f9fc1713db34674e79b00e30723e79d/dyld/Loader.cpp#L1879)
[dlsym](https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f9fc1713db34674e79b00e30723e79d/common/MachOLoaded.cpp#L263)
[dyld_all_image_infos](https://opensource.apple.com/source/dyld/dyld-195.6/include/mach-o/dyld_images.h.auto.html)
[dyld env vars](https://stackoverflow.com/questions/51504439/what-environment-variables-control-dyld)
## Blogposts
[Basic Mach-O (Old)](https://www.m4b.io/reverse/engineering/mach/binaries/2015/03/29/mach-binaries.html)
[Basic Mach-O memory loader](https://blog.xpnsec.com/building-a-mach-o-memory-loader-part-1/)
[My Mach-O posts](https://blog.efiens.com/post/luibo/osx/)
## Tools
[llios](https://github.com/qyang-nj/llios)
[fishhook](https://github.com/facebook/fishhook/blob/main/fishhook.c)
[blacktop/go-macho](https://github.com/blacktop/go-macho)

View File

@ -5,10 +5,11 @@ go 1.17
require ( require (
github.com/alecthomas/kong v0.2.16 github.com/alecthomas/kong v0.2.16
github.com/sirupsen/logrus v1.8.0 github.com/sirupsen/logrus v1.8.0
google.golang.org/protobuf v1.26.0 google.golang.org/protobuf v1.31.0
) )
require ( require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/magefile/mage v1.10.0 // indirect github.com/magefile/mage v1.10.0 // indirect
github.com/pkg/errors v0.8.1 // indirect github.com/pkg/errors v0.8.1 // indirect
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect

View File

@ -3,6 +3,8 @@ github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QL
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
@ -22,3 +24,5 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

View File

@ -7,7 +7,7 @@ import (
type removeClassicSymbol struct{} type removeClassicSymbol struct{}
func (action *removeClassicSymbol) withMacho(mf *MachoFile) error { func (action *removeClassicSymbol) withMacho(mf *MachoFile) error {
mf.Context().RemoveClassicSymbol() mf.Context().RemoveSymbolTable()
return nil return nil
} }

View File

@ -0,0 +1,20 @@
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{}
}

View File

@ -8,8 +8,6 @@ type removeImports struct{}
func (action *removeImports) withMacho(mf *MachoFile) error { func (action *removeImports) withMacho(mf *MachoFile) error {
mf.Context().RemoveBindSymbols() mf.Context().RemoveBindSymbols()
mf.Context().RemoveSymbolTable()
mf.Context().RemoveExportTrie()
return nil return nil
} }
@ -20,4 +18,3 @@ func (action *removeImports) withFat(ff *FatFile) error {
func NewRemoveImportsAction() *removeImports { func NewRemoveImportsAction() *removeImports {
return &removeImports{} return &removeImports{}
} }

View File

@ -0,0 +1,25 @@
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,
}
}

View File

@ -0,0 +1,152 @@
package action
import (
"sort"
"strings"
// log "github.com/sirupsen/logrus"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/protomodel"
)
type saveImports struct {
keepSymbols []string
}
func (action *saveImports) withMacho(mf *MachoFile) error {
action.saveToInfo(mf)
mc := mf.Context()
if mc.Header().IsDylib() {
mc.WriteInfoToData(mf.Info())
}
return nil
}
func (action *saveImports) saveToInfo(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_storage := []*protomodel.MachoInfo_AllImportedSymbols{}
symbols_raw := mc.CollectBindSymbols()
sort.Slice(symbols_raw, func(i, j int) bool {
orderedByLibrary := symbols_raw[i].Dylib() < symbols_raw[j].Dylib()
if symbols_raw[i].Dylib() == symbols_raw[j].Dylib() {
orderedBySymbol := symbols_raw[i].Name() < symbols_raw[j].Name()
return orderedBySymbol
}
return orderedByLibrary
})
libs := []string{}
symbols := []string{}
tables := []*protomodel.MachoInfo_LibraryImportedSymbols{}
var current_table *protomodel.MachoInfo_LibraryImportedSymbols
current_lib := ""
current_symbol := ""
current_lib_idx := -1
current_symbol_idx := -1
intlSymbols := []string{}
for _, symbol := range symbols_raw {
if symbol.Dylib() == "/usr/local/opt/gettext/lib/libintl.8.dylib" {
intlSymbols = append(intlSymbols, symbol.Name())
}
}
action.keepSymbols = append(action.keepSymbols, intlSymbols...)
// now we expect everything is sorted and easier to build strings tables
// this is not fully optimized, there can be repeated symbol name in different libraries
for _, symbol := range symbols_raw {
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()]
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()
}
if current_lib != symbol.Dylib() {
current_lib_idx += len(current_lib) + 1
current_lib = symbol.Dylib()
libs = append(libs, symbol.Dylib())
tables = append(tables, &protomodel.MachoInfo_LibraryImportedSymbols{
LibIndex: uint32(current_lib_idx),
Nsymbols: 0,
Symbols: []*protomodel.MachoInfo_SymbolTable{},
})
current_table = tables[len(tables)-1]
}
if current_symbol != symbol.Name() {
current_symbol_idx += len(current_symbol) + 1
current_symbol = symbol.Name()
symbols = append(symbols, symbol.Name())
}
current_table.Nsymbols += 1
current_table.Symbols = append(current_table.Symbols, &protomodel.MachoInfo_SymbolTable{
SymbolIndex: uint32(current_symbol_idx),
SegmentIndex: symbol.Segment(),
Offset: uint32(offset),
})
}
mf.Info().Symbols = &protomodel.MachoInfo_AllImportedSymbols{
Libs: libs,
Symbols: symbols,
Tables: tables,
}
mf.Info().Main = mc.Main()
return nil
}
func (action *saveImports) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewSaveImportsAction(keepSymbols []string) *saveImports {
return &saveImports{
keepSymbols,
}
}

View File

@ -1,9 +1,11 @@
package wrapper package wrapper
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
// "strings"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -63,6 +65,10 @@ func Cli() {
arg := cli.Lipo.Join arg := cli.Lipo.Join
fat.FatJoin(arg.Macho, arg.Out) fat.FatJoin(arg.Macho, arg.Out)
return return
} else if command == "bcell2header" {
arg := cli.BcellToHeader
bcell2header(arg.Bcell, arg.Out)
return
} }
var pc ProgramContext var pc ProgramContext
@ -73,7 +79,7 @@ func Cli() {
arg := cli.Wrap arg := cli.Wrap
ofile = NewOFile(arg.OFile) ofile = NewOFile(arg.OFile)
pc.remove_codesign = true pc.remove_codesign = true
pc.strip_init_pointers = true pc.remove_inits = true
pc.dylib_to_add = []string{"@rpath/bcell.framework/bcell"} pc.dylib_to_add = []string{"@rpath/bcell.framework/bcell"}
pc.rpath_to_add = []string{"@executable_path/Frameworks"} pc.rpath_to_add = []string{"@executable_path/Frameworks"}
pc.outfile = arg.Out pc.outfile = arg.Out
@ -99,6 +105,30 @@ func Cli() {
ofile = NewOFile(arg.OFile) ofile = NewOFile(arg.OFile)
pc.remove_imports = true pc.remove_imports = true
pc.outfile = arg.Out 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
pc.symbols_keep = arg.KeepImports
default: default:
return return
@ -165,6 +195,12 @@ func displayBcell(bfile string) {
init_ptr.Value, 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 +230,76 @@ func resolveAddresses(dwarf string, load string, addresses []string) {
} }
} }
} }
func bcell2header(bfile string, header string) {
raw_data, err := ioutil.ReadFile(bfile)
data := &protomodel.BcellFile{}
err = proto.Unmarshal(raw_data, data)
if err != nil {
log.Panic("Invalid Protobuf<BcellFile> bcell.dat")
}
f, err := os.Create(header)
if err != nil {
log.Panic("Cannot open header file for writing")
}
defer f.Close()
w := bufio.NewWriter(f)
// fmt.Printf("[+] User Config: %+v\n", data.BcellConfig)
fmt.Fprintf(w, "#include<stdint.h>\n")
fmt.Fprintf(w, "namespace bshield_data{\n")
for arch, info := range data.MachoInfos {
fmt.Fprintf(w, "const char* arch = \"%s\";\n", arch)
fmt.Fprintf(w, "unsigned int pointer_size = %d;\n", info.PointerSize)
fmt.Fprintf(w, "uint64_t image_base = 0x%x;\n", info.ImageBase)
fmt.Fprintf(w, "uint64_t main = 0x%x;\n", info.Main)
fmt.Fprintf(w, "struct init_pointer {uint64_t offset; uint64_t value;};\n")
fmt.Fprintf(w, "int num_init_pointers = %d;\n", len(info.InitPointers))
fmt.Fprintf(w, "struct init_pointer init_pointers_offsets[] = {\n")
for _, init_ptr := range info.InitPointers {
fmt.Fprintf(w, " {0x%x, 0x%x},\n", init_ptr.Offset, init_ptr.Value)
}
fmt.Fprintf(w, "};\n")
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
fmt.Fprintf(w, "char libs[] =\n")
for _, lib := range info.Symbols.Libs {
fmt.Fprintf(w, " \"%s\\0\"\n", lib)
}
fmt.Fprintf(w, ";\n")
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
fmt.Fprintf(w, "char symbols[] =\n")
for _, symbol := range info.Symbols.Symbols {
fmt.Fprintf(w, " \"%s\\0\"\n", symbol)
}
fmt.Fprintf(w, ";\n")
fmt.Fprintf(w, "// very compact symbol table,\n")
fmt.Fprintf(w, "// [lib idx/*4 bytes*/, nsymbol/*4 byte*/]\n")
fmt.Fprintf(w, "// repeat nsymbol times [name offset/*3 bytes*/, segment idx/**/, offset /*4 btyes*/]\n")
fmt.Fprintf(w, "// name offset is 3 bytes because we don't think we should have a table size > 2^(3 * 8)\n")
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
fmt.Fprintf(w, "uint32_t encoded_table[] = {\n")
n_instructions := 0
for i, table := range info.Symbols.Tables {
fmt.Fprintf(w, " // %s\n", info.Symbols.Libs[i])
fmt.Fprintf(w, " %d/*lib offset*/,\n", table.LibIndex)
fmt.Fprintf(w, " %d/*nsymbols*/,\n", table.Nsymbols)
n_instructions += 2
for _, symbol := range table.Symbols {
fmt.Fprintf(w, " %d, 0x%x,\n", (symbol.SymbolIndex<<8)|symbol.SegmentIndex, symbol.Offset)
n_instructions += 2
}
fmt.Fprintf(w, "\n")
}
fmt.Fprintf(w, "};\n")
fmt.Fprintf(w, "uint32_t n_instructions = %d;\n", n_instructions)
}
fmt.Fprintf(w, "}// namespace bshield_data\n")
w.Flush()
}

View File

@ -26,6 +26,7 @@ type RemoveCodesignArgument struct {
type RemoveImportsArgument struct { type RemoveImportsArgument struct {
Out string `short:"o" required name:"outfile" help:"Modified Mach-O/Fat binary output file" type:"path"` 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"` OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
} }
@ -55,7 +56,28 @@ type LipoArgument struct {
} }
type PepeArgument struct { type PepeArgument struct {
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"` 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:"false" 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"`
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 {
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 { type Argument struct {
@ -68,5 +90,14 @@ type Argument struct {
DisplayBcell DisplayBcellArgument `cmd name:"display-bcell" help:"Display Protobuf<BcellFile> content"` DisplayBcell DisplayBcellArgument `cmd name:"display-bcell" help:"Display Protobuf<BcellFile> content"`
Addr2Line Addr2LineArgument `cmd name:"addr2line" help:"Resolve crash address from DWARF file"` Addr2Line Addr2LineArgument `cmd name:"addr2line" help:"Resolve crash address from DWARF file"`
Lipo LipoArgument `cmd help:"split Fat binary or join Mach-O binares"` 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"`
} }

View File

@ -35,16 +35,16 @@ func (printer *InfoPrinter) Print() {
} }
symbols := mc.CollectBindSymbols() symbols := mc.CollectBindSymbols()
for _, sym := range symbols { for _, sym := range symbols {
fmt.Printf( fmt.Printf(
"%s (%s)\n\tStub=0x%x Address=0x%x\n\tDylib=%s\n", "%s (%s)\n\tStub=0x%x Address=0x%x\n\tDylib=%s\n",
sym.Name(), sym.Name(),
sym.Type(), sym.Type(),
sym.Stub(), sym.Stub(),
sym.Address(), sym.Address(),
sym.Dylib(), sym.Dylib(),
) )
} }
fmt.Println("======") fmt.Println("======")
} }

View File

@ -38,11 +38,15 @@ func (uc *UserConfig) Protomodel() *protomodel.Config {
} }
type ProgramContext struct { type ProgramContext struct {
strip_init_pointers bool remove_inits bool
remove_codesign bool remove_codesign bool
remove_imports bool remove_imports bool
remove_others bool
remove_exports bool
remove_symbol_table bool
dylib_to_add []string dylib_to_add []string
rpath_to_add []string rpath_to_add []string
symbols_keep []string
outfile string outfile string
@ -84,24 +88,28 @@ func (pc *ProgramContext) dispatchActions(ofile OFile) {
} }
func (pc *ProgramContext) Process(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 { if pc.remove_codesign {
pc.AddAction(NewRemoveCodeSignatureAction()) pc.AddAction(NewRemoveCodeSignatureAction())
} }
if pc.strip_init_pointers { if pc.remove_inits {
pc.AddAction(NewSaveInitPointerAction()) pc.AddAction(NewSaveInitPointerAction())
pc.AddAction(NewRemoveInitPointerAction()) pc.AddAction(NewRemoveInitPointerAction())
} }
ExperimentalFeature("Remove Unnecessary Info", func() { if pc.remove_imports {
pc.AddAction(NewRemoveUnnecessaryInfoAction()) pc.AddAction(NewSaveImportsAction(pc.symbols_keep))
}) pc.AddAction(NewRemoveImportsAction())
ExperimentalFeature("Remove Classic Symbol", func() { pc.AddAction(NewRewriteImportsWithKeepSymbolsAction(pc.symbols_keep))
}
if pc.remove_symbol_table {
pc.AddAction(NewRemoveClassicSymbolAction()) 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(NewAddRpathAction(pc.rpath_to_add))
pc.AddAction(NewAddDylibAction(pc.dylib_to_add)) pc.AddAction(NewAddDylibAction(pc.dylib_to_add))

View File

@ -4,9 +4,9 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"unsafe" "unsafe"
// "bufio" // "bufio"
"io" "io"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -19,16 +19,19 @@ import "C"
type ImportSymbol struct { type ImportSymbol struct {
name string name string
typ string typ string
dylib string dylib string
segment uint32 segment uint32
segment_offset uint32 segment_offset uint32
address uint64 address uint64
file_address uint64 file_address uint64
lib_ordinal uint32
// push number // push number
pnum uint32 pnum uint32
stub uint64 stub uint64
next int // only for LC_DYLD_CHAINED_FIXUPS
} }
func (sym *ImportSymbol) Name() string { func (sym *ImportSymbol) Name() string {
@ -43,6 +46,10 @@ func (sym *ImportSymbol) Dylib() string {
return sym.dylib return sym.dylib
} }
func (sym *ImportSymbol) LibOrdinal() uint32 {
return sym.lib_ordinal
}
func (sym *ImportSymbol) Address() uint64 { func (sym *ImportSymbol) Address() uint64 {
return sym.address return sym.address
} }
@ -55,6 +62,10 @@ func (sym *ImportSymbol) Stub() uint64 {
return sym.stub return sym.stub
} }
func (sym *ImportSymbol) Segment() uint32 {
return sym.segment
}
func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol { func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol {
if mc.dyldinfo == nil { if mc.dyldinfo == nil {
return mc.CollectBindSymbolsModern() return mc.CollectBindSymbolsModern()
@ -64,135 +75,142 @@ func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol {
} }
func (mc *MachoContext) findSegmentIndexAt(address uint64) int { func (mc *MachoContext) findSegmentIndexAt(address uint64) int {
for i, segment := range mc.Segments() { for i, segment := range mc.Segments() {
if segment.Fileoff() <= address && segment.Fileoff() + segment.Filesize() > address { if segment.Fileoff() <= address && segment.Fileoff()+segment.Filesize() > address {
return i return i
} }
} }
return -1 return -1
} }
// New convention using LC_DYLD_CHAINED_FIXUPS // New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol { func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
start := mc.fixups.dataoff start := mc.fixups.dataoff
size := mc.fixups.datasize size := mc.fixups.datasize
buf := mc.buf[start:start+size] buf := mc.buf[start : start+size]
// all pointers used are based from this **buf** // all pointers used are based from this **buf**
// until buf is freed, all pointers are valid // until buf is freed, all pointers are valid
// remember to copy before moving out // remember to copy before moving out
header := (*C.uchar)(unsafe.Pointer(&buf[0])) header := (*C.uchar)(unsafe.Pointer(&buf[0]))
imports_table := C.GetImportsTable(header); imports_table := C.GetImportsTable(header)
// for i := 0; i < int(imports_table.size); i++ { // for i := 0; i < int(imports_table.size); i++ {
// s := C.GetImportsAt(&imports_table, C.int(i)) // s := C.GetImportsAt(&imports_table, C.int(i))
// name := C.GoString(s.name) // name := C.GoString(s.name)
// symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:]) // symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// symbol_table = append(symbol_table, symbol) // symbol_table = append(symbol_table, symbol)
// fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name) // fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name)
// } // }
var syms []*ImportSymbol var syms []*ImportSymbol
var sym ImportSymbol var sym ImportSymbol
segment_i := 0 segment_i := 0
for { for {
var fix C.struct_SegmentFix var fix C.struct_SegmentFix
fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix)) fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix))
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr)) status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1 segment_i += 1
if status == 2 { if status == 2 {
break; break
} }
if status == 3 { if status == 3 {
continue continue
} }
// fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count) // fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
// fmt.Printf("pages=%x\n", fix.pages) // fmt.Printf("pages=%x\n", fix.pages)
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count)) pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
for page_i := 0; page_i < int(fix.page_count); page_i++ { reader := bytes.NewReader(mc.buf)
// fmt.Printf(" page offset=%x\n", pages[page_i]) for page_i := 0; page_i < int(fix.page_count); page_i++ {
// fmt.Printf(" page offset=%x\n", pages[page_i])
address := int64(fix.segment) + int64(pages[page_i]) address := int64(fix.segment) + int64(pages[page_i])
mc.file.Seek(address, io.SeekStart) reader.Seek(address, io.SeekStart)
code := make([]byte, 8) code := make([]byte, 8)
for { for {
mc.file.Read(code) reader.Read(code)
v := binary.LittleEndian.Uint64(code) v := mc.byteorder.Uint64(code)
var bind C.int var bind C.int
var ret1 C.ulonglong var ret1 C.ulonglong
var ret2 C.ulonglong var ret2 C.ulonglong
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v), next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
&bind, &ret1, &ret2) &bind, &ret1, &ret2)
if bind == 1 { if bind == 1 {
s := C.GetImportsAt(&imports_table, C.int(ret1)) s := C.GetImportsAt(&imports_table, C.int(ret1))
name := C.GoString(s.name) name := C.GoString(s.name)
dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:]) dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// fmt.Printf("%x bind=%d (%s)%s\n", address, bind, dylib, name) fmt.Printf("// 0x%x bind=%d (%s)%s\n", address, bind, dylib, name)
sym.address = uint64(address) sym.address = uint64(address)
sym.name = name sym.name = name
sym.dylib = dylib sym.dylib = dylib
sym.typ = "lazy" sym.typ = "lazy"
sym.lib_ordinal = uint32(s.lib_ordinal)
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address))) sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
sym.file_address = uint64(address) sym.file_address = uint64(address)
new_sym := sym sym.next = int(next)
syms = append(syms, &new_sym) new_sym := sym
} syms = append(syms, &new_sym)
} else {
fmt.Printf("// 0x%x rebase=%d target=0x%x high8=0x%x\n", address, bind, ret1, ret2)
}
address += 8 if int(next) == 0 {
if int(next) == 0 { break
break }
} // because the pointer move up 8 bytes already so we minus 8
} address += int64(next * 4)
mc.file.Seek(0, io.SeekStart) reader.Seek(int64(next*4)-8, io.SeekCurrent)
} }
} reader.Seek(0, io.SeekStart)
}
}
return syms return syms
} }
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner // Old convention using LC_DYLD_INFO_ONLY section and bytecode runner
func (mc *MachoContext) CollectBindSymbolsLegacy() []*ImportSymbol { func (mc *MachoContext) CollectBindSymbolsLegacy() []*ImportSymbol {
noLazy := (func() []*ImportSymbol { noLazy := (func() []*ImportSymbol {
start := mc.dyldinfo.bind_off start := mc.dyldinfo.bind_off
size := mc.dyldinfo.bind_size size := mc.dyldinfo.bind_size
end := start + size end := start + size
buf := bytes.NewBuffer(mc.buf[start:end]) buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "no lazy") return mc.readBindStream(buf, "no lazy")
})() })()
lazy := (func() []*ImportSymbol { lazy := (func() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size size := mc.dyldinfo.lazy_bind_size
end := start + size end := start + size
buf := bytes.NewBuffer(mc.buf[start:end]) buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "lazy") return mc.readBindStream(buf, "lazy")
})() })()
weak := (func() []*ImportSymbol { weak := (func() []*ImportSymbol {
start := mc.dyldinfo.weak_bind_off start := mc.dyldinfo.weak_bind_off
size := mc.dyldinfo.weak_bind_size size := mc.dyldinfo.weak_bind_size
end := start + size end := start + size
buf := bytes.NewBuffer(mc.buf[start:end]) buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "weak") return mc.readBindStream(buf, "weak")
})() })()
var symbols []*ImportSymbol var symbols []*ImportSymbol
symbols = append(symbols, noLazy...) symbols = append(symbols, noLazy...)
symbols = append(symbols, lazy...) symbols = append(symbols, lazy...)
symbols = append(symbols, weak...) symbols = append(symbols, weak...)
return symbols return symbols
} }
func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportSymbol { func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportSymbol {
size := buf.Len() size := buf.Len()
if size == 0 { if size == 0 {
return []*ImportSymbol{} return []*ImportSymbol{}
} }
@ -226,7 +244,7 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
case BIND_OPCODE_DO_BIND: case BIND_OPCODE_DO_BIND:
if sym.name != "" { if sym.name != "" {
new_sym := sym new_sym := sym
new_sym.typ = typ new_sym.typ = typ
syms = append(syms, &new_sym) syms = append(syms, &new_sym)
// fmt.Printf("Offset 0x%x: Symbol %+v\n", symoffset, sym) // fmt.Printf("Offset 0x%x: Symbol %+v\n", symoffset, sym)
@ -235,7 +253,7 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
"symbol": sym.name, "symbol": sym.name,
}).Trace("Bind") }).Trace("Bind")
sym.name = "" sym.name = ""
sym.address += 8 sym.address += 8
} }
offset += 1 offset += 1
break break
@ -264,9 +282,9 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
sym.name, _ = buf.ReadString(0) sym.name, _ = buf.ReadString(0)
// ReadString input the 0x00 byte to buffer // ReadString input the 0x00 byte to buffer
// while we are string so we can remove that // while we are string so we can remove that
sym.name = sym.name[:len(sym.name)-1] sym.name = sym.name[:len(sym.name)-1]
offset += uint(len(sym.name)) offset += uint(len(sym.name))
break break
@ -290,12 +308,12 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
offset += br offset += br
break break
case BIND_OPCODE_SET_TYPE_IMM: case BIND_OPCODE_SET_TYPE_IMM:
fmt.Println("// symbol type", imm) fmt.Println("// symbol type", imm)
break break
default: default:
fmt.Println("BIND OPCODE NOT SUPPORTED", op, imm) fmt.Println("BIND OPCODE NOT SUPPORTED", op, imm)
break break
} }
} }

View File

@ -1,14 +1,23 @@
package macho package macho
import ( import (
"bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"math/rand"
"strings"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios" . "ios-wrapper/pkg/ios"
"ios-wrapper/pkg/protomodel"
) )
// #include "fixups.h"
import "C"
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) { func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
if mc.Is64bit() { if mc.Is64bit() {
mc.file.Seek(int64(Header_size_64), io.SeekStart) mc.file.Seek(int64(Header_size_64), io.SeekStart)
@ -185,10 +194,6 @@ func (mc *MachoContext) RemoveUnnecessaryInfo() bool {
return false return false
} }
func (mc *MachoContext) RemoveClassicSymbol() bool {
return false
}
func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) { func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) {
var offset uint64 var offset uint64
payload := lcmd.Serialize(mc) payload := lcmd.Serialize(mc)
@ -270,140 +275,474 @@ func (mc *MachoContext) UpdateHeaderRemoveLcmd(size uint32) {
func (mc *MachoContext) RemoveBindSymbols() { func (mc *MachoContext) RemoveBindSymbols() {
if !mc.WriteEnabled() { if !mc.WriteEnabled() {
return return
} }
rand.Seed(time.Now().UnixNano())
if mc.dyldinfo == nil { if mc.dyldinfo == nil {
mc.removeBindSymbolsModern() mc.removeBindSymbolsModern()
} else { } else {
mc.removeBindSymbolsLegacy() mc.removeBindSymbolsLegacy()
} }
// Objective-C stub replaces main which can only appears in executable
if mc.Header().IsExecutable() {
mc.ReworkForObjc()
}
calculateHash := func(name string) uint32 { // due to some limitations when design this tool
var h uint32 = 0x811c9dc5 // we write the c code to stdout lol
for _, s := range name { for _, symbol := range mc.CollectBindSymbols() {
h ^= uint32(s) if symbol.Type() != "lazy" {
h *= 0x01000193 continue
} }
return h
}
// due to some limitations when design this tool if mc.dyldinfo != nil {
// we write the c code to stdout lol // for legacy resolve the opcodes can be rewritten as 0x00
fmt.Println("struct imported_symbol {const char* name; const char* lib; uint32_t hash; int segment_i; uint64_t offset;};") mc.file.WriteAt(make([]byte, 8), int64(symbol.file_address))
fmt.Println("struct imported_symbol imported_table[] = {") } else {
count := 0 // for modern resolve the opcodes must not be rewritten as 0x00
for _, symbol := range mc.CollectBindSymbols() { // because it contains 2 types of opcodes, BIND and REBASE
if symbol.Type() != "lazy" { // we only fix BIND and leave REBASE intact
continue // However, each opcodes has a *next* field to the next opcode
} // So if we want to leave the header intact (contains pointers, size)
count += 1 // We should rewrite this as REBASE opcode (so no symbol lookup happens)
dylib_hash := calculateHash(symbol.Dylib()) // and it can continue
seg := mc.segments[symbol.segment]
var offset uint64 // we can write random values, because the loader just do
// (high8 << 56 | target) - mach_header
if symbol.address >= seg.Vmaddr() { // or something, so no symbol lookup and no error at runtime
// this is virtual address target := rand.Int()
offset = symbol.address - seg.Vmaddr() high8 := rand.Int()
} else { value := C.MakeRebaseFixupOpcode(C.int(symbol.next), C.ulonglong(target), C.ulonglong(high8))
// this is file address v := make([]byte, 8)
offset = symbol.address - seg.Fileoff() mc.byteorder.PutUint64(v, uint64(value))
} mc.file.WriteAt(v, int64(symbol.file_address))
}
fmt.Printf("{\"%s\", \"%s\", 0x%x, 0x%x, 0x%x},\n", }
symbol.Name(), symbol.Dylib(), dylib_hash, symbol.segment, offset);
mc.file.WriteAt(make([]byte, 8), int64(symbol.file_address))
}
fmt.Println("};")
fmt.Printf("uint32_t nimports = %d;\n", count);
} }
func (mc *MachoContext) removeBindSymbolsModern() { func (mc *MachoContext) removeBindSymbolsModern() {
// we don't mess up the chain // we don't mess up the chain
// we clear the imports table, and the raw opcodes // we clear the imports table, and the raw opcodes
// clearing imports table disables static analysis // clearing imports table disables static analysis
// clearing opcodes forces runtime manual mapping // clearing opcodes forces runtime manual mapping
// imports item are defined by mc.fixups.imports_format // imports item are defined by mc.fixups.imports_format
// basic case is dyld_chained_import, 4 bytes // basic case is dyld_chained_import, 4 bytes
start := mc.fixups.dataoff start := mc.fixups.dataoff
size := mc.fixups.datasize size := mc.fixups.datasize
fixups := new(Fixups) fixups := new(Fixups)
fixups.Deserialize(mc, mc.buf[start:start+size]) fixups.Deserialize(mc, mc.buf[start:start+size])
start = mc.fixups.dataoff + fixups.imports_offset start = mc.fixups.dataoff + fixups.imports_offset
size = fixups.imports_count * 4 size = fixups.imports_count * 4
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size) fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start)) mc.file.WriteAt(make([]byte, size), int64(start))
// string reference are at the end of this section // string reference are at the end of this section
start = mc.fixups.dataoff + fixups.symbols_offset start = mc.fixups.dataoff + fixups.symbols_offset
size = mc.fixups.Datasize() - fixups.symbols_offset size = mc.fixups.Datasize() - fixups.symbols_offset
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size) fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start)) mc.file.WriteAt(make([]byte, size), int64(start))
fixups.imports_count = 0 fixups.imports_count = 0
mc.file.WriteAt(fixups.Serialize(mc), int64(mc.fixups.dataoff)) mc.file.WriteAt(fixups.Serialize(mc), int64(mc.fixups.dataoff))
} }
func (mc *MachoContext) removeBindSymbolsLegacy() { func (mc *MachoContext) removeBindSymbolsLegacy() {
start := mc.dyldinfo.lazy_bind_off start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size size := mc.dyldinfo.lazy_bind_size
// set lazy opcodes to 0x00 == DO_BIND // set lazy opcodes to 0x00 == DO_BIND
// but no symbol state to bind // but no symbol state to bind
mc.file.WriteAt(make([]byte, size), int64(start)) mc.file.WriteAt(make([]byte, size), int64(start))
} }
func (mc *MachoContext) RemoveSymbolTable() { func (mc *MachoContext) ReworkForObjc() {
// try to remove symtab and dysymtab text_start := 0
mc.removeSymtabCommand() data_end := 0
mc.removeDySymtabCommand() lc_main_offset := int64(0)
}
func (mc *MachoContext) removeSymtabCommand() { ptr := int64(0)
ptr := int64(0)
if mc.Is64bit() { if mc.Is64bit() {
ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart) ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart)
} else { } else {
ptr, _ = mc.file.Seek(int64(Header_size), io.SeekStart) ptr, _ = mc.file.Seek(int64(Header_size), io.SeekStart)
} }
var symtab_fix *Symtab
for _, cmd := range mc.commands { for _, cmd := range mc.commands {
if cmd.Cmd() != LC_SYMTAB { if cmd.Cmd() == LC_MAIN {
ptr += int64(cmd.Cmdsize()) lc_main_offset = ptr + 8
ptr += int64(cmd.Cmdsize())
continue continue
} }
var symtab = cmd.(*Symtab) if cmd.Cmd() != LC_SEGMENT_64 {
symtab_fix = symtab ptr += int64(cmd.Cmdsize())
continue
}
var segment = cmd.(*Segment64)
// erase strings referenced if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
start := int64(symtab_fix.stroff) section_ptr := ptr + 0x40 + 8
size := symtab_fix.strsize for _, section := range segment.Sections() {
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size) if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__text")) == 0 {
mc.file.WriteAt(make([]byte, size), start) text_start = int(section.Offset())
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__init_offsets")) == 0 {
// mc.file.WriteAt([]byte("__init_offsetx"), section_ptr)
// edit flags to not S_MOD_INIT_FUNC
mc.file.WriteAt([]byte{0, 0, 0, 0}, section_ptr+0x40)
}
section_ptr += 16*2 + 8*2 + 4*8
}
}
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 {
// end of __DATA segment, should have enough space for a pointer
// erase nlist64 symbol items // __bss section is dynamically allocated at the end to or something, hmmge
start = int64(symtab_fix.symoff) // assume that it order correctly, which it should if compiled and not modified
size = symtab_fix.nsyms * 16 // each section has their addr field which we can use that with segment virtual address
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size) // to calculate the offset of the last section from segment starts
mc.file.WriteAt(make([]byte, size), start) // then use the size of section to calculate the end of segment in file
sections := segment.Sections()
last := sections[len(sections)-1]
data_end = int(last.Addr() - segment.Vmaddr() + segment.Fileoff() + last.Size())
}
ptr += int64(cmd.Cmdsize())
}
mc.file.Seek(0, io.SeekStart)
symtab_fix.symoff = 0 // dummy value past the end of __DATA segment (logical size),
symtab_fix.nsyms = 0 // its physical size is still a page
symtab_fix.stroff = 0 // mc.file.WriteAt([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, int64(0x81d8))
symtab_fix.strsize = 0
// we use 2 registers, x8 x9
// stack values:
// [ return address, header, argc, argv, env, apple ]
// we need to store the return address, and parameters passed to main
// we also store our header address to not calculate many times
// must expand stack to store arguments passed
// must use newly allocated stack region
// must save return address
// must recover stack to same value before calling main
// must recover link register before calling main
// we use shorthand store/load multiple
// arm also has different indexing instruction, so be careful
// https://developer.arm.com/documentation/102374/0101/Loads-and-stores---addressing
/*
adr x8, 0
# x9 = (offset end of __DATA) - (offset shellcode)
movz x9, #0x9999
add x8, x8, x9
stp x30, x8, [sp], #-0x10
stp x3, x2, [sp], #-0x10
stp x1, x0, [sp], #-0x10
# custom intializer
ldr x9, [x8]
blr x9
ldp x1, x0, [sp, #0x10]!
ldp x3, x2, [sp, #0x10]!
ldp x30, x8, [sp, #0x10]!
# original main
# link register is set so jump only
ldr x9, [x8, #8]
br x9
*/
shellcode := []uint32{}
ins_size_byte := 4
main_offset := int(mc.entryoff)
var shellcode_offset int
isArm := (mc.header.cputype & 0xff) == 12
if isArm {
shellcode = []uint32{
0x10000008,
0, // x9 = (offset end of __DATA) - (offset shellcode)
0x8B090108,
0xA8BF23FE,
0xA8BF0BE3,
0xA8BF03E1,
0xF9400109,
0xD63F0120,
0xA9C103E1,
0xA9C10BE3,
0xA9C123FE,
0xF9400509,
0xD61F0120,
}
shellcode_offset = text_start - (ins_size_byte * len(shellcode))
encode_movz := func(v int) uint32 {
return uint32(uint32(v)<<5 | uint32(0x694)<<21 | uint32(0x09))
}
// movz_shellcode_offset := encode_movz(shellcode_offset)
// movz_main_offset := encode_movz(main_offset)
// movz_data_end_offset := encode_movz(data_end)
movz_calculate_offset := encode_movz(data_end - shellcode_offset)
shellcode[1] = movz_calculate_offset
// shellcode[10] = movz_data_end_offset
// shellcode[19] = movz_main_offset
fmt.Printf("// shellcode_offset=%x\n", shellcode_offset)
fmt.Printf("// main_offset=%x\n", main_offset)
fmt.Printf("// data_end=%x\n", data_end)
fmt.Printf("// movz_calculate_offset=%x\n", movz_calculate_offset)
// fmt.Printf("// movz_shellcode_offset=%x\n", movz_shellcode_offset)
// fmt.Printf("// movz_main_offset=%x\n", movz_main_offset)
// fmt.Printf("// movz_data_end_offset=%x\n", movz_data_end_offset)
fmt.Printf("// lc_main_offset=%x\n", lc_main_offset)
} else {
shellcode_start := []uint8{
0x4c, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00,
0x49, 0xC7, 0xC1,
}
shellcode_end := []uint8{
0x4d, 0x01, 0xc8,
0x57,
0x56,
0x52,
0x51,
0x41, 0x50,
0x4d, 0x8b, 0x08,
0x41, 0xff, 0xd1,
0x41,
0x58,
0x59,
0x5a,
0x5e,
0x5f, 0x4d, 0x8b, 0x48, 0x08,
0x41, 0xff, 0xe1,
// pad to %4
0x00, 0x00,
}
offset := []uint8{0x00, 0x00, 0x00, 0x00} // offset
shellcode_size := len(shellcode_start) + len(offset) + len(shellcode_end)
// could use buffer encoding, but for correctness,
// we do this by hand
encode_movz := func(v int) {
for i := 0; i < 4; i++ {
offset[i] = uint8(v >> (i * 8))
}
}
// ┌─────────────────┐
// │ │
// shellcode starts ────┼─────────────────┼───── │ │instruction
// │ │ │ │fetch RIP size
// RIP returns ────┼─────────────────┼───── ▲ │ │
// │ │ │ │
// │ │ │ │ shellcode length
// shellcode ends │ │ │ offset │
// __text ────┼─────────────────┼───── │ range │
// │ │ │ │ __DATA ends - __text
// │ │ │ │
// __DATA ends ────┼─────────────────┼───── ▼ │
// │ │
// │ │
// │ │
// │ │
// │ │
// └─────────────────┘
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start)))
shellcode_offset = text_start - shellcode_size
shellcode_bytes := append(shellcode_start, offset...)
shellcode_bytes = append(shellcode_bytes, shellcode_end...)
for i := 0; i < len(shellcode_bytes); i += 4 {
val := 0
// little endian
val |= int(shellcode_bytes[i+0]) << 0
val |= int(shellcode_bytes[i+1]) << 8
val |= int(shellcode_bytes[i+2]) << 16
val |= int(shellcode_bytes[i+3]) << 24
shellcode = append(shellcode, uint32(val))
}
fmt.Printf("// shellcode_offset=%x\n", shellcode_offset)
fmt.Printf("// main_offset=%x\n", main_offset)
fmt.Printf("// data_end=%x\n", data_end)
fmt.Printf("// lc_main_offset=%x\n", lc_main_offset)
}
offset := int64(shellcode_offset)
{
// fix main to point to our newly created shellcode
bs := make([]byte, 8)
mc.byteorder.PutUint64(bs, uint64(offset))
mc.file.WriteAt(bs, int64(lc_main_offset))
}
bs := make([]byte, 4)
for _, ins := range shellcode {
mc.byteorder.PutUint32(bs, ins)
mc.file.WriteAt(bs, offset)
offset += 4
}
}
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
// some symbols are annoyingly distributed by another library
// dispite the name asking for X, the dyld loads a Y library
// LC_DYLD_ID of Y is equal to X and dyld can resolve these symbols
// because we do not search for library using LC_DYLD_ID,
// paths are mistaken and will not resolve symbols
//
// the most common library that has this behavior is libintl
// and fixing the resolver takes time, we temporarily ignore this library
// and so we keep symbols referenced by libintl
intlSymbols := []string{}
for _, symbol := range allSymbols {
if symbol.Dylib() == "/usr/local/opt/gettext/lib/libintl.8.dylib" {
intlSymbols = append(intlSymbols, symbol.Name())
}
}
keepSymbols = append(keepSymbols, intlSymbols...)
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()
mc.removeDySymtabCommand()
}
func (mc *MachoContext) removeSymtabCommand() {
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)
}
var symtab_fix *Symtab
for _, cmd := range mc.commands {
if cmd.Cmd() != LC_SYMTAB {
ptr += int64(cmd.Cmdsize())
continue
}
var symtab = cmd.(*Symtab)
symtab_fix = symtab
// erase strings referenced
start := int64(symtab_fix.stroff)
size := symtab_fix.strsize
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), start)
// erase nlist64 symbol items
start = int64(symtab_fix.symoff)
size = symtab_fix.nsyms * 16
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), start)
symtab_fix.symoff = 0
symtab_fix.nsyms = 0
symtab_fix.stroff = 0
symtab_fix.strsize = 0
mc.file.Seek(ptr, io.SeekStart) mc.file.Seek(ptr, io.SeekStart)
mc.file.Write(symtab_fix.Serialize(mc)) mc.file.Write(symtab_fix.Serialize(mc))
break break
} }
mc.file.Seek(0, io.SeekStart) mc.file.Seek(0, io.SeekStart)
} }
func (mc *MachoContext) removeDySymtabCommand() { func (mc *MachoContext) removeDySymtabCommand() {
ptr := int64(0) ptr := int64(0)
if mc.Is64bit() { if mc.Is64bit() {
ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart) ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart)
} else { } else {
@ -411,33 +750,160 @@ func (mc *MachoContext) removeDySymtabCommand() {
} }
for _, cmd := range mc.commands { for _, cmd := range mc.commands {
if cmd.Cmd() != LC_DYSYMTAB { if cmd.Cmd() != LC_DYSYMTAB {
ptr += int64(cmd.Cmdsize()) ptr += int64(cmd.Cmdsize())
continue continue
} }
var dysymtab = cmd.(*DySymtab) var dysymtab = cmd.(*DySymtab)
dysymtab_fix := dysymtab dysymtab_fix := dysymtab
dysymtab_fix.indirectsymoff = 0 dysymtab_fix.indirectsymoff = 0
dysymtab_fix.nindirectsyms = 0 dysymtab_fix.nindirectsyms = 0
mc.file.Seek(ptr, io.SeekStart) mc.file.Seek(ptr, io.SeekStart)
mc.file.Write(dysymtab_fix.Serialize(mc)) mc.file.Write(dysymtab_fix.Serialize(mc))
} }
} }
func (mc *MachoContext) RemoveExportTrie() { func (mc *MachoContext) RemoveExportTrie() {
var start int64 var start int64
var size int var size int
if mc.dyldinfo != nil { if mc.dyldinfo != nil {
// legacy export trie // legacy export trie
start = int64(mc.dyldinfo.export_off) start = int64(mc.dyldinfo.export_off)
size = int(mc.dyldinfo.export_size) size = int(mc.dyldinfo.export_size)
mc.file.WriteAt(make([]byte, size), start) mc.file.WriteAt(make([]byte, size), start)
} else if mc.exports != nil { } else if mc.exports != nil {
// modern export trie // modern export trie
start = int64(mc.exports.dataoff) start = int64(mc.exports.dataoff)
size = int(mc.exports.datasize) size = int(mc.exports.datasize)
mc.file.WriteAt(make([]byte, size), start) mc.file.WriteAt(make([]byte, size), start)
} else { } else {
// no export trie (??) // no export trie (??)
// should never occur unless this binary is modified // should never occur unless this binary is modified
} }
}
func (mc *MachoContext) AddSection(segname string, name string, size int) Section {
// mc.file.WriteAt(mc.header.Serialize(mc), 0)
var ret Section
var buffer bytes.Buffer
for _, command := range mc.commands {
switch command.(type) {
case *Segment64:
var virtualAddr uint64
var fileOffset uint64
var segment = command.(*Segment64)
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte(segname)) != 0 {
buffer.Write(segment.Serialize(mc))
continue
} else {
virtualAddr = segment.Vmaddr()
fileOffset = segment.Fileoff()
for _, section := range segment.Sections() {
virtualAddr += section.Size()
// if section.Offset() != 0 {
fileOffset += section.Size()
// }
}
align := uint64(4)
alignment := align - (fileOffset % align)
fileOffset += alignment
virtualAddr += alignment
enoughSpace := segment.Fileoff()+segment.Filesize() >= fileOffset+uint64(size)
if !enoughSpace {
fmt.Println("Not enough space to store saved info in __DATA segment, need resize (not supported now)")
panic("Not enough space to store saved info in __DATA segment, need resize (not supported now)")
}
}
var section Section64
section.sectname = make([]byte, 16)
copy(section.sectname, name)
section.segname = make([]byte, 16)
copy(section.segname, segname)
section.size = uint64(size)
section.reloff = 0
section.nreloc = 0
section.flags = 0
section.align = 3
section.reserved1 = 0
section.reserved2 = 0
section.reserved3 = 0
// addr will increment from the last section
// offset will increment from the last section + size
// be careful of Virtual section (bss)
section.addr = virtualAddr
section.offset = uint32(fileOffset)
segment.nsects += 1
buffer.Write(segment.Serialize(mc))
buffer.Write(section.Serialize(mc))
fmt.Printf("Add a new section with addr=0x%x, fileoffset=0x%x\n", section.addr, section.offset)
ret = &section
continue
default:
buffer.Write(command.Serialize(mc))
continue
}
}
mc.header.sizeofcmds = uint32(buffer.Len())
header := mc.header.Serialize(mc)
mc.file.WriteAt(header, 0)
mc.file.WriteAt(buffer.Bytes(), int64(len(header)))
return ret
}
func (mc *MachoContext) WriteInfoToData(info *protomodel.MachoInfo) {
encode := func() []byte {
buffer := new(bytes.Buffer)
for _, table := range info.Symbols.Tables {
binary.Write(buffer, mc.byteorder, table.LibIndex)
binary.Write(buffer, mc.byteorder, table.Nsymbols)
for _, symbol := range table.Symbols {
binary.Write(buffer, mc.byteorder, (symbol.SymbolIndex<<8)|symbol.SegmentIndex)
binary.Write(buffer, mc.byteorder, symbol.Offset)
}
}
instructions := buffer.Bytes()
buffer = new(bytes.Buffer)
for _, lib := range info.Symbols.Libs {
buffer.WriteString(lib)
buffer.WriteByte(0)
}
liblist := buffer.Bytes()
buffer = new(bytes.Buffer)
for _, symbol := range info.Symbols.Symbols {
buffer.WriteString(symbol)
buffer.WriteByte(0)
}
symbollist := buffer.Bytes()
buffer = new(bytes.Buffer)
// offset to liblist
binary.Write(buffer, mc.byteorder, uint32(len(instructions)))
// offset to symbollist
binary.Write(buffer, mc.byteorder, uint32(len(instructions)+len(liblist)))
buffer.Write(instructions)
buffer.Write(liblist)
buffer.Write(symbollist)
return buffer.Bytes()
}
encoded := encode()
// encoded := []byte{0x11,0x22,0x33, 0x44}
section := mc.AddSection("__DATA", "selfbind", len(encoded))
if mc.Is64bit() {
var s *Section64 = section.(*Section64)
mc.file.WriteAt(encoded, int64(s.Offset()))
} else {
var s *Section32 = section.(*Section32)
mc.file.WriteAt(encoded, int64(s.Offset()))
}
} }

View File

@ -94,6 +94,38 @@ 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;
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) { void ParseFixUps(uint8_t* buffer) {
struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer; struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer;
printf("starts=0x%x\n", header->starts_offset); printf("starts=0x%x\n", header->starts_offset);

View File

@ -30,4 +30,8 @@ struct ImportTable GetImportsTable(uint8_t* header_ptr);
struct ImportSymbol GetImportsAt(struct ImportTable* table, int i); struct ImportSymbol GetImportsAt(struct ImportTable* table, int i);
int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix); 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); 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 #endif

View File

@ -37,6 +37,14 @@ func (h *Header) Cpusubtype() uint32 {
return h.cpusubtype 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 { func (h *Header) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, h.magic) binary.Write(buf, mc.byteorder, h.magic)
@ -127,6 +135,16 @@ func (lcmd *LoadCmd) Cmdname() string {
return "LC_DATA_IN_CODE" return "LC_DATA_IN_CODE"
case LC_SYMTAB: case LC_SYMTAB:
return "LC_SYMTAB" return "LC_SYMTAB"
case LC_BUILD_VERSION:
return "LC_BUILD_VERSION"
case LC_UUID:
return "LC_UUID"
case LC_SOURCE_VERSION:
return "LC_SOURCE_VERSION"
case LC_DYLD_CHAINED_FIXUPS:
return "LC_DYLD_CHAINED_FIXUPS"
case LC_DYLD_EXPORTS_TRIE:
return "LC_DYLD_EXPORTS_TRIE"
default: default:
// TODO: Update // TODO: Update
return fmt.Sprintf("LC_DONT_KNOW_0x%x", lcmd.Cmd()) return fmt.Sprintf("LC_DONT_KNOW_0x%x", lcmd.Cmd())
@ -397,13 +415,21 @@ func (lcmd *DyldInfo) Deserialize(mc *MachoContext, buf []byte) {
} }
type Fixups struct { type Fixups struct {
fixups_version uint32 fixups_version uint32
starts_offset uint32 starts_offset uint32
imports_offset uint32 imports_offset uint32
symbols_offset uint32 symbols_offset uint32
imports_count uint32 imports_count uint32
imports_format uint32 imports_format uint32
symbols_format uint32 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 { func (lcmd *Fixups) Serialize(mc *MachoContext) []byte {
@ -516,25 +542,25 @@ func (lcmd *Symtab) Deserialize(mc *MachoContext, buf []byte) {
} }
type DySymtab struct { type DySymtab struct {
c LoadCmd c LoadCmd
ilocalsym uint32 ilocalsym uint32
nlocalsym uint32 nlocalsym uint32
iextdefsym uint32 iextdefsym uint32
nextdefsym uint32 nextdefsym uint32
iundefsym uint32 iundefsym uint32
nundefsym uint32 nundefsym uint32
tocoff uint32 tocoff uint32
ntoc uint32 ntoc uint32
modtaboff uint32 modtaboff uint32
nmodtab uint32 nmodtab uint32
extrefsymoff uint32 extrefsymoff uint32
nextrefsyms uint32 nextrefsyms uint32
indirectsymoff uint32 indirectsymoff uint32
nindirectsyms uint32 nindirectsyms uint32
extreloff uint32 extreloff uint32
nextrel uint32 nextrel uint32
localrefoff uint32 localrefoff uint32
nlocref uint32 nlocref uint32
} }
func (lcmd *DySymtab) Cmd() uint32 { func (lcmd *DySymtab) Cmd() uint32 {
@ -558,20 +584,20 @@ func (lcmd *DySymtab) Serialize(mc *MachoContext) []byte {
binary.Write(buf, mc.byteorder, lcmd.nlocalsym) binary.Write(buf, mc.byteorder, lcmd.nlocalsym)
binary.Write(buf, mc.byteorder, lcmd.iextdefsym) binary.Write(buf, mc.byteorder, lcmd.iextdefsym)
binary.Write(buf, mc.byteorder, lcmd.nextdefsym) binary.Write(buf, mc.byteorder, lcmd.nextdefsym)
binary.Write(buf, mc.byteorder, lcmd.iundefsym) binary.Write(buf, mc.byteorder, lcmd.iundefsym)
binary.Write(buf, mc.byteorder, lcmd.nundefsym) binary.Write(buf, mc.byteorder, lcmd.nundefsym)
binary.Write(buf, mc.byteorder, lcmd.tocoff) binary.Write(buf, mc.byteorder, lcmd.tocoff)
binary.Write(buf, mc.byteorder, lcmd.ntoc) binary.Write(buf, mc.byteorder, lcmd.ntoc)
binary.Write(buf, mc.byteorder, lcmd.modtaboff) binary.Write(buf, mc.byteorder, lcmd.modtaboff)
binary.Write(buf, mc.byteorder, lcmd.nmodtab) binary.Write(buf, mc.byteorder, lcmd.nmodtab)
binary.Write(buf, mc.byteorder, lcmd.extrefsymoff) binary.Write(buf, mc.byteorder, lcmd.extrefsymoff)
binary.Write(buf, mc.byteorder, lcmd.nextrefsyms) binary.Write(buf, mc.byteorder, lcmd.nextrefsyms)
binary.Write(buf, mc.byteorder, lcmd.indirectsymoff) binary.Write(buf, mc.byteorder, lcmd.indirectsymoff)
binary.Write(buf, mc.byteorder, lcmd.nindirectsyms) binary.Write(buf, mc.byteorder, lcmd.nindirectsyms)
binary.Write(buf, mc.byteorder, lcmd.extreloff) binary.Write(buf, mc.byteorder, lcmd.extreloff)
binary.Write(buf, mc.byteorder, lcmd.nextrel) binary.Write(buf, mc.byteorder, lcmd.nextrel)
binary.Write(buf, mc.byteorder, lcmd.localrefoff) binary.Write(buf, mc.byteorder, lcmd.localrefoff)
binary.Write(buf, mc.byteorder, lcmd.nlocref) binary.Write(buf, mc.byteorder, lcmd.nlocref)
return buf.Bytes() return buf.Bytes()
} }
@ -583,18 +609,18 @@ func (lcmd *DySymtab) Deserialize(mc *MachoContext, buf []byte) {
binary.Read(r, mc.byteorder, &lcmd.nlocalsym) binary.Read(r, mc.byteorder, &lcmd.nlocalsym)
binary.Read(r, mc.byteorder, &lcmd.iextdefsym) binary.Read(r, mc.byteorder, &lcmd.iextdefsym)
binary.Read(r, mc.byteorder, &lcmd.nextdefsym) binary.Read(r, mc.byteorder, &lcmd.nextdefsym)
binary.Read(r, mc.byteorder, &lcmd.iundefsym) binary.Read(r, mc.byteorder, &lcmd.iundefsym)
binary.Read(r, mc.byteorder, &lcmd.nundefsym) binary.Read(r, mc.byteorder, &lcmd.nundefsym)
binary.Read(r, mc.byteorder, &lcmd.tocoff) binary.Read(r, mc.byteorder, &lcmd.tocoff)
binary.Read(r, mc.byteorder, &lcmd.ntoc) binary.Read(r, mc.byteorder, &lcmd.ntoc)
binary.Read(r, mc.byteorder, &lcmd.modtaboff) binary.Read(r, mc.byteorder, &lcmd.modtaboff)
binary.Read(r, mc.byteorder, &lcmd.nmodtab) binary.Read(r, mc.byteorder, &lcmd.nmodtab)
binary.Read(r, mc.byteorder, &lcmd.extrefsymoff) binary.Read(r, mc.byteorder, &lcmd.extrefsymoff)
binary.Read(r, mc.byteorder, &lcmd.nextrefsyms) binary.Read(r, mc.byteorder, &lcmd.nextrefsyms)
binary.Read(r, mc.byteorder, &lcmd.indirectsymoff) binary.Read(r, mc.byteorder, &lcmd.indirectsymoff)
binary.Read(r, mc.byteorder, &lcmd.nindirectsyms) binary.Read(r, mc.byteorder, &lcmd.nindirectsyms)
binary.Read(r, mc.byteorder, &lcmd.extreloff) binary.Read(r, mc.byteorder, &lcmd.extreloff)
binary.Read(r, mc.byteorder, &lcmd.nextrel) binary.Read(r, mc.byteorder, &lcmd.nextrel)
binary.Read(r, mc.byteorder, &lcmd.localrefoff) binary.Read(r, mc.byteorder, &lcmd.localrefoff)
binary.Read(r, mc.byteorder, &lcmd.nlocref) binary.Read(r, mc.byteorder, &lcmd.nlocref)
} }

View File

@ -29,11 +29,11 @@ type MachoContext struct {
linkedits []*LinkEdit linkedits []*LinkEdit
segments []Segment segments []Segment
symtab *Symtab symtab *Symtab
dysymtab *DySymtab dysymtab *DySymtab
dyldinfo *DyldInfo dyldinfo *DyldInfo
fixups *LinkEdit fixups *LinkEdit
exports *LinkEdit exports *LinkEdit
} }
func (mc *MachoContext) FileSize() uint32 { func (mc *MachoContext) FileSize() uint32 {
@ -64,6 +64,18 @@ func (mc *MachoContext) Segments() []Segment {
return mc.segments return mc.segments
} }
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 { func (mc *MachoContext) WriteEnabled() bool {
return mc.file != nil return mc.file != nil
} }
@ -235,12 +247,12 @@ func (mc *MachoContext) Parse(r *bufio.Reader) error {
lcmd.Deserialize(mc, command_buf) lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd) mc.commands = append(mc.commands, lcmd)
mc.linkedits = append(mc.linkedits, lcmd) mc.linkedits = append(mc.linkedits, lcmd)
if lcmd.Cmd() == LC_DYLD_CHAINED_FIXUPS { if lcmd.Cmd() == LC_DYLD_CHAINED_FIXUPS {
mc.fixups = lcmd mc.fixups = lcmd
} }
if lcmd.Cmd() == LC_DYLD_EXPORTS_TRIE { if lcmd.Cmd() == LC_DYLD_EXPORTS_TRIE {
mc.exports = lcmd mc.exports = lcmd
} }
break break
case LC_SYMTAB: case LC_SYMTAB:
@ -248,12 +260,14 @@ func (mc *MachoContext) Parse(r *bufio.Reader) error {
lcmd.Deserialize(mc, command_buf) lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd) mc.commands = append(mc.commands, lcmd)
mc.symtab = lcmd mc.symtab = lcmd
break
case LC_DYSYMTAB: case LC_DYSYMTAB:
lcmd := new(DySymtab) lcmd := new(DySymtab)
lcmd.Deserialize(mc, command_buf) lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd) mc.commands = append(mc.commands, lcmd)
mc.dysymtab = lcmd mc.dysymtab = lcmd
break
default: default:
lcmd := new(LoadCmd) lcmd := new(LoadCmd)

View File

@ -72,7 +72,6 @@ func (mc *MachoContext) CollectSymbols() []*Symbol {
var flags uint8 var flags uint8
var sect uint8 var sect uint8
var desc uint16 var desc uint16
var value32 uint32
var value64 uint64 var value64 uint64
binary.Read(symtab_buffer, mc.byteorder, &strx) binary.Read(symtab_buffer, mc.byteorder, &strx)
@ -83,6 +82,7 @@ func (mc *MachoContext) CollectSymbols() []*Symbol {
binary.Read(symtab_buffer, mc.byteorder, &value64) binary.Read(symtab_buffer, mc.byteorder, &value64)
} else { } else {
// always use value64 // always use value64
var value32 uint32
binary.Read(symtab_buffer, mc.byteorder, &value32) binary.Read(symtab_buffer, mc.byteorder, &value32)
value64 = uint64(value32) value64 = uint64(value32)
} }

View File

@ -17,18 +17,38 @@ message MachoInfo {
uint64 value = 2; // address of the init function uint64 value = 2; // address of the init function
} }
// iOS library rewrite these as opcodes and dyld processes // right now we waste memory to store name/hash for all symbols
// message LazySymbol { // should consider compress them, dyld stores the index in list of LC_DYLIB
// string name = 1; message BindSymbol {
// int32 dylib_ordinal = 2; // could be -1 -2 string name = 1;
// uint32 segment = 3; string libname = 2;
// uint32 segment_offset = 4; uint32 libhash = 3;
// } uint32 segment = 4; // segment index
uint64 offset = 5; // offset in segment
}
message SymbolTable {
uint32 symbolIndex = 1;
uint32 segmentIndex = 2;
uint32 offset = 3;
}
message LibraryImportedSymbols {
uint32 libIndex = 1;
uint32 nsymbols = 2;
repeated SymbolTable symbols = 3;
}
message AllImportedSymbols {
repeated string libs = 1;
repeated string symbols = 2;
repeated LibraryImportedSymbols tables = 3;
}
PointerSize pointer_size = 1; PointerSize pointer_size = 1;
uint64 image_base = 2; uint64 image_base = 2;
repeated InitPointer init_pointers = 3; uint64 main = 3;
// saved or read from header -> dyld_info -> lazyoff repeated InitPointer init_pointers = 4;
// uint64 lazy_symbol_address = 4; // repeated BindSymbol symbols = 5;
// repeated LazySymbol lazy_symbols = 5; AllImportedSymbols symbols = 5;
} }

View File

@ -0,0 +1,57 @@
#import <Foundation/Foundation.h>
#include <stdio.h>
@interface Foo : NSObject
@end
@implementation Foo
- (void)bar {
NSLog(@"%@", self);
}
@end
@interface Bar : NSObject
@end
@implementation Bar
static int x;
+ (void)load {
NSLog(@"%@", self);
// NSLog(@"x=%d", x)
printf("printf in [Bar load]\n");
x = 1;
}
- (void)dummy {
NSLog(@"dummy bar x=%d", x);
}
@end
__attribute__((constructor)) static void
hmmge(int argc, char** argv) {
// create a dummy blank function to be replaced to call OBJC load
printf("hmmge=%p\n", hmmge);
printf("hmmge argc=%d\n", argc);
for (int i = 0; i < argc; i++) {
printf(" hmmge argv[%d]=%s\n", i, argv[i]);
}
NSLog(@"hmmge in objc-c");
Bar *bar = [[Bar alloc] init];
[bar dummy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
Foo *foo = [[Foo alloc] init];
[foo bar];
}
printf("argc=%d\n", argc);
for (int i = 0; i < argc; i++) {
printf(" argv[%d]=%s\n", i, argv[i]);
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
VERSION=${1:-14} VERSION=${1:-14}
OUT=./out OUT=./out
LOGIC=2 LOGIC=${2}
mkdir -p $OUT mkdir -p $OUT
@ -39,21 +39,80 @@ clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc
# create our dummy lib first # create our dummy lib first
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc
# build a references libb # build a references libb
clang++ -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.cc clang++ -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -Xlinker -no_data_const -lb a.cc
# extract symbols from a # 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
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
# build libb with symbols extracted from a # 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 clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
out/a codesign --force --deep -s - $OUT/a-fixed
$OUT/a-fixed
elif [[ $LOGIC -eq 3 ]]
then
# remove imports test
# libc to test reexport custom lib
clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc
# create our dummy lib first
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc
# build a references libb
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 --keep-imports _printf $OUT/a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports $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
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports _printf --keep-imports ___stack_chk_guard $OUT/libb.dylib
# resign
codesign --force --deep -s - $OUT/a-fixed
codesign --force --deep -s - $OUT/libb.dylib
# export OBJC_PRINT_LOAD_METHODS=1
# export OBJC_PRINT_CLASS_SETUP=1
$OUT/a-fixed
# unset OBJC_PRINT_LOAD_METHODS
# unset OBJC_PRINT_CLASS_SETUP
else else
# dummy test build # remove imports test
clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc # test rpath
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -install_name @rpath/libc.dylib -shared c.cc
clang++ -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.cc # linked with libd
# with rpath = $OUT
clang++ -mmacosx-version-min=$VERSION -Xlinker -no_data_const -o $OUT/a \
-rpath ./heheeeekkkkkkk \
-rpath $OUT \
-rpath ./hehe \
-rpath ./haha \
$OUT/libc.dylib a.cc \
# extract symbols from a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell -l out/libb.dylib --remove-imports --remove-exports $OUT/a
# build restoration libb with symbols extracted from a
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared b.cc
# obfuscate libb (bugged)
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports _printf --keep-imports ___stack_chk_guard $OUT/libb.dylib
# resign
codesign --force --deep -s - $OUT/a-fixed
codesign --force --deep -s - $OUT/libb.dylib
# export OBJC_PRINT_LOAD_METHODS=1
# export OBJC_PRINT_CLASS_SETUP=1
$OUT/a-fixed
# unset OBJC_PRINT_LOAD_METHODS
# unset OBJC_PRINT_CLASS_SETUP
fi fi

File diff suppressed because it is too large Load Diff