Protection against Cycript/Runtime

This post is mainly to demonstrate some possible protections against Cycript/Runtime. There are 5 parts.

  • Stop cycript's choose(ClassName)
  • Memcpy
  • Move to our own memory blocks
  • Useless setter/getter
  • Encrypt memory data

中文版(Chinese ver.)

The following content is based on this assumption:

Here is a class named Person, and its def goes there.

@interface  Person : NSObject {
    NSString * _name;
    int _age;
}

@property (strong, nonatomic, readonly) NSString * name;
@property (nonatomic, readonly) int age;
 
- (instancetype)initWithName:(NSString *)name age:(int)age;

@end

@implementation Person
 
@synthesize name = _name;
@synthesize age = _age;
 
- (instancetype)initWithName:(NSString *)name age:(int)age{
    self = [self init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}

- (void)setName:(NSString *)name {
    if (name != _name) {
        _name = name ;
    }
}

- (void)setAge:(int)age {
    _age = age;
}

- (NSString *)name {
    return _name;
}

- (int)age {
    return _age;
}

@end

Now we need to protect all the data of this class. Though we've declared they are readonly, but you know, this is not a question to those crackers because of the runtime feature of Objective-C. Then what actions should be taken?

  • Stop cycript's choose(ClassName)

There is an easy way to get all instances of a specific class within cycript. That's choose(). (As shown in Figure 1).

Figure 1
Figure 1

Solution: Overload- (NSString *)description method. As shown in Figure 2.

- (NSString *)description {
    return [NSString stringWithFormat:@"This person is named %@, aged %d.", self.name, self.age];
}
Figure 2
Figure 2
  • Memcpy

What if the cracker hooks init method at the very beginning?

Solution:memcpy

First, get the size of Person's instance, it's equal to sizeof(Class Pointer) + all sizeof(member variables)

ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);

Then we can do the memcpy opreation,

Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
void * superman = malloc(object_size);
memcpy(superman, (__bridge void *)normal_man, object_size);

Use __bridge cast when you need it,

[(__bridge Person *)superman setName:@"Superman"];

Code snippets,

    Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];

    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    void * superman = malloc(object_size);
    memcpy(superman, (__bridge void *)normal_man, object_size);

    [(__bridge Person *)superman setName:@"Superman"];
    [(__bridge Person *)superman setAge:20];

    /**
     *  @brief  Use while for demonstration
     */
    while (1) {
        NSLog(@"Normal:   %p %@",normal_man, [normal_man name]);
        NSLog(@"Superman: %p %@",superman, [(__bridge Person *)superman name]);
        sleep(2);
    }

To simulate the situation in this part, we directly NSLog the memory address.

If the cracker uses Cycript, he/she will get something like this. (Figure 3 and Figure 4)

normal_man's memory address is 0x7fbffbe06b00.

Figure 3
Figure 3

The cracker will only get two strings if he/she uses choose().

Now, if the cracker invokes [#0x7fbffbe06b00 setName:@"Cracker"]; to modify the property.

图4
Figure 4

It does change. And superman is safe. (Note that he/she only gets the address of normal_man)

However, if he/she is lucky enough and find the address of superman, superman is in danger, too. As shown in Figure 5.

图5
Figure 5

P.S In fact, cycript does find superman. cycript will check all the memory. Or there won't be two strings shown in Figure 4 and Figure 5.

  • Move to our own memory blocks

How about copy this normal_man to our own memory blocks? Fortunately, I've written a project named MemoryRegion. I guess it can handle this problem.

Code snippets:

    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    MemoryRegion mmgr = MemoryRegion(1024);
    void * superman = mmgr.malloc(object_size);
    memcpy(superman, (__bridge void *)normal_man, object_size);

Actual result, (Figure 6)

Figure 6
Figure 6

Now, choose() cannot find superman.

But they can hook setter and getter! That's another problem!

  • Useless setter/getter

Let's rewrite our setter and getter,

- (void)setName:(NSString *)name {
    _name = @"Naive";
}

- (void)setAge:(int)age {
    _age = INT32_MAX;
}

- (NSString *)name {
    return @"233";
}   

- (int)age {
    return INT32_MIN;
}

Now, they cannot get or set those properties. They have to hook Ivar. Then how should we set/get those properties? Just add two C functions.

__attribute__((always_inline)) void setName(void * obj, NSString * newName) {
    void * ptr = (void *)((long)(long *)(obj) + sizeof(Person *));
    memcpy(ptr, (void*) &newName, sizeof(char) * newName.length);
}

__attribute__((always_inline)) void setAge(void * obj, int newAge) {
    void * ptr = (void *)((long)(long *)obj + sizeof(Person *) + sizeof(NSString *));
    memcpy(ptr, &newAge, sizeof(int));
}

When superman's name needs to be changed,

setName(superman, @"Superman");
setAge (superman, 20);

To get superman's name and age,

NSLog(@"This person is named %@, aged %d", *((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));
  • Encrypt memory data

When you applied those solutions in the previous four parts, most crackers are blocked out.
But there are still some crackers, they can search memory data. Take the age of superman as example,

superman's address is 0x102800f00,then _age is at (0x102800f00 + sizeof(Person *) + sizeof(NSString *)), 0x102800f10,as shown in Figure 7.

Figure 7
Figure 7

Then, we just decrypt on using.

__attribute__((always_inline)) void encryptSuperman(void ** data_ptr, ssize_t length) {
    char * data = (char *) * data_ptr;
    for (ssize_t i = 0; i < length; i++) {
        data[i] ^= [data deleted] - i;
    }
}

__attribute__((always_inline)) void decryptSuperman(void ** data_ptr, ssize_t length) {
    char * data = (char *) * data_ptr;
    for (ssize_t i = 0; i < length; i++) {
        data[i] ^= [data deleted] - i;
    }
}
    Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];

    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    MemoryRegion mmgr = MemoryRegion(1024);
    void * superman = mmgr.malloc(object_size);
    memcpy(superman, (__bridge void *)normal_man, object_size);

    setName(superman, @"Superman");
    setAge (superman, 20);

    encryptSuperman(&superman, object_size);

    /**
     *  @brief  Use while for demonstration
     */
    while (1) {
        NSLog(@"Normal:   %p %@",normal_man,[normal_man name]);
        NSLog(@"Superman: %p",superman);

        decryptSuperman(&superman, object_size);
        NSLog(@"This person is named %@, aged %d",*((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));

        encryptSuperman(&superman, object_size);
        sleep(5);
    }

Now, read memory (Figure 8)

Figure 8
Figure 8

Seems OK!

Demonstration code, #/HookMeIfYouCan

5 thoughts on “Protection against Cycript/Runtime”

  1. Ok, good to know. Is source code for articles available on github ? If no, could you upload it ? Thnx

  2. Hi,

    I've buy a iOS App RE book, and found link to your blog. I think its awesome, a lot of reverse engineering info. Any plans to translate some articles to english ?

    Thanks

    1. Hello,

      Yes, I'm going to pick out some articles.
      But I guess this will take about one or two months, for I'm busy with my school work:)

      Thanks

Leave a Reply

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

twelve − 8 =