add method 1 hooking for x86_64; method 3 first commit
This commit is contained in:
parent
57b0ae26a7
commit
06525b8a5e
@ -10,11 +10,11 @@ void* sel_lookUpByName(const char* name);
|
|||||||
|
|
||||||
@implementation Foo
|
@implementation Foo
|
||||||
- (void)bar {
|
- (void)bar {
|
||||||
NSLog(@"Invoke instance method %@", self);
|
NSLog(@"Invoke instance method original bar in Foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tobehijacked:(NSString*)input {
|
- (void)tobehijacked:(NSString*)input {
|
||||||
NSLog(@"Invoke tobehijacked method %@", input);
|
NSLog(@"Invoke tobehijacked method %@ from Foo", input);
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
// #include <Foundation/Foundation.h>
|
// #include <Foundation/Foundation.h>
|
||||||
#include <objc/objc.h>
|
#include <objc/objc.h>
|
||||||
@ -11,6 +12,8 @@
|
|||||||
|
|
||||||
char *pwd;
|
char *pwd;
|
||||||
uint32_t pwd_len;
|
uint32_t pwd_len;
|
||||||
|
clock_t start, end;
|
||||||
|
#define ISARM(header) ((*((uint32_t *)(header)+1) & 0xff) == 0xc)
|
||||||
|
|
||||||
int custom_strcmp(const char *p1, const char *p2) {
|
int custom_strcmp(const char *p1, const char *p2) {
|
||||||
const unsigned char *s1 = (const unsigned char *)p1;
|
const unsigned char *s1 = (const unsigned char *)p1;
|
||||||
@ -816,6 +819,7 @@ void test(struct libcache &cache);
|
|||||||
__attribute__((constructor)) static void
|
__attribute__((constructor)) static void
|
||||||
bruh(int argc, const char *const argv[], const char *const envp[],
|
bruh(int argc, const char *const argv[], const char *const envp[],
|
||||||
const char *const apple[], const struct ProgramVars *vars) {
|
const char *const apple[], const struct ProgramVars *vars) {
|
||||||
|
start = clock();
|
||||||
printf("=== manual symbol bind starts ===\n");
|
printf("=== manual symbol bind starts ===\n");
|
||||||
// set_cwd(envp);
|
// set_cwd(envp);
|
||||||
|
|
||||||
@ -931,7 +935,7 @@ void build_cache(struct libcache &cache, void *main) {
|
|||||||
char *name = dyld_get_image_name_func(i);
|
char *name = dyld_get_image_name_func(i);
|
||||||
bootstrap_libcache_item(&cache.libs[i], header, name);
|
bootstrap_libcache_item(&cache.libs[i], header, name);
|
||||||
cache.libs[i].hash = calculate_libname_hash(&cache, name);
|
cache.libs[i].hash = calculate_libname_hash(&cache, name);
|
||||||
printf("%p %s\n", header, name);
|
// printf("%p %s\n", header, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1267,14 +1271,76 @@ void volatile custom_initializer(int argc, const char *const argv[],
|
|||||||
|
|
||||||
printf("[+] initializers completed\n");
|
printf("[+] initializers completed\n");
|
||||||
free(custom_initializer_i);
|
free(custom_initializer_i);
|
||||||
|
end = clock();
|
||||||
|
double cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
|
||||||
|
printf("restoration library time: %lf\n", cpu_time_used);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache);
|
void fix_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);
|
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache);
|
||||||
|
|
||||||
|
// method are splited into 3 kinds, but for simplicity, we think of it as
|
||||||
|
// 2 kinds: big and small
|
||||||
|
// our example are small method list, which all pointers are relative and 32-bit
|
||||||
|
// the size should be 0xc == 12 but we have padding 4-byte 0x0 for some reason?
|
||||||
|
union _objc_method{
|
||||||
|
struct {
|
||||||
|
const char* name;
|
||||||
|
const char* types;
|
||||||
|
void* imp;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
int32_t sel_offset;
|
||||||
|
int32_t typ_offset;
|
||||||
|
int32_t imp_offset;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct method_t {
|
||||||
|
const char* name; /* Pointer to name (or selector reference?) */
|
||||||
|
const char* types; /* Pointer to type info */
|
||||||
|
void* imp; /* Pointer to implementation (code) */
|
||||||
|
};
|
||||||
|
|
||||||
|
// entsize & 0x80000000 is small method kind
|
||||||
|
// entsize = kind | sizeof(_objc_method)
|
||||||
|
struct _method_list_t {
|
||||||
|
uint32_t entsize; // sizeof(struct _objc_method)
|
||||||
|
uint32_t method_count;
|
||||||
|
union _objc_method method_list[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _class_ro_t {
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t const instanceStart;
|
||||||
|
uint32_t const instanceSize;
|
||||||
|
uint32_t const reserved; // only when building for 64bit targets
|
||||||
|
const uint8_t * const ivarLayout;
|
||||||
|
const char *const name;
|
||||||
|
struct _method_list_t * baseMethods;
|
||||||
|
const /*struct _protocol_list_t*/void *const baseProtocols;
|
||||||
|
const /*struct _ivar_list_t*/void *const ivars;
|
||||||
|
const uint8_t * const weakIvarLayout;
|
||||||
|
const /*struct _prop_list_t*/void *const properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _class_t {
|
||||||
|
struct _class_t *isa;
|
||||||
|
struct _class_t * superclass;
|
||||||
|
void *cache;
|
||||||
|
void *vtable;
|
||||||
|
struct _class_ro_t *ro;
|
||||||
|
};
|
||||||
void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
void fix_objc(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
printf("[+] dealing with Objective-C\n");
|
printf("[+] dealing with Objective-C\n");
|
||||||
|
#ifdef METH1
|
||||||
fix_objc_classdata(libfixing, cache);
|
fix_objc_classdata(libfixing, cache);
|
||||||
|
#endif
|
||||||
|
#ifdef METH3
|
||||||
|
printf("METH3\n");
|
||||||
|
fix_class_refs(libfixing, cache);
|
||||||
|
#endif
|
||||||
run_objc_readclass(libfixing, cache);
|
run_objc_readclass(libfixing, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1365,46 +1431,6 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache)
|
|||||||
// classref can also point to outside classes that are imported
|
// classref can also point to outside classes that are imported
|
||||||
if (custom_strncmp(secname, "__objc_data", 16) == 0) {
|
if (custom_strncmp(secname, "__objc_data", 16) == 0) {
|
||||||
|
|
||||||
// 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?
|
|
||||||
struct _objc_method {
|
|
||||||
int32_t sel_offset;
|
|
||||||
int32_t typ_offset;
|
|
||||||
int32_t imp_offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
struct _objc_method method_list[];
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 * const superclass;
|
|
||||||
void *cache;
|
|
||||||
void *vtable;
|
|
||||||
struct _class_ro_t *ro;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
uint64_t addr = *((uint64_t *)sections_ptr + 4);
|
||||||
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
uint64_t size = *((uint64_t *)sections_ptr + 5);
|
||||||
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
|
struct _class_t *data_ptr = (struct _class_t *)(addr + slide);
|
||||||
@ -1420,7 +1446,7 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache)
|
|||||||
for (int i_method = 0; i_method < methods->method_count; i_method++) {
|
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
|
// 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
|
// if not using reference, then the variable will be a COPY value and the address is localized
|
||||||
struct _objc_method* method = &methods->method_list[i_method];
|
union _objc_method* method = &methods->method_list[i_method];
|
||||||
if (methods->entsize & 0x80000000) {
|
if (methods->entsize & 0x80000000) {
|
||||||
const char* imp = *(char**)((char*)(&method->sel_offset) + method->sel_offset);
|
const char* imp = *(char**)((char*)(&method->sel_offset) + method->sel_offset);
|
||||||
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
|
if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) {
|
||||||
@ -1443,6 +1469,18 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache)
|
|||||||
printf(" typ=%x --> %s\n", method->typ_offset, (char*)&method->typ_offset + method->typ_offset);
|
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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1461,6 +1499,112 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache)
|
|||||||
VM_PROT_READ | VM_PROT_EXECUTE);
|
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) {
|
void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache) {
|
||||||
// Manually run the Objective-C runtime for each class
|
// Manually run the Objective-C runtime for each class
|
||||||
//
|
//
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
set -e
|
set -e
|
||||||
|
clear
|
||||||
VERSION=${1:-14}
|
VERSION=${1:-14}
|
||||||
|
METH=${2}
|
||||||
OUT=./out
|
OUT=./out
|
||||||
LOGIC=${2}
|
LOGIC=3
|
||||||
|
make -C ../../macho-go
|
||||||
mkdir -p $OUT
|
mkdir -p $OUT
|
||||||
|
|
||||||
echo "using mach-o version $VERSION"
|
echo "using mach-o version $VERSION"
|
||||||
@ -81,9 +82,17 @@ clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a a.mm
|
|||||||
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --keep-imports _printf $OUT/a
|
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --keep-imports _printf $OUT/a
|
||||||
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --dylibs=./out/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a
|
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --dylibs=./out/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a
|
||||||
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
|
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
|
||||||
|
|
||||||
|
if [ "$METH" = "METH1" ]; then
|
||||||
# build libb with symbols extracted from a
|
# build libb with symbols extracted from a
|
||||||
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
|
clang++ -D $METH -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
|
||||||
../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
|
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib
|
||||||
|
|
||||||
|
elif [ "$METH" = "METH3" ]; then
|
||||||
|
clang -mmacosx-version-min=$VERSION -fobjc-arc -ObjC -c -o $OUT/hooking.o hooking.mm
|
||||||
|
clang++ -mmacosx-version-min=$VERSION -D $METH -c -o $OUT/b.o b.cc
|
||||||
|
clang++ -fobjc-arc -ObjC -shared -Wl,-reexport_library -o $OUT/libb.dylib $OUT/b.o $OUT/hooking.o
|
||||||
|
fi
|
||||||
|
|
||||||
# resign
|
# resign
|
||||||
codesign --force --deep -s - $OUT/a-fixed
|
codesign --force --deep -s - $OUT/a-fixed
|
||||||
|
31
research/custom_loader/hooking.mm
Normal file
31
research/custom_loader/hooking.mm
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <objc/message.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@interface Hehe : NSObject
|
||||||
|
- (void)bar;
|
||||||
|
- (void)tobehijacked:(NSString*)input;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface Hooker : Hehe
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Hehe
|
||||||
|
- (void)bar {
|
||||||
|
NSLog(@"Invoke instance method %@", self);
|
||||||
|
}
|
||||||
|
- (void)tobehijacked:(NSString*)input {
|
||||||
|
NSLog(@"Invoke tobehijacked method %@", input);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation Hooker
|
||||||
|
- (void)tobehijacked:(NSString*)input {
|
||||||
|
NSLog(@"Hijacked tobehijacked method %@ from Hooker", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)bar {
|
||||||
|
[super bar];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in New Issue
Block a user