Android攻防四

来源:互联网 发布:国际航协2015数据 编辑:程序博客网 时间:2024/06/07 23:15

模糊测试技术分为非智能模糊测试技术(dumb-fuzzing)和智能模糊测试(smart-fuzzing)

虽然智能模糊测试也可以用变异的方式来生成输入,但是它主要依赖生成式方法,这种方法通常使用基于输入数据格式的自定义程序或语法,从零开始生成整个输入

 

将输入解析成不同的数据结构,然后在不同的逻辑层而进行变异,会是一种强大的技术。一个例子,用一个生成的子树来替换DOM树的一个或几个HTML节点。一个使用解析器的混合方法能够将模糊测试限定在输入中的一些选定字段或区域。

 

不要修改消息完整性的数据或一些幻数(magic number),否则将降低代码覆盖率

 

可以使用调试器来获取崩溃时更为细粒度的信息(如寄存器的值),也可以使用插桩工具(如valgrind)来观测非正常行为。函数截获(API Hooking)也是一种有用的技术,尤其是使用模糊测试来寻找非内存破坏漏洞时。

 

ARM处理器有好几种执行模式,所以ARM系统中的符号不仅可以确定名称和类型,还能用来确定在执行函数时处理器所处的模式。因此在调用Android上的ARM二进制程序时,符号是最宝贵的资源。

 

工具链(toolchain)就是开发产品所用到的一系列工具,通常,一套工具链包含编译器、链接器、调试器,以及任何必要的系统库

 

Dalvik实现了一个标准的调试接口,称为JAVA调试线协议(java debug wine protocol,JVWP)

 

在android平台上开放原生代码(nativecode)所用的C和C++编程语言缺乏dalvik提供的内存安全特性

 

动态二进制注入(bynamic binary instrumentation,DBI)是一种将额外代码插入程序正常执行流的方法,通常被称为hooking。一般是首先构造一些自定义代码,然后将其注入目标进程中。与断点一样,DBI也需要对一些重要的代码进行重写。然而,DBI并非插入一条断点指令,而是插入跳转指令,将执行流重定向到所插入的自定义代码上。被注入的自定义代码可直接访问进程内存空间,不必进行额外的上下文切换来获取内容(和ptrace一样)

 

DBI 是一项强大的技术,不只应用于调试,也可用来在运行时修补漏洞,扩展功能,在代码中增加新的接口进行测试等等

 

未定义行为(undefined behaviour)指那些规范中没有定义的行为,可能是由于不同的底层架构、内存模型或者边角细节造成的

 

能够创建新断点的断点是非常强大的工具,也被称作互相依赖的断点,它最重要的作用是消除噪音。

 

ARM使用的ABI规则:

函数的参数超过四个,超过的部分会使用栈来传递;

局部变量如果不能存储在寄存器中,则在当前栈帧中分配。特别是大于32比特的变量和指针引用的变量。

 

非节点函数(non-leaf function)的返回地址存储在栈上

 

如果一个函数中用到了栈,那么它通常会以prologue代码开始,以epilogue代码结束。Prologue用来初始栈帧,epilogue代码则用来还原栈帧。Prologue代码把函数执行过程中会遭到破坏的寄存器值保存在栈上;函数返回时epilogue代码就会恢复相应的寄存器值。Prologue代码也会通过调整栈指针来为栈上的局部变量分配空间。栈空间从虚拟内存高地址往低地址方向增长,所以栈指针指向的地址会在prologue代码中变低,在epilogue中变高。

 

栈的本质概念只是不同函数之间的ABI约定

 

生存域超过一个范围的非局部对象必须分配在堆上

 

分配器的作用是将操作系统分配出来的内存页划分成内存块,其中包含与分配相关的控制信息头,以及请求的程序内存。虽然请求的内存可以达到字节的粒度,但是内存块的默认大小是请求字节数向上取整得到的8的倍数。

 

为了提高内存分配与释放的性能,dlmaloc在内存块中存储了控制数据。在释放的内存块中,实际数据部分的开头也包含额外的信息。对于小于256字节的内存块,这些额外信息还是两个向前向后的指针,用来把这些相同大小的已释放内存块组成一个双向链表。对于大于256字节的内存块,这些释放的内存块组成一个特里(trie)结构,需要存储更多的指针

 

对于堆溢出或是其他破坏内存块控制数据的攻击,内存块的合并会导致控制数据移动至前面的内存块中。从而无法控制

 

C++之所以有多态特性,根本是因为引入了虚函数(virtual function)

 

GCC会把一个虚函数的指针(virtualfunction table pointer,vftable)放在对象的开始位置。这个指针指向的是由所有函数的指针组成的表。编译器并没有把所有函数的指针都放在每一个对象内存中

 

二进制文件包含了每个基类的虚函数表。对象的虚函数表由构造函数进行初始化。

 

虚函数调用了暗含了类实例内存的间接访问,类实例往往分配在堆中

 

当堆上发生内存破坏漏洞时,攻击者可以通过控制虚函数表指针让它指向任何地方。

 

Webkit渲染引擎就带有一个堆分配器来优化渲染树(render tree)的生成速度。每个DOM树都对应着一个渲染树结构。渲染树的每一个节点(即C++对象)都使用特殊的分配器render aremna来分配

 

Vold守护进程不断监听NETLINK套接字,等待新磁盘相关事件的通知消息,来实现磁盘的自动挂载。这些消息一般由内核发送给所有注册了特定类型消息的用户态程序

 

ROP是一种利用内存中现有原生代码作为攻击载荷,而不是注入自定义指令载荷(即shellcode)的漏洞录用方法

 

XN保护技术让操作系统能够将内存页标记为可执行或不可执行

 

ARM处理器支持两种主要的执行模式:ARM和Thumb模式(包含Thumb2扩展),两种模式间的切换使用一种叫做Interworking的技术

 

例如,bx lr指令会查看lr寄存器的是最低比特位:如果是1,则切换到thumb模式,如果是0,则切换到ARM模式

 

Thumb指令使用了一些特殊的push和pop指令,来隐式地操作sp寄存器(栈顶指针)。而不是显式地进行引用。Thumb还有一个特殊的扩展,pop指令会利用Pc寄存器来处理写入,类似于bx lr指令

 

由于android系统并没有堆可执行代码的内存映射进行签名,所以攻击者可以使用rop链分配一页(4096个字节)可执行的内存,然后把原生的载荷复制过来,跳转过去执行。使用这种方法就能够比较通用地运行任意用户空间的载荷

 

将栈顶指针指向任意用户控制的数据后,就可以构造足够长的rop链,来分配可执行的内存,然后将载荷复制进去,最后将控制流重定向到那里执行。

 

运行ROP载荷的第一步,通常就是把栈顶指针指向攻击者控制的数据,例如堆。这一步也被乘坐迁移(pivoting)

 

在arm这样的RISC中架构中,搜索gadget是非常容易的。它们都采用长度固定的指令编码方式,用反汇编器生成的反汇编列表就能实现

 

Android的linux内核不仅是单内核架构,而且本身就是一个二进制文件,通常命名为zImage,zImage文件通常包含引导程序,解压程序,以及压缩后的内核代码和数据

 

Linux内核支持三种不同的压缩算法:gzip,lzma和lzo

 

Android设备启动linux内核的时候有两种模式:第一种是正常的启动过程,使用boot分区,第二种是启动恢复过程,使用recovery分区

 

Linux内核还为配置子系统提供了多种配置界面,包括基于QT的图形用户界面(GUI)(makexconfig),基于文本的菜单(make memnuconfig)和问答接口(make config)

 

使用可加载内核模块(CKM)来扩展linux内核非常方便,无需编译整个内核。构造rootkit时,修改内核代码(或数据)是必须的

 

触发内核漏洞会导致panic,hang和内存破坏

 

内存崩溃信息通常被称为Oops

 

页表有许多用处,主要用于把虚拟内核转换为物理内存地址,还可以用来追踪内存权限和swap状态

 

RIL(radio interface layer)无线接口层是android平台中负责移动通信的核心组件

 

RIL的主要功能是与数字基带进行交互

 

Android电话栈(androidtelephony stack)由4部分组成,自顶向下分别是:电话和短信应用程序、应用程序框架、RIL守护进程和内核级设备驱动。Android系统的代码分别由Java和C/C++实现,前者运行在dalvik虚拟机中,后者则以本地机器码的形式直接运行

 

 

具体到android电话栈,dalvik与本地代码的区别为:应用程序部分用java写,在dalvik