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 {
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 {
@ -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

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

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

@ -4,9 +4,9 @@ import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"
"unsafe"
// "bufio"
"io"
"io"
log "github.com/sirupsen/logrus"
@ -19,16 +19,19 @@ import "C"
type ImportSymbol struct {
name string
typ string
typ string
dylib string
segment uint32
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()
@ -64,135 +75,142 @@ func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol {
}
func (mc *MachoContext) findSegmentIndexAt(address uint64) int {
for i, segment := range mc.Segments() {
if segment.Fileoff() <= address && segment.Fileoff() + segment.Filesize() > address {
return i
}
}
return -1
for i, segment := range mc.Segments() {
if segment.Fileoff() <= address && segment.Fileoff()+segment.Filesize() > address {
return i
}
}
return -1
}
// New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
start := mc.fixups.dataoff
size := mc.fixups.datasize
buf := mc.buf[start:start+size]
start := mc.fixups.dataoff
size := mc.fixups.datasize
buf := mc.buf[start : start+size]
// all pointers used are based from this **buf**
// 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);
// all pointers used are based from this **buf**
// 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)
// for i := 0; i < int(imports_table.size); i++ {
// s := C.GetImportsAt(&imports_table, C.int(i))
// name := C.GoString(s.name)
// symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// symbol_table = append(symbol_table, symbol)
// fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name)
// }
// for i := 0; i < int(imports_table.size); i++ {
// s := C.GetImportsAt(&imports_table, C.int(i))
// name := C.GoString(s.name)
// symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// symbol_table = append(symbol_table, symbol)
// fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name)
// }
var syms []*ImportSymbol
var sym ImportSymbol
segment_i := 0
for {
var fix C.struct_SegmentFix
fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix))
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1
if status == 2 {
break;
}
if status == 3 {
continue
}
// 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))
for page_i := 0; page_i < int(fix.page_count); page_i++ {
// fmt.Printf(" page offset=%x\n", pages[page_i])
segment_i := 0
for {
var fix C.struct_SegmentFix
fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix))
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1
if status == 2 {
break
}
if status == 3 {
continue
}
// 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)
address := int64(fix.segment) + int64(pages[page_i])
reader.Seek(address, io.SeekStart)
code := make([]byte, 8)
code := make([]byte, 8)
for {
mc.file.Read(code)
v := binary.LittleEndian.Uint64(code)
for {
reader.Read(code)
v := mc.byteorder.Uint64(code)
var bind C.int
var ret1 C.ulonglong
var ret2 C.ulonglong
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
&bind, &ret1, &ret2)
var bind C.int
var ret1 C.ulonglong
var ret2 C.ulonglong
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
&bind, &ret1, &ret2)
if bind == 1 {
s := C.GetImportsAt(&imports_table, C.int(ret1))
name := C.GoString(s.name)
dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
if bind == 1 {
s := C.GetImportsAt(&imports_table, C.int(ret1))
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.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)
new_sym := sym
syms = append(syms, &new_sym)
}
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
}
}
mc.file.Seek(0, io.SeekStart)
}
}
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)
}
reader.Seek(0, io.SeekStart)
}
}
return syms
}
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner
func (mc *MachoContext) CollectBindSymbolsLegacy() []*ImportSymbol {
noLazy := (func() []*ImportSymbol {
start := mc.dyldinfo.bind_off
size := mc.dyldinfo.bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "no lazy")
})()
noLazy := (func() []*ImportSymbol {
start := mc.dyldinfo.bind_off
size := mc.dyldinfo.bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "no lazy")
})()
lazy := (func() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "lazy")
})()
lazy := (func() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "lazy")
})()
weak := (func() []*ImportSymbol {
start := mc.dyldinfo.weak_bind_off
size := mc.dyldinfo.weak_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "weak")
})()
weak := (func() []*ImportSymbol {
start := mc.dyldinfo.weak_bind_off
size := mc.dyldinfo.weak_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "weak")
})()
var symbols []*ImportSymbol
symbols = append(symbols, noLazy...)
symbols = append(symbols, lazy...)
symbols = append(symbols, weak...)
var symbols []*ImportSymbol
symbols = append(symbols, noLazy...)
symbols = append(symbols, lazy...)
symbols = append(symbols, weak...)
return symbols
return symbols
}
func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportSymbol {
size := buf.Len()
size := buf.Len()
if size == 0 {
return []*ImportSymbol{}
}
@ -226,7 +244,7 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
case BIND_OPCODE_DO_BIND:
if sym.name != "" {
new_sym := sym
new_sym.typ = typ
new_sym.typ = typ
syms = append(syms, &new_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,
}).Trace("Bind")
sym.name = ""
sym.address += 8
sym.address += 8
}
offset += 1
break
@ -264,9 +282,9 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
sym.name, _ = buf.ReadString(0)
// ReadString input the 0x00 byte to buffer
// while we are string so we can remove that
sym.name = sym.name[:len(sym.name)-1]
// ReadString input the 0x00 byte to buffer
// while we are string so we can remove that
sym.name = sym.name[:len(sym.name)-1]
offset += uint(len(sym.name))
break
@ -290,12 +308,12 @@ func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportS
offset += br
break
case BIND_OPCODE_SET_TYPE_IMM:
fmt.Println("// symbol type", imm)
break
case BIND_OPCODE_SET_TYPE_IMM:
fmt.Println("// symbol type", imm)
break
default:
fmt.Println("BIND OPCODE NOT SUPPORTED", op, imm)
fmt.Println("BIND OPCODE NOT SUPPORTED", op, imm)
break
}
}

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)
@ -270,140 +275,474 @@ func (mc *MachoContext) UpdateHeaderRemoveLcmd(size uint32) {
func (mc *MachoContext) RemoveBindSymbols() {
if !mc.WriteEnabled() {
return
}
return
}
rand.Seed(time.Now().UnixNano())
if mc.dyldinfo == nil {
mc.removeBindSymbolsModern()
} else {
mc.removeBindSymbolsLegacy()
}
// Objective-C stub replaces main which can only appears in executable
if mc.Header().IsExecutable() {
mc.ReworkForObjc()
}
calculateHash := func(name string) uint32 {
var h uint32 = 0x811c9dc5
for _, s := range name {
h ^= uint32(s)
h *= 0x01000193
}
return h
}
// due to some limitations when design this tool
// we write the c code to stdout lol
for _, symbol := range mc.CollectBindSymbols() {
if symbol.Type() != "lazy" {
continue
}
// 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]
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
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);
mc.file.WriteAt(make([]byte, 8), int64(symbol.file_address))
}
fmt.Println("};")
fmt.Printf("uint32_t nimports = %d;\n", count);
// 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))
}
}
}
func (mc *MachoContext) removeBindSymbolsModern() {
// we don't mess up the chain
// we clear the imports table, and the raw opcodes
// clearing imports table disables static analysis
// clearing opcodes forces runtime manual mapping
// we don't mess up the chain
// we clear the imports table, and the raw opcodes
// clearing imports table disables static analysis
// clearing opcodes forces runtime manual mapping
// imports item are defined by mc.fixups.imports_format
// basic case is dyld_chained_import, 4 bytes
// imports item are defined by mc.fixups.imports_format
// basic case is dyld_chained_import, 4 bytes
start := mc.fixups.dataoff
size := mc.fixups.datasize
fixups := new(Fixups)
fixups.Deserialize(mc, mc.buf[start:start+size])
start := mc.fixups.dataoff
size := mc.fixups.datasize
fixups := new(Fixups)
fixups.Deserialize(mc, mc.buf[start:start+size])
start = mc.fixups.dataoff + fixups.imports_offset
size = fixups.imports_count * 4
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start))
start = mc.fixups.dataoff + fixups.imports_offset
size = fixups.imports_count * 4
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start))
// string reference are at the end of this section
start = mc.fixups.dataoff + fixups.symbols_offset
size = mc.fixups.Datasize() - fixups.symbols_offset
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start))
// string reference are at the end of this section
start = mc.fixups.dataoff + fixups.symbols_offset
size = mc.fixups.Datasize() - fixups.symbols_offset
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), int64(start))
fixups.imports_count = 0
mc.file.WriteAt(fixups.Serialize(mc), int64(mc.fixups.dataoff))
fixups.imports_count = 0
mc.file.WriteAt(fixups.Serialize(mc), int64(mc.fixups.dataoff))
}
func (mc *MachoContext) removeBindSymbolsLegacy() {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
// set lazy opcodes to 0x00 == DO_BIND
// but no symbol state to bind
mc.file.WriteAt(make([]byte, size), int64(start))
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
// set lazy opcodes to 0x00 == DO_BIND
// but no symbol state to bind
mc.file.WriteAt(make([]byte, size), int64(start))
}
func (mc *MachoContext) RemoveSymbolTable() {
// try to remove symtab and dysymtab
mc.removeSymtabCommand()
mc.removeDySymtabCommand()
}
func (mc *MachoContext) ReworkForObjc() {
text_start := 0
data_end := 0
lc_main_offset := int64(0)
func (mc *MachoContext) removeSymtabCommand() {
ptr := 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)
}
var symtab_fix *Symtab
for _, cmd := range mc.commands {
if cmd.Cmd() != LC_SYMTAB {
ptr += int64(cmd.Cmdsize())
if cmd.Cmd() == LC_MAIN {
lc_main_offset = ptr + 8
ptr += int64(cmd.Cmdsize())
continue
}
var symtab = cmd.(*Symtab)
symtab_fix = symtab
if cmd.Cmd() != LC_SEGMENT_64 {
ptr += int64(cmd.Cmdsize())
continue
}
var segment = cmd.(*Segment64)
// 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)
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
// 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)
// __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)
symtab_fix.symoff = 0
symtab_fix.nsyms = 0
symtab_fix.stroff = 0
symtab_fix.strsize = 0
// 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()
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.Write(symtab_fix.Serialize(mc))
break
break
}
mc.file.Seek(0, io.SeekStart)
mc.file.Seek(0, io.SeekStart)
}
func (mc *MachoContext) removeDySymtabCommand() {
ptr := int64(0)
ptr := int64(0)
if mc.Is64bit() {
ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart)
} else {
@ -411,33 +750,160 @@ func (mc *MachoContext) removeDySymtabCommand() {
}
for _, cmd := range mc.commands {
if cmd.Cmd() != LC_DYSYMTAB {
ptr += int64(cmd.Cmdsize())
ptr += int64(cmd.Cmdsize())
continue
}
var dysymtab = cmd.(*DySymtab)
dysymtab_fix := dysymtab
dysymtab_fix.indirectsymoff = 0
dysymtab_fix.nindirectsyms = 0
var dysymtab = cmd.(*DySymtab)
dysymtab_fix := dysymtab
dysymtab_fix.indirectsymoff = 0
dysymtab_fix.nindirectsyms = 0
mc.file.Seek(ptr, io.SeekStart)
mc.file.Write(dysymtab_fix.Serialize(mc))
}
}
func (mc *MachoContext) RemoveExportTrie() {
var start int64
var size int
if mc.dyldinfo != nil {
// legacy export trie
start = int64(mc.dyldinfo.export_off)
size = int(mc.dyldinfo.export_size)
mc.file.WriteAt(make([]byte, size), start)
} else if mc.exports != nil {
// modern export trie
start = int64(mc.exports.dataoff)
size = int(mc.exports.datasize)
mc.file.WriteAt(make([]byte, size), start)
} else {
// no export trie (??)
// should never occur unless this binary is modified
}
var start int64
var size int
if mc.dyldinfo != nil {
// legacy export trie
start = int64(mc.dyldinfo.export_off)
size = int(mc.dyldinfo.export_size)
mc.file.WriteAt(make([]byte, size), start)
} else if mc.exports != nil {
// modern export trie
start = int64(mc.exports.dataoff)
size = int(mc.exports.datasize)
mc.file.WriteAt(make([]byte, size), start)
} else {
// no export trie (??)
// 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())
@ -397,13 +415,21 @@ func (lcmd *DyldInfo) Deserialize(mc *MachoContext, buf []byte) {
}
type Fixups struct {
fixups_version uint32
starts_offset uint32
imports_offset uint32
symbols_offset uint32
fixups_version uint32
starts_offset uint32
imports_offset uint32
symbols_offset uint32
imports_count 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 {
@ -516,25 +542,25 @@ func (lcmd *Symtab) Deserialize(mc *MachoContext, buf []byte) {
}
type DySymtab struct {
c LoadCmd
ilocalsym uint32
nlocalsym uint32
iextdefsym uint32
nextdefsym uint32
iundefsym uint32
nundefsym uint32
tocoff uint32
ntoc uint32
modtaboff uint32
nmodtab uint32
extrefsymoff uint32
nextrefsyms uint32
indirectsymoff uint32
nindirectsyms uint32
extreloff uint32
nextrel uint32
localrefoff uint32
nlocref uint32
c LoadCmd
ilocalsym uint32
nlocalsym uint32
iextdefsym uint32
nextdefsym uint32
iundefsym uint32
nundefsym uint32
tocoff uint32
ntoc uint32
modtaboff uint32
nmodtab uint32
extrefsymoff uint32
nextrefsyms uint32
indirectsymoff uint32
nindirectsyms uint32
extreloff uint32
nextrel uint32
localrefoff uint32
nlocref 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.iextdefsym)
binary.Write(buf, mc.byteorder, lcmd.nextdefsym)
binary.Write(buf, mc.byteorder, lcmd.iundefsym)
binary.Write(buf, mc.byteorder, lcmd.nundefsym)
binary.Write(buf, mc.byteorder, lcmd.tocoff)
binary.Write(buf, mc.byteorder, lcmd.ntoc)
binary.Write(buf, mc.byteorder, lcmd.modtaboff)
binary.Write(buf, mc.byteorder, lcmd.nmodtab)
binary.Write(buf, mc.byteorder, lcmd.extrefsymoff)
binary.Write(buf, mc.byteorder, lcmd.nextrefsyms)
binary.Write(buf, mc.byteorder, lcmd.indirectsymoff)
binary.Write(buf, mc.byteorder, lcmd.nindirectsyms)
binary.Write(buf, mc.byteorder, lcmd.extreloff)
binary.Write(buf, mc.byteorder, lcmd.nextrel)
binary.Write(buf, mc.byteorder, lcmd.localrefoff)
binary.Write(buf, mc.byteorder, lcmd.nlocref)
binary.Write(buf, mc.byteorder, lcmd.iundefsym)
binary.Write(buf, mc.byteorder, lcmd.nundefsym)
binary.Write(buf, mc.byteorder, lcmd.tocoff)
binary.Write(buf, mc.byteorder, lcmd.ntoc)
binary.Write(buf, mc.byteorder, lcmd.modtaboff)
binary.Write(buf, mc.byteorder, lcmd.nmodtab)
binary.Write(buf, mc.byteorder, lcmd.extrefsymoff)
binary.Write(buf, mc.byteorder, lcmd.nextrefsyms)
binary.Write(buf, mc.byteorder, lcmd.indirectsymoff)
binary.Write(buf, mc.byteorder, lcmd.nindirectsyms)
binary.Write(buf, mc.byteorder, lcmd.extreloff)
binary.Write(buf, mc.byteorder, lcmd.nextrel)
binary.Write(buf, mc.byteorder, lcmd.localrefoff)
binary.Write(buf, mc.byteorder, lcmd.nlocref)
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.iextdefsym)
binary.Read(r, mc.byteorder, &lcmd.nextdefsym)
binary.Read(r, mc.byteorder, &lcmd.iundefsym)
binary.Read(r, mc.byteorder, &lcmd.nundefsym)
binary.Read(r, mc.byteorder, &lcmd.tocoff)
binary.Read(r, mc.byteorder, &lcmd.ntoc)
binary.Read(r, mc.byteorder, &lcmd.modtaboff)
binary.Read(r, mc.byteorder, &lcmd.nmodtab)
binary.Read(r, mc.byteorder, &lcmd.extrefsymoff)
binary.Read(r, mc.byteorder, &lcmd.nextrefsyms)
binary.Read(r, mc.byteorder, &lcmd.indirectsymoff)
binary.Read(r, mc.byteorder, &lcmd.nindirectsyms)
binary.Read(r, mc.byteorder, &lcmd.extreloff)
binary.Read(r, mc.byteorder, &lcmd.nextrel)
binary.Read(r, mc.byteorder, &lcmd.localrefoff)
binary.Read(r, mc.byteorder, &lcmd.nlocref)
binary.Read(r, mc.byteorder, &lcmd.iundefsym)
binary.Read(r, mc.byteorder, &lcmd.nundefsym)
binary.Read(r, mc.byteorder, &lcmd.tocoff)
binary.Read(r, mc.byteorder, &lcmd.ntoc)
binary.Read(r, mc.byteorder, &lcmd.modtaboff)
binary.Read(r, mc.byteorder, &lcmd.nmodtab)
binary.Read(r, mc.byteorder, &lcmd.extrefsymoff)
binary.Read(r, mc.byteorder, &lcmd.nextrefsyms)
binary.Read(r, mc.byteorder, &lcmd.indirectsymoff)
binary.Read(r, mc.byteorder, &lcmd.nindirectsyms)
binary.Read(r, mc.byteorder, &lcmd.extreloff)
binary.Read(r, mc.byteorder, &lcmd.nextrel)
binary.Read(r, mc.byteorder, &lcmd.localrefoff)
binary.Read(r, mc.byteorder, &lcmd.nlocref)
}

View File

@ -29,11 +29,11 @@ type MachoContext struct {
linkedits []*LinkEdit
segments []Segment
symtab *Symtab
dysymtab *DySymtab
dysymtab *DySymtab
dyldinfo *DyldInfo
fixups *LinkEdit
exports *LinkEdit
dyldinfo *DyldInfo
fixups *LinkEdit
exports *LinkEdit
}
func (mc *MachoContext) FileSize() uint32 {
@ -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
}
@ -235,12 +247,12 @@ func (mc *MachoContext) Parse(r *bufio.Reader) error {
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.linkedits = append(mc.linkedits, lcmd)
if lcmd.Cmd() == LC_DYLD_CHAINED_FIXUPS {
mc.fixups = lcmd
}
if lcmd.Cmd() == LC_DYLD_EXPORTS_TRIE {
mc.exports = lcmd
}
if lcmd.Cmd() == LC_DYLD_CHAINED_FIXUPS {
mc.fixups = lcmd
}
if lcmd.Cmd() == LC_DYLD_EXPORTS_TRIE {
mc.exports = lcmd
}
break
case LC_SYMTAB:
@ -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