17 Commits

Author SHA1 Message Date
06bbde2612 go fmt 2025-01-13 12:54:05 -06:00
1c495989d4 simple xor string data 2024-08-29 15:23:50 +07:00
083556f914 correctly parse add instruction 2024-08-29 15:22:56 +07:00
88b79bccb9 fix get bits 2024-08-29 15:22:42 +07:00
9cdf9f0ff5 overwrite old cstring data with random 2024-08-26 16:08:10 +07:00
7fa3ba0b7d remote strings PoC for C/Obj-C/Swift 2024-08-26 16:01:42 +07:00
cc34751c9a restore one string freely 2024-08-26 16:01:27 +07:00
c241e78cd8 working static strings migration 2024-08-26 16:00:54 +07:00
0640d38627 re-read internal buffer after moving file's contents 2024-08-26 16:00:17 +07:00
d9024990f9 parsing some special arm instructions 2024-08-26 15:59:43 +07:00
04979b0afd collect rebase symbols 2024-08-26 15:59:30 +07:00
9b2796b2a1 basic POC for string removal 2024-08-22 17:41:56 +07:00
925429c4a9 boilerplate code to research string removal 2024-08-22 17:40:39 +07:00
9b85e4938f add rewrite header function
fully rewrite the header from commands list
2024-08-22 17:39:31 +07:00
5e601eaa4a fix serializer wrong size with addr field for LC_SEGMENT 2024-08-22 17:38:22 +07:00
d534d62f5e add cli parsing for remove strings 2024-08-22 17:37:28 +07:00
d11ef20f4a update go version 2024-08-22 17:36:57 +07:00
18 changed files with 985 additions and 138 deletions

View File

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

View 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{}
}

View File

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

View File

@ -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"`

View File

@ -46,7 +46,7 @@ func (printer *InfoPrinter) Print() {
) )
} }
mc.CollectObjectiveCClasses() mc.CollectObjectiveCClasses()
fmt.Println("======") fmt.Println("======")
} }

View File

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

View 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;
}

View 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);

View File

@ -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++ {
// loop through each page in segment, each page has size fix.page_size // loop through each page in segment, each page has size fix.page_size
// the first item in page is offset through pages[page_i] // the first item in page is offset through pages[page_i]
address := int64(fix.segment) + int64(page_i) * int64(fix.page_size) + 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
} }

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"strings" "strings"
"unsafe" "unsafe"
. "ios-wrapper/pkg/ios" . "ios-wrapper/pkg/ios"
) )
@ -15,10 +15,10 @@ import (
import "C" import "C"
func (mc *MachoContext) CollectObjectiveCClasses() { func (mc *MachoContext) CollectObjectiveCClasses() {
var objc_const *bytes.Reader var objc_const *bytes.Reader
var objc_const_start uint64 var objc_const_start uint64
var objc_const_end uint64 var objc_const_end uint64
// var objc_methname []byte // var objc_methname []byte
for _, cmd := range mc.commands { for _, cmd := range mc.commands {
if cmd.Cmd() == LC_MAIN { if cmd.Cmd() == LC_MAIN {
@ -29,18 +29,18 @@ func (mc *MachoContext) CollectObjectiveCClasses() {
} }
var segment = cmd.(*Segment64) var segment = cmd.(*Segment64)
// we assume the binary comes in perfect ordering, that is as laid out below // 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 { if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
for _, section := range segment.Sections() { for _, section := range segment.Sections() {
buffer := make([]byte, section.Size()) buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset())) 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_stubs")) == 0 {
} }
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methlist")) == 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 { if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
// objc_methname := buffer // 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_classname")) == 0 {
} }
@ -50,8 +50,8 @@ func (mc *MachoContext) CollectObjectiveCClasses() {
} }
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 { if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 {
for _, section := range segment.Sections() { for _, section := range segment.Sections() {
buffer := make([]byte, section.Size()) buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset())) 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_classlist")) == 0 {
} }
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 { if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 {
@ -59,16 +59,16 @@ func (mc *MachoContext) CollectObjectiveCClasses() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_imageinfo")) == 0 { if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_imageinfo")) == 0 {
} }
} }
} }
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 { if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 {
for _, section := range segment.Sections() { for _, section := range segment.Sections() {
buffer := make([]byte, section.Size()) buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset())) mc.file.ReadAt(buffer, int64(section.Offset()))
reader := bytes.NewReader(buffer) reader := bytes.NewReader(buffer)
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_const")) == 0 { if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_const")) == 0 {
objc_const = reader objc_const = reader
objc_const_start = uint64(section.Offset()) objc_const_start = uint64(section.Offset())
objc_const_end = objc_const_start + section.Size() 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_selrefs")) == 0 {
} }
@ -77,90 +77,89 @@ func (mc *MachoContext) CollectObjectiveCClasses() {
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_superrefs")) == 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 { if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_data")) == 0 {
// this section contains a series of class_t // this section contains a series of class_t
// struct _class_t { // struct _class_t {
// struct _class_t *isa; // struct _class_t *isa;
// struct _class_t * const superclass; // struct _class_t * const superclass;
// void *cache; // void *cache;
// IMP *vtable; // IMP *vtable;
// struct class_ro_t *ro; // struct class_ro_t *ro;
// }; // };
for i := uint64(0); i < (section.Size() / uint64(mc.pointersize * 5)); i++ { for i := uint64(0); i < (section.Size() / uint64(mc.pointersize*5)); i++ {
var isa uint64 var isa uint64
var superclass uint64 var superclass uint64
var cache uint64 var cache uint64
var vtable uint64 var vtable uint64
var ro uint64 var ro uint64
binary.Read(reader, mc.byteorder, &isa) binary.Read(reader, mc.byteorder, &isa)
binary.Read(reader, mc.byteorder, &superclass) binary.Read(reader, mc.byteorder, &superclass)
binary.Read(reader, mc.byteorder, &cache) binary.Read(reader, mc.byteorder, &cache)
binary.Read(reader, mc.byteorder, &vtable) binary.Read(reader, mc.byteorder, &vtable)
binary.Read(reader, mc.byteorder, &ro) 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)
fmt.Printf("at=0x%x\n", section.Offset() + uint32(i) * mc.pointersize * 5) var bind int
fmt.Printf("isa=0x%x superclass=0x%x\n", isa, superclass) var ret1 uint64
fmt.Printf("cache=0x%x vtable=0x%x\n", cache, vtable) var ret2 uint64
fmt.Printf("ro=0x%x\n", ro) 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)),
)
var bind int // is rebase, because ro points to objc_const
var ret1 uint64 // and address is in range
var ret2 uint64 if bind != 1 && ret1 >= objc_const_start && ret1 < objc_const_end {
C.ParseFixValue(C.int(2), C.uint64_t(ro), offset := ret1 - objc_const_start
(*C.int)(unsafe.Pointer(&bind)), objc_const.Seek(int64(offset), 0)
(*C.uint64_t)(unsafe.Pointer(&ret1)),
(*C.uint64_t)(unsafe.Pointer(&ret2)),
)
// is rebase, because ro points to objc_const // struct _class_ro_t {
// and address is in range // uint32_t const flags;
if (bind != 1 && ret1 >= objc_const_start && ret1 < objc_const_end) { // uint32_t const instanceStart;
offset := ret1 - objc_const_start // uint32_t const instanceSize;
objc_const.Seek(int64(offset), 0) // 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;
// };
// struct _class_ro_t { var tmp uint32
// uint32_t const flags; var ivarLayout uint64 // ptr
// uint32_t const instanceStart; var name uint64 // ptr
// uint32_t const instanceSize; var baseMethods uint64 // ptr
// uint32_t const reserved; // only when building for 64bit targets var baseProtocols uint64 // ptr
// const uint8_t * const ivarLayout; var ivars uint64 // ptr
// const char *const name; var weakIvarLayout uint64 // ptr
// const struct _method_list_t * const baseMethods; var properties uint64 // ptr
// const struct _protocol_list_t *const baseProtocols; binary.Read(objc_const, mc.byteorder, &tmp)
// const struct _ivar_list_t *const ivars; binary.Read(objc_const, mc.byteorder, &tmp)
// const uint8_t * const weakIvarLayout; binary.Read(objc_const, mc.byteorder, &tmp)
// const struct _prop_list_t * const properties; 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)
var tmp uint32 fmt.Printf("method list: %x\n", baseMethods)
var ivarLayout uint64 // ptr }
var name uint64 // ptr fmt.Printf("========\n")
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")
}
} }
} }
} }
} }
} }
@ -482,7 +481,7 @@ func (mc *MachoContext) ReworkForObjc() {
} }
} }
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start))+3) 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...)
@ -519,7 +518,7 @@ func (mc *MachoContext) ReworkForObjc() {
offset += 4 offset += 4
} }
// make __TEXT writable lol // make __TEXT writable lol
mc.file.Seek(0, 0) mc.file.Seek(0, 0)
mc.file.WriteAt([]byte{0x7}, 0xa0) mc.file.WriteAt([]byte{0x7}, 0xa0)
} }

1
research/strings_empty/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
out/*

75
research/strings_empty/build.sh Executable file
View 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

View 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;
}

View File

@ -0,0 +1,6 @@
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}

View File

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}

View File

@ -0,0 +1 @@
print("Hello, World!")