背景
Android应用程序在很大程度上是用Java实现的,从设计上来说,Java本身是不存在内存损坏问题。但事,使用JNI库的本地应用程序很容易出现这种错误。
Android在开发过程中,使用一些不安全的系统函数,一些不好编程习惯都会导致开发的应用容易存在内存损坏(溢出、越界等)的漏洞,这种内存的问题也是攻击者的主要攻击对象。
此类错误源于编程错误,导致程序访问意外的内存位置。在适当的条件下,攻击者可以利用这种行为劫持易受攻击程序的执行流并执行任意代码。
利用内存损坏的主要目标通常是将程序流重定向到攻击者放置汇编的机器指令(称为shellcode)的位置。
下面就梳理下这种内存不安全的一些表现形式
缓冲区溢出
这描述了一个编程错误,即应用程序在特定操作的分配内存范围之外进行写入。攻击者可以利用此漏洞覆盖位于相邻内存中的重要控制数据,如函数指针。缓冲区溢出以前是最常见的内存损坏缺陷,但近年来由于多种因素而变得不那么普遍。值得注意的是,开发人员意识到使用不安全的C库函数的风险是一种常见的做法,而且捕获缓冲区溢出错误相对简单。但是,这类缺陷仍然还是存在的。
要识别潜在的缓冲区溢出,使用不安全的字符串函数(strcpy、strcat、其它以“str”前缀开头的函数等)和潜在的易受攻击的编程构造,例如将用户输入复制到有限大小的缓冲区。
对于不安全的字符串函数,应将以下内容视为危险标志:Strcat、strcpy、strncat、strlcat、strlcpy、sprintf、gets。
越界访问
错误的指针运算可能导致指针或索引引用超出预期内存结构(例如缓冲区或列表)边界的位置。当应用程序试图写入越界地址时,会发生崩溃或意外行为。
如果攻击者能够控制目标偏移量并在一定程度上操纵编写的内容,则可能会利用代码执行漏洞。
悬挂指针
当删除或取消分配具有对内存位置的传入引用的对象,但未重置对象指针时,会发生悬挂指针。
如果程序稍后使用悬空指针来调用已释放对象的虚拟函数,虽然也可以读取或写入悬挂指针引用的对象变量或其他内存结构则,可能通过覆盖原始vtable指针来劫持执行。
整数溢出
当算术运算的结果超过程序员定义的整数类型的最大值时,这将导致值“环绕”最大整数值,不可避免地导致存储一个小值。相反,当算术运算的结果小于整数类型的最小值时,当结果大于预期值时,会发生整数下溢。特定整数上溢/下溢错误是否可利用取决于整数的使用方式。例如,如果整数类型表示缓冲区的长度,这可能会造成缓冲区溢出漏洞。
格式字符串漏洞
当未经检查的用户输入被传递给C函数printf家族的格式字符串参数时,攻击者可能会注入格式令牌(如“%C”和“%n”)以访问内存。由于其灵活性,格式化字符串错误易于利用。如果程序输出字符串格式化操作的结果,攻击者可以任意读取和写入内存,从而绕过ASLR等保护功能。
常见格式字符串漏洞函数如下:
降低安全风险小方案
1.当使用整数变量进行数组索引、缓冲区长度计算或任何其他安全关键操作时,请验证是否使用了无符号整数类型,并执行前提条件测试以防止整数换行的可能性。
2.不使用不安全的字符串函数,如strcpy,大多数其他以“str”前缀开头的函数,sprint,vsprintf,gets等,可以采用安全的函数例如strncpy、memcpy_s等函数
3.如果应用程序包含C++代码,则使用ANSI C++字符串类;
4.如果是memcpy,请确保检查目标缓冲区至少与源缓冲区大小相同,并且两个缓冲区不重叠。
5.不要使用没有不受信任的数据连接到格式字符串中。
6.作为“for”或“while”循环实现的复制操作的实例,并验证是否正确执行了长度检查。
小结
内存损坏错误最好通过输入模糊化来发现:这是一种自动黑盒软件测试技术,通过这种技术,畸形数据会不断发送到应用程序,以调查潜在的漏洞状况。在此过程中,将监控应用程序的故障和崩溃。如果发生崩溃,希望(至少对安全测试人员来说)是造成崩溃的条件揭示了一个可利用的安全漏洞。
模糊测试技术或脚本(通常称为“模糊器”)通常会以半正确的方式生成结构化输入的多个实例。本质上,生成的值或参数至少部分被目标应用程序接受,但也包含无效元素,可能会触发输入处理缺陷和意外的程序行为。一个好的模糊器暴露了大量可能的程序执行路径(即高覆盖输出)。输入要么从零开始生成,要么从变异已知的有效输入数据中导出。