Compare commits
21 Commits
main
...
remove-str
Author | SHA1 | Date | |
---|---|---|---|
06bbde2612 | |||
1c495989d4 | |||
083556f914 | |||
88b79bccb9 | |||
9cdf9f0ff5 | |||
7fa3ba0b7d | |||
cc34751c9a | |||
c241e78cd8 | |||
0640d38627 | |||
d9024990f9 | |||
04979b0afd | |||
9b2796b2a1 | |||
925429c4a9 | |||
9b85e4938f | |||
5e601eaa4a | |||
d534d62f5e | |||
d11ef20f4a | |||
8e1e176068 | |||
06525b8a5e | |||
57b0ae26a7 | |||
f795e9b99d |
@ -1,6 +1,6 @@
|
|||||||
module ios-wrapper
|
module ios-wrapper
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/kong v0.2.16
|
github.com/alecthomas/kong v0.2.16
|
||||||
|
20
macho-go/internal/wrapper/action/remove_strings.go
Normal file
20
macho-go/internal/wrapper/action/remove_strings.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "ios-wrapper/internal/wrapper/ofile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type removeStrings struct{}
|
||||||
|
|
||||||
|
func (action *removeStrings) withMacho(mf *MachoFile) error {
|
||||||
|
mf.Context().RemoveStrings()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *removeStrings) withFat(ff *FatFile) error {
|
||||||
|
return defaultWithFat(action, ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoveStringsAction() *removeStrings {
|
||||||
|
return &removeStrings{}
|
||||||
|
}
|
@ -117,6 +117,7 @@ func Cli() {
|
|||||||
pc.remove_inits = true
|
pc.remove_inits = true
|
||||||
pc.remove_codesign = true
|
pc.remove_codesign = true
|
||||||
pc.remove_others = true
|
pc.remove_others = true
|
||||||
|
pc.remove_string = true
|
||||||
}
|
}
|
||||||
pc.remove_imports = arg.RemoveBindSymbols
|
pc.remove_imports = arg.RemoveBindSymbols
|
||||||
pc.remove_codesign = arg.RemoveCodeSign
|
pc.remove_codesign = arg.RemoveCodeSign
|
||||||
@ -124,6 +125,7 @@ func Cli() {
|
|||||||
pc.remove_others = arg.RemoveOthers
|
pc.remove_others = arg.RemoveOthers
|
||||||
pc.remove_exports = arg.RemoveExports
|
pc.remove_exports = arg.RemoveExports
|
||||||
pc.remove_symbol_table = arg.RemoveSymbolTable
|
pc.remove_symbol_table = arg.RemoveSymbolTable
|
||||||
|
pc.remove_string = arg.RemoveStrings
|
||||||
pc.dylib_to_add = arg.Dylibs
|
pc.dylib_to_add = arg.Dylibs
|
||||||
pc.rpath_to_add = arg.Rpath
|
pc.rpath_to_add = arg.Rpath
|
||||||
pc.outfile = arg.Out
|
pc.outfile = arg.Out
|
||||||
@ -264,41 +266,54 @@ func bcell2header(bfile string, header string) {
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(w, "};\n")
|
fmt.Fprintf(w, "};\n")
|
||||||
|
|
||||||
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
if info.Symbols != nil {
|
||||||
fmt.Fprintf(w, "char libs[] =\n")
|
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
||||||
for _, lib := range info.Symbols.Libs {
|
fmt.Fprintf(w, "char libs[] =\n")
|
||||||
fmt.Fprintf(w, " \"%s\\0\"\n", lib)
|
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, "__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)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
||||||
|
fmt.Fprintf(w, "char libs[] = {};\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
||||||
|
fmt.Fprintf(w, "char symbols[] = {};\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
||||||
|
fmt.Fprintf(w, "uint32_t encoded_table[] = {};\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "uint32_t n_instructions = %d;\n", 0)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "};\n")
|
|
||||||
fmt.Fprintf(w, "uint32_t n_instructions = %d;\n", n_instructions)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
|
||||||
fmt.Fprintf(w, "uint32_t special_selectors_idx[] = {\n")
|
fmt.Fprintf(w, "uint32_t special_selectors_idx[] = {\n")
|
||||||
|
@ -61,6 +61,7 @@ type PepeArgument struct {
|
|||||||
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
|
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
|
||||||
Dylibs []string `short:"l" help:"Add more LC_DYLIB"`
|
Dylibs []string `short:"l" help:"Add more LC_DYLIB"`
|
||||||
Rpath []string `short:"r" help:"Add more LC_RPATH"`
|
Rpath []string `short:"r" help:"Add more LC_RPATH"`
|
||||||
|
RemoveStrings bool `default:"false" negatable:"" help:"Remove static strings found in DATA and DATA_CONST section"`
|
||||||
RemoveCodeSign bool `default:"false" negatable:"" help:"Remove LC_CODE_SIGNATURE"`
|
RemoveCodeSign bool `default:"false" negatable:"" help:"Remove LC_CODE_SIGNATURE"`
|
||||||
RemoveExports bool `default:"false" negatable:"" help:"Clear the export table/trie"`
|
RemoveExports bool `default:"false" negatable:"" help:"Clear the export table/trie"`
|
||||||
RemoveSymbolTable bool `default:"false" negatable:"" help:"Remove LC_SYMTAB and LC_DYSYMTAB"`
|
RemoveSymbolTable bool `default:"false" negatable:"" help:"Remove LC_SYMTAB and LC_DYSYMTAB"`
|
||||||
|
@ -46,6 +46,8 @@ func (printer *InfoPrinter) Print() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mc.CollectObjectiveCClasses()
|
||||||
|
|
||||||
fmt.Println("======")
|
fmt.Println("======")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ type ProgramContext struct {
|
|||||||
remove_others bool
|
remove_others bool
|
||||||
remove_exports bool
|
remove_exports bool
|
||||||
remove_symbol_table bool
|
remove_symbol_table bool
|
||||||
|
remove_string bool
|
||||||
dylib_to_add []string
|
dylib_to_add []string
|
||||||
rpath_to_add []string
|
rpath_to_add []string
|
||||||
symbols_keep []string
|
symbols_keep []string
|
||||||
@ -106,6 +107,9 @@ func (pc *ProgramContext) Process(ofile OFile) {
|
|||||||
if pc.remove_exports {
|
if pc.remove_exports {
|
||||||
pc.AddAction(NewRemoveExportsAction())
|
pc.AddAction(NewRemoveExportsAction())
|
||||||
}
|
}
|
||||||
|
if pc.remove_string {
|
||||||
|
pc.AddAction(NewRemoveStringsAction())
|
||||||
|
}
|
||||||
ExperimentalFeature("Remove Unnecessary Info", func() {
|
ExperimentalFeature("Remove Unnecessary Info", func() {
|
||||||
if pc.remove_others {
|
if pc.remove_others {
|
||||||
pc.AddAction(NewRemoveUnnecessaryInfoAction())
|
pc.AddAction(NewRemoveUnnecessaryInfoAction())
|
||||||
|
174
macho-go/pkg/ios/macho/arm.c
Normal file
174
macho-go/pkg/ios/macho/arm.c
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define true 1
|
||||||
|
#define false 0
|
||||||
|
|
||||||
|
|
||||||
|
// this file encodes and decodes arm instructions
|
||||||
|
// only support for ARMv8 or (aarch64) because
|
||||||
|
// it is the default ISA in Apple devices
|
||||||
|
// https://yurichev.com/mirrors/ARMv8-A_Architecture_Reference_Manual_(Issue_A.a).pdf
|
||||||
|
|
||||||
|
// using bit field in little endian is crazy
|
||||||
|
// but clang does not support switching endianess (fuck u llvm)
|
||||||
|
// gcc does, but default on Apple compiler is clang
|
||||||
|
// TODO: default to using big endian for bit field parsing
|
||||||
|
|
||||||
|
// this file assumes everything is LITTLE ENDIAN
|
||||||
|
// the struct is assigned as bit field, and parsed using get_bits
|
||||||
|
// to encode the instruction, can use the bit field and load each bits
|
||||||
|
// through shifting, by default it has 0 padded so should work
|
||||||
|
|
||||||
|
// mask to delete last 12 bits
|
||||||
|
uint32_t ZEROS_12_LOWER = ~0xFFF;
|
||||||
|
|
||||||
|
uint32_t get_bits(uint32_t value, uint32_t from, uint32_t to) {
|
||||||
|
// should assert compiler error
|
||||||
|
if (to < from) return false;
|
||||||
|
if (from == to) {
|
||||||
|
// bit at position is set
|
||||||
|
return (value & (1 << from)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (value << (31 - to)) >> (from + (31 - to));
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_bit_set(uint32_t value, uint32_t at) {
|
||||||
|
return (value & (1 << at)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct add {
|
||||||
|
uint32_t sf : 1;
|
||||||
|
uint32_t op : 1;
|
||||||
|
uint32_t s : 1;
|
||||||
|
uint32_t sig : 5;
|
||||||
|
uint32_t shift : 2;
|
||||||
|
uint32_t imm : 12;
|
||||||
|
uint32_t rn : 5;
|
||||||
|
uint32_t rd : 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct add to_add(uint32_t inst) {
|
||||||
|
struct add parsed;
|
||||||
|
parsed.sf = is_bit_set(inst, 31);
|
||||||
|
parsed.op = is_bit_set(inst, 30);
|
||||||
|
parsed.s = is_bit_set(inst, 29);
|
||||||
|
parsed.sig = get_bits(inst, 24, 28);
|
||||||
|
parsed.shift = get_bits(inst, 22, 23);
|
||||||
|
parsed.imm = get_bits(inst, 10, 21);
|
||||||
|
parsed.rn = get_bits(inst, 5, 9);
|
||||||
|
parsed.rd = get_bits(inst, 0, 4);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_add(struct add parsed, uint32_t *inst) {
|
||||||
|
*inst = 0;
|
||||||
|
*inst |= parsed.sf << 31;
|
||||||
|
*inst |= parsed.op << 30;
|
||||||
|
*inst |= parsed.s << 29;
|
||||||
|
*inst |= parsed.sig << 24;
|
||||||
|
*inst |= parsed.shift << 22;
|
||||||
|
*inst |= parsed.imm << 10;
|
||||||
|
*inst |= parsed.rn << 5;
|
||||||
|
*inst |= parsed.rd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_imm_set(uint32_t *inst, uint32_t offset) {
|
||||||
|
struct add parsed = to_add(*inst);
|
||||||
|
if (parsed.op != 0 || parsed.sig != 0b10001) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parsed.imm = offset; // auto truncate?
|
||||||
|
from_add(parsed, inst);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t add_imm_get(uint32_t inst) {
|
||||||
|
struct add parsed = to_add(inst);
|
||||||
|
if (parsed.shift != 0) {
|
||||||
|
printf("add instruction shift != 0 is not supported\n");
|
||||||
|
*(char*)0 = 0;
|
||||||
|
}
|
||||||
|
return parsed.imm;
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_add(uint32_t inst) {
|
||||||
|
struct add parsed = to_add(inst);
|
||||||
|
if (parsed.op != 0 || parsed.sig != 0b10001) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct adrp {
|
||||||
|
uint32_t op : 1;
|
||||||
|
uint32_t immlo : 2;
|
||||||
|
uint32_t sig : 5;
|
||||||
|
uint32_t immhi : 19;
|
||||||
|
uint32_t rd : 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct adrp to_adrp(uint32_t inst) {
|
||||||
|
struct adrp parsed;
|
||||||
|
parsed.op = is_bit_set(inst, 31);
|
||||||
|
parsed.immlo = get_bits(inst, 29, 30);
|
||||||
|
parsed.sig = get_bits(inst, 24, 28);
|
||||||
|
parsed.immhi = get_bits(inst, 5, 23);
|
||||||
|
parsed.rd = get_bits(inst, 0, 4);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_adrp(struct adrp parsed, uint32_t *inst) {
|
||||||
|
*inst = 0;
|
||||||
|
*inst |= parsed.op << 31;
|
||||||
|
*inst |= parsed.immlo << 29;
|
||||||
|
*inst |= parsed.sig << 24;
|
||||||
|
*inst |= parsed.immhi << 5;
|
||||||
|
*inst |= parsed.rd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_adrp(uint32_t inst) {
|
||||||
|
struct adrp parsed = to_adrp(inst);
|
||||||
|
if (parsed.op != 1 || parsed.sig != 0b10000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change adrp imm to something else
|
||||||
|
int adrp_imm_set(uint32_t *inst, uint32_t offset) {
|
||||||
|
struct adrp parsed = to_adrp(*inst);
|
||||||
|
if (parsed.op != 1 || parsed.sig != 0b10000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint32_t imm = 0;
|
||||||
|
|
||||||
|
// imm = parsed.immhi << 14;
|
||||||
|
// imm |= parsed.immlo << 12;
|
||||||
|
// printf("old adrp is %x\n", imm);
|
||||||
|
// printf(" immlo %x\n", parsed.immlo);
|
||||||
|
// printf(" immhi %x\n", parsed.immhi);
|
||||||
|
|
||||||
|
// adrp: register = (base masked lower 12-bit) + imm
|
||||||
|
parsed.immlo = get_bits(offset >> 12, 0, 1);
|
||||||
|
parsed.immhi = offset >> 14;
|
||||||
|
|
||||||
|
// imm = parsed.immhi << 14;
|
||||||
|
// imm |= parsed.immlo << 12;
|
||||||
|
// printf("new adrp is %x\n", imm);
|
||||||
|
// printf(" immlo %x\n", parsed.immlo);
|
||||||
|
// printf(" immhi %x\n", parsed.immhi);
|
||||||
|
|
||||||
|
from_adrp(parsed, inst);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t adrp_imm_get(uint32_t inst) {
|
||||||
|
struct adrp parsed = to_adrp(inst);
|
||||||
|
uint32_t imm = 0;
|
||||||
|
imm = parsed.immhi << 14;
|
||||||
|
imm |= parsed.immlo << 12;
|
||||||
|
return imm;
|
||||||
|
}
|
7
macho-go/pkg/ios/macho/arm.h
Normal file
7
macho-go/pkg/ios/macho/arm.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
int is_add(uint32_t inst);
|
||||||
|
int add_imm_set(uint32_t *inst, uint32_t offset);
|
||||||
|
uint32_t add_imm_get(uint32_t inst);
|
||||||
|
|
||||||
|
int is_adrp(uint32_t inst);
|
||||||
|
int adrp_imm_set(uint32_t *inst, uint32_t offset);
|
||||||
|
uint32_t adrp_imm_get(uint32_t inst);
|
@ -27,6 +27,9 @@ type ImportSymbol struct {
|
|||||||
file_address uint64
|
file_address uint64
|
||||||
lib_ordinal uint32
|
lib_ordinal uint32
|
||||||
|
|
||||||
|
target uint32
|
||||||
|
high8 uint32
|
||||||
|
|
||||||
// push number
|
// push number
|
||||||
pnum uint32
|
pnum uint32
|
||||||
stub uint64
|
stub uint64
|
||||||
@ -122,16 +125,19 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
|
|||||||
if status == 3 {
|
if status == 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
|
|
||||||
// fmt.Printf("pages=%x\n", fix.pages)
|
fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
|
||||||
|
|
||||||
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
|
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
|
||||||
reader := bytes.NewReader(mc.buf)
|
reader := bytes.NewReader(mc.buf)
|
||||||
for page_i := 0; page_i < int(fix.page_count); page_i++ {
|
for page_i := 0; page_i < int(fix.page_count); page_i++ {
|
||||||
// fmt.Printf(" page offset=%x\n", pages[page_i])
|
// loop through each page in segment, each page has size fix.page_size
|
||||||
|
// the first item in page is offset through pages[page_i]
|
||||||
address := int64(fix.segment) + int64(pages[page_i])
|
address := int64(fix.segment) + int64(page_i)*int64(fix.page_size) + int64(pages[page_i])
|
||||||
reader.Seek(address, io.SeekStart)
|
reader.Seek(address, io.SeekStart)
|
||||||
|
|
||||||
|
fmt.Printf(" page %d offset=%x\n", page_i, address)
|
||||||
|
|
||||||
code := make([]byte, 8)
|
code := make([]byte, 8)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -141,6 +147,10 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
|
|||||||
var bind C.int
|
var bind C.int
|
||||||
var ret1 C.ulonglong
|
var ret1 C.ulonglong
|
||||||
var ret2 C.ulonglong
|
var ret2 C.ulonglong
|
||||||
|
if fix.format != 2 && fix.format != 6 {
|
||||||
|
fmt.Printf("format is %d\n", fix.format)
|
||||||
|
}
|
||||||
|
|
||||||
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
|
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
|
||||||
&bind, &ret1, &ret2)
|
&bind, &ret1, &ret2)
|
||||||
|
|
||||||
@ -164,6 +174,14 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
|
|||||||
syms = append(syms, &new_sym)
|
syms = append(syms, &new_sym)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("// 0x%x rebase=%d target=0x%x high8=0x%x\n", address, bind, ret1, ret2)
|
fmt.Printf("// 0x%x rebase=%d target=0x%x high8=0x%x\n", address, bind, ret1, ret2)
|
||||||
|
sym.typ = "rebase"
|
||||||
|
sym.target = uint32(ret1)
|
||||||
|
sym.high8 = uint32(ret2)
|
||||||
|
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
|
||||||
|
sym.file_address = uint64(address)
|
||||||
|
sym.next = int(next)
|
||||||
|
new_sym := sym
|
||||||
|
syms = append(syms, &new_sym)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(next) == 0 {
|
if int(next) == 0 {
|
||||||
@ -176,6 +194,7 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
|
|||||||
reader.Seek(0, io.SeekStart)
|
reader.Seek(0, io.SeekStart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fmt.Printf("number of imports %d\n", len(syms))
|
||||||
return syms
|
return syms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
. "ios-wrapper/pkg/ios"
|
. "ios-wrapper/pkg/ios"
|
||||||
@ -16,6 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// #include "fixups.h"
|
// #include "fixups.h"
|
||||||
|
// #include "arm.h"
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
|
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
|
||||||
@ -194,6 +198,29 @@ func (mc *MachoContext) RemoveUnnecessaryInfo() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mc *MachoContext) RewriteHeader() {
|
||||||
|
var offset uint64
|
||||||
|
var start uint64
|
||||||
|
|
||||||
|
if mc.Is64bit() {
|
||||||
|
start = Header_size_64
|
||||||
|
} else {
|
||||||
|
start = Header_size
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.file.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
|
offset = start
|
||||||
|
for _, cmd := range mc.commands {
|
||||||
|
nwrite, _ := mc.file.WriteAt(cmd.Serialize(mc), int64(offset))
|
||||||
|
offset += uint64(nwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.header.ncmds = uint32(len(mc.commands))
|
||||||
|
mc.header.sizeofcmds = uint32(offset - start)
|
||||||
|
mc.file.WriteAt(mc.header.Serialize(mc), 0)
|
||||||
|
}
|
||||||
|
|
||||||
func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) {
|
func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) {
|
||||||
var offset uint64
|
var offset uint64
|
||||||
payload := lcmd.Serialize(mc)
|
payload := lcmd.Serialize(mc)
|
||||||
@ -318,6 +345,7 @@ func (mc *MachoContext) RemoveBindSymbols() {
|
|||||||
value := C.MakeRebaseFixupOpcode(C.int(symbol.next), C.ulonglong(target), C.ulonglong(high8))
|
value := C.MakeRebaseFixupOpcode(C.int(symbol.next), C.ulonglong(target), C.ulonglong(high8))
|
||||||
v := make([]byte, 8)
|
v := make([]byte, 8)
|
||||||
mc.byteorder.PutUint64(v, uint64(value))
|
mc.byteorder.PutUint64(v, uint64(value))
|
||||||
|
fmt.Printf("change to rebase at %x\n", symbol.file_address)
|
||||||
mc.file.WriteAt(v, int64(symbol.file_address))
|
mc.file.WriteAt(v, int64(symbol.file_address))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -534,6 +562,328 @@ func (mc *MachoContext) RemoveExportTrie() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this function breaks the file by adding another segment at
|
||||||
|
// the end of the file
|
||||||
|
func (mc *MachoContext) RemoveStrings() {
|
||||||
|
// add a new writable segment with a section
|
||||||
|
// loop over the instructions for adrp,add instructions
|
||||||
|
// if the access points to old cstring section, update
|
||||||
|
// with new values from the new segment and section.
|
||||||
|
|
||||||
|
// data references, e.g., pointer to string, are compiled
|
||||||
|
// into Rebase symbol, so actively check for rebase and
|
||||||
|
// rewrite the rebase value to new segment, section offset.
|
||||||
|
|
||||||
|
// save the strings into a file for recovery. should keep linear
|
||||||
|
// format as before, or customized order, if want complex.
|
||||||
|
|
||||||
|
// __LINKEDIT must be at the end of the section for the binary
|
||||||
|
// to be able to resign, so we have to move __LINKEDIT down
|
||||||
|
// by how much? by the page aligned size of the added segment
|
||||||
|
|
||||||
|
// but __LINKEDIT also contains data for link time data
|
||||||
|
// (fixed ups and bytecode chain) so have to modify
|
||||||
|
// their reference down too,
|
||||||
|
// symtab and dysymtab can be ignored, by removing them lmao
|
||||||
|
|
||||||
|
var cstring *Section64
|
||||||
|
for _, command := range mc.commands {
|
||||||
|
switch command.(type) {
|
||||||
|
case *Segment64:
|
||||||
|
var segment = command.(*Segment64)
|
||||||
|
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
|
||||||
|
for _, section := range segment.Sections() {
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__cstring")) == 0 {
|
||||||
|
cstring = section.(*Section64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cstring == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get last segment, as the start point we extend from
|
||||||
|
// this assumes that the segment are ordered correctly,
|
||||||
|
// first segment offset is lower then second segment offset,
|
||||||
|
// and so on, yielding last segment is the last part of the
|
||||||
|
// binary. Our new segment add another part to the binary
|
||||||
|
// at the end.
|
||||||
|
|
||||||
|
// last segment is always the linkedits
|
||||||
|
last_segment := mc.Segments()[len(mc.Segments())-1]
|
||||||
|
fmt.Printf("last segment %v %s\n", last_segment, string(last_segment.SegName()))
|
||||||
|
|
||||||
|
// all data must be inside the segment (or in header)
|
||||||
|
|
||||||
|
// occupy the segment of linkedit and move linkedit down
|
||||||
|
|
||||||
|
segstart := last_segment.Vmaddr()
|
||||||
|
|
||||||
|
// segment must be page aligned, and the size is based on
|
||||||
|
// the section size
|
||||||
|
secstart := segstart
|
||||||
|
secsize := cstring.Size()
|
||||||
|
|
||||||
|
filestart := last_segment.Fileoff()
|
||||||
|
// align to page address, not sure if neccessary, because the
|
||||||
|
// loader can pick up from anywhere and load in memory (mmap)
|
||||||
|
if filestart%0x4000 != 0 {
|
||||||
|
filestart += 0x4000 - (filestart % 0x4000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("section size %x\n", secsize)
|
||||||
|
secsize_aligned := uint64(0)
|
||||||
|
if secsize%0x4000 == 0 {
|
||||||
|
// very rare, but possible, it occupies whole pages
|
||||||
|
secsize_aligned = secsize
|
||||||
|
} else {
|
||||||
|
secsize_aligned = secsize + (0x4000 - (secsize % 0x4000))
|
||||||
|
}
|
||||||
|
filesize := secsize_aligned
|
||||||
|
|
||||||
|
segname := make([]byte, 16)
|
||||||
|
copy(segname, []byte("__BSHIELD"))
|
||||||
|
secname := make([]byte, 16)
|
||||||
|
copy(secname, []byte("__secrets"))
|
||||||
|
|
||||||
|
fmt.Printf("segstart %x\n", segstart)
|
||||||
|
fmt.Printf("file_start %x\n", filestart)
|
||||||
|
|
||||||
|
// size of the section and segment is defined by the total
|
||||||
|
// space for strings required
|
||||||
|
|
||||||
|
new_cstring_section := Section64{
|
||||||
|
sectname: secname,
|
||||||
|
segname: segname,
|
||||||
|
addr: secstart,
|
||||||
|
size: secsize,
|
||||||
|
offset: uint32(filestart),
|
||||||
|
align: 3, // idk, see AddSection
|
||||||
|
reloff: 0,
|
||||||
|
nreloc: 0,
|
||||||
|
flags: 0,
|
||||||
|
reserved1: 0,
|
||||||
|
reserved2: 0,
|
||||||
|
reserved3: 0,
|
||||||
|
}
|
||||||
|
string_segment := Segment64{
|
||||||
|
c: LoadCmd{cmd: LC_SEGMENT_64, cmdsize: 0},
|
||||||
|
segname: segname,
|
||||||
|
vmaddr: segstart,
|
||||||
|
vmsize: secsize_aligned,
|
||||||
|
fileoff: filestart,
|
||||||
|
filesize: filesize,
|
||||||
|
maxprot: 3, // read/write
|
||||||
|
initprot: 3, // read/write
|
||||||
|
nsects: 1,
|
||||||
|
flags: 0,
|
||||||
|
sections: []*Section64{&new_cstring_section},
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite the header to be correct
|
||||||
|
// mc.AddLoadCmd(&string_segment)
|
||||||
|
|
||||||
|
edit_segname := make([]byte, 16)
|
||||||
|
copy(edit_segname, []byte("__LINKEDIT"))
|
||||||
|
edit_segment := Segment64{
|
||||||
|
c: LoadCmd{cmd: LC_SEGMENT_64, cmdsize: 0},
|
||||||
|
segname: edit_segname,
|
||||||
|
vmaddr: segstart + secsize_aligned, // move down
|
||||||
|
vmsize: last_segment.Vmsize(),
|
||||||
|
fileoff: filestart + filesize,
|
||||||
|
filesize: last_segment.Filesize(),
|
||||||
|
maxprot: 1, // read/write
|
||||||
|
initprot: 1, // read/write
|
||||||
|
nsects: 0,
|
||||||
|
flags: 0,
|
||||||
|
sections: []*Section64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify the segment list
|
||||||
|
mc.segments[len(mc.segments)-1] = &string_segment
|
||||||
|
mc.segments = append(mc.segments, &edit_segment)
|
||||||
|
|
||||||
|
// modify the command list
|
||||||
|
for i, cmd := range mc.commands {
|
||||||
|
if cmd.(*Segment64) == last_segment.(*Segment64) {
|
||||||
|
mc.commands = append(mc.commands[:i+1], mc.commands[i:]...)
|
||||||
|
mc.commands[i] = &string_segment
|
||||||
|
mc.commands[i+1] = &edit_segment
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify offset in other commands to use new link edit offset
|
||||||
|
|
||||||
|
edit_offset_migrate := func(file_offset uint64) uint64 {
|
||||||
|
// they should keep the old offset,
|
||||||
|
// but the base related to linkedit is modified
|
||||||
|
relative_offset := file_offset - last_segment.Fileoff()
|
||||||
|
return relative_offset + edit_segment.Fileoff()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmd := range mc.commands {
|
||||||
|
if lcmd, ok := cmd.(*LinkEdit); ok {
|
||||||
|
lcmd.dataoff = uint32(edit_offset_migrate(uint64(lcmd.dataoff)))
|
||||||
|
}
|
||||||
|
if lcmd, ok := cmd.(*Symtab); ok {
|
||||||
|
lcmd.stroff = uint32(edit_offset_migrate(uint64(lcmd.stroff)))
|
||||||
|
lcmd.symoff = uint32(edit_offset_migrate(uint64(lcmd.symoff)))
|
||||||
|
}
|
||||||
|
if lcmd, ok := cmd.(*DySymtab); ok {
|
||||||
|
lcmd.indirectsymoff = uint32(edit_offset_migrate(uint64(lcmd.indirectsymoff)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.RewriteHeader()
|
||||||
|
|
||||||
|
tmp_file := mc.file.Name()
|
||||||
|
|
||||||
|
// has to reopen file as append
|
||||||
|
mc.file.Close()
|
||||||
|
mc.file, _ = os.OpenFile(tmp_file, os.O_RDWR|os.O_APPEND, 0644)
|
||||||
|
|
||||||
|
// make extra space
|
||||||
|
expected_end := edit_segment.Fileoff() + edit_segment.Filesize()
|
||||||
|
end, _ := mc.file.Seek(0, io.SeekEnd)
|
||||||
|
if end < int64(expected_end) {
|
||||||
|
mc.file.WriteAt(make([]byte, expected_end-uint64(end)), end)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close and reopen as read/write, the buffer at the end is now empty
|
||||||
|
mc.file.Close()
|
||||||
|
mc.file, _ = os.OpenFile(tmp_file, os.O_RDWR, 0644)
|
||||||
|
|
||||||
|
// peek at old link edit and move down
|
||||||
|
old_linkedit := make([]byte, last_segment.Filesize())
|
||||||
|
mc.file.ReadAt(old_linkedit, int64(last_segment.Fileoff()))
|
||||||
|
mc.file.WriteAt(old_linkedit, int64(edit_segment.Fileoff()))
|
||||||
|
|
||||||
|
// prepare dummy bytes into new string segment, 0 for now
|
||||||
|
// this is a way to divert their effort, writing fake strings
|
||||||
|
// will be written again at runtime
|
||||||
|
dummy := make([]byte, edit_segment.Fileoff()-string_segment.Fileoff())
|
||||||
|
mc.file.ReadAt(dummy, int64(cstring.Offset()))
|
||||||
|
// copy(dummy, []byte("We R BShield\n"))
|
||||||
|
for i := 0; i < len(dummy); i++ {
|
||||||
|
dummy[i] = dummy[i] ^ 0x4f
|
||||||
|
}
|
||||||
|
mc.file.WriteAt(dummy, int64(string_segment.Fileoff()))
|
||||||
|
|
||||||
|
// TODO: erase old strings
|
||||||
|
cstring_start := uint64(cstring.Offset())
|
||||||
|
random := make([]byte, cstring.Size())
|
||||||
|
rand.Read(random)
|
||||||
|
mc.file.WriteAt(random, int64(cstring_start))
|
||||||
|
|
||||||
|
// re-read internal buffer
|
||||||
|
last, _ := mc.file.Seek(0, io.SeekEnd)
|
||||||
|
mc.buf = make([]byte, last)
|
||||||
|
mc.file.Seek(0, io.SeekStart)
|
||||||
|
if _, err := io.ReadFull(mc.file, mc.buf); err != nil {
|
||||||
|
// panic?
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop over __TEXT,__text and find all occurances of (adrp, add)
|
||||||
|
// edit the offset to points to new region
|
||||||
|
// because adrp sets the register to the address page at its address
|
||||||
|
// (for page align 0x4000), e.g.,
|
||||||
|
// `adrp x0` instruction at 0x100003f70, yields x0 = 0x100003000
|
||||||
|
// technically, adrp can offset as far as 33-bit, roughly 4GB memory
|
||||||
|
// so we should be very free, because very few program goes this far
|
||||||
|
// but if this happens, god bless you
|
||||||
|
|
||||||
|
// encoding ADRP is actually hard hmmge?
|
||||||
|
|
||||||
|
// this part uses file offsets for calculations
|
||||||
|
|
||||||
|
in_cstring := func(offset uint64) bool {
|
||||||
|
cstring_start := uint64(cstring.Offset())
|
||||||
|
cstring_end := cstring_start + cstring.Size()
|
||||||
|
return (offset >= cstring_start) && (offset < cstring_end)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := mc.segments[1]
|
||||||
|
text_start := text.Fileoff()
|
||||||
|
text_end := text_start + text.Filesize()
|
||||||
|
|
||||||
|
inst := make([]byte, 4)
|
||||||
|
for addr := text_start; addr < text_end; addr = addr + 4 {
|
||||||
|
mc.file.ReadAt(inst, int64(addr))
|
||||||
|
inst_adrp := binary.LittleEndian.Uint32(inst)
|
||||||
|
mc.file.ReadAt(inst, int64(addr+4))
|
||||||
|
inst_add := binary.LittleEndian.Uint32(inst)
|
||||||
|
|
||||||
|
if !(C.is_adrp(C.uint(inst_adrp)) != 0 && C.is_add(C.uint(inst_add)) != 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
base := (addr >> 12) << 12
|
||||||
|
|
||||||
|
// calculate the old string reference
|
||||||
|
ref_base := C.adrp_imm_get(C.uint(inst_adrp))
|
||||||
|
ref_offset := C.add_imm_get(C.uint(inst_add))
|
||||||
|
ref := base + uint64(ref_base+ref_offset)
|
||||||
|
|
||||||
|
if !in_cstring(ref) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
oldstr := uint64(ref)
|
||||||
|
oldstr_relative := oldstr - uint64(cstring.Offset())
|
||||||
|
|
||||||
|
// find the new string address
|
||||||
|
// using oldstr relative address to cstring section
|
||||||
|
newstr := uint64(new_cstring_section.Offset()) + oldstr_relative
|
||||||
|
newstr_base := (newstr >> 12) << 12 // to calculate new offset in adrp
|
||||||
|
newstr_offset := newstr - newstr_base // to calculate new offset in add
|
||||||
|
|
||||||
|
C.adrp_imm_set((*C.uint32_t)(unsafe.Pointer(&inst_adrp)), C.uint(newstr_base-base))
|
||||||
|
C.add_imm_set((*C.uint32_t)(unsafe.Pointer(&inst_add)), C.uint(newstr_offset))
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(inst, inst_adrp)
|
||||||
|
mc.file.WriteAt(inst, int64(addr))
|
||||||
|
binary.LittleEndian.PutUint32(inst, inst_add)
|
||||||
|
mc.file.WriteAt(inst, int64(addr+4))
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify the rebase table (for both opcode and fixups chain versions)
|
||||||
|
// this is for pointer references
|
||||||
|
|
||||||
|
isModernSymbol := mc.dyldinfo == nil
|
||||||
|
isLegacySymbol := !isModernSymbol
|
||||||
|
for _, symbol := range mc.CollectBindSymbols() {
|
||||||
|
if isLegacySymbol {
|
||||||
|
} else {
|
||||||
|
// (high8 << 56 | target) - mach_header
|
||||||
|
|
||||||
|
ref := uint64(symbol.high8<<56 | symbol.target)
|
||||||
|
if !in_cstring(ref) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
oldstr := ref
|
||||||
|
oldstr_relative := oldstr - uint64(cstring.Offset())
|
||||||
|
newstr := uint64(new_cstring_section.Offset()) + oldstr_relative
|
||||||
|
|
||||||
|
target := newstr & 0x00FFFFFFFFFFFFFF
|
||||||
|
high8 := newstr >> 56
|
||||||
|
value := C.MakeRebaseFixupOpcode(C.int(symbol.next), C.ulonglong(target), C.ulonglong(high8))
|
||||||
|
v := make([]byte, 8)
|
||||||
|
mc.byteorder.PutUint64(v, uint64(value))
|
||||||
|
fmt.Printf("string rebase change at %x\n", symbol.file_address)
|
||||||
|
mc.file.WriteAt(v, int64(symbol.file_address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (mc *MachoContext) AddSection(segname string, name string, size int) Section {
|
func (mc *MachoContext) AddSection(segname string, name string, size int) Section {
|
||||||
// mc.file.WriteAt(mc.header.Serialize(mc), 0)
|
// mc.file.WriteAt(mc.header.Serialize(mc), 0)
|
||||||
var ret Section
|
var ret Section
|
||||||
|
@ -54,6 +54,7 @@ int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix) {
|
|||||||
fix->format = chain_header->pointer_format;
|
fix->format = chain_header->pointer_format;
|
||||||
fix->page_count = chain_header->page_count;
|
fix->page_count = chain_header->page_count;
|
||||||
fix->pages = chain_header->page_start;
|
fix->pages = chain_header->page_start;
|
||||||
|
fix->page_size = chain_header->page_size;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ struct SegmentFix {
|
|||||||
uint64_t segment;
|
uint64_t segment;
|
||||||
uint32_t format;
|
uint32_t format;
|
||||||
uint32_t page_count;
|
uint32_t page_count;
|
||||||
|
uint16_t page_size;
|
||||||
uint16_t* pages;
|
uint16_t* pages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ func (lcmd *Segment64) Vmaddr() uint64 {
|
|||||||
return lcmd.vmaddr
|
return lcmd.vmaddr
|
||||||
}
|
}
|
||||||
func (lcmd *Segment64) Vmsize() uint64 {
|
func (lcmd *Segment64) Vmsize() uint64 {
|
||||||
return lcmd.vmaddr
|
return lcmd.vmsize
|
||||||
}
|
}
|
||||||
func (lcmd *Segment64) Fileoff() uint64 {
|
func (lcmd *Segment64) Fileoff() uint64 {
|
||||||
return lcmd.fileoff
|
return lcmd.fileoff
|
||||||
|
@ -6,10 +6,163 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
. "ios-wrapper/pkg/ios"
|
. "ios-wrapper/pkg/ios"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// #include "fixups.h"
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func (mc *MachoContext) CollectObjectiveCClasses() {
|
||||||
|
var objc_const *bytes.Reader
|
||||||
|
var objc_const_start uint64
|
||||||
|
var objc_const_end uint64
|
||||||
|
// var objc_methname []byte
|
||||||
|
|
||||||
|
for _, cmd := range mc.commands {
|
||||||
|
if cmd.Cmd() == LC_MAIN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cmd.Cmd() != LC_SEGMENT_64 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var segment = cmd.(*Segment64)
|
||||||
|
|
||||||
|
// we assume the binary comes in perfect ordering, that is as laid out below
|
||||||
|
|
||||||
|
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
|
||||||
|
for _, section := range segment.Sections() {
|
||||||
|
buffer := make([]byte, section.Size())
|
||||||
|
mc.file.ReadAt(buffer, int64(section.Offset()))
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_stubs")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methlist")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
|
||||||
|
// objc_methname := buffer
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classname")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methtype")) == 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 {
|
||||||
|
for _, section := range segment.Sections() {
|
||||||
|
buffer := make([]byte, section.Size())
|
||||||
|
mc.file.ReadAt(buffer, int64(section.Offset()))
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classlist")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_imageinfo")) == 0 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 {
|
||||||
|
for _, section := range segment.Sections() {
|
||||||
|
buffer := make([]byte, section.Size())
|
||||||
|
mc.file.ReadAt(buffer, int64(section.Offset()))
|
||||||
|
reader := bytes.NewReader(buffer)
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_const")) == 0 {
|
||||||
|
objc_const = reader
|
||||||
|
objc_const_start = uint64(section.Offset())
|
||||||
|
objc_const_end = objc_const_start + section.Size()
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classrefs")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_superrefs")) == 0 {
|
||||||
|
}
|
||||||
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_data")) == 0 {
|
||||||
|
// this section contains a series of class_t
|
||||||
|
// struct _class_t {
|
||||||
|
// struct _class_t *isa;
|
||||||
|
// struct _class_t * const superclass;
|
||||||
|
// void *cache;
|
||||||
|
// IMP *vtable;
|
||||||
|
// struct class_ro_t *ro;
|
||||||
|
// };
|
||||||
|
|
||||||
|
for i := uint64(0); i < (section.Size() / uint64(mc.pointersize*5)); i++ {
|
||||||
|
var isa uint64
|
||||||
|
var superclass uint64
|
||||||
|
var cache uint64
|
||||||
|
var vtable uint64
|
||||||
|
var ro uint64
|
||||||
|
binary.Read(reader, mc.byteorder, &isa)
|
||||||
|
binary.Read(reader, mc.byteorder, &superclass)
|
||||||
|
binary.Read(reader, mc.byteorder, &cache)
|
||||||
|
binary.Read(reader, mc.byteorder, &vtable)
|
||||||
|
binary.Read(reader, mc.byteorder, &ro)
|
||||||
|
|
||||||
|
fmt.Printf("at=0x%x\n", section.Offset()+uint32(i)*mc.pointersize*5)
|
||||||
|
fmt.Printf("isa=0x%x superclass=0x%x\n", isa, superclass)
|
||||||
|
fmt.Printf("cache=0x%x vtable=0x%x\n", cache, vtable)
|
||||||
|
fmt.Printf("ro=0x%x\n", ro)
|
||||||
|
|
||||||
|
var bind int
|
||||||
|
var ret1 uint64
|
||||||
|
var ret2 uint64
|
||||||
|
C.ParseFixValue(C.int(2), C.uint64_t(ro),
|
||||||
|
(*C.int)(unsafe.Pointer(&bind)),
|
||||||
|
(*C.uint64_t)(unsafe.Pointer(&ret1)),
|
||||||
|
(*C.uint64_t)(unsafe.Pointer(&ret2)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// is rebase, because ro points to objc_const
|
||||||
|
// and address is in range
|
||||||
|
if bind != 1 && ret1 >= objc_const_start && ret1 < objc_const_end {
|
||||||
|
offset := ret1 - objc_const_start
|
||||||
|
objc_const.Seek(int64(offset), 0)
|
||||||
|
|
||||||
|
// struct _class_ro_t {
|
||||||
|
// uint32_t const flags;
|
||||||
|
// uint32_t const instanceStart;
|
||||||
|
// uint32_t const instanceSize;
|
||||||
|
// uint32_t const reserved; // only when building for 64bit targets
|
||||||
|
// const uint8_t * const ivarLayout;
|
||||||
|
// const char *const name;
|
||||||
|
// const struct _method_list_t * const baseMethods;
|
||||||
|
// const struct _protocol_list_t *const baseProtocols;
|
||||||
|
// const struct _ivar_list_t *const ivars;
|
||||||
|
// const uint8_t * const weakIvarLayout;
|
||||||
|
// const struct _prop_list_t * const properties;
|
||||||
|
// };
|
||||||
|
|
||||||
|
var tmp uint32
|
||||||
|
var ivarLayout uint64 // ptr
|
||||||
|
var name uint64 // ptr
|
||||||
|
var baseMethods uint64 // ptr
|
||||||
|
var baseProtocols uint64 // ptr
|
||||||
|
var ivars uint64 // ptr
|
||||||
|
var weakIvarLayout uint64 // ptr
|
||||||
|
var properties uint64 // ptr
|
||||||
|
binary.Read(objc_const, mc.byteorder, &tmp)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &tmp)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &tmp)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &tmp)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &ivarLayout)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &name)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &baseMethods)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &baseProtocols)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &ivars)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &weakIvarLayout)
|
||||||
|
binary.Read(objc_const, mc.byteorder, &properties)
|
||||||
|
|
||||||
|
fmt.Printf("method list: %x\n", baseMethods)
|
||||||
|
}
|
||||||
|
fmt.Printf("========\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type SpecialSelector struct {
|
type SpecialSelector struct {
|
||||||
idx uint
|
idx uint
|
||||||
name string
|
name string
|
||||||
@ -30,8 +183,12 @@ func (sel *SpecialSelector) Name() string {
|
|||||||
// we currently have the following symbols guaranteed to be in this list:
|
// we currently have the following symbols guaranteed to be in this list:
|
||||||
// - load
|
// - load
|
||||||
// - retain
|
// - retain
|
||||||
|
//
|
||||||
|
// besides special selectors, selectors of outside classes must also be
|
||||||
|
// registered through the cache
|
||||||
|
// selectors of outside classes are defined as not being referenced by
|
||||||
|
// internal classes in __objc_data
|
||||||
func (mc *MachoContext) CollectSpecialSelectors() []*SpecialSelector {
|
func (mc *MachoContext) CollectSpecialSelectors() []*SpecialSelector {
|
||||||
|
|
||||||
var special_selectors []*SpecialSelector
|
var special_selectors []*SpecialSelector
|
||||||
var methods []byte
|
var methods []byte
|
||||||
var methname_offset uint32
|
var methname_offset uint32
|
||||||
@ -137,7 +294,7 @@ func (mc *MachoContext) ReworkForObjc() {
|
|||||||
// selector should points to this load selector to make objc thinks that it's "load"
|
// selector should points to this load selector to make objc thinks that it's "load"
|
||||||
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
|
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
|
||||||
// mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr)
|
// mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr)
|
||||||
mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset()))
|
// mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset()))
|
||||||
}
|
}
|
||||||
section_ptr += 16*2 + 8*2 + 4*8
|
section_ptr += 16*2 + 8*2 + 4*8
|
||||||
}
|
}
|
||||||
@ -324,7 +481,7 @@ func (mc *MachoContext) ReworkForObjc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start)))
|
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start)) + 3)
|
||||||
|
|
||||||
shellcode_offset = text_start - shellcode_size
|
shellcode_offset = text_start - shellcode_size
|
||||||
shellcode_bytes := append(shellcode_start, offset...)
|
shellcode_bytes := append(shellcode_start, offset...)
|
||||||
@ -360,4 +517,8 @@ func (mc *MachoContext) ReworkForObjc() {
|
|||||||
mc.file.WriteAt(bs, offset)
|
mc.file.WriteAt(bs, offset)
|
||||||
offset += 4
|
offset += 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make __TEXT writable lol
|
||||||
|
mc.file.Seek(0, 0)
|
||||||
|
mc.file.WriteAt([]byte{0x7}, 0xa0)
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,19 @@
|
|||||||
#include <objc/message.h>
|
#include <objc/message.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void* sel_lookUpByName(const char* name);
|
||||||
|
|
||||||
|
|
||||||
@interface Foo : NSObject
|
@interface Foo : NSObject
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation Foo
|
@implementation Foo
|
||||||
- (void)bar {
|
- (void)bar {
|
||||||
NSLog(@"[Foo bar]: %@", self);
|
NSLog(@"Invoke instance method original bar in Foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tobehijacked:(NSString*)input {
|
||||||
|
NSLog(@"Invoke tobehijacked method %@ from Foo", input);
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -18,48 +25,99 @@
|
|||||||
static int x;
|
static int x;
|
||||||
|
|
||||||
+ (void)load {
|
+ (void)load {
|
||||||
NSLog(@"%@", self);
|
|
||||||
// NSLog(@"x=%d", x)
|
|
||||||
printf("printf in [Bar load]\n");
|
|
||||||
x = 1;
|
x = 1;
|
||||||
|
printf("Invoke +load method\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dummy {
|
- (void)dummy {
|
||||||
NSLog(@"dummy bar x=%d", x);
|
NSLog(@"Static value check after +load should be 1: x=%d", x);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@interface FakeNSDateFormatter : NSDateFormatter {
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation FakeNSDateFormatter
|
||||||
|
- (NSDate*)dateFromString:(NSString*)dateString {
|
||||||
|
NSLog(@"Hijacked the NSDateFormatter");
|
||||||
|
return [super dateFromString:dateString];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
__attribute__((constructor)) static void
|
__attribute__((constructor)) static void
|
||||||
hmmge(int argc, char** argv) {
|
hmmge(int argc, char** argv) {
|
||||||
// create a dummy blank function to be replaced to call OBJC load
|
printf("Invoke C constructor\n");
|
||||||
printf("hmmge=%p\n", hmmge);
|
printf("Checking for arguments to be passed correctly\n");
|
||||||
printf("hmmge argc=%d\n", argc);
|
printf(" argc=%d\n", argc);
|
||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
printf(" hmmge argv[%d]=%s\n", i, argv[i]);
|
printf(" argv[%d]=%s\n", i, argv[i]);
|
||||||
}
|
}
|
||||||
NSLog(@"hmmge in objc-c");
|
NSLog(@"Using Objective-C in C constructor");
|
||||||
|
NSLog(@"Test static Objective-C class is initialized and +load completed");
|
||||||
Bar *bar = [[Bar alloc] init];
|
Bar *bar = [[Bar alloc] init];
|
||||||
[bar dummy];
|
[bar dummy];
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char * argv[]) {
|
int main(int argc, const char * argv[], char* envp[]) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSLog(@"main()");
|
NSLog(@"Invoke main()");
|
||||||
NSLog(@"selector for \"bar:\" %p", @selector(bar:));
|
|
||||||
|
|
||||||
|
// Foo bar using Objective-C syntax
|
||||||
Foo *foo = [[Foo alloc] init];
|
Foo *foo = [[Foo alloc] init];
|
||||||
[foo bar];
|
[foo bar];
|
||||||
|
|
||||||
NSLog(@"directly call \"bar\" %p through objc_msgSend %p with object foo %p\n", @selector(bar), objc_msgSend, foo);
|
// Foo bar with selector and msgSend
|
||||||
|
NSLog(@"Directly call \"bar\" %p through objc_msgSend %p with object foo %p", @selector(bar), objc_msgSend, foo);
|
||||||
typedef void (*barfunc)(id, SEL);
|
typedef void (*barfunc)(id, SEL);
|
||||||
barfunc bar_ = (barfunc)&objc_msgSend;
|
barfunc bar_ = (barfunc)&objc_msgSend;
|
||||||
bar_(foo, @selector(bar));
|
bar_(foo, @selector(bar));
|
||||||
|
|
||||||
|
NSString *dummyinput = @"dummy input";
|
||||||
|
[foo tobehijacked:dummyinput];
|
||||||
|
NSLog(@"The above invocation should be hijacked with input at %p", dummyinput);
|
||||||
|
|
||||||
|
NSString *dateString = @"2024-01-01T00:00:00.000Z";
|
||||||
|
|
||||||
|
// NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||||
|
// [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
|
||||||
|
// NSDate *date = [dateFormatter dateFromString:dateString];
|
||||||
|
|
||||||
|
// this is to test the idea for hooking,
|
||||||
|
// basically, we create a middle-class inherits the class to be used
|
||||||
|
//
|
||||||
|
// example using NSDateFormatter:
|
||||||
|
// - Create a FakeNSDateFormatter inherits NSDateFormatter
|
||||||
|
// - Have an overloaded function that calls [super inherited]
|
||||||
|
// - The internal struct class_t has superclass points to NSDateFormatter
|
||||||
|
// FakeNSDateFormatter *dateFormatter = [[FakeNSDateFormatter alloc] init];
|
||||||
|
// [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
|
||||||
|
// NSDate *date = [dateFormatter dateFromString:dateString];
|
||||||
|
// NSLog(@"Test hijacked/hooked Objective-C result %@", date);
|
||||||
|
|
||||||
|
NSLog(@"Selector \"dateFromString:\" using @selector %p", @selector(dateFromString:));
|
||||||
|
NSLog(@"Selector \"bar:\" using @selector %p", @selector(bar:));
|
||||||
|
NSLog(@"Selector \"dummy\" using @selector %p", @selector(dummy));
|
||||||
|
|
||||||
|
NSLog(@"[Bar dummy] implementation is at %p\n", [foo methodForSelector:@selector(bar:)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("argc=%d\n", argc);
|
printf("Selector lookup 'dateFromString:' addr: %p\n", sel_lookUpByName("dateFromString:"));
|
||||||
|
printf("Selector lookup 'bar:' addr: %p\n", sel_lookUpByName("bar:"));
|
||||||
|
printf("Selector lookup 'dummy' addr: %p\n", sel_lookUpByName("dummy"));
|
||||||
|
|
||||||
|
printf("Test if arguments are passed correctly to main(argc, argv, env)\n");
|
||||||
|
printf(" argc=%d\n", argc);
|
||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
printf(" argv[%d]=%s\n", i, argv[i]);
|
printf(" argv[%d]=%s\n", i, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*envp) {
|
||||||
|
printf(" env[]=%s\n", *envp);
|
||||||
|
envp++;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,17 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// #include <Foundation/Foundation.h>
|
||||||
|
#include <objc/objc.h>
|
||||||
|
|
||||||
#include "out/b.h"
|
#include "out/b.h"
|
||||||
|
|
||||||
char *pwd;
|
char *pwd;
|
||||||
uint32_t pwd_len;
|
uint32_t pwd_len;
|
||||||
|
clock_t start, end;
|
||||||
|
#define ISARM(header) ((*((uint32_t *)(header)+1) & 0xff) == 0xc)
|
||||||
|
|
||||||
int custom_strcmp(const char *p1, const char *p2) {
|
int custom_strcmp(const char *p1, const char *p2) {
|
||||||
const unsigned char *s1 = (const unsigned char *)p1;
|
const unsigned char *s1 = (const unsigned char *)p1;
|
||||||
@ -813,6 +819,7 @@ void test(struct libcache &cache);
|
|||||||
__attribute__((constructor)) static void
|
__attribute__((constructor)) static void
|
||||||
bruh(int argc, const char *const argv[], const char *const envp[],
|
bruh(int argc, const char *const argv[], const char *const envp[],
|
||||||
const char *const apple[], const struct ProgramVars *vars) {
|
const char *const apple[], const struct ProgramVars *vars) {
|
||||||
|
start = clock();
|
||||||
printf("=== manual symbol bind starts ===\n");
|
printf("=== manual symbol bind starts ===\n");
|
||||||
// set_cwd(envp);
|
// set_cwd(envp);
|
||||||
|
|
||||||
@ -906,16 +913,16 @@ void build_cache(struct libcache &cache, void *main) {
|
|||||||
typedef char *(*dyld_get_image_name_t)(int);
|
typedef char *(*dyld_get_image_name_t)(int);
|
||||||
typedef void *(*dyld_get_image_header_t)(int);
|
typedef void *(*dyld_get_image_header_t)(int);
|
||||||
|
|
||||||
char *dyld_image_count_s = (char*)"__dyld_image_count";
|
char *dyld_image_count_s = (char *)"__dyld_image_count";
|
||||||
int (*dyld_image_count_func)(void) = (dyld_image_count_t)find_in_export_trie(
|
int (*dyld_image_count_func)(void) = (dyld_image_count_t)find_in_export_trie(
|
||||||
libdyld, libdyld_export_trie, dyld_image_count_s);
|
libdyld, libdyld_export_trie, dyld_image_count_s);
|
||||||
|
|
||||||
char *dyld_get_image_header_s = (char*)"__dyld_get_image_header";
|
char *dyld_get_image_header_s = (char *)"__dyld_get_image_header";
|
||||||
void *(*dyld_get_image_header_func)(int) =
|
void *(*dyld_get_image_header_func)(int) =
|
||||||
(dyld_get_image_header_t)find_in_export_trie(libdyld, libdyld_export_trie,
|
(dyld_get_image_header_t)find_in_export_trie(libdyld, libdyld_export_trie,
|
||||||
dyld_get_image_header_s);
|
dyld_get_image_header_s);
|
||||||
|
|
||||||
char *dyld_get_image_name_s = (char*)"__dyld_get_image_name";
|
char *dyld_get_image_name_s = (char *)"__dyld_get_image_name";
|
||||||
char *(*dyld_get_image_name_func)(int) =
|
char *(*dyld_get_image_name_func)(int) =
|
||||||
(dyld_get_image_name_t)find_in_export_trie(libdyld, libdyld_export_trie,
|
(dyld_get_image_name_t)find_in_export_trie(libdyld, libdyld_export_trie,
|
||||||
dyld_get_image_name_s);
|
dyld_get_image_name_s);
|
||||||
@ -928,7 +935,7 @@ void build_cache(struct libcache &cache, void *main) {
|
|||||||
char *name = dyld_get_image_name_func(i);
|
char *name = dyld_get_image_name_func(i);
|
||||||
bootstrap_libcache_item(&cache.libs[i], header, name);
|
bootstrap_libcache_item(&cache.libs[i], header, name);
|
||||||
cache.libs[i].hash = calculate_libname_hash(&cache, name);
|
cache.libs[i].hash = calculate_libname_hash(&cache, name);
|
||||||
printf("%p %s\n", header, name);
|
// printf("%p %s\n", header, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1181,11 +1188,13 @@ void fix(struct libcache &cache) {
|
|||||||
// for (int i = 0; i < 0x2ac; i++) {
|
// for (int i = 0; i < 0x2ac; i++) {
|
||||||
// text_start[0xb8c + i] = text_start[0xb8c + i] ^ 0xcc;
|
// text_start[0xb8c + i] = text_start[0xb8c + i] ^ 0xcc;
|
||||||
// }
|
// }
|
||||||
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
|
|
||||||
// VM_PROT_READ | VM_PROT_EXECUTE);
|
|
||||||
|
|
||||||
fix_objc(libfixing, cache);
|
fix_objc(libfixing, cache);
|
||||||
fix_initializer(libfixing, cache);
|
fix_initializer(libfixing, cache);
|
||||||
|
|
||||||
|
// _TEXT must be RX or RW no RWX
|
||||||
|
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
|
||||||
|
// VM_PROT_READ | VM_PROT_EXECUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void volatile custom_initializer(int argc, const char *const argv[],
|
void volatile custom_initializer(int argc, const char *const argv[],
|
||||||
@ -1262,9 +1271,341 @@ void volatile custom_initializer(int argc, const char *const argv[],
|
|||||||
|
|
||||||
printf("[+] initializers completed\n");
|
printf("[+] initializers completed\n");
|
||||||
free(custom_initializer_i);
|
free(custom_initializer_i);
|
||||||
|
end = clock();
|
||||||
|
double cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
|
||||||
|
printf("restoration library time: %lf\n", cpu_time_used);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache);
|
||||||
|
void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache);
|
||||||
|
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache);
|
||||||
|
|
||||||
|
// method are splited into 3 kinds, but for simplicity, we think of it as
|
||||||
|
// 2 kinds: big and small
|
||||||
|
// our example are small method list, which all pointers are relative and 32-bit
|
||||||
|
// the size should be 0xc == 12 but we have padding 4-byte 0x0 for some reason?
|
||||||
|
union _objc_method{
|
||||||
|
struct {
|
||||||
|
const char* name;
|
||||||
|
const char* types;
|
||||||
|
void* imp;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
int32_t sel_offset;
|
||||||
|
int32_t typ_offset;
|
||||||
|
int32_t imp_offset;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct method_t {
|
||||||
|
const char* name; /* Pointer to name (or selector reference?) */
|
||||||
|
const char* types; /* Pointer to type info */
|
||||||
|
void* imp; /* Pointer to implementation (code) */
|
||||||
|
};
|
||||||
|
|
||||||
|
// entsize & 0x80000000 is small method kind
|
||||||
|
// entsize = kind | sizeof(_objc_method)
|
||||||
|
struct _method_list_t {
|
||||||
|
uint32_t entsize; // sizeof(struct _objc_method)
|
||||||
|
uint32_t method_count;
|
||||||
|
union _objc_method method_list[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _class_ro_t {
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t const instanceStart;
|
||||||
|
uint32_t const instanceSize;
|
||||||
|
uint32_t const reserved; // only when building for 64bit targets
|
||||||
|
const uint8_t * const ivarLayout;
|
||||||
|
const char *const name;
|
||||||
|
struct _method_list_t * baseMethods;
|
||||||
|
const /*struct _protocol_list_t*/void *const baseProtocols;
|
||||||
|
const /*struct _ivar_list_t*/void *const ivars;
|
||||||
|
const uint8_t * const weakIvarLayout;
|
||||||
|
const /*struct _prop_list_t*/void *const properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _class_t {
|
||||||
|
struct _class_t *isa;
|
||||||
|
struct _class_t * superclass;
|
||||||
|
void *cache;
|
||||||
|
void *vtable;
|
||||||
|
struct _class_ro_t *ro;
|
||||||
|
};
|
||||||
void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
|
printf("[+] dealing with Objective-C\n");
|
||||||
|
#ifdef METH1
|
||||||
|
fix_objc_classdata(libfixing, cache);
|
||||||
|
#endif
|
||||||
|
#ifdef METH3
|
||||||
|
printf("METH3\n");
|
||||||
|
fix_class_refs(libfixing, cache);
|
||||||
|
#endif
|
||||||
|
run_objc_readclass(libfixing, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_objc_hijack(void* self, void* selector, void* input) {
|
||||||
|
printf("[Foo tobehijacked] function is HIJACKED\n");
|
||||||
|
printf("arg1=%p arg2=%p arg3=%p\n", self, selector, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a subroutine to perform hooking of fixed-binary classes
|
||||||
|
// by iterating in the __objc_classref which internally points to
|
||||||
|
// __objc_data for a list of _class_t structs
|
||||||
|
// each _classt_t has a _class_ro_t containing pointers to
|
||||||
|
// the components of an instance, including methods, properties, ivars, ...
|
||||||
|
//
|
||||||
|
// in this function, we only work on hooking/hijacking of class methods
|
||||||
|
// by fixing the method list which to be read by Objective-C runtime during readClass
|
||||||
|
// the method list is a list of {selector, type, implementation} (all pointers)
|
||||||
|
// by fixing the implementation (should point to a function) the readClass
|
||||||
|
// thinks that it is the function associated with the method name/selector
|
||||||
|
//
|
||||||
|
// by now, all rebases have been rebased and pointers should be pointing correctly
|
||||||
|
// however, selectors are to be constructed, unless erased
|
||||||
|
void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
|
void *header = libfixing->header;
|
||||||
|
const uint32_t magic = *(uint32_t *)header;
|
||||||
|
char *ptr = (char *)header;
|
||||||
|
if (magic == magic64) {
|
||||||
|
ptr += 0x20;
|
||||||
|
} else {
|
||||||
|
ptr += 0x20 - 0x4;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t ncmds = *((uint32_t *)header + 4);
|
||||||
|
char *command_ptr = ptr;
|
||||||
|
|
||||||
|
uint64_t linkedit_vmaddr;
|
||||||
|
uint64_t linkedit_fileoffset;
|
||||||
|
uint64_t slide;
|
||||||
|
|
||||||
|
uint64_t methlist_start;
|
||||||
|
uint64_t methlist_size;
|
||||||
|
|
||||||
|
uint32_t libsystem_hash =
|
||||||
|
calculate_libname_hash(&cache, "/usr/lib/libSystem.B.dylib");
|
||||||
|
typedef void *(*vm_protect_t)(void *, uint64_t, uint64_t, int, int);
|
||||||
|
typedef void *(*mach_task_self_t)();
|
||||||
|
mach_task_self_t mach_task_self_func =
|
||||||
|
(mach_task_self_t)custom_dlsym(&cache, libsystem_hash, "_mach_task_self");
|
||||||
|
vm_protect_t vm_protect_func =
|
||||||
|
(vm_protect_t)custom_dlsym(&cache, libsystem_hash, "_vm_protect");
|
||||||
|
|
||||||
|
for (int i = 0; i < ncmds; i++) {
|
||||||
|
const uint32_t cmd = *((uint32_t *)ptr + 0);
|
||||||
|
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
|
||||||
|
if (cmd == LC_SEGMENT_64) {
|
||||||
|
char *name = (char *)((uint64_t *)ptr + 1);
|
||||||
|
uint64_t vmaddr = *((uint64_t *)ptr + 3);
|
||||||
|
uint64_t fileoffset = *((uint64_t *)ptr + 5);
|
||||||
|
// this assumes that __TEXT comes before __DATA_CONST
|
||||||
|
if (custom_strcmp(name, "__TEXT") == 0) {
|
||||||
|
slide = (uint64_t)header - vmaddr;
|
||||||
|
|
||||||
|
uint64_t nsect = *((uint32_t *)ptr + 8 * 2);
|
||||||
|
char *sections_ptr = (char *)((uint32_t *)ptr + 18);
|
||||||
|
for (int sec = 0; sec < nsect; sec++) {
|
||||||
|
char *secname = sections_ptr;
|
||||||
|
// to be able to fix method list for hooking, we need this section
|
||||||
|
// to be writable
|
||||||
|
if (custom_strncmp(secname, "__objc_methlist", 16) == 0) {
|
||||||
|
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
||||||
|
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
||||||
|
|
||||||
|
methlist_start = addr + slide;
|
||||||
|
methlist_size = size;
|
||||||
|
|
||||||
|
printf("setting __objc_methlist to RW: addr=%p size=%x\n", addr + slide, size);
|
||||||
|
vm_protect_func(mach_task_self_func(), methlist_start, methlist_size, 0, VM_PROT_READ | VM_PROT_WRITE);
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (custom_strcmp(name, "__DATA") == 0) {
|
||||||
|
uint64_t nsect = *((uint32_t *)ptr + 8 * 2);
|
||||||
|
char *sections_ptr = (char *)((uint32_t *)ptr + 18);
|
||||||
|
for (int sec = 0; sec < nsect; sec++) {
|
||||||
|
char *secname = sections_ptr;
|
||||||
|
// we can iterate in the __objc_data rather than __objc_classref
|
||||||
|
// classref can also point to outside classes that are imported
|
||||||
|
if (custom_strncmp(secname, "__objc_data", 16) == 0) {
|
||||||
|
|
||||||
|
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
||||||
|
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
||||||
|
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
|
||||||
|
|
||||||
|
for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) {
|
||||||
|
// ro can be null for some reasons
|
||||||
|
// baseMethods is null if the class is a metaclass
|
||||||
|
if (!(data_ptr->ro && data_ptr->ro->baseMethods)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char* class_name = data_ptr->ro->name;
|
||||||
|
struct _method_list_t* methods = data_ptr->ro->baseMethods;
|
||||||
|
for (int i_method = 0; i_method < methods->method_count; i_method++) {
|
||||||
|
// have to use reference because the relative offset is calculated with the variable address
|
||||||
|
// if not using reference, then the variable will be a COPY value and the address is localized
|
||||||
|
union _objc_method* method = &methods->method_list[i_method];
|
||||||
|
if (methods->entsize & 0x80000000) {
|
||||||
|
const char* imp = *(char**)((char*)(&method->sel_offset) + method->sel_offset);
|
||||||
|
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
|
||||||
|
// char* current_imp = (char*)(&method->imp_offset) + method->imp_offset;
|
||||||
|
|
||||||
|
// encode the relative pointer
|
||||||
|
uint64_t replace = (uint64_t)test_objc_hijack;
|
||||||
|
uint64_t original = (uint64_t)&method->imp_offset;
|
||||||
|
printf("modify the Objective-C method at %p\n", &method->imp_offset);
|
||||||
|
if (replace > original) {
|
||||||
|
method->imp_offset = (int32_t)(replace - original);
|
||||||
|
} else {
|
||||||
|
method->imp_offset = -(int32_t)(original - replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" method=%p\n", method);
|
||||||
|
printf(" sel=%x --> %p\n", method->sel_offset, (char*)(&method->sel_offset) + method->sel_offset);
|
||||||
|
printf(" %s\n", name);
|
||||||
|
printf(" typ=%x --> %s\n", method->typ_offset, (char*)&method->typ_offset + method->typ_offset);
|
||||||
|
printf(" fun=%x --> %p\n", method->imp_offset, (char*)(&method->imp_offset) + method->imp_offset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const char* imp = method->name;
|
||||||
|
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
|
||||||
|
void* replace = (void*)test_objc_hijack;
|
||||||
|
printf("modify the Objective-C method at %p with legacy format.\n", &method->imp);
|
||||||
|
method->imp = replace;
|
||||||
|
}
|
||||||
|
printf(" method=%p\n", method);
|
||||||
|
printf(" sel=%s\n", method->name);
|
||||||
|
printf(" typ=%p\n", method->types);
|
||||||
|
printf(" fun=%p\n", method->imp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
} else if (custom_strcmp(name, "__LINKEDIT") == 0) {
|
||||||
|
linkedit_vmaddr = vmaddr;
|
||||||
|
linkedit_fileoffset = fileoffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += cmdsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _TEXT must be RX or RW no RWX
|
||||||
|
vm_protect_func(mach_task_self_func(), methlist_start, methlist_size, 0,
|
||||||
|
VM_PROT_READ | VM_PROT_EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t find_replace_cls_refs(struct libcache cache) {
|
||||||
|
void *header = cache.thislib;
|
||||||
|
const uint32_t magic = *(uint32_t *)header;
|
||||||
|
char *ptr = (char *)header;
|
||||||
|
if (magic == magic64) {
|
||||||
|
ptr += 0x20;
|
||||||
|
} else {
|
||||||
|
ptr += 0x20 - 0x4;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t ncmds = *((uint32_t *)header + 4);
|
||||||
|
char *command_ptr = ptr;
|
||||||
|
uint64_t slide;
|
||||||
|
for (int i = 0; i < ncmds; i++){
|
||||||
|
const uint32_t cmd = *((uint32_t *)ptr + 0);
|
||||||
|
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
|
||||||
|
if (cmd == LC_SEGMENT_64){
|
||||||
|
char* name = (char*)((uint64_t*)ptr + 1);
|
||||||
|
uint64_t vmaddr = *((uint64_t*)ptr + 3);
|
||||||
|
if (custom_strcmp(name, "__TEXT") == 0)
|
||||||
|
slide = (uint64_t)header - vmaddr;
|
||||||
|
|
||||||
|
if (custom_strcmp(name, "__DATA") == 0){
|
||||||
|
uint64_t nsect = *((uint32_t*)ptr + 8 * 2);
|
||||||
|
char* sections_ptr = (char*)((uint32_t*)ptr + 18);
|
||||||
|
for (int sec = 0; sec < nsect; sec++){
|
||||||
|
char* secname = sections_ptr;
|
||||||
|
if (custom_strncmp(secname, "__objc_data", 11) == 0){
|
||||||
|
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
||||||
|
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
||||||
|
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
|
||||||
|
for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) {
|
||||||
|
if (!data_ptr->ro)
|
||||||
|
continue;
|
||||||
|
if (data_ptr->ro->flags & 0x01) { continue; }
|
||||||
|
if (custom_strcmp(data_ptr->ro->name, "Hooker") == 0){
|
||||||
|
printf("Found Hooker @ %p\n", data_ptr);
|
||||||
|
return (uint64_t)data_ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += cmdsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
|
uint64_t replace = find_replace_cls_refs(cache);
|
||||||
|
void *header = libfixing->header;
|
||||||
|
const uint32_t magic = *(uint32_t *)header;
|
||||||
|
char *ptr = (char *)header;
|
||||||
|
if (magic == magic64) {
|
||||||
|
ptr += 0x20;
|
||||||
|
} else {
|
||||||
|
ptr += 0x20 - 0x4;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t ncmds = *((uint32_t *)header + 4);
|
||||||
|
char *command_ptr = ptr;
|
||||||
|
uint64_t slide;
|
||||||
|
for (int i = 0; i < ncmds; i++){
|
||||||
|
const uint32_t cmd = *((uint32_t *)ptr + 0);
|
||||||
|
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
|
||||||
|
if (cmd == LC_SEGMENT_64){
|
||||||
|
char* name = (char*)((uint64_t*)ptr + 1);
|
||||||
|
uint64_t vmaddr = *((uint64_t*)ptr + 3);
|
||||||
|
if (custom_strcmp(name, "__TEXT") == 0)
|
||||||
|
slide = (uint64_t)header - vmaddr;
|
||||||
|
|
||||||
|
if (custom_strcmp(name, "__DATA") == 0){
|
||||||
|
uint64_t nsect = *((uint32_t*)ptr + 8 * 2);
|
||||||
|
char* sections_ptr = (char*)((uint32_t*)ptr + 18);
|
||||||
|
for (int sec = 0; sec < nsect; sec++){
|
||||||
|
char* secname = sections_ptr;
|
||||||
|
if (custom_strncmp(secname, "__objc_classrefs", 16) == 0){
|
||||||
|
uint64_t addr = *((uint64_t*)sections_ptr + 4) + slide;
|
||||||
|
uint64_t size = *((uint64_t*)sections_ptr + 5);
|
||||||
|
struct _class_t* target_clsref = NULL;
|
||||||
|
for (int nclass = 0; nclass < size / sizeof(uint64_t*); nclass++){
|
||||||
|
target_clsref = (_class_t*)(*((uint64_t *)addr + nclass));
|
||||||
|
// printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref);
|
||||||
|
if (custom_strcmp(target_clsref->ro->name, "Foo") == 0){
|
||||||
|
// TODO
|
||||||
|
printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref);
|
||||||
|
*((uint64_t *)addr + nclass) = replace;
|
||||||
|
printf("New clasref @ %p: %p\n", (uint64_t *)addr + nclass, *((uint64_t *)addr + nclass));
|
||||||
|
struct _class_t* hooker = (struct _class_t*)replace;
|
||||||
|
printf("superclass hooker: %p\n", target_clsref->superclass);
|
||||||
|
hooker->superclass = target_clsref;
|
||||||
|
printf("New superclass hooker: %p\n", hooker->superclass);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += cmdsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
// Manually run the Objective-C runtime for each class
|
// Manually run the Objective-C runtime for each class
|
||||||
//
|
//
|
||||||
|
|
||||||
@ -1332,7 +1673,7 @@ void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
|||||||
uint64_t *data_ptr = (uint64_t *)(addr + slide);
|
uint64_t *data_ptr = (uint64_t *)(addr + slide);
|
||||||
|
|
||||||
uint32_t trie_size;
|
uint32_t trie_size;
|
||||||
char* symbol = (char*)"__dyld_get_objc_selector";
|
char *symbol = (char *)"__dyld_get_objc_selector";
|
||||||
void *libdyld = cache.libdyld;
|
void *libdyld = cache.libdyld;
|
||||||
void *libdyld_export_trie = get_export_trie(libdyld, trie_size);
|
void *libdyld_export_trie = get_export_trie(libdyld, trie_size);
|
||||||
typedef void *(*dyld_get_objc_selector_t)(const char *);
|
typedef void *(*dyld_get_objc_selector_t)(const char *);
|
||||||
@ -1346,6 +1687,13 @@ void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
|||||||
const char *name = bshield_data::special_selectors_name[i];
|
const char *name = bshield_data::special_selectors_name[i];
|
||||||
data_ptr[idx] = (uint64_t)dyld_get_objc_selector_func(name);
|
data_ptr[idx] = (uint64_t)dyld_get_objc_selector_func(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void *(*sel_lookUpByName_t)(const char *);
|
||||||
|
sel_lookUpByName_t sel_lookUpByName =
|
||||||
|
(sel_lookUpByName_t)custom_dlsym(
|
||||||
|
&cache, "/usr/lib/libobjc.A.dylib", "_sel_lookUpByName");
|
||||||
|
printf("selector gogogo: %p\n",
|
||||||
|
sel_lookUpByName("dateFromString:"));
|
||||||
}
|
}
|
||||||
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# set -ex
|
set -e
|
||||||
|
clear
|
||||||
VERSION=${1:-14}
|
VERSION=${1:-14}
|
||||||
|
METH=${2}
|
||||||
OUT=./out
|
OUT=./out
|
||||||
LOGIC=${2}
|
LOGIC=3
|
||||||
|
make -C ../../macho-go
|
||||||
mkdir -p $OUT
|
mkdir -p $OUT
|
||||||
|
|
||||||
echo "using mach-o version $VERSION"
|
echo "using mach-o version $VERSION"
|
||||||
@ -75,15 +76,23 @@ clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc
|
|||||||
# create our dummy lib first
|
# create our dummy lib first
|
||||||
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc
|
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc
|
||||||
# build a references libb
|
# build a references libb
|
||||||
clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.mm
|
clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a a.mm
|
||||||
|
|
||||||
# extract symbols from a
|
# 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 --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 --remove-symbol-table --remove-others $OUT/a
|
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --dylibs=./out/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a
|
||||||
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
|
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
|
||||||
|
|
||||||
|
if [ "$METH" = "METH1" ]; then
|
||||||
# build libb with symbols extracted from a
|
# build libb with symbols extracted from a
|
||||||
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
|
clang++ -D $METH -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 --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
|
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
|
||||||
|
|
||||||
|
elif [ "$METH" = "METH3" ]; then
|
||||||
|
clang -mmacosx-version-min=$VERSION -fobjc-arc -ObjC -c -o $OUT/hooking.o hooking.mm
|
||||||
|
clang++ -mmacosx-version-min=$VERSION -D $METH -c -o $OUT/b.o b.cc
|
||||||
|
clang++ -fobjc-arc -ObjC -shared -Wl,-reexport_library -o $OUT/libb.dylib $OUT/b.o $OUT/hooking.o
|
||||||
|
fi
|
||||||
|
|
||||||
# resign
|
# resign
|
||||||
codesign --force --deep -s - $OUT/a-fixed
|
codesign --force --deep -s - $OUT/a-fixed
|
||||||
|
31
research/custom_loader/hooking.mm
Normal file
31
research/custom_loader/hooking.mm
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <objc/message.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@interface Hehe : NSObject
|
||||||
|
- (void)bar;
|
||||||
|
- (void)tobehijacked:(NSString*)input;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface Hooker : Hehe
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Hehe
|
||||||
|
- (void)bar {
|
||||||
|
NSLog(@"Invoke instance method %@", self);
|
||||||
|
}
|
||||||
|
- (void)tobehijacked:(NSString*)input {
|
||||||
|
NSLog(@"Invoke tobehijacked method %@", input);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Hooker
|
||||||
|
- (void)tobehijacked:(NSString*)input {
|
||||||
|
NSLog(@"Hijacked tobehijacked method %@ from Hooker", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)bar {
|
||||||
|
[super bar];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
1
research/strings_empty/.gitignore
vendored
Normal file
1
research/strings_empty/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
out/*
|
75
research/strings_empty/build.sh
Executable file
75
research/strings_empty/build.sh
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
set -e
|
||||||
|
clear
|
||||||
|
VERSION=${1:-14}
|
||||||
|
OUT=./out
|
||||||
|
LOGIC=1
|
||||||
|
make -C ../../macho-go
|
||||||
|
mkdir -p $OUT
|
||||||
|
|
||||||
|
echo "using mach-o version $VERSION"
|
||||||
|
if [[ $VERSION -ge 14 ]]
|
||||||
|
then
|
||||||
|
echo "Resulting binary uses MODERN symbol resolver"
|
||||||
|
else
|
||||||
|
echo "Resulting binary uses LEGACY symbol resolver"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'fly'
|
||||||
|
______
|
||||||
|
_\ _~-\___
|
||||||
|
= = ==(____AA____D
|
||||||
|
\_____\___________________,-~~~~~~~`-.._
|
||||||
|
/ o O o o o o O O o o o o o o O o |\_
|
||||||
|
`~-.__ ___..----.. )
|
||||||
|
`---~~\___________/------------`````
|
||||||
|
= ===(_________D
|
||||||
|
fly
|
||||||
|
|
||||||
|
# this is a joke for those who knows
|
||||||
|
# https://www.blackhat.com/presentations/bh-dc-09/Iozzo/BlackHat-DC-09-Iozzo-let-your-mach0-fly-whitepaper.pdf
|
||||||
|
echo "make your Mach-O fly"
|
||||||
|
|
||||||
|
if [[ $LOGIC -eq 0 ]]
|
||||||
|
then
|
||||||
|
|
||||||
|
clang-format -i -style=llvm *.cc
|
||||||
|
|
||||||
|
elif [[ $LOGIC -eq 1 ]]
|
||||||
|
then
|
||||||
|
|
||||||
|
# build test binaries
|
||||||
|
clang -mmacosx-version-min=$VERSION -o $OUT/c_code tests/c_code.c
|
||||||
|
clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/objc_code tests/objc_code.m
|
||||||
|
swiftc -o $OUT/swift_code tests/swift_code.swift
|
||||||
|
|
||||||
|
# c program
|
||||||
|
../../macho-go/bin/ios-wrapper pepe -o $OUT/c_code_fixed -b $OUT/c_code.bcell -l $OUT/librestore_c.dylib --remove-strings $OUT/c_code
|
||||||
|
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/c_code.bcell -o $OUT/restore.h
|
||||||
|
clang++ -mmacosx-version-min=$VERSION -o $OUT/librestore_c.dylib -shared -Wl,-reexport_library restore.cc
|
||||||
|
|
||||||
|
# objc program
|
||||||
|
../../macho-go/bin/ios-wrapper pepe -o $OUT/objc_code_fixed -b $OUT/objc_code.bcell -l $OUT/librestore_objc.dylib --remove-strings $OUT/objc_code
|
||||||
|
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/c_code.bcell -o $OUT/restore.h
|
||||||
|
clang++ -mmacosx-version-min=$VERSION -o $OUT/librestore_objc.dylib -shared -Wl,-reexport_library restore.cc
|
||||||
|
|
||||||
|
# swift program
|
||||||
|
../../macho-go/bin/ios-wrapper pepe -o $OUT/swift_code_fixed -b $OUT/swift_code.bcell -l $OUT/librestore_swift.dylib --remove-strings $OUT/swift_code
|
||||||
|
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/c_code.bcell -o $OUT/restore.h
|
||||||
|
clang++ -mmacosx-version-min=$VERSION -o $OUT/librestore_swift.dylib -shared -Wl,-reexport_library restore.cc
|
||||||
|
|
||||||
|
# executable
|
||||||
|
chmod +x $OUT/c_code_fixed
|
||||||
|
chmod +x $OUT/objc_code_fixed
|
||||||
|
chmod +x $OUT/swift_code_fixed
|
||||||
|
|
||||||
|
# resign
|
||||||
|
codesign --force --deep -s - $OUT/c_code_fixed
|
||||||
|
codesign --force --deep -s - $OUT/objc_code_fixed
|
||||||
|
codesign --force --deep -s - $OUT/swift_code_fixed
|
||||||
|
|
||||||
|
# run
|
||||||
|
$OUT/c_code_fixed
|
||||||
|
$OUT/objc_code_fixed
|
||||||
|
$OUT/swift_code_fixed
|
||||||
|
|
||||||
|
fi
|
167
research/strings_empty/restore.cc
Normal file
167
research/strings_empty/restore.cc
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// #include <Foundation/Foundation.h>
|
||||||
|
#include <objc/objc.h>
|
||||||
|
|
||||||
|
#include "out/restore.h"
|
||||||
|
|
||||||
|
int custom_strcmp(const char *p1, const char *p2) {
|
||||||
|
const unsigned char *s1 = (const unsigned char *)p1;
|
||||||
|
const unsigned char *s2 = (const unsigned char *)p2;
|
||||||
|
unsigned char c1, c2;
|
||||||
|
do {
|
||||||
|
c1 = (unsigned char)*s1++;
|
||||||
|
c2 = (unsigned char)*s2++;
|
||||||
|
if (c1 == '\0')
|
||||||
|
return c1 - c2;
|
||||||
|
} while (c1 == c2);
|
||||||
|
return c1 - c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int custom_strncmp(const char *s1, const char *s2, register size_t n) {
|
||||||
|
register unsigned char u1, u2;
|
||||||
|
|
||||||
|
while (n-- > 0) {
|
||||||
|
u1 = (unsigned char)*s1++;
|
||||||
|
u2 = (unsigned char)*s2++;
|
||||||
|
if (u1 != u2)
|
||||||
|
return u1 - u2;
|
||||||
|
if (u1 == '\0')
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t magic64 = 0xfeedfacf;
|
||||||
|
const uint32_t magic32 = 0xfeedface;
|
||||||
|
|
||||||
|
struct ProgramVars {
|
||||||
|
void *mh; // mach_header or mach_header64
|
||||||
|
int *NXArgcPtr;
|
||||||
|
const char ***NXArgvPtr;
|
||||||
|
const char ***environPtr;
|
||||||
|
const char **__prognamePtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void restore_strings(void* main);
|
||||||
|
|
||||||
|
__attribute__((constructor)) static void
|
||||||
|
bruh(int argc, const char *const argv[], const char *const envp[],
|
||||||
|
const char *const apple[], const struct ProgramVars *vars) {
|
||||||
|
void* main = (void *)(vars->mh);
|
||||||
|
restore_strings(main);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// strings in __TEXT,__cstring has been removed and this
|
||||||
|
/// function tries to recover those strings. Using either
|
||||||
|
/// these methods below.
|
||||||
|
///
|
||||||
|
/// 1. Recover __TEXT,__cstring
|
||||||
|
/// 2. Build a new segment with section for strings
|
||||||
|
///
|
||||||
|
/// (1) might seem reasonable at first, but requires __TEXT
|
||||||
|
/// segment to be writable. Although we can make that, but
|
||||||
|
/// we are not sure if the modification is allowed by Apple.
|
||||||
|
///
|
||||||
|
/// (2) actually require a little bit more work, by defining
|
||||||
|
/// a new segment with a section inside. This segment is
|
||||||
|
/// mounted readable/writable. Not only that, all string
|
||||||
|
/// references must also be updated.
|
||||||
|
/// In code, ARMv8, the sequence `adrp` `add` referencing
|
||||||
|
/// string must now be updated with new parameters as the
|
||||||
|
/// address/offset has now been changed.
|
||||||
|
/// In ARMv8, every instruction is 8 bytes, so looping
|
||||||
|
/// through all the code and change the instruction is easy.
|
||||||
|
///
|
||||||
|
/// It can be seen that opting for method (2) is safer,
|
||||||
|
/// as Apple allows for arbitrary segment. This option
|
||||||
|
/// requires that there is enough space left for a new segment.
|
||||||
|
/// Calculated, it should be around 152 bytes.
|
||||||
|
///
|
||||||
|
/// 4 + 4 + 16 + 8*4 + 4*4 + 16 + 16 + 8*2 + 4*8
|
||||||
|
/// ^~~~^ ^~~~~~~~~~~~~^ ^~~~~~~~~~~~~~~~~~^
|
||||||
|
/// 1 2 3
|
||||||
|
///
|
||||||
|
/// 1: load command header
|
||||||
|
/// 2: segment data
|
||||||
|
/// 3: section
|
||||||
|
///
|
||||||
|
/// However, if we can expand the old section, and remove the
|
||||||
|
/// old section entry, then we only need 72 bytes. Because,
|
||||||
|
/// we only move the section entry.
|
||||||
|
///
|
||||||
|
/// Remember to update the command count in macho header (+1).
|
||||||
|
void restore_strings(void* main) {
|
||||||
|
printf("=== rebuilding the strings ===\n");
|
||||||
|
|
||||||
|
void* header = main;
|
||||||
|
const uint32_t magic = *(uint32_t *)header;
|
||||||
|
char *ptr = (char *)header;
|
||||||
|
if (magic == magic64) {
|
||||||
|
ptr += 0x20;
|
||||||
|
} else {
|
||||||
|
ptr += 0x20 - 0x4;
|
||||||
|
}
|
||||||
|
const uint32_t ncmds = *((uint32_t *)header + 4);
|
||||||
|
|
||||||
|
uint32_t slide = 0;
|
||||||
|
|
||||||
|
char* secrets = 0;
|
||||||
|
uint64_t secrets_size = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < ncmds; i++) {
|
||||||
|
const uint32_t cmd = *((uint32_t *)ptr + 0);
|
||||||
|
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
|
||||||
|
if (cmd == LC_SEGMENT_64) {
|
||||||
|
char *name = (char *)((uint64_t *)ptr + 1);
|
||||||
|
uint64_t vmaddr = *((uint64_t *)ptr + 3);
|
||||||
|
uint64_t fileoffset = *((uint64_t *)ptr + 5);
|
||||||
|
// this assumes that __TEXT comes before __DATA_CONST
|
||||||
|
if (custom_strcmp(name, "__TEXT") == 0) {
|
||||||
|
slide = (uint64_t)header - vmaddr;
|
||||||
|
|
||||||
|
} else if (custom_strcmp(name, "__BSHIELD") == 0) {
|
||||||
|
printf("found __BSHIELD segment at %p\n", ptr);
|
||||||
|
|
||||||
|
uint64_t nsect = *((uint32_t *)ptr + 8 * 2);
|
||||||
|
char *sections_ptr = (char *)((uint32_t *)ptr + 18);
|
||||||
|
for (int sec = 0; sec < nsect; sec++) {
|
||||||
|
char *secname = sections_ptr;
|
||||||
|
if (custom_strncmp(secname, "__secrets", 16) == 0) {
|
||||||
|
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
||||||
|
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
||||||
|
printf("secrets offset 0x%lx\n", addr);
|
||||||
|
|
||||||
|
secrets = (char*)(addr + slide);
|
||||||
|
secrets_size = size;
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += cmdsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("secrets %p\n", secrets);
|
||||||
|
printf("secrets_size = 0x%lx\n", secrets_size);
|
||||||
|
for (size_t i = 0; i < 0x4000; i++) {
|
||||||
|
secrets[i] = secrets[i] ^ 0x4f;
|
||||||
|
}
|
||||||
|
// secrets[0] = 'F';
|
||||||
|
// secrets[1] = 'R';
|
||||||
|
// secrets[2] = 'E';
|
||||||
|
// secrets[3] = 'E';
|
||||||
|
// secrets[4] = ' ';
|
||||||
|
// secrets[5] = 'S';
|
||||||
|
// secrets[6] = 'P';
|
||||||
|
// secrets[7] = 'A';
|
||||||
|
// secrets[8] = 'C';
|
||||||
|
// secrets[9] = 'E';
|
||||||
|
// secrets[10] = '\n';
|
||||||
|
// secrets[11] = 0;
|
||||||
|
}
|
6
research/strings_empty/tests/c_code.c
Normal file
6
research/strings_empty/tests/c_code.c
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, World!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
8
research/strings_empty/tests/objc_code.m
Normal file
8
research/strings_empty/tests/objc_code.m
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSLog(@"Hello, World!");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
1
research/strings_empty/tests/swift_code.swift
Normal file
1
research/strings_empty/tests/swift_code.swift
Normal file
@ -0,0 +1 @@
|
|||||||
|
print("Hello, World!")
|
Loading…
Reference in New Issue
Block a user