Monitoring Ivar Changes in Objective-C

As we've mentioned in the last post, Protection against Message Forward in Objective-C, there're at least two tools for tracing the calling sequence of the methods,

However, they just cannot handle it in the scene below,

@interface ProtectedClass : NSObject {
@public
    NSString * _password;
}
@property (nonatomic, getter=password, setter=setPassword:) NSString * password;
@end
/// ...omited...
    ProtectedClass * obj = [[ProtectedClass alloc] init];
    obj->_password = @"喵咕咪~"; // directly access, undectectable in BigBang or ANYMethodLog
    [obj setPassword:@"喵"]; // BigBang or ANYMethodLog dectectable
/// ...omited...

Because it's not necessarily to call getter or setter in Objective-C when access or change an ivar. Since Objective-C is just a superset of C, so the object (or instance) in Objective-C acts pretty much like the struct in C. You can directly access its member if you have the memory address. Let's check out what happens when compiling.

Here is our code, written in Objective-C, and it's probably quite often to be seen in your projects.

Objective-C code
Objective-C code

And when Xcode generates the assemble code, (noticing the line 78 and 79)

Assemble code
Assemble code

They've been transformed to objc_storeStrong and objc_msgSend respectively. For the latter one, i.e, objc_msgSend, we're pretty familiar with it. (If not, you may refer to Objective-C 消息发送与转发机制原理) As for the former one, objc_storeStrong, well, it's actually quite simple. According to the clang document,

void objc_storeStrong(id *object, id value) {
    id oldValue = *object;
    value = [value retain];
    *object = value;
    [oldValue release];
}

That's straightforward. And in simple words, objc_storeStrong stores the object value to the address object points to. There's nothing related to Objective-C's message mechanism, just simple memory accessing. This code explains why BigBang or ANYMethodLog failed to detect such changes. (In fact, Objective-C's KVO mechanism would also failed.)

But luckily, there's a objc_storeStrong function, we could hook objc_storeStrong and search the address object belongs to which instance, and then we can get notified if any ivar is changed directly. And here we use fishhook.

        // directly acccess
        rebind_symbols((struct rebinding []){
            {"objc_storeStrong", (void *)meow_objc_storeStrong, (void **)&meow_orig_objc_storeStrong},
            {"objc_storeWeak", (void *)meow_objc_storeWeak, (void **)&meow_orig_objc_storeWeak}
        }, 2);

        // add ivar in runtime
        rebind_symbols((struct rebinding []){
            {"class_addIvar", (void *)meow_class_addIvar, (void **)&meow_orig_class_addIvar},
            {"class_addProperty", (void *)meow_class_addProperty, (void **)&meow_orig_class_addProperty}
        }, 2);

These would be enough for monitoring ivar changes for a given class. And then, we need every new instance of the given class, so we need to hook -[aClass init] and class_createInstance.

        // hook class_createInstance
        rebind_symbols((struct rebinding []){
            {"class_createInstance", (void *)meow_class_createInstance, (void **)&meow_orig_class_createInstance}
        }, 1);

        // hook [aClass init]
        Method initMethod = class_getInstanceMethod(aClass, @selector(init));
        IMP meow_orig_init = method_getImplementation(initMethod);
        IMP meow_init = imp_implementationWithBlock(^(id object, SEL sel){
            id obj = ((id(*)(id, SEL))(meow_orig_init))(object, sel);

            // hold weak reference to that object
            __weak id weakObject = obj;
            NSLog(@"[INFO] new object of %s. range <%p, %p>", class_getName(self._class), obj, (long long *)(__bridge void *)(obj) + self.classSize);
            [self.objects addObject:weakObject];
            return obj;
        });
        class_replaceMethod(self._class, @selector(init), meow_init, "@@:@");

However, some instance may be already inited, thus why we need choose, a function from cycript.

- (void)updateObjects {
    NSArray * objects = [choose choose:self._class];
    for (id object in objects) {
        NSLog(@"[INFO] object:%@", object);
        [Meow printAllIvar:object];

        // hold weak reference to those objects
        __weak id weakObject = object;
        [self.objects addObject:weakObject];
    }
}

Now, we've the ability to capture (almost, since you can memcpy an instance) every single new instance and all existed instances of given class. And once you have the instance, you can do things like class_copyIvarList, object_getIvar, ivar_getName and so on and so forth. (As a matter of fact, that's basically the omitted codes of Meow)

Meow~
Meow~

This tool is on my GitHub, Meow.

3 thoughts on “Monitoring Ivar Changes in Objective-C”

Leave a Reply

Your email address will not be published. Required fields are marked *

5 × 5 =