保护App重要数据,防止CycriptRuntime修改

    xiaoxiao2025-01-07  11

    这一篇文章着重于保护重要数据不被攻击者使用Cycript或者Runtime修改,概要内容如下:

    防止choose(类名)

    禁忌,二重存在

    自己的内存块

    虚伪的setter/getter

    加密内存数据

    English version is here

    以下内容均以此假想情况为基础: 我们有一个Person类,它的定义如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @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

    现在我们需要保护这个类的数据,虽然我们在@property里声明了这两个都是readonly,但是因为Objective-C的runtime特性,这个属性说了基本等于没说(对于破解者而言)。 那么我们要怎么做才能保护呢?

    防止choose(类名)

    我们知道,在Cycript中可以很方便的使用choose(类名)来获取到App中该类所有的实例变量(图1),那么我们就先从这里下手吧!

    解决方案: 重载- (NSString *)description方法。效果如图2所示。

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

    禁忌,二重存在

    上面虽然在cycript中用choose函数拿不到了,但是如果一开始就被Hook了init方法怎么办呢?

    解决方案:memcpy一份。

    首先确定Person类实例的大小:(类指针大小+所有成员变量大小)

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

    然后就可以愉快的memcpy了:

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

    在用的时候,通过__bridge转换:

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

    代码片段:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14      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  为了演示方便加的while       */      while  (1) {          NSLog(@ "Normal:   %p %@" ,normal_man, [normal_man name]);          NSLog(@ "Superman: %p %@" ,superman, [(__bridge Person *)superman name]);          sleep(2);      }

    那么为了模拟实际情况(即init方法被Hook,拿到了normal_man的地址),我们直接在NSLog里输出。

    使用Cycript攻击的实际效果如图3、图4:

    通过Hook init方法,拿到了normal_man的地址0x7fbffbe06b00。

    在Cycript中使用choose,只能看见两个字符串。现在直接调用[#0x7fbffbe06b00 setName:@"Cracker"];更改name属性。

    可以看到normal_man的name的确被更改了。而我们memcpy的superman表示无压力。

    那么superman的地址也被找到了的话,怎么办呢?如图5

    P.S 事实上,它也的确被找到了,cycript会检索所有malloc的内存,图4、图5里,choose执行后的两句NSString就是证明,只不过因为我们重载了description方法,才没有直接看到地址。

    自己的内存块

    那么我们把这个normal_man复制到自己的一个内存区块如何呢?正好借用之前写的MemoryRegion。试试看吧!

    代码片段:(其余部分与上面的相同)

    1 2 3      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);

    实际效果(图6):

    可以看到,现在choose找不到处于MemoryRegion中的superman。

    不过就算找不到,Cracker还可以Hook这个类的setter和getter呀!我们又要如何应对呢?

    虚伪的setter/getter

    让我们把setter和getter改成这个样子:

    1 2 3 4 5 6 7 8 9 10 11 12 - (void)setName:(NSString *)name {      _name = @ "Naive" ; } - (void)setAge:(int)age {      _age = INT32_MAX; } - (NSString *)name {      return  @ "233" ; }    - (int)age {      return  INT32_MIN; }

    这样Cracker们通过setter方法就改不了了,也不能通过getter来获取,只能HookIvar了。当然我们也是,那么我们自己要怎么修改呢?添加两个C函数吧!

    1 2 3 4 5 6 7 8 __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)); }

    在修改的时候使用:

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

    在获取的时候:

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

    加密内存区块

    在我们把Person类改成上面那个样子之后,已经能阻止大部分只用cycript就想调戏我们的App的人了。

    然而,如果Cracker们搜索内存的话,还是有可能找到一些数据的,比如这里superman的年龄,

    superman的内存地址是0x102800f00,_age在(0x102800f00 + sizeof(Person *) + sizeof(NSString *)),也就是0x102800f10,如图7。

    那么我们不用的时候加密这块内存,用的时候再解密,演示用的加密、解密函数如下,

    1 2 3 4 5 6 7 8 9 10 11 12 __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] ^= 0xBBC - 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] ^= 0xBBC - i;      } }

    使用代码:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19      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  为了演示方便加的while       */      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); }

    现在再来看看内存里的数据(图8):

    嗯,似乎是没问题了呢~

    完整示例代码,https://github.com/BlueCocoa/HookMeIfYouCan

    http://www.cocoachina.com/ios/20150511/11801.html

    转载请注明原文地址: https://ju.6miu.com/read-1295246.html
    最新回复(0)