对于《捉虫日记》的学习笔记。
第1章 捉虫
通用技巧
静态代码分析+动态分析(如程序调试、模糊测试)
静态代码分析并不需要从头到尾的阅读,其可能的关键入手点如下。
(1)找到程序的输入点——即用户的输入数据进入软件的对外接口,这些数据可能来自网络、文件、执行环境等。找到输入点之后就需要重点关注程序对于用户输入数据的处理代码,并找出处理过程中可能存在的安全漏洞。
(2)关注程序中出现的不安全的库函数或者可能触发漏洞的汇编指令。
- 在寻找bug的过程中,通常要使用到调试器和反汇编工具(如
IDA-pro
)。
第2章 回到90年代(VLC媒体播放器的栈缓冲区溢出漏洞)
在打算寻找某款软件的bug之前,首先需要花一些时间去熟悉该软件的内部工作机制,从而找到入手点。
发现漏洞
第一步:生成VLC中解复用器的清单——这一步的通用描述相当于找到要分析的软件的主要功能代码,以缩小分析目标并提高效率;
第二步:识别输入数据——具体定位到程序代码中的某个具体变量(除了静态分析,也可以通过动态调试如数据访问断点来定位),如作者就是定位到了如下图所示的结构体成员变量s;
第三步:跟踪输入数据——在程序的功能代码中,搜索对于上述具体变量的引用,找到后可对相关处理代码进行分析,以寻找bug。如作者就是在所有的解复用器文件中搜索形如
p_demux->s
的引用。
漏洞利用
对于上图的补充说明如下:第二步实际上是想办法构造一个能够使程序执行到漏洞代码的样本,作者在分析时并未找到漏洞文件格式的规范文档,所以是直接通过阅读软件源码,找到样本要执行到漏洞代码所需要满足的限制条件。
漏洞修正
漏洞补丁1——
漏洞补丁2——
在分析那些有相关补丁的漏洞时,我们可以通过补丁信息(修改的地方)快速定位漏洞点。
经验教训
一些参考链接
自己的思考
——————思考1
作者提到除了文中指出的漏洞,函数parse_master()中还有另一个缓冲区溢出漏洞。于是结合漏洞补丁2,分析以找出其中的缓冲区溢出漏洞。(要分析寻找某个类型的漏洞,需要对其原理十分清楚,最开始一直找不到漏洞点,就是因为对于整数溢出漏洞的不熟悉,后来对其原理进行学习,最终分析出代码里的bug。)
这里的变量i
和i_map_size
都是有符号整型,并且都是由用户的输入确定的,由他们计算得到的p_sys->i_seq_table_size
也是有符号整型,并且是可控的,但是malloc
函数的参数是一个无符号整型size_t
,强制将有符号整形的计算结果转换成无符号整形就可能会导致整数溢出。
可能导致malloc申请的空间很小,不满足原本申请p_sys->i_seq_table_size
个sizeof(ty_seq_table_t)
的需求,从而导致后面循环赋值的时候发生缓冲区溢出。
参考资料如下——
(1)缓冲区溢出分析第11课:整数溢出的原理;
(2)3.1.2 整数溢出;
(3)malloc,calloc区别;
——————思考2
作者在发现此漏洞的过程中只用了静态分析的方法,结合动态分析,应该能够提高分析效率。
——————思考3
从本例中可以延伸出一类比较明显的漏洞点:在进行内存读写操作的时候,向固定大小的缓冲区读入长度为n的数据,而这个长度是由用户输入直接或间接确定的。
第3章 突破区域限制(Solaris操作系统的内核漏洞-未定义的错误状态)
发现漏洞
作者在阅读内核源代码时,主要关注的是实现用户空间到内核空间(user-to-kernel)接口的部分,比如用于用户态应用程序与内核间通信的IOCTL和系统调用。——也就是说,还是重点关注用户输入相关的部分。
第一步:列出内核的IOCTL并找到处理这些IOCTL的源代码文件——这一步的通用描述相当于找到要分析的目标功能的主要实现代码;
第二步:识别输入数据——如作者就是定位到了如下图所示的结构体msgb;
第三步:跟踪输入数据——在程序的功能代码中,搜索对于上述具体变量的引用, 如作者就是在代码中搜索对于
mblk_t
的引用。 最终通过代码审计,作者发现了一个空指针解引用的漏洞。
这个bug的存在部分原因是函数ipif_lookup_on_name()
通过两条不同路径向它的调用者返回错误状态:通过函数的返回值(return(null))
以及通过变量 error(*error!=0)
。每次调用这个函数时,内核代码的作者必须确保这两个错误状态都正确设置并且在调用函数里正确求值,这样编程容易出错。
如下图所示,这个漏洞的代码审计设计到源代码中不止一个.c文件及函数,分析需要足够的耐性和细致,而且还需要不断的练习和对于各种漏洞原理的熟悉带来的对于bug的敏感程度。
漏洞利用
对于上图的补充说明如下:在利用这个漏洞成功使系统崩溃后,要实现任意代码执行,需要解决以下问题——
(1)空指针解引用触发时阻止系统崩溃——在触发空指针解引用之前映射零页内存,就能阻止系统崩溃;
由于内核空间和用户空间共享相同的零页内存,所以在用户空间通过mmap
函数(如下图)映射零页内存,在内核空间中解引用零地址时就不会导致非法访问的报错崩溃。
注意——每个特定进程的用户态地址空间是唯一的,而内核地址空间是所有进程共享的。在一个进程中映射NULL页仅仅是在那个进程的地址空间里映射。
(2)控制EIP/RIP;
我们能控制的数据只有传给内核的IOCTL数据和传给进程的用户空间数据,包括零页内存。获得控制的唯一方法是让内核引用某些零页内存上的数据,之后这些数据可用来控制内核的执行流。
简单的说,就是去寻找代码执行过程中引用的,并且值能被我们输入的数据控制的函数指针变量。找到这样的指针,就有了执行任意代码攻击的可能性。接下来就需要根据我们想要控制的EIP的具体值,参考结构体数据的成员变量结构,结合执行到调用目标指针变量处输入数据需要满足的一些限制条件,构造符合条件的输入数据(包括传给内核的IOCTL数据和零页内存上的数据),以实现利用。
作者是通过调试内核崩溃文件来查看利用代码是否成功达到控制eip的目的的。
控制了EIP之后,就可以想办法寻找合适的shellcode,然后实现具体利用了。
参考资料——
(1)认真分析mmap:是什么 为什么 怎么用;
(2)Attacking the Core : Kernel Exploiting Notes;
经验教训
自己的思考
从本例中可以延伸出一类漏洞点:空指针异常,可能原因——对于函数返回值并未进行验证;几个不同的函数同时影响到某个变量的值,某些执行路径就可能会引发数据错误。
第4章 空指针万岁(FFmpeg中一个导致空指针解引用的类型转换漏洞)
通常情况下这类bug不是大问题,因为影响的是用户空间库,也就是说在最坏情况下,它会使一个用户空间应用程序崩溃。但这个bug与一般的空指针解引用不同,可能被利用来执行任意代码。
发现漏洞
第一步:列出FFmpeg的解复用器——这一步的通用描述相当于找到要分析的目标功能的主要实现代码;
第二步:识别输入数据——寻找大部分功能代码中都会引用到的数据变量(如结构体等);
第三步:跟踪输入数据;
在知道了漏洞类型和所属源代码文件后,我首先尝试自行寻找该漏洞。尝试结果如下图——
如果header_size
为0,那么malloc(0)
并不会返回一个null
指针,而是会返回一个不可控的“野指针”(所以判断malloc
的返回值是否为null
的if判断其实是没起到作用的),在后面调用free()
操作时就会导致应用程序的崩溃。
参考教材,发现分析错误,这里并不存在空指针异常,查阅对于malloc函数的官方说明文档如下, 由官方说明可知,该处并不存在空指针异常。
参考资料——
(1)malloc(0)的返回值;
(2)linux c开发: 关于malloc(0);
(3)malloc()参数为0的情况;
作者分析发现的实际漏洞信息如下——
注意:由于这里存在漏洞的代码处,4个用户控制的内存位置分别被4个用户控制的数据字节覆写,也即是每一行对应下面这样的伪代码,所以存在可利用性。
NULL[user controlled value].offset = user controlled data
也就是说,如果正确设置了数值,user controlled data
会写到NuLL+ current track
的内存位置,而current track
的值也是用户控制的。
为什么自己尝试没有找到漏洞?原因如下——
(1)代码版本不同,没注意到分析的代码并不是具有该漏洞的版本;
(2)对于malloc(0)
的理解出错,应该首先参考官方说明文档;
(3)作者在文中提到是一个类型转换引起的空指针解引用,而我忽略了类型转换这个细节,直接入手寻找空指针。
漏洞利用
第三步:修改这个strk块以使FFmpeg崩溃。
补充1:在GDB中调试应用程序,程序发生崩溃之后,可利用如下命令辅助分析崩溃原因。
- info registers——查看崩溃发生时程序的寄存器内容;
- x/1i $eip——崩溃发生前应用程序执行的最后一条指令。
补充2:为了实现利用,我们还需要从汇编层面上分析如何从我们输入的current track
的值计算得到最终报错时的内存地址,如下图。
第四步:修改这个strk块以控制EIP(利用关键:覆写got表地址)。
总结:利用步骤——利用漏洞实现任意地址写任意数据,然后通过覆写got表地址控制EIP,进而控制函数执行流程。
漏洞修正
Linux下针对GOT表覆写这种攻击手段的安全防护机制/漏洞利用缓解技术——RETRO缓解技术——有一种操作模式叫作完全RELRO( Full retro),它(重)映射GOT表所在内存地址为只读,攻击者在试图覆写只读的GOT表时就会由于段错误而崩溃。
经验和教训
自己的思考
(1)从本例中可以延伸出一类攻击点/漏洞点:由隐式数据类型转换(尤其是从无符号整型变量到有符号整型变量的转换)引发的漏洞(最常见的是对于if等判断条件的绕过导致后续处理出现问题),如上述由之引发的空指针解引用漏洞。
(2)拓展思考:如何在开启了
Full retro
的情况下实现漏洞利用?
第5章 浏览即遭劫持(浏览器和浏览器插件的漏洞)
ps:对于浏览器漏洞,之前没怎么了解过,所以这里的学习以看明白为目的,感觉浏览器漏洞的发掘需要很熟悉其攻击面,分析方法比普通软件的静态审计复杂,一般都需要结合静态分析和动态分析技术。
发现漏洞
第一步:列出WebEx注册的对象和导出方法——这一步的通用描述相当于找到软件中存在的可能被攻击的目标;
第二步:在浏览器中测试导出方法;
第三步:找到二进制文件中的对象方法;
第四步:找到用户控制的输入数值;
第五步:逆向工程这个对象方法;
最终作者发现的漏洞点如下——
漏洞利用
在利用此漏洞成功实现控制EIP的目的后,就可以利用堆喷射技术达到利用该漏洞实现任意代码执行攻击的目的。
经验和教训
一些参考链接
第6章 一个内核统治一切(防病毒软件avast!专业版4.7中的一个溢出漏洞)
发现漏洞
第一步:为内核调试准备一个VMware客户机;
第二步:生成一个avast!创建的驱动和设备对象列表;
第三步:检查设备的安全设置;
第四步:列出IOCTL;
第五步:找出用户控制的输入数据;
第六步:逆向工程IOCTL处理程序;
再一次告诉我们要想挖掘漏洞,必须对目标产品非常熟悉,能够准确的定位到其中存在的输入点和攻击面。
补充知识点——关于IOCTL
- ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
- 用法: int ioctl(int handle, int cmd,[int *argdx, int argcx])
- fd就是用户程序打开设备时使用open函数返回的文件标示符。
- cmd就是用户程序对设备的控制命令。
- 后面的是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
- 如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。
第一步
作者使用WinDbg配置远程内核调试(详见本书附录内容B.3 Windows内核调试)。
第二步
作者利用软件DriverView识别avast!加载的驱动程序列表;用IDA Pro
反汇编分析获取目标驱动程序的设备对象列表。
第三步
作者使用WinObj检查avast!加载的目标驱动程序中的设备对象的安全设置。
通过查看设备的安全设置,也可以初步筛选出一些有价值的目标,比如若设备对象允许系统每个用户(Everyone组)读写该设备(如下图)。这意味着系统的每个用户向驱动程序实现的 IOCTL发送数据都是允许的,该设备则比较有分析价值。
第四步
为了列出驱动程序实现的IOCTL,我们必须找到这个驱动的IOCTL调度例程。找到调度例程之后,需要在函数中搜索实现的IOCTL。整个分析过程的关键在于——
- 对于IOCTL的整体处理流程及过程中涉及到的Windows系统API及结构体的熟悉和理解。(查阅相关开发手册,在这里相关的是
WDK
的ntddk.h
) - 用IDA Pro逆向分析代码。
- 利用Windbg动态调试获取相关的结构体信息。
第五步
得到驱动支持的IOCTL列表后,作者尝试定位包含用户提供的IOCTL输入数据的缓冲区。(感觉这一步应该是为了后续逆向分析找bug的时候能够更方便的跟踪用户的输入数据。)
第六步
漏洞利用
在有了整体的利用思路之后,还需要找到合适的覆写目标地址(为了达到控制EIP的目的)。不然就算能够在可控地址上写入可控数据,但程序接下来的执行过程中并不会调用可控数据中的具体地址,我们也没法成功的控制应用程序的执行流程。
经验和教训
作为一名程序员及内核驱动开发者——