一、软件安全概论
软件不安全性的两种表现
- 运行不稳定,导致软件崩溃或非正常退出
- 恶意攻击,达到信息窃取、系统破坏的等目的
软件不安全的原因
共同的基本原因:软件在设计、编码、测试和运行阶段,没有发现软件中的各种安全隐患,导致软件的不安全。
(1)软件的生产没有严格遵守软件工程流程。
(2)大多数系统软件和商业软件结构庞大且复杂,无法持续安全运行
(3)编码者没有采用科学的编码方法。
(4)测试不到位(不过有时是无法到位)。主要是测试用例的设计无法涵盖尽可能典型的安全问题。
错误与缺陷
错误是指软件实现过程出现的问题,大多数的错误可以很容易发现并修复,如缓冲区溢出、死锁、不安全的系统调用、不完整的输入检测机制和不完善的数据保护措施等;
缺陷是一个更深层次的问题,它往往产生于设计阶段并在代码中实例化且难于发现,如设计期间的功能划分问题等,这种问题带来的危害更大,但是不属于编程的范畴。
软件安全防护手段
安全设计与开发:SDL
保障运行环境:保障软件自身运行环境,加强系统自身的数据完整性校验
加强软件自身行为认证:软件动态可信认证在确保软件数据完整性的前提下,如何确保软件的行为总是以预期的方式,朝着预期的目标运行。
恶意软件检测与查杀:通常采用病毒特征值检测、虚拟机、启发式扫描、主动防御、云查杀等等几种方法来对病毒进行检测。
黑客攻击防护:防火墙,入侵检测系统IDS,入侵防护系统IPS,基于主机的漏洞攻击阻断技术
系统还原:将关键系统文件或指定磁盘分区还原为之前的备份状态,从而将已有系统中的恶意程序全部清除,以保护系统安全。
虚拟隔离:虚拟机(如VMware),沙箱,也叫沙盘或沙盒(如SandBox)
二、软件安全基础知识
- 几个难的汇编指令:
xchg:让两个寄存器或内存单元的值互换,而无需使用其他寄存器或内存来进行传递。
汇编求补指令neg 汇编dec(减1)实例加解析 汇编inc(加1)实例加解析
ror/rol:将某个二进制数字进行指定位数的移位,并将移出的位重新放置到高位或低位。ROL指令向左循环移位,ROR指令向右循环移位。
scas:用于在字符串比较时执行比较和交换操作。
jcc(Jump if Condition is met):条件转移指令
CLI(Clear Interrupt Flag):用于清除中断标志位,禁用中断。
CLD (Clear Direction Flag):CLD指令用于清除方向标志位。方向标志位影响一些字符串处理指令的方向,CLD确保这些指令向前移动,而不是向后。
STD (Set Direction Flag):STD指令用于设置方向标志位。与CLD相反,STD会设置方向标志,影响一些字符串处理指令的方向,使其向后移动。
STI (Set Interrupt Flag):STI指令用于设置中断标志位,启用中断。执行这个指令后,CPU将允许中断请求,并在中断到达时响应。
大端存储:指数据的低位保存在内存的高地址中,数据的高位保存在 内存的低地址中。
小端存储:指数据的低位保存在内存的低地址中, 数据的高位保存在 内存的高地址中
(年年考,年年忘)
- x86处理器支持3种工作模式:实模式、保护模式和虚拟8086模式
- 实模式
- 保护模式
- 虚拟8086模式
- 三种模式关系
- 计算机系统引导过程
- BIOS自检:检测系统中的一些关键设备(如内存和显卡等)是否存在和能否正常 工作,进行初始化,并将控制权交给后续引导程序。
- 硬盘主引导程序(MBR(Master Boot Record)):硬盘第一个扇区
- 活动分区引导程序(PBR,Partition Boot Record):分区的第一个扇区
功能:加载操作系统引导程序,如Windows XP系统的NTLDR
- 操作系统引导--以Windows NTLDR为例
- 系统内核加载
- 系统引导与恶意软件的关联:
- 进程地址空间:可以想象为一个大数组,数组大小由地址空间长度决定
- 虚拟地址空间:每个进程都有自己独立的虚拟空间,内存地址可固定。每个进程只能访问自己的地址空间,有效进行进程隔离。
- 物理地址空间:实实在在存在于计算机中,可想象为物理内存
- Linux系统权限管理:基于用户角色的管理机制即UGO+RWX/ACL权限控制
- setUID功能:对于带有 SetUID 权限位的二进制程序,任何用户执行时,都会以该二进制程序所属的用户身份执行。
- 常用可执行文件格式
- 深入了解 ELF每个结构的细节
- 简述代码中关于.data、.bss、.rodata、.text段的意义
- 程序头表和节头表
二者区别:程序头表主要用于描述程序的总体信息,而节头表则用于描述程序中各个节的属性和位置信息。
- ELF节头表:
三、软件缺陷与漏洞机理基础
- 软件漏洞三要素:
- 软件系统存在缺陷或弱点
- 攻击者可以接触到该缺陷或者弱点
- 攻击者可以利用该缺陷或者弱点
- 漏洞的定义:系统设计、实现或操作管理中存在的缺陷或者弱点,能被利用而违背系统的安全策略
- 软件漏洞基本信息包含:软件名称,受影响的软件版本,漏洞的根本原因,漏洞的触发条件,Poc-验证漏洞存在的代码,漏洞的攻击能力,EXP-漏洞利用的代码
- CVE(Common Vulnerability and Exposures):通用漏洞列表
- CWE(Common Weakness Enumeration):常见缺陷列表,一个对已知漏洞和安全缺陷的标准化名称的列表。
- 漏洞分类:
按照漏洞威胁分类:获取访问权限漏洞,权限提升漏洞,拒绝服务漏洞,恶意软件植入漏洞,数据丢失或者泄露漏洞
按照漏洞成因:输入验证错误,访问验证错误,竞争条件,意外情况处置错误,设计错误,配置错误,环境错误
按照漏洞严重性:
A类漏洞(高):威胁性最大的漏洞,往往由较差的系统管理或错误设置造成。
B类漏洞(中):较为严重的漏洞,例如允许本地用户获得增加的和未授权的访问。
C类漏洞(低):严重性不是很大的漏洞,例如允许拒绝服务的漏洞
按照漏洞被利用的方式:本地攻击,远程主动攻击,远程被动攻击
- CNVD:国家信息安全漏洞共享平台(Chinese National Vulnerability Database)
- 软件漏洞的生命周期:漏洞挖掘--漏洞重现--漏洞诊断--漏洞修复--补丁测试--补丁推送
- 漏洞利用对系统的威胁:
- 非法获得访问权限:未经授权使用资源
- 权限提升:用户账号从低权限到高权限
- 拒绝服务:使得计算机软件或者系统(OS)无法正常工作、无法提供正常的服务。
- 恶意软件植入。主动植入(利用系统正常功能或者漏洞将恶意代码植入到目标中,不需要用户的干预),被动植入(将恶意代码植入到目标时需要借助用户的操作)
- 数据丢失或者泄露:指数据被破坏、删除或者非法读取
- 典型漏洞类型:
内存安全漏洞:
- 越界写/读
- 缓冲区溢出
- 整数溢出
- 释放后使用
- 空指针
- 条件竞争
- 失控的资源消耗
网络安全漏洞:
- 跨站脚本攻击
- 注入类漏洞
- 路径穿越
- CSRF&SSRF
- 缓冲区溢出漏洞:多余的数据就会溢出到相邻的内存地址中,重写已分配在该存储空间的原有数据,并且有可能改变执行路径和指令。
- 缓冲区溢出产生的原因:计算机程序体系没有严格区分用户数据和程序控制指令;部分编程语言(如C,C++)具有直接访问内存的能力;向数据区写入大量数据,可以实现对非数据区域的覆盖
- 跨站脚本攻击:是指攻击者将恶意脚本代码嵌入Web页面里,当用户浏览该Web页面时,嵌入其中的脚本会被执行,从而达到攻击用户、获取用户信息,甚至获取网站权限的特殊目的。
这是一种被动式的攻击方式,因为它常常是将恶意代码嵌入到正常网页中,然后攻击者需要等待用户访问该网页从而触发漏洞被利用。
- 注入攻击:系统命令注入、命令注入、代码注入和SQL注入
- CSRF(Cross Site Request Forgery)客户端请求伪造:攻击者利用受害者已经在某个网站上建立的身份验证,通过伪造请求来执行未经授权的操作。
- 缓冲区漏洞和注入类漏洞的区别:
缓冲区漏洞:攻击完成必须有MemoryError,越界写操作或者越界读操作
注入类漏洞:攻击完成不需要任何MemoryError,攻击者诱导程序执行想要的,但是恶意的行为
四、内存安全漏洞
- 栈:先入后出,生长方向与内存的生长方向正好相反, 从高地址向低地址生长
- SP(ESP,RSP):栈顶指针,随着数据入栈出栈而发生变化
- BP(EBP,RBP):基地址指针,用于标识栈中一个相对稳定的位置。通过BP,可以方便地引用函数参数以及局部变量
- IP(EIP,RIP):指令寄存器,在将某个函数的栈帧压入栈中时,其中就包含当前的IP值,即函数调用返回后下一个执行语句的地址
- 函数调用过程:把参数压入栈--保存指令寄存器中的内容,作为返回地址--放入堆栈当前的基址寄存器--把当前的栈指针(ESP)拷贝到基址寄存器,作为新的基地址--为本地变量留出一定空间,把ESP减去适当的数值
- 【视频】如何从汇编角度理解函数调用过程
- 栈溢出的利用方式:覆盖局部变量,覆盖返回地址,覆盖SEH中的handler。
- Shellcode:是指能完成特殊任务的自包含的二进制代码,根据不同的任务可能是发出一条系统调用或建立一个高权限的Shell。它的最终目的是取得目标机器的控制权,所以一般被攻击者利用系统的漏洞送入系统中执行,从而获取特殊权限的执行环境,或给自己设立有特权的帐户。
- Payload:一般把Shellcode以及实现跳转到Shellcode的那部分填充代码合称为Payload
- 网络安全 payload、shellcode、exp、poc
- SEH(结构化异常处理):Windows操作系统提供的异常处理机制,用于处理在程序执行期间发生的异常情况。
- 堆:用于存放程序运行中请求操作系统分配给自己的内存段。

- 堆结构:堆表+堆块
空表:有128项,每项标识指定大小的空闲块。空闲块大小=索引项(ID)*8。Free[0]标识大于等于1024 Byte的空闲块。双向链表。
快表:128项,采用单项链表,链中的堆从不发生合并,每项最多4个节点
堆块:块首+块身。
块首:头部8个字节,用来标识自身信息(如大小,空闲还是占有等)
块身:数据存储区域,紧跟块首。
- 堆基础知识
- 堆溢出示例
- 堆溢出利用-DWORD SHOOT
- 基础堆溢出原理与DWORD SHOOT实现
- 整数溢出:计算机中整数都有一个宽度,当试图保存一个比它可以表示的最大值还大的数时,就会发生整数溢出。
- 整数值范围:
- 符号扩展:负数扩展后,还是负数,且值不变;正数扩展后还是正数,值不变。
int main()
{
char a = -1;
unsigned char b = a;
int n = (int)a;
printf("%d \n", n); //程序输出-1
n = (int)b;
printf("%d \n", n); //程序输出255
}
//解释 :把a赋值给b的时候,b成了255,因为b没有符号位。
- 简单的整数溢出
- 整数溢出三种典型问题:宽度溢出,运算溢出,符号溢出
- 好好说话之Use After Free
- 释放后使用(Use After Free)漏洞三大步骤:
- 一内存区域A被分配,且有指针p指向它
- 该内存区域A被回收,但该指针p仍然指向这个区域
- 解引用悬挂指针p从而访问被释放的内存区域A
- 悬挂指针:指向已释放内存区域的指针
- Double free
- 格式化字符串漏洞
格式化字符串函数(printf)的栈结构图:
产生原因:
*printf()是不定参数输入 ,*printf()不会检查输入参数的个数
可以进行信息泄露的格式化:“%s %d %x …”
关键受攻击的格式化:“%n”。简单来说,%n 是将当前 printf 已打印的字符数写入一个 指定的变量
防御措施:
格式化串溢出通过静态扫描较容易发现;部分编译器已经可以限制部分格式化字符串问题。
五、漏洞利用和发现
- 0day VUL:0day漏洞(vulnerability),指的是被发现后立即被恶意利用的安全漏洞。
漏洞被发现--T0,漏洞信息公布--T1,漏洞被修复--T2。
- Exploit:利用漏洞实现Shellcode的植入和触发的过程.
Exploit 约等于 Payload + Shellcode
Payload部署基本的数据(用于漏洞的触发),携带Shellcode
因此,Payload与漏洞关联,Shellcode独立于漏洞
- 漏洞利用的思路:
利用目标:
- 修改内存变量(邻接变量) :
- 修改代码逻辑(代码的任意跳转)
- 修改函数的返回地址
- 修改函数指针(C++)【虚函数】
- 修改异常处理函数指针(SEH,VEH,UEF,TEH)
- 修改线程同步的函数指针
利用过程:
- 定位漏洞点:利用静态分析和动态调试确定漏洞机理,如堆溢出,栈溢出,整数溢出的数据结构,影响的范围
- 按照利用要求,编写Shellcode
- 溢出,覆盖代码指针,使得Shellcode获得可执行权
- Shellcode:攻击者植入目标内存中的代码片段。
特点:长度受限,不能使用特定字符(例如\x00等),API函数自搜索和重定位能力(由于shellcode没有PE头,因此shellcode中使用的API和数据必须由shellcode自己进行搜索和重定位),一定的兼容性(为了支持更多的操作系统平台,shellcode需要具有一定的兼容性)
- shellcode设计流程:
- 编写shellcode(高级语言)
- 反汇编该shellcode
- 从汇编级分析程序执行流程
- 生成完成的shellcode(机器代码)
- 优化Shellcode(编码,长度)
- 适配漏洞
- DEP保护(Data Execution Prevention):通过将可执行代码标记为不可执行内存页,来防止恶意代码在不经意间执行。
核心思想:将内存分块后,设置不同的保护标志,代码区块拥有执行权限,而数据的区块仅有读写权限,进而控制数据区域内的shellcode无法执行。
DEP保护可以有效地防止缓冲区溢出攻击,从而提高计算机系统的安全性。在Windows系统中,DEP是默认启用的安全功能之一。启用DEP后,操作系统会阻止应用程序在非执行内存页中执行代码,从而防止攻击者利用缓冲区溢出漏洞执行恶意代码。
- Pwn入门之ret2libc详解
- ctf-wiki:retlibc
- 防止ret2libc的办法:
ASCII armoring:想办法让libc所有函数的地址都包含一个零字节,让strcpy拷贝函数在遇到零地址时结束拷贝,攻击失败!
- ret2plt 介绍 (攻破ASCII armoring)
- Ret2libc和ROP的区别
- ASLR(地址空间布局随机化)
- ROP攻击的原理,缓冲区溢出漏洞利用(ret2text+ret2shellcode)
- 程序切片:影响指定值的程序语句和判定表达式组成的语句集合,包括前向切片和后向切片。采用控制依赖和数据依赖实现。
- 程序切片(定义+用途)
- 符号执行:通过分析程序来得到让特定代码区域执行的输入
- 污点分析:把输入标记为Source( Taint),把敏感函数或者敏感操作标记为Sink。
- 静态分析工具:依据CVE公共漏洞字典表、OWASP十大Web漏洞,以及设备、软件厂商公布的漏洞库,结合软件设计规范或者编程规范对各种程序语言编写的源代码进行安全审计的工具。
比如:RIPS,Fortify SCA,VCG(VisualCodeGrepper),Findbugs-Java Bug pattern
- 动态分析:收集程序多次执行的运行过程的状态信息,结合输入和输出,检测程序存在的缺陷和漏洞。缘起:静态分析的结果不准确,存在误报较多。
分析方法有动态切片和污点传播。(要不实践试一下?)
动态分析工具:MEMcheck,BitBlaze,Fuzzing
- 静态分析vs动态分析
- Fuzzing模糊测试:一种基于错误注入的黑盒随机测试技术,向目标程序输入随机或者半随机(semi-valid)的测试集,分析程序的执行结果及检测程序状态,发现目标程序中隐藏的各种安全漏洞。
- 面相二进制程序的逆向分析:
- 程序理解:(密码)算法的理解学习,代码检查,代码比较,查找恶意代码,查找软件bug,查找软件漏洞。
- 代码优化:平台间的移植,修补bug,增加新的特性,代码恢复优化
- 流程逆向:关键函数(加密、认证),函数的关联
- 数据格式逆向:格式的分割,类型的推理,字段值溯源
六、安全防护
- 栈保护机制(Stack Protector / Stack Guard / Stack Canary):在返回地址和父函数栈基址之前加入一个随机值
具体工作机制:在函数开始时往栈中压入一个可以检验的随机数,在函数结束时验证栈中的随机数是否一致。
- Canary的基本原理与绕过
- Canary的各种绕过姿势
- FORTIFY_SOURCE:为字符串操作函数提供了轻量级的缓冲区溢出攻击和格式化字符串攻击检查,它会将危险函数替换为安全函数
-D_FORIFY_SOURCE=1时,开启缓冲区溢出攻击检查;
-D_FORIFY_SOURCE=2时,开启缓冲区溢出以及格式化字符串攻击检查。
- gcc安全特性之FORTIFY_SOURCE
-
数据执行保护:
-
地址空间布局随机化-ASLR:通过在内存空间中分配和保护代码,数据和堆栈的随机位置来打乱攻击者的预期内存布局,使攻击者难以准确地确定目标地址。
-
PIE(position-independent executable):在每次加载程序时都改变加载的基地址,不会影响指令间的相对地址。
PIE是一种可执行文件格式,它使得程序在内存中的位置无关,从而增加了攻击者利用缓冲区溢出等漏洞的难度。而ASLR是一种系统级别的安全机制,它通过随机化进程的地址空间布局来防止攻击者预测目标地址,进一步增强了系统的安全性。
在Linux系统中,PIE是ASLR的一部分。当一个程序被标记为PIE时,它会在运行时被加载到随机地址,使得攻击者难以找到目标地址。同时,ASLR还涉及到对堆、栈、共享库映射等线性区布局的随机化,进一步增加了攻击者预测目的地址的难度。
因此,PIE和ASLR是相互关联的安全技术,它们共同提高了软件的安全性。