保护App重要数据,防止Cycript/Runtime修改

2024-02-14 06:18

本文主要是介绍保护App重要数据,防止Cycript/Runtime修改,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!




这一篇文章着重于保护重要数据不被攻击者使用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),那么我们就先从这里下手吧!

1431308375706144.png

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

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

1431308403659869.png

禁忌,二重存在

上面虽然在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。

1431308455143487.png

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

1431308475318686.png

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

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

1431308494680358.png

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):

1431308525455461.png

可以看到,现在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。

1431308580600870.png

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

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):

1431308614750175.png

嗯,似乎是没问题了呢~

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




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






这篇关于保护App重要数据,防止Cycript/Runtime修改的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/707718

相关文章

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动