软件安全基础
基本概念
1.病毒分类
病毒
- 感染性
- 潜伏性
- 破坏性
两个特性
-
自行执行 它通常将自己的代码置于另一个程序的执行路径中。
-
自我复制 通过计算机本机复制
蠕虫
特点:
- 利用网络进行复制和传播
和普通病毒的区别
- 普通病毒需要传播受感染的驻留文件来进行复制,而蠕虫不使用驻留文件即可在系统之间进行自我复制。
- 普通病毒的传染能力主要是针对计算机内的文件系统而言,而蠕虫病毒的传染目标是互联网内的所有计算机
木马
特点
- 不具破坏性
- 具有潜伏性
- 不具传染性
2.软件漏洞
分类
- 0day漏洞
- 1day漏洞
- 已公开漏洞
3.渗透测试
- 白盒测试
- 黑盒测试
- 隐秘测试
二、堆栈基础
1、栈
函数调用
- 当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。
- 每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。
- 当函数返回时,系统栈会弹出该函数所对应的栈帧。
如上图所示,在函数调用的过程中,系统栈中操作如下:
-
在main函数调用func_A的时候,首先在自己的栈帧中压入函数返回地址,然后为func_A创建新栈帧并压入系统栈。
-
在func_A调用func_B的时候,同样先在自己的栈帧中压入函数返回地址,然后为func_B创建新栈帧并压入系统栈。
-
在func_B返回时,func_B的栈帧被弹出系统栈,func_A栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址重新跳到func_A代码区中执行。
-
在func_A返回时,func_A的栈帧被弹出系统栈,main函数栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址跳到main函数代码区中执行。
寄存器
ESP
- 栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
EBP
- 基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
ESP和EBP中间便是栈帧,栈帧主要存储三类信息
- 局部变量 :为函数局部变量开辟的内存空间
- 栈帧状态值:保存前栈帧的顶部和底部,用于在本帧被弹出后恢复出上一个栈帧。
- 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。
EIP
- 指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。可以说如果控制了EIP寄存器的内容,就控制了进程——我们让EIP指向哪里,CPU就会去执行哪里的指令。
2、堆
堆块结构
堆的结构包括两部分堆表和堆块
- 堆表一般位于整个堆区的开始位置,用于索引堆区中所有堆块的重要信息
堆块块结构分为块首和块身
- 块首是用来标识这个堆块自身的信息,例如块大小、空闲还是占用等;块身紧随其后,是最终分配给用户使用的数据区。
堆块状态
- 占有态
- 闲置态
对于空闲态堆块而言,块首额外存储了两个4字节的指针:Flink指针和Blink指针,用于链接系统中的其他空闲堆块。其中,Flink前向指针存储了前一个空闲块的地址,Blink后向指针存储了后一个空闲块的地址。
堆表
- 空表索引的第二项(free[1])标识了堆中所有大小为8字节的空闲堆块。之后每个索引项指示的空闲堆块递增8字节。
- 空表索引的第一项free[0]所标识的空表相对比较特殊,这条双向链表链入了所有大于等于1024字节小于512KB的堆块,升序排列。这个空表通常又称为零号空表。
堆块的分配和释放
- 普通空表分配时首先寻找最优的空闲块分配,若失败,一个稍大些的块会被用于分配。这种次优分配发生时,会先从大块中按请求的大小精确地“割”出一块进行分配,然后给剩下的部分重新标注块首,链入空表。也就是说,空表分配存在找零钱的情况。
- 零号空表中按照大小升序链着大小不同的空闲块,故在分配时先从free[0]反向查找最后一个块(即最大块),看能否满足要求,如果满足要求,再正向搜索最小能满足要求的空闲堆块进行分配。
- 堆块释放。堆块的释放操作包括将堆块状态由占用态改为空闲态、链入相应的堆表。所有释放的堆块都链入相应的表尾。
- 堆块合并。堆块的分配和释放操作可能引发堆块合并,即当堆管理系统发现两个空闲堆块相邻时,就会进行堆块合并操作。
堆块的合并包括几个动作:将堆块从空表中卸下、合并堆块、修改合并后的块首、链接入新的链表(合并的时候还有一种操作叫内存紧缩)。
三、汇编基础
1.寄存器
2.寻址方式
- 顺序寻址
- 跳跃寻址
- 操作数寻址
举例
MOV指令,其用法为 : MOV 目的操作数, 源操作数
表示将一个数据从源地址传送到目标地址。- 立即寻址 立即寻址方式的特点是指令执行时间很短,因为它不需要访问内存取数,从而节省了访问内存的时间。 如:MOV CL, 05H
- 直接寻址是一种基本的寻址方法,其特点是在指令中直接给出操作数的有效地址。如:MOV AL,[3100H]
注意:地址要写在括号“[”,“]”内。 - 间接寻址:指令地址字段中的形式地址不是操作数的真正地址,而是操作数地址的指示器 如:MOV [BX], 12H 这是一种寄存器间接寻址,BX寄存器存操作数的偏移地址,操作数的物理地址应该是DS:BX。表示将12H这个数据存储到DS:BX中。
3. 常用汇编指令
四、调试基础
1.PE文件
PE(Portable Executable)是Win32平台下可执行文件遵守的数据格式。常见的可执行文件(如“.exe”文件和“.dll”文件)都是典型的PE文件。
加壳
加壳过的程序可以直接运行,但是不能查看源代码。要经过脱壳才可以查看源代码。
2.工具
OllyDBG
IDA pro
五、漏洞分析
1、缓冲区溢出漏洞
缓冲区溢出是指程序运行时,向固定大小的缓冲区写入超过其容量的数据,多余的数据会越过缓冲区的边界覆盖相邻内存空间,从而造成溢出。
缓冲区的大小是由用户输入的数据决定的,如果程序不对用户输入的超长数据作长度检查,同时用户又对程序进行了非法操作或者错误输入,就会造成缓冲区溢出。
栈溢出漏洞
请看如下示例,即为栈溢出漏洞
在函数f中,所声明的数组buff长度为1,但是由于没有对访问下标的值进行校验,程序中对数组外的内存进行了读写,这是一个典型的溢出漏洞。
栈溢出漏洞利用
- 修改返回地址
1 | void stack_overflow(char* argument) |
- 调用临界变量
1 |
|
堆溢出漏洞
Dword Shoot攻击
2、格式化字符串漏洞
printf(“My Name is: %s” , “bingtangguan”)
执行该函数后将返回字符串:My Name is:bingtangguan该printf函数的第一个参数就是格式化字符串,它来告诉程序将数据以什么格式输出。
- 数据泄露
C语言中的格式化函数(*printf族函数,包括printf,fprintf,sprintf,snprintf等)允许可变参数,它根据传入的格式化字符串获知可变参数的个数和类型,并依据格式化符号进行参数的输出。
如果调用这些函数时,给出了格式化符号串,但没有提供实际对应参数时,这些函数会将格式化字符串后面的多个栈中的内容取出作为参数,并根据格式化符号将其输出。
1 | void formatstring_func1(char *buf) |
调用时如果传入”%x%x…%x”,则printf会打印出堆栈中的内容,不断增加%x的个数会逐渐显示堆栈中高地址的数据,从而导致堆栈中的数据泄漏。
- 数据写入
特性二:利用%n格式符写入数据
更危险的是格式化符号%n,它的作用是将格式化函数输出字符串的长度,写入函数参数指定的位置。%n不向printf传递格式化信息,而是令printf把自己到该点已打出的字符总数放到相应变元指向的整形变量中,比如
printf(“Jamsa%n”, &first_count)
将向整型变量first_count处写入整数5。
特性三:自定义写入字符串宽度
关于打印字符串宽度的问题,在格式符中间加上一个十进制整数来表示输出的最少位数,若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。我们把上一段代码做一下修改并看一下效果:
1 |
|
3、整数溢出漏洞
当对整数进行加、乘等运算时,计算的结果如果大于该类型的整数所表示的范围时,就会发生整数溢出。
4、攻击C++虚函数
对象使用虚函数时通过(1)调用虚表指针找到虚表,然后(2)从虚表中取出最终的函数入口地址进行调用。
如果虚表里存储的虚函数指针被篡改,程序调用虚函数的时候就会执行篡改后的指定地址的shellcode,就会发动虚函数攻击。
六、漏洞利用
将漏洞利用过程比作导弹发射的过程:Exploit、payload和shellcode分别是导弹发射装置、导弹和弹头。
1.shellcode编码流程
- 用C++写shellcode
- 转换为对应的汇编语言
- 根据汇编找出对应机器码
2.Windows安全防护技术
-
ASRL
地址空间分布随机化ASLR(addressspace layout randomization)是一项通过将系统关键地址随机化,从而使攻击者无法获得需要跳转的精确地址的技术。 -
GS Stack protection
GS Stack Protection技术是一项缓冲区溢出的检测防护技术。VC++编译器中提供了一个/GS编译选项,在使用VC7.0、Visual Studio 2005及后续版本编译时都支持该选项,如选择该选项,编译器针对函数调用和返回时添加保护和检查功能的代码,在函数被调用时,在缓冲区和函数返回地址增加一个32位的随机数security_cookie,在函数返回时,调用检查函数检查security_cookie的值是否有变化。
- 绕过方法
Visual Studio在实现GS安全机制的时候,除了增加Cookie,还会 对栈中变量进行重新排序,比如:将字符串缓冲区分配在栈帧的最高地址上,因此,当字符串缓冲区溢出,就不能覆盖本地变量了。
但是,考虑到效率问题,它仅按照函数隐患及危害程度进行选择性保护,因此有一部分函数可能没有得到有效的保护。比如:结构成员因为互操作性问题而不能重新排列,因此当它们包含缓冲区时,这个缓冲区溢出就可以将之后其它成员覆盖和控制。
-
DEP
数据执行保护DEP(data execute prevention)技术可以限制内存堆栈区的代码为不可执行状态,从而防范溢出后代码的执行。 -
SafeSEH
SafeSEH就是一项保护SEH函数不被非法利用的技术。微软在编译器中加入了/SafeSEH选项,采用该选项编译的程序将PE文件中所有合法的SEH异常处理函数的地址解析出来制成一张SEH函数表,放在PE文件的数据块中,用于异常处理时候进行匹配检查。
3.地址定位技术
- 静态shellcode地址的利用技术
- 基于跳板指令的地址定位技术
- 内存喷洒技术
4.API函数自搜索技术
编写通用shellcode,shellcode自身就必须具备动态的自动搜索所需API函数地址的能力,即API函数自搜索技术。
5.返回导向编程
在DEP保护下,怎么去编写shellcode来完成函数调用呢?
ROP的全称为 Return-oriented programming(返回导向编程);
是一种新型的基于代码复用技术的攻击,它从已有的库或可执行文件中提取指令片段,构建恶意代码。
七、漏洞挖掘
1.符号执行
Wiki中的定义是:在计算机科学中,符号执行技术指的是通过程序分析的方法,确定哪些输入向量会对应导致程序的执行结果为某个向量的方法(绕)。通俗的说,如果把一个程序比作DOTA英雄,英雄的最终属性值为程序的输出(包括攻击力、防御力、血槽、蓝槽),英雄的武器出装为程序的输入(出A杖还是BKB)。那么符号执行技术的任务就是,给定了一个英雄的最终属性值,分析出该英雄可以通过哪些出装方式达到这种最终属性值效果。
可以发现,符号执行技术是一种白盒的静态分析技术。即分析程序可能的输入需要能够获取到目标源代码的支持。同时,它是静态的,因为并没有实际的执行程序本身,而是分析程序的执行路径。如果把上述英雄的最终属性值替换成程序形成的bug状态,比如,存在数组越界复制的状态,那么,我们就能够利用此技术挖掘漏洞的输入向量了。
- 变量符号化
变量符号化的意思很好理解,就是简单的模拟抽象符号去代替具体数值,这样的代数思想我们在中学就学过 :比如程序有三个输入,我们不做输入,而是用X,Y,Z三个代数符号来表示输入,遍历所有分支 - 程序执行
符号执行技术虽然叫执行,但实际上它并没有执行,只是将所有的分支情况模拟一遍,得到所有可能的符号解
1 | 1 void foobar(int a,int b){ |
以上述代码为例进行符号执行,完成后得到三条路径,可以对路径约束求解就可以获得到达该路径的一组输入
- 约束求解
符号执行得到的约束条件,可以通过约束求解器进行求解。
主流的约束求解器主要有两种理论模型:SAT求解器和SMT求解器。
Z3是一个主要的约束求解器
2.污点分析
污点分析可以抽象成一个三元组(sources,sinks,sanitizers)的形式:source即污点源,代表直接引入不受信任的数据或者机密数据到系统中;sink即污点汇聚点,代表直接产生安全敏感操作(违反数据完整性)或者泄露隐私数据到外界(违反数据保密性);
sanitizer即无害处理,代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害。
污点分析就是分析程序中由污点源引入的数据是否能够不经无害处理,而直接传播到污点汇聚点。如果不能,说明系统是信息流安全的;否则,说明系统产生了隐私数据泄露或危险数据操作等安全问题。
可以分成3个阶段:识别污点源和汇聚点、污点传播分析和无害处理。
- 识别污点源和汇聚点
- 污点传播分析
显式流分析
显式流分析关注的是程序中数据的直接流动。具体来说,它追踪数据从一个变量到另一个变量的显式传递方式。例如,在程序代码中,数据从变量A传递到变量B,然后再传递到变量C,这样的流动方式就是显式的。显式流分析通常通过数据流分析(Data Flow Analysis)技术实现
隐式流分析
隐式流分析则关注程序中数据的间接流动。即使数据没有直接传递,也可能通过程序的控制流导致信息泄露或其他问题。例如,变量A的值影响了程序的执行路径,而执行路径的选择又间接影响了变量B的值。这样的流动方式就是隐式的。隐式流分析需要分析程序的控制流(Control Flow)
- 无害处理
3.词法分析
主要思想是将代码文本与归纳好的缺陷模式(比如边界条件检查)进行匹配,以此发现漏洞。
优点:算法简单,检测性能较高
缺点:只能进行表面的词法检测,不能进行语义方面的深层次分析,因此可以检测的安全缺陷和漏洞较少,会出现较高的漏报和误报,尤其对于高危漏洞无法进行有效检测。
4.数据流分析
5.模糊测试
模糊测试(Fuzzing)是一种自动化或半自动化的安全漏洞检测技术,通过向目标软件输入大量的畸形数据并监测目标系统的异常来发现潜在的软件漏洞。
模糊测试属于黑盒测试的一种,它是一种有效的动态漏洞分析技术,黑客和安全技术人员使用该项技术已经发现了大量的未公开漏洞。
- 确定测试对象和输入数据
由于所有可被利用的漏洞都是由于应用程序接受了用户输入的数据造成的,并且在处理输入数据时没有首先过滤非法数据或者进行校验确认。对模糊测试来说首要的问题是确定可能的输入数据,畸形输入数据的枚举对模糊测试至关重要。 - 生成模糊测试数据
一旦确定了输入数据,接着就可以生成模糊测试用的畸形数据。根据目标程序及输入数据格式的不同,可相应选择不同的测试数据生成算法。 - 检测模糊测试数据
检测模糊测试数据的过程首先要启动目标程序,然后把生成的测试数据输入到应用程序中进行处理。 - 监测程序异常
在模糊测试过程中,一个非常重要但却经常被忽视的步骤是对程序异常的监测。实时监测目标程序的运行,就能追踪到引发目标程序异常的源测试数据。 - (5)确定可利用性
一旦监测到程序出现的异常,还需要进一步确定所发现的异常情况是否能被进一步利用。这个步骤不是模糊测试必需的步骤,只是检测这个异常对应的漏洞是否可以被利用。这个步骤一般由手工完成,需要分析人员具备深厚的漏洞挖掘和分析经验。
6.AFL模糊测试框架
7.程序切片
8.程序插桩
程序插桩,是借助往被测程序中插入操作,来实现测试目的的方法。简单的说,插桩就是在代码中插入一段我们自定义的代码,它的目的在于通过我们插入程序中的自定义的代码,得到期望得到的信息,比如程序的控制流和数据流信息,以此来实现测试或者其他目的。
最简单的插桩是在程序中插入输出语句,以监测变量的取值或者状态是否符合预期。这种插桩手段在服务类应用程序、基于日志的程序调错等。
断点是一种特殊的插桩,是在程序的特定部位插入语句来检查变量的特性。
- Pin插桩