Protection against Message Forward in Objective-C

A Brief Review


Runtime is one of the powerful features of Objective-C, it provides us the ability to add/replace a method of some class, retrieve/set the implementation of a specified method, and even add a class in runtime.

However, just like the sunlight shines not only on the good guys, but also on the bad ones. There are some tools in iOS Jailbreak community which take the advantage of Objective-C's runtime feature, to be specific, the ability of message forward. Within this feature, they can log the calling sequence of the methods in the application for further exploitation. And it's not that hard to inject such tools into your application, since there are many ways such as DYLD_INSERT_LIBRARIES or modifying the MachO load commands, and they got some sets of tools like iOSOpenDev, and the new replacement of iOSOpenDev, MonkeyDev by @AloneMonkey.

And there're at least 2 tools which can log the calling sequence of the methods,

Hooked via forwardInvocation
Hooked via forwardInvocation

After spending some time on reading the codes, they are built on the top of message forward ability of Objective-C. Now, let's simply explore the message forward feature.

Simply Explore Objective-C Message Forward


For all classes that directly or indirectly inherited from NSObject, there's a default instance method named -[class forwardInvocation:], it accepts one NSInvocation object which has these properties

  • target, the receiver of the message
  • selector, which method should the receiver call
  • methodSignature, the 'type' of the method, used for correctly retrieve arguments and return value

If we want message forward feature in our class, then we just override the default implementation inherited from NSObject. It usually goes like below according to the Apple Objective-C Runtime Programming Guide.

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
         [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

And in normal development, there is one more instance method should be implemented and it's named -[class methodSignatureForSelector:]. The return value is exactly the property methodSignature of the NSInvocation object.

The BigBang Example


Now, let's look at codesourse/BigBang, one of the tools that can be used for logging calling sequence of methods. Before we get started, I'd like to mention a truly great and very detailed article on Objective-C's message forward mechanism, Objective-C 消息发送与转发机制原理.

The BigBang's implementation is clear and simple,

  1. Get each selector copied with prefix "BigBang_"
  2. All original selectors points to _objc_msgForward
  3. replace the class's message forward implementation
  4. Once the original selector is called, objc would process the message forward procedure, and this procedure is under BigBang's control
  5. In the message forward procedure, BigBang log the name of the original selector, args and return value, assign the "BigBang_" prefixed selector to invocation, and then invoke

We could rewrite the BigBang tool in pseudocode as below.

// the entry
+ (void)hookClass:(NSString *)theClass
{

    // this for loop applies replacement
    for each method in theClass {
        retrieve selector, type encoding (i.e methodSignature) and IMP
        filter according to the selector's name (skip some hidden methods, such as .cxx_destruct)

        // created a new selector with the old one's IMP
        add the "BigBang_" prefix to the selector
        add this new selector with old selector's IMP to the class

        // when calling the old selector, use message forward
        set old selector's IMP to _objc_msgForward(a.k.a, forwardInvocation:)
    }

    // a universal forward invocation implementation
    IMP forwardInvocationImpl = imp_implementationWithBlock(^(id object, NSInvocation * invocation) {
        retrieve selector from the NSInvocation object

        // this completes the forwarding, i.e
        // -[theClass oldSelector] -> -[theClass BigBang_oldSelector]
        add the "BigBang_" prefix to the selector
        assign the new one to the NSInvocation object

        get the arguments and return value from the NSInvocation object

        // the ultimate goal
        print the name of the old selector, arguments passed to it, and the return value

        // original IMP is invoked, but now the selector is prefixed with "BigBang_"
        invoke -[theClass BigBang_oldSelector] 
    }

    set the forwardInvocationImpl as -[theClass forwardInvocation:] IMP
}

As you can see, both the idea and the implementation of BigBang are simple (but not trivial), and now, what should we do to protect our important classes?

Here is how we should do it


As a matter of fact, we could reasonably assume that all functions are hooked, since they're of course capable of doing it (well, at least, some of they're). It seems that this drives us to the wall. But luckily, there're builtin functions. These functions are provided by the complier, and translated into assemble instructions during the compiling. So, there's just no way to hook them.

Here is how we should do it. We need to add a variadic method to the class we wanna protect.

@interface ProtectedClass : NSObject
 
- (void)someImportantMethod:(id)arg;
 
// make this a deceptive name 
- (void)aDeceptiveMethodName:(id)arg, ... NS_REQUIRES_NIL_TERMINATION;
 
@end

Well, but you may ask why do we use the variadic method. Here is the explanation.

Firstly, since the BigBang and some other tools take advantage of forwarding, then we just need to find something which cannot be forwarded, and variadic parameter is exactly what we expected. The mechanism of C language (Objective-C is a superset on C) just NOT ALLOW such behavior, in fact, such code (as below) will NEVER GET COMPILED. You may refer to C FAQ Question 15.12.

void my_printf(char * fmt, ...) {
    printf(fmt, ...); // won't compile
}

Second, va_* functions are all builtin functions, and there's just no way for attackers to hook builtin functions.

#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_end(ap)          __builtin_va_end(ap)
#define va_arg(ap, type)    __builtin_va_arg(ap, type)

Last, this variadic method will be shown or dumped as -[ProtectedClass aDeceptiveMethodName:] both in runtime and static analysis. With a deceptive name, it would be really hard (but not impossible) for attackers to identify that this is in fact a variadic method.

Ok, now we need a function to call it.

static void inline protection() {
    // We should also protect the selector's name here, but this beyonds the scope of this post
    // In fact, there're many means, you may refer to one of my old post:
    //     Avoid XRef in Hopper and IDA
    IMP target = method_getImplementation(class_getInstanceMethod([ProtectedClass class], NSSelectorFromString(@"aDeceptiveMethodName:")));

    // cast the IMP to the variadic function type
    typedef void(*targetMethodImplmentation)(id, SEL, id, /* magic int */ ...);
    id obj = [[ProtectedClass alloc] init];
    ((targetMethodImplmentation)target)(obj, NSSelectorFromString(@"aDeceptiveMethodName:"), nil, 1, nil);
}

Now, try to retrieve the int value 1 we just passed to it.

// make this a deceptive name
- (void)aDeceptiveMethodName:(id)arg, ... {
    // perhaps you really need to do something before this to cover up
    va_list arg_ptr;
    int magic = 0;
    va_start(arg_ptr, arg);
    magic = va_arg(arg_ptr, int);
    va_end(arg_ptr);
    if (magic != 1) {
        NSLog(@"Error: forwarded message!");

        // use longjmp so that the call stack gets cleaned
        // and also to protect this deceptive method
        longjmp(protectionJMP, 1);
    } else {
        NSLog(@"So far so good.");
    }
}

What if the magic is not 1, call exit()? Remember our previous assumption, all functions could have been hooked. Even if the exit() is not hooked, attackers can set a breakpoint on exit, and then the call stack is preserved, the deceptive variadic method is exposed.

Thus why we take advantage of longjmp(). longjmp() will clean the call stack, and the call stack will be like this below.

protected call stack
protected call stack

Of course, longjmp could have been hooked, too. But we can refer to its source code and clean the call stack by ourselves.

The complete example is on my GitHub, BlueCocoa/no_forward_invocation.

声明: 本文为0xBBC原创, 转载注明出处喵~

《Protection against Message Forward in Objective-C》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注