[iOS Knowledge Brief]-Runtime-Message Forwarding

Posted by steveclondon on Tue, 17 Sep 2019 15:12:53 +0200

1 message flow

1.1 messaging

The compiler processes OC method calls into objc_msgSend calls. The function is assembled and implemented to deal with variable parameters and return values.

Reciver traverses the method list to find the IMP pointer jump according to the class of isa pointer. If not, look up according to the superClass pointer.

If it is not found, it enters the message forwarding process.

In addition, there is a method cache in the implementation of objc_msgSend. Each class has a cache that caches its own and parent's methods. Find the cache first.

1.2 Message Forwarding

1.2.1 Stage I Dynamic Method Analysis:

+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector

For example:

+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically))
    {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSel];
}

1.2.2 Phase II complete message forwarding:

(1) replacement receiver

- (id)forwardingTargetForSelector:(SEL)selector

(2) Full message forwarding

- (void)forwardInvocation:(NSInvocation *)invocation

Here, to trigger forward Invocation: method, you must implement method Signature ForSelector: method, or indirectly through protocol declaration.

More specifically, when a method cannot be found in the method cache and method list, the message forwarding _objc_msgForward function is entered.

<objc/message.h>
_objc_msgForward
_objc_msgForward_stret (for methods with a struct return)

Core Foundation calls this sentence at init:

objc_setForwardHandler(___forwarding_prep_0___, ___forwarding_prep_1___);

This is why the following functions are seen in the call stack, all implemented in CoreFoundation.

frame #1: 0x0000000105b93c34 CoreFoundation`___forwarding___ + 772
frame #2: 0x0000000105b95da8 CoreFoundation`__forwarding_prep_0___ + 120

_ The forwarding_ implementation is similar to the following:

void __forwarding__(BOOL isStret, id receiver, SEL sel, ...) {
  id forwardingTarget = [receiver forwardingTargetForSelector:sel];
  if (forwardingTarget) {
    return objc_msgSend(forwardingTarget, sel, ...);
  }

  NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
  if (methodSignature) {
    NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature ...];
    [receiver forwardInvocation:invocation];

    void *returnValue = NULL;
    [invocation getReturnValue:&value];
    return returnValue;
  }

  // doesNotRecognizeSelector: should throw to avoid the SIGKILL.
  [receiver doesNotRecognizeSelector:sel];
  kill(getpid(), 9);
}

1.2.3 last

- (void)doesNotRecognizeSelector:(SEL)aSelector;

Reference resources

  • http://www.arigrant.com/blog/2013/12/13/a-selector-left-unhandled
  • http://www.arigrant.com/blog/2014/2/12/why-objcmsgsend-must-be-written-in-assembly

Finally, there's the details of the call stack.

0   CoreFoundation                      0x017e57e4 __exceptionPreprocess + 180
1   libobjc.A.dylib                     0x015658e5 objc_exception_throw + 44
2   CoreFoundation                      0x01882843 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
3   CoreFoundation                      0x017d5b0b ___forwarding___ + 1019
4   CoreFoundation                      0x017d56ee _CF_forwarding_prep_0 + 14
5   UIKit                               0x002987ea -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1097
6   UIKit                               0x0028bada -[UIView(Hierarchy) addSubview:] + 56
7   Zoof                                0x00002c3f main + 639
8   libdyld.dylib                       0x01dca70d start + 1

Look at the call stack, as above, _CF_forwarding_prep_0 before what OC method call triggered, call stack can not be seen. The reason is: functions such as _objc_msgForward are implemented in a stack of assemblies, in which function calls are b/br, not bl/blr, and will not save the next instruction to the LR register, forming a frame stack, so when calling stack backtracking, it is impossible to find the LR in the previous function from the current function, and possibly the LR in the last function, or even more. Upper strata. For example, the following assembly implements:

STATIC_ENTRY __objc_msgForward_impcache

MESSENGER_START
nop
MESSENGER_END_SLOW

// No stret specialization.
b	__objc_msgForward

END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward

adrp	x17, __objc_forward_handler@PAGE
ldr	x17, [x17, __objc_forward_handler@PAGEOFF]
br	x17

END_ENTRY __objc_msgForward

2 Relevant Applications

2.1 JSPatch Principle

For the OC method to be replaced by js, forward Invocation of the corresponding class: IMP of the method will be replaced by its JPForward Invocation function to HOOK, and the IMP of the corresponding method will be replaced by _objc_msgForward, so that calling the corresponding OC method will be unified into the JPForward Invocation function. Follow-up to find jsFunc, package OC parameters, call JSValue's callWithArguments: method can be invoked to the JS method.

2.2 Can't find a way to crash defense

NSObject class, hookforwarding Target ForSelector: method, returns a custom class object, and implements an empty method with the same name. The code is similar to the following:

#import <objc/runtime.h>
#import <objc/message.h>

static void* smartFunc(id sel , SEL fun ,...)
{
    return NULL;
}

@interface TSmartFunction : NSObject
- (BOOL)addFunction:(SEL)sel;
@end

@implementation TSmartFunction
- (BOOL)addFunction:(SEL)sel {
    return class_addMethod([TSmartFunction class], sel, (IMP)smartFunc, "");
}
@end

@interface NSObject(TForwardingTarget)
- (id)TForwardingTargetForSelector:(SEL)aSelector;
@end

@implementation NSObject(TForwardingTarget)
- (id)TForwardingTargetForSelector:(SEL)aSelector {
    BOOL bResponse = [self respondsToSelector:aSelector];
    NSMethodSignature* signatrue = [self methodSignatureForSelector:aSelector];
    if (bResponse || signatrue) {
        return [self TForwardingTargetForSelector:aSelector];
    } else {
        TSmartFunction* func = [[TSmartFunction alloc] init];
        [func addFunction:aSelector];
        return func;
    }
}
@end

@interface NSObject(TSwizzle)
@end

@implementation NSObject(TSwizzle)
+ (BOOL)TSwizzleMethod:(SEL)origSel withMethod:(SEL)altSel error:(NSError**)error {
    Method origMethod = class_getInstanceMethod(self, origSel);
    if (!origMethod) {
        return NO;
    }
    
    Method altMethod = class_getInstanceMethod(self, altSel);
    if (!altMethod) {
        return NO;
    }
    
    method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
    return YES;
}
@end

@implementation TMsgForward
+ (void)load {
    [[NSObject class] TSwizzleMethod:@selector(forwardingTargetForSelector:)
                          withMethod:@selector(TForwardingTargetForSelector:) error:nil];
}
@end

2.3 Proxy Objects

Slightly.

3 Appendix Code

In Core Foundation:

int ___CFInitialize() {
    rax = *(int8_t *)___CFInitializing;
    rax = rax | *(int8_t *)___CFInitialized;
    if (rax != 0x0) goto loc_267d;

loc_1894:
    *(int8_t *)___CFInitializing = 0x1;
    *0x5decf8 = pthread_self();
    *(int8_t *)___CFProphylacticAutofsAccess = 0x1;
    rbx = 0x8;
    do {
            rdi = *(rbx + 0x5dc5f8);
            if (rdi != 0x0) {
                    rax = getenv(rdi);
            }
            else {
                    rax = 0x0;
            }
            *(rbx + ___CFEnv) = rax;
            rbx = rbx + 0x10;
    } while (rbx != 0x198);
    ___exceptionInit();
    objc_setForwardHandler(___forwarding_prep_0___, ___forwarding_prep_1___);
    objc_setEnumerationMutationHandler(___NSFastEnumerationMutationHandler);
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    var_20 = rax;
    var_30 = zero_extend_64(xmm7);
    var_40 = zero_extend_64(xmm6);
    var_50 = zero_extend_64(xmm5);
    var_60 = zero_extend_64(xmm4);
    var_70 = zero_extend_64(xmm3);
    var_80 = zero_extend_64(xmm2);
    var_90 = zero_extend_64(xmm1);
    var_A0 = zero_extend_64(xmm0);
    var_A8 = arg5;
    var_B0 = arg4;
    var_B8 = arg3;
    var_C0 = arg2;
    var_C8 = arg1;
    rax = ____forwarding___(&var_D0, 0x0, arg2, arg3, arg4);
    if (rax != 0x0) {
            rax = *rax;
    }
    else {
            rax = objc_msgSend(var_D0, var_C8);
    }
    return rax;
}
int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4) {
    r8 = arg4;
    rsi = arg1;
    r12 = arg0;
    rax = COND_BYTE_SET(NE);
    r13 = *_objc_msgSend_stret;
    if (rsi == 0x0) {
            r13 = *_objc_msgSend;
    }
    var_40 = *(r12 + rax * 0x8 + 0x8);
    rbx = *(r12 + rax * 0x8);
    var_38 = rax * 0x8;
    if ((rbx & 0x1) == 0x0) goto loc_7dc58;

loc_7dc37:
    rax = rbx >> 0x1 & 0x7;
    if (rax == 0x7) {
            rax = (rbx >> 0x4 & 0xff) + 0x8;
    }
    if (rax == 0x0) goto loc_7dfd5;

loc_7dc58:
    var_58 = rsi;
    var_48 = r12;
    r12 = object_getClass(rbx);
    r15 = class_getName(r12);
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_7dcdf;

loc_7dc8c:
    rax = [rbx forwardingTargetForSelector:var_40];
    if ((rax == 0x0) || (rax == rbx)) goto loc_7dcdf;

loc_7dca6:
    r12 = var_48;
    if ((rax & 0x1) == 0x0) goto loc_7dccf;

loc_7dcae:
    rcx = rax >> 0x1 & 0x7;
    if (rcx == 0x7) {
            rcx = (rax >> 0x4 & 0xff) + 0x8;
    }
    if (rcx == 0x0) goto loc_7dfd2;

loc_7dccf:
    *(r12 + var_38) = rax;
    r12 = 0x0;
    goto loc_7e008;

loc_7e008:
    if (**___stack_chk_guard == **___stack_chk_guard) {
            rax = r12;
    }
    else {
            rax = __stack_chk_fail();
    }
    return rax;

loc_7dfd2:
    rbx = rax;
    goto loc_7dfd5;

loc_7dfd5:
    var_30 = **___stack_chk_guard;
    r14 = _getAtomTarget(rbx);
    *(r12 + var_38) = r14;
    ___invoking___(r13, r12, r12, 0x400, 0x0, r9, stack[-104], var_58, var_50, var_48, var_40, var_38, var_30, var_28, stack[-40], stack[-32], stack[-24], stack[-16], stack[-8], stack[0]);
    if (*r12 == r14) {
            *r12 = rbx;
    }
    goto loc_7e008;

loc_7dcdf:
    var_38 = rbx;
    if (strncmp(r15, "_NSZombie_", 0xa) == 0x0) goto loc_7e047;

loc_7dcff:
    r14 = var_38;
    if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_7e094;

loc_7dd1d:
    r12 = [r14 methodSignatureForSelector:var_40];
    rbx = var_58;
    if (r12 == 0x0) goto loc_7e0f3;

loc_7dd3d:
    r15 = [r12 _frameDescriptor];
    if (((*(int16_t *)(*r15 + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
            rdx = sel_getName(var_40);
            rcx = " not";
            if ((*(int16_t *)(*r15 + 0x22) & 0xffff & 0x40) != 0x0) {
                    rcx = "";
            }
            r8 = " not";
            if (rbx != 0x0) {
                    r8 = "";
            }
            _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[-104]);
    }
    rax = object_getClass(r14);
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_50 = r15;
    if (rax != 0x0) {
            if (*0x5de480 != 0xffffffffffffffff) {
                    dispatch_once(0x5de480, ^ {/* block implemented at ______forwarding____block_invoke */ } });
            }
            r13 = [NSInvocation requiredStackSizeForSignature:r12];
            rsi = *0x5de478;
            r15 = &stack[-104] - (rsi + 0xf & 0xfffffffffffffff0);
            __bzero(r15, rsi);
            objc_constructInstance(*0x5de470, r15);
            var_40 = r13;
            [r15 _initWithMethodSignature:r12 frame:var_48 buffer:&stack[-8] - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
            [r14 _forwardStackInvocation:r15];
            rbx = 0x1;
    }
    else {
            if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) != 0x0) {
                    r15 = [NSInvocation _invocationWithMethodSignature:r12 frame:var_48];
                    [r14 forwardInvocation:r15];
            }
            else {
                    r15 = 0x0;
                    _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", r14, object_getClassName(r14), r8, r9, stack[-104]);
            }
            var_40 = 0x0;
            rbx = 0x0;
    }
    if (*(int8_t *)&r15->_retainedArgs != 0x0) {
            rax = *var_50;
            if (*(int8_t *)(rax + 0x22) < 0x0) {
                    rcx = *ivar_offset(_frame);
                    rdx = *(int32_t *)(rax + 0x1c);
                    rsi = *(int8_t *)(rax + 0x20) & 0xff;
                    memmove(*(rsi + var_48 + rdx), *(rsi + rdx + *(r15 + rcx)), *(int32_t *)(*rax + 0x10));
            }
    }
    r14 = [r12 methodReturnType];
    rax = *(int8_t *)r14;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
            r12 = r15->_retdata;
            if (rbx != 0x0) {
                    r12 = [[NSData dataWithBytes:r12 length:var_40] bytes];
                    [r15 release];
                    rax = *(int8_t *)r14;
            }
            if (rax == 0x44) {
                    asm { fld        tword [r12] };
            }
    }
    else {
            r12 = ____forwarding___.placeholder;
            if (rbx != 0x0) {
                    r12 = ____forwarding___.placeholder;
                    [r15 release];
            }
    }
    goto loc_7e008;

loc_7e0f3:
    r15 = sel_getName(var_40);
    r8 = sel_getUid(r15);
    if (r8 != var_40) {
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_40, r15, r8, r9, stack[-104]);
    }
    if (class_respondsToSelector(object_getClass(var_38), @selector(doesNotRecognizeSelector:)) == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", var_38, object_getClassName(var_38), r8, r9, stack[-104]);
            asm { ud2 };
            rax = loc_7e172(rdi, rsi, rdx, rcx, r8);
    }
    else {
            [var_38 doesNotRecognizeSelector:var_40];
            asm { ud2 };
            rax = loc_7e185();
    }
    return rax;

loc_7e094:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_38, r14, object_getClassName(var_38), r9, stack[-104]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_38, r14, r8, r9, stack[-104]);
    }
    goto loc_7e0f3;

loc_7e047:
    if (*(int8_t *)___CFOASafe != 0x0) {
            ___CFRecordAllocationEvent();
    }
    _CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p", r15 + 0xa, sel_getName(var_40), var_38, r9, stack[-104]);
    asm { ud2 };
    rax = loc_7e094(rdi, rsi, rdx, rcx, r8);
    return rax;
}