Compare commits

...

3 Commits

6 changed files with 644 additions and 34 deletions

View File

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

View File

@ -6,10 +6,164 @@ import (
"fmt"
"io"
"strings"
"unsafe"
. "ios-wrapper/pkg/ios"
)
// #include "fixups.h"
import "C"
func (mc *MachoContext) CollectObjectiveCClasses() {
var objc_const *bytes.Reader
var objc_const_start uint64
var objc_const_end uint64
// var objc_methname []byte
for _, cmd := range mc.commands {
if cmd.Cmd() == LC_MAIN {
continue
}
if cmd.Cmd() != LC_SEGMENT_64 {
continue
}
var segment = cmd.(*Segment64)
// we assume the binary comes in perfect ordering, that is as laid out below
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 {
for _, section := range segment.Sections() {
buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset()))
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_stubs")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methlist")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
// objc_methname := buffer
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classname")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methtype")) == 0 {
}
}
}
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 {
for _, section := range segment.Sections() {
buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset()))
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classlist")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_imageinfo")) == 0 {
}
}
}
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 {
for _, section := range segment.Sections() {
buffer := make([]byte, section.Size())
mc.file.ReadAt(buffer, int64(section.Offset()))
reader := bytes.NewReader(buffer)
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_const")) == 0 {
objc_const = reader
objc_const_start = uint64(section.Offset())
objc_const_end = objc_const_start + section.Size()
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classrefs")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_superrefs")) == 0 {
}
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_data")) == 0 {
// this section contains a series of class_t
// struct _class_t {
// struct _class_t *isa;
// struct _class_t * const superclass;
// void *cache;
// IMP *vtable;
// struct class_ro_t *ro;
// };
for i := uint64(0); i < (section.Size() / uint64(mc.pointersize * 5)); i++ {
var isa uint64
var superclass uint64
var cache uint64
var vtable uint64
var ro uint64
binary.Read(reader, mc.byteorder, &isa)
binary.Read(reader, mc.byteorder, &superclass)
binary.Read(reader, mc.byteorder, &cache)
binary.Read(reader, mc.byteorder, &vtable)
binary.Read(reader, mc.byteorder, &ro)
fmt.Printf("at=0x%x\n", section.Offset() + uint32(i) * mc.pointersize * 5)
fmt.Printf("isa=0x%x superclass=0x%x\n", isa, superclass)
fmt.Printf("cache=0x%x vtable=0x%x\n", cache, vtable)
fmt.Printf("ro=0x%x\n", ro)
var bind int
var ret1 uint64
var ret2 uint64
C.ParseFixValue(C.int(2), C.uint64_t(ro),
(*C.int)(unsafe.Pointer(&bind)),
(*C.uint64_t)(unsafe.Pointer(&ret1)),
(*C.uint64_t)(unsafe.Pointer(&ret2)),
)
// is rebase, because ro points to objc_const
// and address is in range
if (bind != 1 && ret1 >= objc_const_start && ret1 < objc_const_end) {
offset := ret1 - objc_const_start
objc_const.Seek(int64(offset), 0)
// struct _class_ro_t {
// uint32_t const flags;
// uint32_t const instanceStart;
// uint32_t const instanceSize;
// uint32_t const reserved; // only when building for 64bit targets
// const uint8_t * const ivarLayout;
// const char *const name;
// const struct _method_list_t * const baseMethods;
// const struct _protocol_list_t *const baseProtocols;
// const struct _ivar_list_t *const ivars;
// const uint8_t * const weakIvarLayout;
// const struct _prop_list_t * const properties;
// };
var tmp uint32
var ivarLayout uint64 // ptr
var name uint64 // ptr
var baseMethods uint64 // ptr
var baseProtocols uint64 // ptr
var ivars uint64 // ptr
var weakIvarLayout uint64 // ptr
var properties uint64 // ptr
binary.Read(objc_const, mc.byteorder, &tmp)
binary.Read(objc_const, mc.byteorder, &tmp)
binary.Read(objc_const, mc.byteorder, &tmp)
binary.Read(objc_const, mc.byteorder, &tmp)
binary.Read(objc_const, mc.byteorder, &ivarLayout)
binary.Read(objc_const, mc.byteorder, &name)
binary.Read(objc_const, mc.byteorder, &baseMethods)
binary.Read(objc_const, mc.byteorder, &baseProtocols)
binary.Read(objc_const, mc.byteorder, &ivars)
binary.Read(objc_const, mc.byteorder, &weakIvarLayout)
binary.Read(objc_const, mc.byteorder, &properties)
fmt.Printf("method list: %x\n", baseMethods)
}
fmt.Printf("========\n")
}
}
}
}
}
}
type SpecialSelector struct {
idx uint
name string
@ -30,8 +184,12 @@ func (sel *SpecialSelector) Name() string {
// we currently have the following symbols guaranteed to be in this list:
// - load
// - retain
//
// besides special selectors, selectors of outside classes must also be
// registered through the cache
// selectors of outside classes are defined as not being referenced by
// internal classes in __objc_data
func (mc *MachoContext) CollectSpecialSelectors() []*SpecialSelector {
var special_selectors []*SpecialSelector
var methods []byte
var methname_offset uint32
@ -137,7 +295,7 @@ func (mc *MachoContext) ReworkForObjc() {
// selector should points to this load selector to make objc thinks that it's "load"
if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 {
// mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr)
mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset()))
// mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset()))
}
section_ptr += 16*2 + 8*2 + 4*8
}
@ -324,7 +482,7 @@ func (mc *MachoContext) ReworkForObjc() {
}
}
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start)))
encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start))+3)
shellcode_offset = text_start - shellcode_size
shellcode_bytes := append(shellcode_start, offset...)
@ -360,4 +518,8 @@ func (mc *MachoContext) ReworkForObjc() {
mc.file.WriteAt(bs, offset)
offset += 4
}
// make __TEXT writable lol
mc.file.Seek(0, 0)
mc.file.WriteAt([]byte{0x7}, 0xa0)
}

View File

@ -2,12 +2,19 @@
#include <objc/message.h>
#include <stdio.h>
void* sel_lookUpByName(const char* name);
@interface Foo : NSObject
@end
@implementation Foo
- (void)bar {
NSLog(@"[Foo bar]: %@", self);
NSLog(@"Invoke instance method original bar in Foo");
}
- (void)tobehijacked:(NSString*)input {
NSLog(@"Invoke tobehijacked method %@ from Foo", input);
}
@end
@ -18,48 +25,99 @@
static int x;
+ (void)load {
NSLog(@"%@", self);
// NSLog(@"x=%d", x)
printf("printf in [Bar load]\n");
x = 1;
printf("Invoke +load method\n");
}
- (void)dummy {
NSLog(@"dummy bar x=%d", x);
NSLog(@"Static value check after +load should be 1: x=%d", x);
}
@end
@interface FakeNSDateFormatter : NSDateFormatter {
}
@end
@implementation FakeNSDateFormatter
- (NSDate*)dateFromString:(NSString*)dateString {
NSLog(@"Hijacked the NSDateFormatter");
return [super dateFromString:dateString];
}
@end
__attribute__((constructor)) static void
hmmge(int argc, char** argv) {
// create a dummy blank function to be replaced to call OBJC load
printf("hmmge=%p\n", hmmge);
printf("hmmge argc=%d\n", argc);
printf("Invoke C constructor\n");
printf("Checking for arguments to be passed correctly\n");
printf(" argc=%d\n", argc);
for (int i = 0; i < argc; i++) {
printf(" hmmge argv[%d]=%s\n", i, argv[i]);
printf(" argv[%d]=%s\n", i, argv[i]);
}
NSLog(@"hmmge in objc-c");
NSLog(@"Using Objective-C in C constructor");
NSLog(@"Test static Objective-C class is initialized and +load completed");
Bar *bar = [[Bar alloc] init];
[bar dummy];
}
int main(int argc, const char * argv[]) {
int main(int argc, const char * argv[], char* envp[]) {
@autoreleasepool {
NSLog(@"main()");
NSLog(@"selector for \"bar:\" %p", @selector(bar:));
NSLog(@"Invoke main()");
// Foo bar using Objective-C syntax
Foo *foo = [[Foo alloc] init];
[foo bar];
NSLog(@"directly call \"bar\" %p through objc_msgSend %p with object foo %p\n", @selector(bar), objc_msgSend, foo);
// Foo bar with selector and msgSend
NSLog(@"Directly call \"bar\" %p through objc_msgSend %p with object foo %p", @selector(bar), objc_msgSend, foo);
typedef void (*barfunc)(id, SEL);
barfunc bar_ = (barfunc)&objc_msgSend;
bar_(foo, @selector(bar));
NSString *dummyinput = @"dummy input";
[foo tobehijacked:dummyinput];
NSLog(@"The above invocation should be hijacked with input at %p", dummyinput);
NSString *dateString = @"2024-01-01T00:00:00.000Z";
// NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
// [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
// NSDate *date = [dateFormatter dateFromString:dateString];
// this is to test the idea for hooking,
// basically, we create a middle-class inherits the class to be used
//
// example using NSDateFormatter:
// - Create a FakeNSDateFormatter inherits NSDateFormatter
// - Have an overloaded function that calls [super inherited]
// - The internal struct class_t has superclass points to NSDateFormatter
// FakeNSDateFormatter *dateFormatter = [[FakeNSDateFormatter alloc] init];
// [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
// NSDate *date = [dateFormatter dateFromString:dateString];
// NSLog(@"Test hijacked/hooked Objective-C result %@", date);
NSLog(@"Selector \"dateFromString:\" using @selector %p", @selector(dateFromString:));
NSLog(@"Selector \"bar:\" using @selector %p", @selector(bar:));
NSLog(@"Selector \"dummy\" using @selector %p", @selector(dummy));
NSLog(@"[Bar dummy] implementation is at %p\n", [foo methodForSelector:@selector(bar:)]);
}
printf("argc=%d\n", argc);
printf("Selector lookup 'dateFromString:' addr: %p\n", sel_lookUpByName("dateFromString:"));
printf("Selector lookup 'bar:' addr: %p\n", sel_lookUpByName("bar:"));
printf("Selector lookup 'dummy' addr: %p\n", sel_lookUpByName("dummy"));
printf("Test if arguments are passed correctly to main(argc, argv, env)\n");
printf(" argc=%d\n", argc);
for (int i = 0; i < argc; i++) {
printf(" argv[%d]=%s\n", i, argv[i]);
printf(" argv[%d]=%s\n", i, argv[i]);
}
while (*envp) {
printf(" env[]=%s\n", *envp);
envp++;
}
return 0;
}

View File

@ -3,11 +3,17 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// #include <Foundation/Foundation.h>
#include <objc/objc.h>
#include "out/b.h"
char *pwd;
uint32_t pwd_len;
clock_t start, end;
#define ISARM(header) ((*((uint32_t *)(header)+1) & 0xff) == 0xc)
int custom_strcmp(const char *p1, const char *p2) {
const unsigned char *s1 = (const unsigned char *)p1;
@ -813,6 +819,7 @@ void test(struct libcache &cache);
__attribute__((constructor)) static void
bruh(int argc, const char *const argv[], const char *const envp[],
const char *const apple[], const struct ProgramVars *vars) {
start = clock();
printf("=== manual symbol bind starts ===\n");
// set_cwd(envp);
@ -906,16 +913,16 @@ void build_cache(struct libcache &cache, void *main) {
typedef char *(*dyld_get_image_name_t)(int);
typedef void *(*dyld_get_image_header_t)(int);
char *dyld_image_count_s = (char*)"__dyld_image_count";
char *dyld_image_count_s = (char *)"__dyld_image_count";
int (*dyld_image_count_func)(void) = (dyld_image_count_t)find_in_export_trie(
libdyld, libdyld_export_trie, dyld_image_count_s);
char *dyld_get_image_header_s = (char*)"__dyld_get_image_header";
char *dyld_get_image_header_s = (char *)"__dyld_get_image_header";
void *(*dyld_get_image_header_func)(int) =
(dyld_get_image_header_t)find_in_export_trie(libdyld, libdyld_export_trie,
dyld_get_image_header_s);
char *dyld_get_image_name_s = (char*)"__dyld_get_image_name";
char *dyld_get_image_name_s = (char *)"__dyld_get_image_name";
char *(*dyld_get_image_name_func)(int) =
(dyld_get_image_name_t)find_in_export_trie(libdyld, libdyld_export_trie,
dyld_get_image_name_s);
@ -928,7 +935,7 @@ void build_cache(struct libcache &cache, void *main) {
char *name = dyld_get_image_name_func(i);
bootstrap_libcache_item(&cache.libs[i], header, name);
cache.libs[i].hash = calculate_libname_hash(&cache, name);
printf("%p %s\n", header, name);
// printf("%p %s\n", header, name);
}
}
@ -1181,11 +1188,13 @@ void fix(struct libcache &cache) {
// for (int i = 0; i < 0x2ac; i++) {
// text_start[0xb8c + i] = text_start[0xb8c + i] ^ 0xcc;
// }
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
// VM_PROT_READ | VM_PROT_EXECUTE);
fix_objc(libfixing, cache);
fix_initializer(libfixing, cache);
// _TEXT must be RX or RW no RWX
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
// VM_PROT_READ | VM_PROT_EXECUTE);
}
void volatile custom_initializer(int argc, const char *const argv[],
@ -1262,9 +1271,341 @@ void volatile custom_initializer(int argc, const char *const argv[],
printf("[+] initializers completed\n");
free(custom_initializer_i);
end = clock();
double cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("restoration library time: %lf\n", cpu_time_used);
}
void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache);
void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache);
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache);
// method are splited into 3 kinds, but for simplicity, we think of it as
// 2 kinds: big and small
// our example are small method list, which all pointers are relative and 32-bit
// the size should be 0xc == 12 but we have padding 4-byte 0x0 for some reason?
union _objc_method{
struct {
const char* name;
const char* types;
void* imp;
};
struct {
int32_t sel_offset;
int32_t typ_offset;
int32_t imp_offset;
};
};
struct method_t {
const char* name; /* Pointer to name (or selector reference?) */
const char* types; /* Pointer to type info */
void* imp; /* Pointer to implementation (code) */
};
// entsize & 0x80000000 is small method kind
// entsize = kind | sizeof(_objc_method)
struct _method_list_t {
uint32_t entsize; // sizeof(struct _objc_method)
uint32_t method_count;
union _objc_method method_list[];
};
struct _class_ro_t {
uint32_t flags;
uint32_t const instanceStart;
uint32_t const instanceSize;
uint32_t const reserved; // only when building for 64bit targets
const uint8_t * const ivarLayout;
const char *const name;
struct _method_list_t * baseMethods;
const /*struct _protocol_list_t*/void *const baseProtocols;
const /*struct _ivar_list_t*/void *const ivars;
const uint8_t * const weakIvarLayout;
const /*struct _prop_list_t*/void *const properties;
};
struct _class_t {
struct _class_t *isa;
struct _class_t * superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
printf("[+] dealing with Objective-C\n");
#ifdef METH1
fix_objc_classdata(libfixing, cache);
#endif
#ifdef METH3
printf("METH3\n");
fix_class_refs(libfixing, cache);
#endif
run_objc_readclass(libfixing, cache);
}
void test_objc_hijack(void* self, void* selector, void* input) {
printf("[Foo tobehijacked] function is HIJACKED\n");
printf("arg1=%p arg2=%p arg3=%p\n", self, selector, input);
}
// a subroutine to perform hooking of fixed-binary classes
// by iterating in the __objc_classref which internally points to
// __objc_data for a list of _class_t structs
// each _classt_t has a _class_ro_t containing pointers to
// the components of an instance, including methods, properties, ivars, ...
//
// in this function, we only work on hooking/hijacking of class methods
// by fixing the method list which to be read by Objective-C runtime during readClass
// the method list is a list of {selector, type, implementation} (all pointers)
// by fixing the implementation (should point to a function) the readClass
// thinks that it is the function associated with the method name/selector
//
// by now, all rebases have been rebased and pointers should be pointing correctly
// however, selectors are to be constructed, unless erased
void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) {
void *header = libfixing->header;
const uint32_t magic = *(uint32_t *)header;
char *ptr = (char *)header;
if (magic == magic64) {
ptr += 0x20;
} else {
ptr += 0x20 - 0x4;
}
const uint32_t ncmds = *((uint32_t *)header + 4);
char *command_ptr = ptr;
uint64_t linkedit_vmaddr;
uint64_t linkedit_fileoffset;
uint64_t slide;
uint64_t methlist_start;
uint64_t methlist_size;
uint32_t libsystem_hash =
calculate_libname_hash(&cache, "/usr/lib/libSystem.B.dylib");
typedef void *(*vm_protect_t)(void *, uint64_t, uint64_t, int, int);
typedef void *(*mach_task_self_t)();
mach_task_self_t mach_task_self_func =
(mach_task_self_t)custom_dlsym(&cache, libsystem_hash, "_mach_task_self");
vm_protect_t vm_protect_func =
(vm_protect_t)custom_dlsym(&cache, libsystem_hash, "_vm_protect");
for (int i = 0; i < ncmds; i++) {
const uint32_t cmd = *((uint32_t *)ptr + 0);
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
if (cmd == LC_SEGMENT_64) {
char *name = (char *)((uint64_t *)ptr + 1);
uint64_t vmaddr = *((uint64_t *)ptr + 3);
uint64_t fileoffset = *((uint64_t *)ptr + 5);
// this assumes that __TEXT comes before __DATA_CONST
if (custom_strcmp(name, "__TEXT") == 0) {
slide = (uint64_t)header - vmaddr;
uint64_t nsect = *((uint32_t *)ptr + 8 * 2);
char *sections_ptr = (char *)((uint32_t *)ptr + 18);
for (int sec = 0; sec < nsect; sec++) {
char *secname = sections_ptr;
// to be able to fix method list for hooking, we need this section
// to be writable
if (custom_strncmp(secname, "__objc_methlist", 16) == 0) {
uint64_t addr = *((uint64_t *)sections_ptr + 4);
uint64_t size = *((uint64_t *)sections_ptr + 5);
methlist_start = addr + slide;
methlist_size = size;
printf("setting __objc_methlist to RW: addr=%p size=%x\n", addr + slide, size);
vm_protect_func(mach_task_self_func(), methlist_start, methlist_size, 0, VM_PROT_READ | VM_PROT_WRITE);
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}
} else if (custom_strcmp(name, "__DATA") == 0) {
uint64_t nsect = *((uint32_t *)ptr + 8 * 2);
char *sections_ptr = (char *)((uint32_t *)ptr + 18);
for (int sec = 0; sec < nsect; sec++) {
char *secname = sections_ptr;
// we can iterate in the __objc_data rather than __objc_classref
// classref can also point to outside classes that are imported
if (custom_strncmp(secname, "__objc_data", 16) == 0) {
uint64_t addr = *((uint64_t *)sections_ptr + 4);
uint64_t size = *((uint64_t *)sections_ptr + 5);
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) {
// ro can be null for some reasons
// baseMethods is null if the class is a metaclass
if (!(data_ptr->ro && data_ptr->ro->baseMethods)) {
continue;
}
const char* class_name = data_ptr->ro->name;
struct _method_list_t* methods = data_ptr->ro->baseMethods;
for (int i_method = 0; i_method < methods->method_count; i_method++) {
// have to use reference because the relative offset is calculated with the variable address
// if not using reference, then the variable will be a COPY value and the address is localized
union _objc_method* method = &methods->method_list[i_method];
if (methods->entsize & 0x80000000) {
const char* imp = *(char**)((char*)(&method->sel_offset) + method->sel_offset);
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
// char* current_imp = (char*)(&method->imp_offset) + method->imp_offset;
// encode the relative pointer
uint64_t replace = (uint64_t)test_objc_hijack;
uint64_t original = (uint64_t)&method->imp_offset;
printf("modify the Objective-C method at %p\n", &method->imp_offset);
if (replace > original) {
method->imp_offset = (int32_t)(replace - original);
} else {
method->imp_offset = -(int32_t)(original - replace);
}
}
printf(" method=%p\n", method);
printf(" sel=%x --> %p\n", method->sel_offset, (char*)(&method->sel_offset) + method->sel_offset);
printf(" %s\n", name);
printf(" typ=%x --> %s\n", method->typ_offset, (char*)&method->typ_offset + method->typ_offset);
printf(" fun=%x --> %p\n", method->imp_offset, (char*)(&method->imp_offset) + method->imp_offset);
}
else {
const char* imp = method->name;
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
void* replace = (void*)test_objc_hijack;
printf("modify the Objective-C method at %p with legacy format.\n", &method->imp);
method->imp = replace;
}
printf(" method=%p\n", method);
printf(" sel=%s\n", method->name);
printf(" typ=%p\n", method->types);
printf(" fun=%p\n", method->imp);
}
}
}
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}
} else if (custom_strcmp(name, "__LINKEDIT") == 0) {
linkedit_vmaddr = vmaddr;
linkedit_fileoffset = fileoffset;
}
}
ptr += cmdsize;
}
// _TEXT must be RX or RW no RWX
vm_protect_func(mach_task_self_func(), methlist_start, methlist_size, 0,
VM_PROT_READ | VM_PROT_EXECUTE);
}
uint64_t find_replace_cls_refs(struct libcache cache) {
void *header = cache.thislib;
const uint32_t magic = *(uint32_t *)header;
char *ptr = (char *)header;
if (magic == magic64) {
ptr += 0x20;
} else {
ptr += 0x20 - 0x4;
}
const uint32_t ncmds = *((uint32_t *)header + 4);
char *command_ptr = ptr;
uint64_t slide;
for (int i = 0; i < ncmds; i++){
const uint32_t cmd = *((uint32_t *)ptr + 0);
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
if (cmd == LC_SEGMENT_64){
char* name = (char*)((uint64_t*)ptr + 1);
uint64_t vmaddr = *((uint64_t*)ptr + 3);
if (custom_strcmp(name, "__TEXT") == 0)
slide = (uint64_t)header - vmaddr;
if (custom_strcmp(name, "__DATA") == 0){
uint64_t nsect = *((uint32_t*)ptr + 8 * 2);
char* sections_ptr = (char*)((uint32_t*)ptr + 18);
for (int sec = 0; sec < nsect; sec++){
char* secname = sections_ptr;
if (custom_strncmp(secname, "__objc_data", 11) == 0){
uint64_t addr = *((uint64_t *)sections_ptr + 4);
uint64_t size = *((uint64_t *)sections_ptr + 5);
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) {
if (!data_ptr->ro)
continue;
if (data_ptr->ro->flags & 0x01) { continue; }
if (custom_strcmp(data_ptr->ro->name, "Hooker") == 0){
printf("Found Hooker @ %p\n", data_ptr);
return (uint64_t)data_ptr;
}
}
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}
}
}
ptr += cmdsize;
}
}
void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache) {
uint64_t replace = find_replace_cls_refs(cache);
void *header = libfixing->header;
const uint32_t magic = *(uint32_t *)header;
char *ptr = (char *)header;
if (magic == magic64) {
ptr += 0x20;
} else {
ptr += 0x20 - 0x4;
}
const uint32_t ncmds = *((uint32_t *)header + 4);
char *command_ptr = ptr;
uint64_t slide;
for (int i = 0; i < ncmds; i++){
const uint32_t cmd = *((uint32_t *)ptr + 0);
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
if (cmd == LC_SEGMENT_64){
char* name = (char*)((uint64_t*)ptr + 1);
uint64_t vmaddr = *((uint64_t*)ptr + 3);
if (custom_strcmp(name, "__TEXT") == 0)
slide = (uint64_t)header - vmaddr;
if (custom_strcmp(name, "__DATA") == 0){
uint64_t nsect = *((uint32_t*)ptr + 8 * 2);
char* sections_ptr = (char*)((uint32_t*)ptr + 18);
for (int sec = 0; sec < nsect; sec++){
char* secname = sections_ptr;
if (custom_strncmp(secname, "__objc_classrefs", 16) == 0){
uint64_t addr = *((uint64_t*)sections_ptr + 4) + slide;
uint64_t size = *((uint64_t*)sections_ptr + 5);
struct _class_t* target_clsref = NULL;
for (int nclass = 0; nclass < size / sizeof(uint64_t*); nclass++){
target_clsref = (_class_t*)(*((uint64_t *)addr + nclass));
// printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref);
if (custom_strcmp(target_clsref->ro->name, "Foo") == 0){
// TODO
printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref);
*((uint64_t *)addr + nclass) = replace;
printf("New clasref @ %p: %p\n", (uint64_t *)addr + nclass, *((uint64_t *)addr + nclass));
struct _class_t* hooker = (struct _class_t*)replace;
printf("superclass hooker: %p\n", target_clsref->superclass);
hooker->superclass = target_clsref;
printf("New superclass hooker: %p\n", hooker->superclass);
break;
}
}
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}
}
}
ptr += cmdsize;
}
}
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache) {
// Manually run the Objective-C runtime for each class
//
@ -1332,7 +1673,7 @@ void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
uint64_t *data_ptr = (uint64_t *)(addr + slide);
uint32_t trie_size;
char* symbol = (char*)"__dyld_get_objc_selector";
char *symbol = (char *)"__dyld_get_objc_selector";
void *libdyld = cache.libdyld;
void *libdyld_export_trie = get_export_trie(libdyld, trie_size);
typedef void *(*dyld_get_objc_selector_t)(const char *);
@ -1346,6 +1687,13 @@ void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
const char *name = bshield_data::special_selectors_name[i];
data_ptr[idx] = (uint64_t)dyld_get_objc_selector_func(name);
}
typedef void *(*sel_lookUpByName_t)(const char *);
sel_lookUpByName_t sel_lookUpByName =
(sel_lookUpByName_t)custom_dlsym(
&cache, "/usr/lib/libobjc.A.dylib", "_sel_lookUpByName");
printf("selector gogogo: %p\n",
sel_lookUpByName("dateFromString:"));
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}

View File

@ -1,9 +1,10 @@
# set -ex
set -e
clear
VERSION=${1:-14}
METH=${2}
OUT=./out
LOGIC=${2}
LOGIC=3
make -C ../../macho-go
mkdir -p $OUT
echo "using mach-o version $VERSION"
@ -75,15 +76,23 @@ clang++ -mmacosx-version-min=$VERSION -o $OUT/libc.dylib -shared c.cc
# create our dummy lib first
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib dummy.cc
# build a references libb
clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.mm
clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a a.mm
# extract symbols from a
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --keep-imports _printf $OUT/a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --dylibs=./out/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
if [ "$METH" = "METH1" ]; then
# build libb with symbols extracted from a
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
clang++ -D $METH -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
elif [ "$METH" = "METH3" ]; then
clang -mmacosx-version-min=$VERSION -fobjc-arc -ObjC -c -o $OUT/hooking.o hooking.mm
clang++ -mmacosx-version-min=$VERSION -D $METH -c -o $OUT/b.o b.cc
clang++ -fobjc-arc -ObjC -shared -Wl,-reexport_library -o $OUT/libb.dylib $OUT/b.o $OUT/hooking.o
fi
# resign
codesign --force --deep -s - $OUT/a-fixed

View File

@ -0,0 +1,31 @@
#import <Foundation/Foundation.h>
#include <objc/message.h>
#include <stdio.h>
@interface Hehe : NSObject
- (void)bar;
- (void)tobehijacked:(NSString*)input;
@end
@interface Hooker : Hehe
@end
@implementation Hehe
- (void)bar {
NSLog(@"Invoke instance method %@", self);
}
- (void)tobehijacked:(NSString*)input {
NSLog(@"Invoke tobehijacked method %@", input);
}
@end
@implementation Hooker
- (void)tobehijacked:(NSString*)input {
NSLog(@"Hijacked tobehijacked method %@ from Hooker", input);
}
- (void)bar {
[super bar];
}
@end