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 (
github.com/alecthomas/kong v0.2.16
github.com/sirupsen/logrus v1.8.0
google.golang.org/protobuf v1.26.0
google.golang.org/protobuf v1.31.0
)
require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/magefile/mage v1.10.0 // indirect
github.com/pkg/errors v0.8.1 // 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/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.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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
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{}
func (action *removeClassicSymbol) withMacho(mf *MachoFile) error {
mf.Context().RemoveClassicSymbol()
mf.Context().RemoveSymbolTable()
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 {
mf.Context().RemoveBindSymbols()
mf.Context().RemoveSymbolTable()
mf.Context().RemoveExportTrie()
return nil
}
@ -20,4 +18,3 @@ func (action *removeImports) withFat(ff *FatFile) error {
func NewRemoveImportsAction() *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
import (
"bufio"
"fmt"
"io/ioutil"
"os"
// "strings"
"github.com/alecthomas/kong"
log "github.com/sirupsen/logrus"
@ -63,6 +65,10 @@ 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
@ -73,7 +79,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 +105,30 @@ 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
pc.symbols_keep = arg.KeepImports
default:
return
@ -165,6 +195,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 +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 {
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,28 @@ 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:"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 {
@ -68,5 +90,14 @@ type Argument struct {
DisplayBcell DisplayBcellArgument `cmd name:"display-bcell" help:"Display Protobuf<BcellFile> 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"`
}

View File

@ -38,11 +38,15 @@ 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
symbols_keep []string
outfile string
@ -84,24 +88,28 @@ 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.symbols_keep))
pc.AddAction(NewRemoveImportsAction())
pc.AddAction(NewRewriteImportsWithKeepSymbolsAction(pc.symbols_keep))
}
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))

View File

@ -25,10 +25,13 @@ type ImportSymbol struct {
segment_offset uint32
address uint64
file_address uint64
lib_ordinal uint32
// push number
pnum uint32
stub uint64
next int // only for LC_DYLD_CHAINED_FIXUPS
}
func (sym *ImportSymbol) Name() string {
@ -43,6 +46,10 @@ func (sym *ImportSymbol) Dylib() string {
return sym.dylib
}
func (sym *ImportSymbol) LibOrdinal() uint32 {
return sym.lib_ordinal
}
func (sym *ImportSymbol) Address() uint64 {
return sym.address
}
@ -55,6 +62,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()
@ -82,7 +93,7 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
// until buf is freed, all pointers are valid
// remember to copy before moving out
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++ {
// s := C.GetImportsAt(&imports_table, C.int(i))
@ -102,7 +113,7 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1
if status == 2 {
break;
break
}
if status == 3 {
continue
@ -110,17 +121,18 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
// fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
// fmt.Printf("pages=%x\n", fix.pages)
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
reader := bytes.NewReader(mc.buf)
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])
mc.file.Seek(address, io.SeekStart)
reader.Seek(address, io.SeekStart)
code := make([]byte, 8)
for {
mc.file.Read(code)
v := binary.LittleEndian.Uint64(code)
reader.Read(code)
v := mc.byteorder.Uint64(code)
var bind C.int
var ret1 C.ulonglong
@ -133,25 +145,31 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
name := C.GoString(s.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.name = name
sym.dylib = dylib
sym.typ = "lazy"
sym.lib_ordinal = uint32(s.lib_ordinal)
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
sym.file_address = uint64(address)
sym.next = int(next)
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 {
break
}
// because the pointer move up 8 bytes already so we minus 8
address += int64(next * 4)
reader.Seek(int64(next*4)-8, io.SeekCurrent)
}
mc.file.Seek(0, io.SeekStart)
reader.Seek(0, io.SeekStart)
}
}
return syms

View File

@ -1,14 +1,23 @@
package macho
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"math/rand"
"strings"
"time"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
"ios-wrapper/pkg/protomodel"
)
// #include "fixups.h"
import "C"
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
if mc.Is64bit() {
mc.file.Seek(int64(Header_size_64), io.SeekStart)
@ -185,10 +194,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)
@ -273,50 +278,48 @@ func (mc *MachoContext) RemoveBindSymbols() {
return
}
rand.Seed(time.Now().UnixNano())
if mc.dyldinfo == nil {
mc.removeBindSymbolsModern()
} else {
mc.removeBindSymbolsLegacy()
}
calculateHash := func(name string) uint32 {
var h uint32 = 0x811c9dc5
for _, s := range name {
h ^= uint32(s)
h *= 0x01000193
}
return h
// Objective-C stub replaces main which can only appears in executable
if mc.Header().IsExecutable() {
mc.ReworkForObjc()
}
// 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("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))
} else {
// for modern resolve the opcodes must not be rewritten as 0x00
// because it contains 2 types of opcodes, BIND and REBASE
// we only fix BIND and leave REBASE intact
// However, each opcodes has a *next* field to the next opcode
// So if we want to leave the header intact (contains pointers, size)
// We should rewrite this as REBASE opcode (so no symbol lookup happens)
// and it can continue
// we can write random values, because the loader just do
// (high8 << 56 | target) - mach_header
// or something, so no symbol lookup and no error at runtime
target := rand.Int()
high8 := rand.Int()
value := C.MakeRebaseFixupOpcode(C.int(symbol.next), C.ulonglong(target), C.ulonglong(high8))
v := make([]byte, 8)
mc.byteorder.PutUint64(v, uint64(value))
mc.file.WriteAt(v, int64(symbol.file_address))
}
}
fmt.Println("};")
fmt.Printf("uint32_t nimports = %d;\n", count);
}
func (mc *MachoContext) removeBindSymbolsModern() {
@ -356,6 +359,342 @@ func (mc *MachoContext) removeBindSymbolsLegacy() {
mc.file.WriteAt(make([]byte, size), int64(start))
}
func (mc *MachoContext) ReworkForObjc() {
text_start := 0
data_end := 0
lc_main_offset := int64(0)
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_MAIN {
lc_main_offset = ptr + 8
ptr += int64(cmd.Cmdsize())
continue
}
if cmd.Cmd() != LC_SEGMENT_64 {
ptr += int64(cmd.Cmdsize())
continue
}
var segment = cmd.(*Segment64)
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
section_ptr := ptr + 0x40 + 8
for _, section := range segment.Sections() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__text")) == 0 {
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
// __bss section is dynamically allocated at the end to or something, hmmge
// assume that it order correctly, which it should if compiled and not modified
// each section has their addr field which we can use that with segment virtual address
// to calculate the offset of the last section from segment starts
// 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)
// dummy value past the end of __DATA segment (logical size),
// its physical size is still a page
// mc.file.WriteAt([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, int64(0x81d8))
// 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()
@ -441,3 +780,130 @@ func (mc *MachoContext) RemoveExportTrie() {
// 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) {
struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer;
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);
int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix);
int ParseFixValue(int format, uint64_t value, int* bind, uint64_t* ret1, uint64_t* ret2);
uint64_t MakeRebaseFixupOpcode(int next, uint64_t target, uint64_t high8);
uint64_t MakeBindFixupOpcodeFromRebase(uint64_t rebaseOpcode, uint32_t ordinal);
uint32_t MakeImportTableEntry(uint32_t lib_ordinal, uint32_t name_offset);
#endif

View File

@ -37,6 +37,14 @@ func (h *Header) Cpusubtype() uint32 {
return h.cpusubtype
}
func (h *Header) IsExecutable() bool {
return h.filetype == 0x2
}
func (h *Header) IsDylib() bool {
return h.filetype == 0x6
}
func (h *Header) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, h.magic)
@ -127,6 +135,16 @@ func (lcmd *LoadCmd) Cmdname() string {
return "LC_DATA_IN_CODE"
case 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:
// TODO: Update
return fmt.Sprintf("LC_DONT_KNOW_0x%x", lcmd.Cmd())
@ -406,6 +424,14 @@ type Fixups struct {
symbols_format uint32
}
func (lcmd *Fixups) ImportsOffset(fixups_offset uint32) uint32 {
return lcmd.imports_offset + fixups_offset
}
func (lcmd *Fixups) SymbolsOffset(fixups_offset uint32) uint32 {
return lcmd.symbols_offset + fixups_offset
}
func (lcmd *Fixups) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.fixups_version)

View File

@ -64,6 +64,18 @@ func (mc *MachoContext) Segments() []Segment {
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 {
return mc.file != nil
}
@ -248,12 +260,14 @@ func (mc *MachoContext) Parse(r *bufio.Reader) error {
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.symtab = lcmd
break
case LC_DYSYMTAB:
lcmd := new(DySymtab)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.dysymtab = lcmd
break
default:
lcmd := new(LoadCmd)

View File

@ -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)
}

View File

@ -17,18 +17,38 @@ 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
}
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;
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;
uint64 main = 3;
repeated InitPointer init_pointers = 4;
// repeated BindSymbol 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}
OUT=./out
LOGIC=2
LOGIC=${2}
mkdir -p $OUT
@ -39,21 +39,80 @@ 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++ -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
../../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
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
# dummy test build
# remove imports test
clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc
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/a -L"./out" -lb a.cc
# test rpath
clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -install_name @rpath/libc.dylib -shared c.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

File diff suppressed because it is too large Load Diff