9 Commits

Author SHA1 Message Date
465aed7ba1 benchmark 2024-05-27 11:57:49 +07:00
998d5844e7 Update .gitignore 2024-05-27 11:55:44 +07:00
78a8ca45d5 naive benchmark 2024-05-03 00:38:50 +07:00
3e99eff22d Update .gitignore 2024-05-03 00:38:11 +07:00
8be97742c9 Merge remote-tracking branch 'origin/objc-hooking' into benchmark 2024-05-02 18:39:11 +07:00
792316f4ea add rule for go format 2024-01-11 14:32:45 +07:00
62fa58f039 go fmt 2024-01-11 14:32:45 +07:00
62daeb1c52 Update edit.go: keep LC_SYMTAB 2024-01-10 16:18:21 +07:00
37c2f93383 Update objc.go: fix shellcode offset 2024-01-10 16:17:40 +07:00
26 changed files with 3795 additions and 897 deletions

View File

@ -1,6 +1,6 @@
module ios-wrapper module ios-wrapper
go 1.18 go 1.17
require ( require (
github.com/alecthomas/kong v0.2.16 github.com/alecthomas/kong v0.2.16

View File

@ -1,20 +0,0 @@
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,7 +117,6 @@ 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
@ -125,7 +124,6 @@ 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
@ -266,7 +264,6 @@ func bcell2header(bfile string, header string) {
} }
fmt.Fprintf(w, "};\n") fmt.Fprintf(w, "};\n")
if info.Symbols != nil {
fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n") fmt.Fprintf(w, "__attribute__((section(\"__DATA,bshield\")))\n")
fmt.Fprintf(w, "char libs[] =\n") fmt.Fprintf(w, "char libs[] =\n")
for _, lib := range info.Symbols.Libs { for _, lib := range info.Symbols.Libs {
@ -302,18 +299,6 @@ func bcell2header(bfile string, header string) {
} }
fmt.Fprintf(w, "};\n") fmt.Fprintf(w, "};\n")
fmt.Fprintf(w, "uint32_t n_instructions = %d;\n", n_instructions) 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, "__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,7 +61,6 @@ 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

@ -44,7 +44,6 @@ 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
@ -107,9 +106,6 @@ 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

@ -1,174 +0,0 @@
#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

@ -1,7 +0,0 @@
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,9 +27,6 @@ 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
@ -125,18 +122,15 @@ 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("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count) // fmt.Printf("pages=%x\n", fix.pages)
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count)) 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 // fmt.Printf(" page offset=%x\n", 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])
reader.Seek(address, io.SeekStart)
fmt.Printf(" page %d offset=%x\n", page_i, address) address := int64(fix.segment) + int64(pages[page_i])
reader.Seek(address, io.SeekStart)
code := make([]byte, 8) code := make([]byte, 8)
@ -147,10 +141,6 @@ 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)
@ -174,14 +164,6 @@ 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 {
@ -194,7 +176,6 @@ 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,12 +6,9 @@ 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"
@ -19,7 +16,6 @@ import (
) )
// #include "fixups.h" // #include "fixups.h"
// #include "arm.h"
import "C" import "C"
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) { func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
@ -198,29 +194,6 @@ 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)
@ -345,7 +318,6 @@ 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))
} }
} }
@ -511,10 +483,10 @@ func (mc *MachoContext) removeSymtabCommand() {
fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size) fmt.Printf("// Erase at=0x%x size=0x%x\n", start, size)
mc.file.WriteAt(make([]byte, size), start) mc.file.WriteAt(make([]byte, size), start)
symtab_fix.symoff = 0 // symtab_fix.symoff = 0
symtab_fix.nsyms = 0 // symtab_fix.nsyms = 0
symtab_fix.stroff = 0 // symtab_fix.stroff = 0
symtab_fix.strsize = 0 // symtab_fix.strsize = 0
mc.file.Seek(ptr, io.SeekStart) mc.file.Seek(ptr, io.SeekStart)
mc.file.Write(symtab_fix.Serialize(mc)) mc.file.Write(symtab_fix.Serialize(mc))
break break
@ -562,328 +534,6 @@ 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

@ -54,7 +54,6 @@ 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;
} }

View File

@ -22,7 +22,6 @@ 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;
}; };

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.vmsize return lcmd.vmaddr
} }
func (lcmd *Segment64) Fileoff() uint64 { func (lcmd *Segment64) Fileoff() uint64 {
return lcmd.fileoff return lcmd.fileoff

View File

@ -1 +1,3 @@
out/ out/
coreutils-9.1/
*.tar.xz

View File

@ -1533,7 +1533,7 @@ uint64_t find_replace_cls_refs(struct libcache cache) {
for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) { for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) {
if (!data_ptr->ro) if (!data_ptr->ro)
continue; continue;
if (data_ptr->ro->flags & 0x01) { continue; } if (data_ptr->ro->flags & 0x01/*if meta class*/) { continue; }
if (custom_strcmp(data_ptr->ro->name, "Hooker") == 0){ if (custom_strcmp(data_ptr->ro->name, "Hooker") == 0){
printf("Found Hooker @ %p\n", data_ptr); printf("Found Hooker @ %p\n", data_ptr);
return (uint64_t)data_ptr; return (uint64_t)data_ptr;

View File

@ -0,0 +1,220 @@
# import unittest
import subprocess, resource
import lief
import os
# import time
import re
PATH = "./coreutils-9.1/src"
class Line:
file = None
@classmethod
def init(cls):
cls.file = open("out.csv", "w")
cls.file.write("Name,File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s),File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s)\n")
def write(self):
out = f"{self.name},{self.norm_size},{self.norm_symbols},{self.norm_imports},0,{self.norm_exe:.3f},{self.obf_size},{self.obf_symbols},{self.obf_imports},{self.restore:.3f},{self.obf_exe:.3f}\n"
self.file.write(out)
def __init__(self, name: str) -> None:
self.name = name
self.norm_path = f"{PATH}/{name}"
self.obf_path = f"{PATH}/{name}-dir/out/{name}-fixed"
def numOfSymbols(sym: list) -> int:
count = 0
for i in sym:
if i.type != 0:
count += 1
return count
def numOfImports(imp: list) -> int:
count = 0
for i in imp:
if i.name != "":
count += 1
return count
def setup(binpath: str, libpath: str = None):
if os.path.isdir("/tmp/test"):
os.system("rm -rf /tmp/test")
os.mkdir("/tmp/test")
# if libpath:
# os.system(f"cp {binpath} {libpath} ./test_file.txt /tmp/test")
# else:
# os.system(f"cp {binpath} ./test_file.txt /tmp/test")
os.system("cp ./test_file.txt /tmp/test")
# class Benchmark(unittest.TestCase):
def info(l: Line):
l.norm_size = int(os.path.getsize(l.norm_path) / 1024)
l.obf_size = int(os.path.getsize(l.obf_path) / 1024)
norm = lief.parse(l.norm_path)
obf = lief.parse(l.obf_path)
l.norm_symbols = numOfSymbols(norm.symbols)
l.obf_symbols = numOfSymbols(obf.symbols)
l.norm_imports = numOfImports(norm.imported_functions)
l.obf_imports = numOfImports(obf.imported_functions)
def run(l, cmd):
print(f"[+] Running benchmark for {l.name} with command \"{cmd}\"")
cmd = cmd.split(" ")
setup(l.norm_path)
start = resource.getrusage(resource.RUSAGE_CHILDREN).ru_utime
p1 = subprocess.run([l.norm_path] + cmd, capture_output=True)
end = resource.getrusage(resource.RUSAGE_CHILDREN).ru_utime
l.norm_exe = end - start
setup(l.obf_path)
start = resource.getrusage(resource.RUSAGE_CHILDREN).ru_utime
p2 = subprocess.run([l.obf_path] + cmd, capture_output=True)
end = resource.getrusage(resource.RUSAGE_CHILDREN).ru_utime
if p2.returncode == -11:
print(f"\033[91m[!] Error in {l.name} (segfault)\033[0m")
Line.file.write(f"{l.name},segfault\n")
return
# print(p2.stdout)
match = re.search(b"restoration library time: ([0-9.]+)", p2.stdout)
l.restore = float(match.group(1))
l.obf_exe = end - start
if p2.returncode != p1.returncode:
print(f"\033[91m[!] Error in {l.name} (diff exit code)\033[0m")
Line.file.write(f"{l.name},exit code diff\n")
return
l.write()
# if p1.stdout in p2.stdout:
# l.write()
# else:
# print(f"\033[91m[!] Error in {l.name} (stdout diff)\033[0m")
# print(p1.stdout)
# print("-"*20)
# print(p2.stdout)
# Line.file.write(f"{l.name},stdout diff\n")
def test_basic(name, cmd):
l = Line(name)
info(l)
run(l, cmd)
test_data = [
("md5sum", "/tmp/test/test_file.txt"),
("split", "/tmp/test/test_file.txt /tmp/test/out"),
("cat", "/tmp/test/test_file.txt"),
("mkfifo", "/tmp/test/a"),
("shuf", "--random-source=/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("pathchk", "/tmp/test/test_file.txt"),
("expand", "/tmp/test/test_file.txt"),
("tty", ""),
("basename", "/tmp/test/test_file.txt"),
("nice", ""),
("truncate", "-s 0 /tmp/test/test_file.txt"),
("echo", "hello"),
("du", "-h /tmp"),
("ptx", "/tmp/test/test_file.txt"),
("join", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("df", "--help"),
("pwd", ""),
("test", "-f /tmp/test_file.txt"),
("csplit", "/tmp/test_file.txt 1"),
("sort", "/tmp/test_file.txt"),
("whoami", ""),
("touch", "/tmp/test/a"),
("unlink", "/tmp/test/test_file.txt"),
("b2sum", "/tmp/test/test_file.txt"),
("sleep", "1"),
("fmt", "/tmp/test/test_file.txt"),
("stty", ""),
("logname", ""),
("chgrp", "root /tmp/test/test_file.txt"),
("printenv", ""),
("seq", "1 10"),
("uname", ""),
("sha224sum", "/tmp/test/test_file.txt"),
("od", "/tmp/test/test_file.txt"),
("date", ""),
("base64", "/tmp/test/test_file.txt"),
("realpath", "/tmp/test/test_file.txt"),
("readlink", "/tmp/test/test_file.txt"),
("dircolors", ""),
("timeout", "1s sleep 2"),
("tac", "/tmp/test/test_file.txt"),
("numfmt", "1000"),
("wc", "/tmp/test/test_file.txt"),
("basenc", "/tmp/test/test_file.txt"),
("comm", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("nproc", ""),
("expr", "1"),
("cksum", "/tmp/test/test_file.txt"),
("printf", "hello"),
("groups", ""),
("chcon", "-t s0 /tmp/test/test_file.txt"),
("factor", "10"),
("tail", "-n 1 /tmp/test/test_file.txt"),
("env", ""),
("pr", "/tmp/test/test_file.txt"),
("head", "-n 1 /tmp/test/test_file.txt"),
("kill", "$$"),
("uniq", "/tmp/test/test_file.txt"),
("stat", "-f /tmp/test/test_file.txt"),
("link", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
# ("make-prime-list", "10"), # build fail
("sum", "/tmp/test/test_file.txt"),
("tsort", "/tmp/test/test_file.txt"),
# ("extract-magic", "/tmp/test/test_file.txt"), build fail
("mknod", "/tmp/test/test_file.txt"),
("users", ""),
("dd", "--help"),
("who", ""),
("sha1sum", "/tmp/test/test_file.txt"),
("mktemp", ""),
("cut", "-c 1 /tmp/test/test_file.txt"),
("sha256sum", "/tmp/test/test_file.txt"),
("dir", "/tmp/test/test_file.txt"),
("mkdir", "/tmp/test/a"),
("nl", "/tmp/test/test_file.txt"),
("ginstall", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("shred", "-u /tmp/test/test_file.txt"),
("fold", "-w 10 /tmp/test/test_file.txt"),
("rmdir", "/tmp/test/a"),
("sha384sum", "/tmp/test/test_file.txt"),
("mv", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("dirname", "/tmp/test/test_file.txt"),
("id", ""),
("base32", "/tmp/test/test_file.txt"),
("pinky", ""),
("ln", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("hostid", ""),
("chroot", "/tmp/test /tmp/test/test_file.txt"),
("ls", "/tmp/test"),
("true", ""),
("cp", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("sync", ""),
("yes", "--help"),
("unexpand", "/tmp/test/test_file.txt"),
("chown", "root /tmp/test/test_file.txt"),
("getlimits", ""),
("chmod", "777 /tmp/test/test_file.txt"),
("uptime", ""),
("rm", "/tmp/test/test_file.txt"),
("vdir", "/tmp/test"),
("false", ""),
("sha512sum", "/tmp/test/test_file.txt"),
("tr", "a b /tmp/test/test_file.txt"),
("paste", "/tmp/test/test_file.txt /tmp/test/test_file.txt"),
("nohup", "sleep 1")
]
# core="tee md5sum split cat shuf mkfifo pathchk runcon expand tty basename nice truncate echo du ptx join df pwd test csplit sort whoami touch dcgen unlink b2sum sleep fmt stty logname chgrp printenv seq uname sha224sum od date base64 realpath readlink dircolors timeout tac numfmt wc basenc comm nproc expr stdbuf cksum printf groups chcon factor tail env pr head kill uniq stat link make-prime-list sum tsort extract-magic mknod users dd who sha1sum mktemp cut sha256sum dir mkdir nl ginstall shred fold rmdir sha384sum mv dirname id base32 pinky ln hostid chroot ls true cp sync yes unexpand chown getlimits chmod uptime rm vdir false sha512sum tr paste nohup"
if __name__ == "__main__":
Line.init()
for name, cmd in test_data:
test_basic(name, cmd)
# unittest.main()

View File

@ -0,0 +1,11 @@
curl -LO https://ftp.gnu.org/gnu/coreutils/coreutils-9.1.tar.xz
tar -xvf coreutils-9.1.tar.xz
cd coreutils-9.1
./configure
make
rm coreutils-9.1.tar.xz

View File

@ -0,0 +1,27 @@
rm -r coreutils-9.1/src/*-dir
core="tee md5sum split cat shuf mkfifo pathchk runcon expand tty basename nice truncate echo du ptx join df pwd test csplit sort whoami touch dcgen unlink b2sum sleep fmt stty logname chgrp printenv seq uname sha224sum od date base64 realpath readlink dircolors timeout tac numfmt wc basenc comm nproc expr stdbuf cksum printf groups chcon factor tail env pr head kill uniq stat link make-prime-list sum tsort extract-magic mknod users dd who sha1sum mktemp cut sha256sum dir mkdir nl ginstall shred fold rmdir sha384sum mv dirname id base32 pinky ln hostid chroot ls true cp sync yes unexpand chown getlimits chmod uptime rm vdir false sha512sum tr paste nohup"
for i in $core; do
echo "[+] $i"
WD=coreutils-9.1/src/${i}-dir
OUT=$WD/out
mkdir -p $WD
mkdir -p $OUT
cp b.cc $WD
{
clang++ -mmacosx-version-min=14 -o $OUT/libb.dylib -shared dummy.cc
../../macho-go/bin/ios-wrapper pepe -o $OUT/${i}-fixed -b $OUT/b.bcell --dylibs=./$OUT/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others coreutils-9.1/src/${i}
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
clang++ -mmacosx-version-min=14 -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib $WD/b.cc
codesign --force --deep -s - $OUT/${i}-fixed
codesign --force --deep -s - $OUT/libb.dylib
chmod +x $OUT/${i}-fixed
} > /dev/null 2>&1
done

View File

@ -0,0 +1,104 @@
Name,File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s),File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s)
md5sum,segfault
split,150,1321,101,0,0.003,169,0,4,0.002,0.005
cat,124,916,84,0,0.003,143,0,4,0.002,0.005
mkfifo,121,819,78,0,0.003,140,0,4,0.002,0.004
shuf,149,1255,91,0,0.004,168,0,4,0.002,0.005
pathchk,121,791,79,0,0.003,139,0,4,0.001,0.004
expand,123,904,82,0,0.003,142,0,4,0.002,0.005
tty,120,773,77,0,0.003,139,0,4,0.001,0.004
basename,121,805,74,0,0.003,140,0,4,0.001,0.004
nice,121,802,79,0,0.003,139,0,4,0.002,0.004
truncate,123,868,81,0,0.003,141,0,4,0.002,0.005
echo,118,710,67,0,0.003,137,0,4,0.001,0.004
du,321,2323,130,0,0.003,341,0,4,0.003,0.005
ptx,257,1763,111,0,0.031,277,0,4,0.002,0.038
join,146,1164,90,0,0.013,165,0,4,0.002,0.018
df,197,1797,102,0,0.003,217,0,4,0.002,0.005
pwd,122,861,86,0,0.003,141,0,4,0.002,0.004
test,121,826,71,0,0.003,140,0,4,0.002,0.004
csplit,235,1559,106,0,0.003,255,0,4,0.002,0.005
sort,239,2154,146,0,0.004,258,0,5,0.003,0.007
whoami,120,790,77,0,0.003,139,0,4,0.002,0.004
touch,187,1406,108,0,0.003,206,0,4,0.003,0.006
unlink,121,817,77,0,0.003,140,0,4,0.002,0.004
b2sum,141,956,88,0,0.004,160,0,5,0.002,0.005
sleep,123,848,79,0,0.003,141,0,4,0.002,0.005
fmt,139,908,81,0,0.007,158,0,4,0.003,0.010
stty,158,1023,89,0,0.003,177,0,4,0.002,0.005
logname,120,789,76,0,0.003,139,0,4,0.002,0.004
chgrp,171,1433,105,0,0.003,190,0,4,0.003,0.006
printenv,120,769,75,0,0.003,138,0,4,0.001,0.004
seq,139,887,83,0,0.003,158,0,4,0.002,0.004
uname,120,786,76,0,0.003,139,0,4,0.001,0.004
sha224sum,segfault
od,161,1156,90,0,0.022,181,0,4,0.002,0.023
date,195,1178,95,0,0.003,215,0,4,0.002,0.005
base64,122,856,81,0,0.003,141,0,4,0.002,0.005
realpath,145,1078,81,0,0.003,164,0,4,0.002,0.005
readlink,144,1042,81,0,0.003,163,0,4,0.002,0.005
dircolors,159,1012,96,0,0.003,178,0,4,0.002,0.004
timeout,124,888,95,0,0.006,142,0,4,0.002,0.007
tac,216,1387,97,0,0.003,235,0,4,0.002,0.005
numfmt,160,1079,92,0,0.003,179,0,4,0.002,0.005
wc,146,1104,95,0,0.004,165,0,4,0.002,0.006
basenc,145,1163,81,0,0.003,164,0,4,0.003,0.005
comm,126,948,84,0,0.004,144,0,4,0.002,0.006
nproc,121,817,79,0,0.003,140,0,4,0.002,0.004
expr,232,1436,102,0,0.003,252,0,4,0.002,0.004
cksum,187,1436,115,0,0.004,206,0,5,0.002,0.006
printf,122,853,74,0,0.003,141,0,4,0.001,0.004
groups,122,843,82,0,0.003,141,0,4,0.002,0.005
chcon,169,1380,96,0,0.003,188,0,4,0.002,0.005
factor,183,1284,115,0,0.003,202,0,4,0.003,0.006
tail,165,1192,95,0,0.003,184,0,4,0.003,0.006
env,142,1007,92,0,0.003,161,0,4,0.002,0.004
pr,185,1370,101,0,0.005,204,0,4,0.002,0.007
head,142,994,81,0,0.003,161,0,4,0.002,0.004
kill,121,802,81,0,0.003,140,0,4,0.002,0.004
uniq,142,987,86,0,0.003,161,0,4,0.002,0.006
stat,209,1561,108,0,0.003,228,0,4,0.002,0.005
link,121,818,78,0,0.003,140,0,4,0.002,0.005
sum,140,923,86,0,0.003,159,0,4,0.002,0.005
tsort,123,891,83,0,0.008,142,0,4,0.003,0.015
mknod,123,875,81,0,0.003,142,0,4,0.002,0.005
users,121,818,81,0,0.003,140,0,4,0.002,0.004
dd,165,1284,101,0,0.003,184,0,5,0.002,0.005
who,142,972,97,0,0.003,161,0,4,0.002,0.004
sha1sum,segfault
mktemp,126,960,86,0,0.003,145,0,4,0.002,0.004
cut,140,903,88,0,0.003,159,0,4,0.002,0.005
sha256sum,segfault
dir,281,2551,148,0,0.003,301,0,5,0.003,0.005
mkdir,144,1019,92,0,0.003,163,0,4,0.002,0.005
nl,214,1377,92,0,0.004,233,0,4,0.002,0.006
ginstall,239,2490,161,0,0.003,259,0,4,0.004,0.006
shred,151,1215,109,0,0.003,170,0,4,0.002,0.005
fold,122,843,80,0,0.004,141,0,4,0.002,0.005
rmdir,123,863,83,0,0.003,142,0,4,0.002,0.005
sha384sum,segfault
mv,238,2461,149,0,0.003,258,0,4,0.004,0.007
dirname,121,798,74,0,0.003,139,0,4,0.001,0.004
id,141,940,88,0,0.003,160,0,4,0.002,0.005
base32,122,861,81,0,0.003,141,0,4,0.002,0.005
pinky,125,929,98,0,0.003,144,0,4,0.002,0.005
ln,179,1589,111,0,0.003,198,0,4,0.003,0.006
hostid,120,783,75,0,0.003,139,0,4,0.001,0.004
chroot,148,1170,99,0,0.003,167,0,4,0.003,0.006
ls,281,2551,148,0,0.003,301,0,5,0.003,0.006
true,118,708,65,0,0.003,137,0,4,0.001,0.004
cp,233,2273,148,0,0.003,253,0,4,0.003,0.006
sync,121,809,80,0,0.003,140,0,4,0.002,0.004
yes,121,801,75,0,0.003,140,0,4,0.003,0.008
unexpand,123,907,82,0,0.003,142,0,4,0.003,0.009
chown,172,1449,107,0,0.003,191,0,4,0.003,0.006
getlimits,137,808,78,0,0.004,156,0,4,0.003,0.008
chmod,165,1245,92,0,0.003,184,0,4,0.002,0.004
uptime,140,924,93,0,0.003,159,0,5,0.003,0.009
rm,171,1417,98,0,0.003,190,0,5,0.003,0.008
vdir,281,2551,148,0,0.003,301,0,5,0.005,0.011
false,118,708,65,0,0.003,137,0,4,0.003,0.008
sha512sum,segfault
tr,142,1049,83,0,0.003,161,0,4,0.003,0.009
paste,122,880,77,0,0.004,141,0,4,0.003,0.009
nohup,123,856,82,0,0.005,142,0,4,0.003,0.012
1 Name,File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s),File size(KiB),Number of symbols,Number of imports,Restoration time(s),Execution time(s)
2 md5sum,segfault
3 split,150,1321,101,0,0.003,169,0,4,0.002,0.005
4 cat,124,916,84,0,0.003,143,0,4,0.002,0.005
5 mkfifo,121,819,78,0,0.003,140,0,4,0.002,0.004
6 shuf,149,1255,91,0,0.004,168,0,4,0.002,0.005
7 pathchk,121,791,79,0,0.003,139,0,4,0.001,0.004
8 expand,123,904,82,0,0.003,142,0,4,0.002,0.005
9 tty,120,773,77,0,0.003,139,0,4,0.001,0.004
10 basename,121,805,74,0,0.003,140,0,4,0.001,0.004
11 nice,121,802,79,0,0.003,139,0,4,0.002,0.004
12 truncate,123,868,81,0,0.003,141,0,4,0.002,0.005
13 echo,118,710,67,0,0.003,137,0,4,0.001,0.004
14 du,321,2323,130,0,0.003,341,0,4,0.003,0.005
15 ptx,257,1763,111,0,0.031,277,0,4,0.002,0.038
16 join,146,1164,90,0,0.013,165,0,4,0.002,0.018
17 df,197,1797,102,0,0.003,217,0,4,0.002,0.005
18 pwd,122,861,86,0,0.003,141,0,4,0.002,0.004
19 test,121,826,71,0,0.003,140,0,4,0.002,0.004
20 csplit,235,1559,106,0,0.003,255,0,4,0.002,0.005
21 sort,239,2154,146,0,0.004,258,0,5,0.003,0.007
22 whoami,120,790,77,0,0.003,139,0,4,0.002,0.004
23 touch,187,1406,108,0,0.003,206,0,4,0.003,0.006
24 unlink,121,817,77,0,0.003,140,0,4,0.002,0.004
25 b2sum,141,956,88,0,0.004,160,0,5,0.002,0.005
26 sleep,123,848,79,0,0.003,141,0,4,0.002,0.005
27 fmt,139,908,81,0,0.007,158,0,4,0.003,0.010
28 stty,158,1023,89,0,0.003,177,0,4,0.002,0.005
29 logname,120,789,76,0,0.003,139,0,4,0.002,0.004
30 chgrp,171,1433,105,0,0.003,190,0,4,0.003,0.006
31 printenv,120,769,75,0,0.003,138,0,4,0.001,0.004
32 seq,139,887,83,0,0.003,158,0,4,0.002,0.004
33 uname,120,786,76,0,0.003,139,0,4,0.001,0.004
34 sha224sum,segfault
35 od,161,1156,90,0,0.022,181,0,4,0.002,0.023
36 date,195,1178,95,0,0.003,215,0,4,0.002,0.005
37 base64,122,856,81,0,0.003,141,0,4,0.002,0.005
38 realpath,145,1078,81,0,0.003,164,0,4,0.002,0.005
39 readlink,144,1042,81,0,0.003,163,0,4,0.002,0.005
40 dircolors,159,1012,96,0,0.003,178,0,4,0.002,0.004
41 timeout,124,888,95,0,0.006,142,0,4,0.002,0.007
42 tac,216,1387,97,0,0.003,235,0,4,0.002,0.005
43 numfmt,160,1079,92,0,0.003,179,0,4,0.002,0.005
44 wc,146,1104,95,0,0.004,165,0,4,0.002,0.006
45 basenc,145,1163,81,0,0.003,164,0,4,0.003,0.005
46 comm,126,948,84,0,0.004,144,0,4,0.002,0.006
47 nproc,121,817,79,0,0.003,140,0,4,0.002,0.004
48 expr,232,1436,102,0,0.003,252,0,4,0.002,0.004
49 cksum,187,1436,115,0,0.004,206,0,5,0.002,0.006
50 printf,122,853,74,0,0.003,141,0,4,0.001,0.004
51 groups,122,843,82,0,0.003,141,0,4,0.002,0.005
52 chcon,169,1380,96,0,0.003,188,0,4,0.002,0.005
53 factor,183,1284,115,0,0.003,202,0,4,0.003,0.006
54 tail,165,1192,95,0,0.003,184,0,4,0.003,0.006
55 env,142,1007,92,0,0.003,161,0,4,0.002,0.004
56 pr,185,1370,101,0,0.005,204,0,4,0.002,0.007
57 head,142,994,81,0,0.003,161,0,4,0.002,0.004
58 kill,121,802,81,0,0.003,140,0,4,0.002,0.004
59 uniq,142,987,86,0,0.003,161,0,4,0.002,0.006
60 stat,209,1561,108,0,0.003,228,0,4,0.002,0.005
61 link,121,818,78,0,0.003,140,0,4,0.002,0.005
62 sum,140,923,86,0,0.003,159,0,4,0.002,0.005
63 tsort,123,891,83,0,0.008,142,0,4,0.003,0.015
64 mknod,123,875,81,0,0.003,142,0,4,0.002,0.005
65 users,121,818,81,0,0.003,140,0,4,0.002,0.004
66 dd,165,1284,101,0,0.003,184,0,5,0.002,0.005
67 who,142,972,97,0,0.003,161,0,4,0.002,0.004
68 sha1sum,segfault
69 mktemp,126,960,86,0,0.003,145,0,4,0.002,0.004
70 cut,140,903,88,0,0.003,159,0,4,0.002,0.005
71 sha256sum,segfault
72 dir,281,2551,148,0,0.003,301,0,5,0.003,0.005
73 mkdir,144,1019,92,0,0.003,163,0,4,0.002,0.005
74 nl,214,1377,92,0,0.004,233,0,4,0.002,0.006
75 ginstall,239,2490,161,0,0.003,259,0,4,0.004,0.006
76 shred,151,1215,109,0,0.003,170,0,4,0.002,0.005
77 fold,122,843,80,0,0.004,141,0,4,0.002,0.005
78 rmdir,123,863,83,0,0.003,142,0,4,0.002,0.005
79 sha384sum,segfault
80 mv,238,2461,149,0,0.003,258,0,4,0.004,0.007
81 dirname,121,798,74,0,0.003,139,0,4,0.001,0.004
82 id,141,940,88,0,0.003,160,0,4,0.002,0.005
83 base32,122,861,81,0,0.003,141,0,4,0.002,0.005
84 pinky,125,929,98,0,0.003,144,0,4,0.002,0.005
85 ln,179,1589,111,0,0.003,198,0,4,0.003,0.006
86 hostid,120,783,75,0,0.003,139,0,4,0.001,0.004
87 chroot,148,1170,99,0,0.003,167,0,4,0.003,0.006
88 ls,281,2551,148,0,0.003,301,0,5,0.003,0.006
89 true,118,708,65,0,0.003,137,0,4,0.001,0.004
90 cp,233,2273,148,0,0.003,253,0,4,0.003,0.006
91 sync,121,809,80,0,0.003,140,0,4,0.002,0.004
92 yes,121,801,75,0,0.003,140,0,4,0.003,0.008
93 unexpand,123,907,82,0,0.003,142,0,4,0.003,0.009
94 chown,172,1449,107,0,0.003,191,0,4,0.003,0.006
95 getlimits,137,808,78,0,0.004,156,0,4,0.003,0.008
96 chmod,165,1245,92,0,0.003,184,0,4,0.002,0.004
97 uptime,140,924,93,0,0.003,159,0,5,0.003,0.009
98 rm,171,1417,98,0,0.003,190,0,5,0.003,0.008
99 vdir,281,2551,148,0,0.003,301,0,5,0.005,0.011
100 false,118,708,65,0,0.003,137,0,4,0.003,0.008
101 sha512sum,segfault
102 tr,142,1049,83,0,0.003,161,0,4,0.003,0.009
103 paste,122,880,77,0,0.004,141,0,4,0.003,0.009
104 nohup,123,856,82,0,0.005,142,0,4,0.003,0.012

File diff suppressed because it is too large Load Diff

View File

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

View File

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

@ -1,167 +0,0 @@
#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

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

View File

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

View File

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