iOS 安全攻防之敏感数据保护

2017/07/02 blog

在我们的印象中,iOS 似乎很安全,今年肆虐的wannacry 病毒貌似没有报告说iOS 系统中招的。尽管如此,也不可大意。顺便提醒一下,没有特别需求,最好不要越狱。

我们知道,文件系统就像一本很大的书,当一个文件被删除时,许多人认为页面被一个共享文件删除,就像51区的分类文档一样。事实上,当一个文件被删除时,就像有人那红笔给那个文件打了一个红X,文件内容并没有被真正删除,如果想恢复这个文件,只需要将那把红X 删除就可以了。当然,你知我知,大家知,当然苹果也知。所以在iOS4以后,苹果采用了一种特殊的处理方式,那就是对本地文件系统加密。这样,即使有人恢复了删除的文件,没有密钥,也无法读取内容,这样就打打的增强了数据的安全性。

在iOS4 以后,Apple采用文件系统加密的方式增强数据的安全性。每一个文件都用一个唯一的key 来进行加密,而每一个文件都有一个属性叫cprotect,这个key 存储在cprotect文件属性。事实上,这个用于解密文件系统的key 的值又是用另一个DKey 采用AES 加密的,存储在一个NAND 区域,当一个文件被删除时,这个cprotect 这个属性就设置为discarded。这个删除的文件没有了cprotect 这个属性,这个文件也就无法解密,即使恢复了也毫无意义。

当然,这只是增加破解的难度。熟悉Linux 的人都知道,Linux中有一个日志系统(Windows我就不知道了),这个日志系统记录着系统中所发生的一切,网络传输,文件更改,任务调度等等。当然iOS也有一个日至系统 HFS(Hierarchical File System,分层文件系统)。这个日志系统记录着iOS中所发生的一切。刚刚说了,iOS中所有文件都是加密的,这个日志系统记录的日志文件也是加密的,给这个日至系统加密的秘钥叫EMF key。但是有一个地方没有加密,那就是NAND 分区,这个地方存储着密钥,这个日志系统的密钥就存在这个地方,而这个EMF key 并没有使用所需要的口令来进行加密,所以,任何人在没有用户密码的情况下很容易解密HFS日志。而当一个加密的文件秘钥被写入cprotect 属性时,HFS日志会自动将它的一份副本记录下来。

以上只是从理论上知道了如何破解文件系统。事实上,这只是一个道高一尺,魔高一丈的游戏。作为一个开发人员,我们要做是如何保护好我们的敏感数据。不管怎么样,也得增加破解者的难度吧。

键盘缓存

很多金融APP 在输入密码的时候都是采用自己的键盘,而不是使用系统的键盘。因为系统键盘,都有键盘缓存。而键盘缓存就存在/private/var/mobile/Library/Keyboard/dynamic-text.dat 文件中,导出该缓存文件,查看内容,你会发现,一切输入都是明文存储的。因为系统不会把所有的用户输入记录都当做密码等敏感信息来处理。既然缓存在文件中,当然是把文件删除,然后让其无法再HFS 日志系统无法找到。最好的就是讲这个文件overwrite。

事实上,我们开发是获取不到这个缓存文件的。那也就无法擦除这个文件。然而,我们可以让其不缓存我们得输入。iOS中使用的键盘类会导致数据被自动缓存。当你的应用程序中按下每一个键盘键时,将会被明文缓存。缓存规则如下:

  • 密码框中的数据不会被缓存
  • 全部由数字组成的字符窜是不会被缓存的,即使是在旧的iOS 版本中,这意味着像使用信用卡之类的数据也是相对安全的。
  • 禁用文本的自动更正功能可以防止数据被缓存。
  • 太短的一两个字符的数据不会被缓存。

知道了这些,那么如何禁用文本缓存就好说了:

  1. 把文本框设置为密码,这样就能起到保护作用而不被缓存
    UITextField *textField = [[UITextField alloc] initWithFrame:frame];
    textField.secureTextEntry = YES;
    
  2. 禁用自动纠错功能
    textField.autocorrectionType = UITextAutocorrectionTypeNo;
    
  3. 在iOS中,进程间通信,其中就包括粘贴板,在使用粘贴板时,数据也会被明文存储,若禁用文本框的粘贴复制功能,用户就无法从这里粘贴复制数据。
    - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
        UIMenuController *menuController = [UIMenuController sharedMenuController];
        if (menuController) {
       menuController.menuVisible = NO;
        }
        return YES;
    }
    
相册和个人信息

我们平时删除相册,只是点击删除。事实上真的删除了吗?这个问题很值得怀疑,想想那些年的冠希哥,你还能相信是真的删除了

当你删除一张照片或者某些敏感的个人信息时,被删除的照片或者个人信息海存在HFS 日志系统中,在这种情况下,是完全可以恢复的(当然,你的别人也没兴趣啦)。正确的删除姿势应该是:删除照片后,尽量用其他的信息将日至系统中的信息轮转,以擦除日至系统中的敏感信息。同时也要将存储这些敏感信息的磁盘分区擦除掉。这样即使有一些遗留信息,attacker 也无法拿到有用的信息。

应用软件屏幕截图

当一个应用软件挂起到后台时,会有一个屏幕截图被捕获并写入磁盘中,这是为了下次返回到应用软件可以产生窗口缩放回到屏幕的效果,就好像应用从后台加载起来一样事实上,应用软件需要花一些时间加载回来重新变化为活跃状态,而这个动画则给了软件一些时间。

每一次应用软件挂起时,应用软件的截屏都会重来一次,晚些时候再将截屏软件删除或者覆盖。当一同电话进来时,或者其他可能导致应用软件挂去的事件发生时,也会有屏幕截屏发生。从HFS日志中经常可以找到这些已删除的应用软件截屏,这些也有可能泄露你的应用软件中哪怕是最安全的加密数据的内容。

当一个应用在后台被挂起时,会有一个当前内容的屏幕快照,这样做是为了让用户感觉程序从后台唤起时无缝恢复,但这样做也会导致一些应用数据被泄露的原因,不仅仅是最近的应用状态截图保存在运行的文件系统中,之前很多的快照的备份也会通过HFS日志恢复回来,因此,这也成为数据泄露的渠道之一,幸运的是,苹果提供了许多应用程序回调方法来警告程序将被挂起,以便程序有时间处理这些事务。

禁止屏幕截屏的一个简单方法就是设置keyWindow 的hidden 属性为YES,这样屏幕上显示的内容将被隐藏,返回一个空白的快照来替代任何内容:

[UIApplication sharedApplication].keyWindow.hidden = YES;

另外,如果当前窗口还有其他窗口,当这个窗口被隐藏时,其他窗口就会显示出来,所以,当你使用这种方法时,也要确保其他窗口也要隐藏。

当一个应用即将进入休眠时,applicationWillResignActive方法会被回调,比如说接到一个电话或者切换到其他应用程序时,

- (void)applicationWillResignActive:(UIApplication *)application {
    [UIApplication sharedApplication].keyWindow.hidden = YES;
}

另外,applicationDidEnterBackground 会在应用被压入后台但在屏幕快照前调用:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [UIApplication sharedApplication].keyWindow.hidden = YES;
}
安全擦除

有这样一种情形,当取证人员拿到犯罪嫌疑人的手机,并且可以轻易的恢复犯罪嫌疑人手机的数据并拿到证据时,这是不是很酷,让他们可以大大的炫耀一把。当然我们得目的是保护好我们得数据,而不是增加取证人员获取证据的难度。我们要做是如何防止别人获取我们的隐私,在前面我们提到过,在iOS4以后的系统,文件都是加密的,删除文件后,就会将加密文件的cprotect 属性删除掉,忽略,这样即使有人拿到这个文件的数据,也是加密的,看不到内容。但是别忘了,iOS 系统还有一个HFS journal ,它可是记录着系统中发生的一切,而且这个HFS 日志系统是独立于文件系统的,你删除的文件key 它可是记录着的。这就给了别人恢复文件内容的机会。

要完全删除一个文件,那还是的overwrite,下面就给出wipe a file 的C 代码:

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>

int wipe_file(const char *path) {
    int fd = open(path, O_RDWR);
    unsigned char buf[1024];
    struct stat s;
    off_t nw;
    ssize_t bw;
    int r;
    if (fd < 0) {
        fprintf(stderr, "%s unable to open %s : %s",__func__,path,strerror(errno));
        return fd;
    }
    if ((r = fstat(fd, &s)) != 0) {
        fprintf(stderr, "%s unable to stat file %s : %s",__func__,path,strerror(errno));
    }
    nw = s.st_size;
    memset(buf, 0, sizeof(buf));
    for (;nw; nw -= bw) {
        bw = write(fd, buf, MIN(nw, sizeof(buf)));
    }
    return close(fd);
}

然后在OC 中调用:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths[0] stringByAppendingPathComponent:@"private.sqlite"];
    wipe_file([path UTF8String]);

擦除数据的时候,尽量使用C 来写,不要使用OC ,我们知道OC 有runtime机制,别人很容易就hook 了你的方法,从而无法擦除。那你可能会说,C也可以被hook,这样的话,那就把上面这个擦除函数写成static inline 函数好了,这样别人是无法hook 的了。

static 会让二进制没有清晰的符号表,这样可以防止静态分析,增加程序的安全性

inline 内联函数
  • 内联后的函数将不再是一个函数,它的机器码将被粘贴到任何一个调用它的地方。这样做的好处有:
    • 通过增加文本体积来提高程序的性能,因为内联函数没有函数调用的开销,可以有效的提高性能。
    • 内联函数是将机器码复制到任何调用的地方,这种方式将迫使攻击者捕捉每次代码出现的地方,并进行内存补丁或者找到一种替代的方法。
擦除SQL记录

Search

    Post Directory