【软件与系统安全】笔记与期末复习
关于课程
西电网信院 网安实验班 软件与系统安全
- 学时: 48 (讲授40 + 线上8)
- 期末考试: 60%
- 栈溢出利用作业: 15%
- AFL的使用作业: 15%
- 线上学习成绩: 10%
笔记列表
【软件与系统安全笔记】一、引入_框架主义者的博客-CSDN博客
【软件与系统安全笔记】二、软件与系统安全基础_框架主义者的博客-CSDN博客
【软件与系统安全笔记】三、基础技术_框架主义者的博客-CSDN博客
【软件与系统安全笔记】四、内存破坏漏洞_框架主义者的博客-CSDN博客
【软件与系统安全笔记】五、内存破坏防御_框架主义者的博客-CSDN博客
【软件与系统安全笔记】六、恶意代码的机理及其防护_框架主义者的博客-CSDN博客
【软件与系统安全笔记】七、模糊测试_框架主义者的博客-CSDN博客
【软件与系统安全笔记】八、软件自我保护_框架主义者的博客-CSDN博客
课程资料
- intro.pdf 课程概述
- intro-companion.pdf 软件安全现状(思政内容)
- sec-basics.pdf 软件与系统安全基础:信任、威胁模型、安全策略与策略执行、通用的安全设计原则
- tech_basics.pdf 基础技术:x86、内存、寄存器、数据类型、指令集、调用惯例、ELF
- attack-surface.pdf
- mem-vul-part.pdf 内存破坏漏洞
复习重点
简答题(70分)+综合题(30分)
浅灰色不重要:
-
概述 -
软件与系统安全基础
-
软件漏洞利用与防护
-
恶意代码的机理及其防护 “六、恶意代码的机理及其防护”15
-
模糊测试技术
- 模糊测试(只考偏原理的), “模糊测试”19
AFL简介,
-
软件保护技术 “软件保护技术”20
-
渗透测试及其他高级技术(线上)“九、Web 安全”24
信安班重点
- 3
变量存储位置
栈的工作原理
栈溢出、防护技术、DEP,金丝雀
- 4
堆的工作原理
堆溢出
- 5
整数溢出
格式化字符串漏洞
- 6
SQL注入
数组越界访问漏洞
- 7
释放重引用漏洞
- 8
绕过DEP “数据执行保护(DEP)”11
return to libc “代码重用攻击: Return to libc”27
返回导向编程 “ROP 面向返回的编程”14
- 9
*返回导向编程:*地址空间随机化 “ASLR Linux 的地址空间布局随机化”13
- 10
Fuzzing 定义、原理、特点
- 11
木马实现原理 “木马的实现原理与攻击步骤”28
- 12
病毒
分类"计算机病毒的分类"29
感染目标、过程
隐藏技术 “病毒的隐藏技术”30
花指令
多态 “病毒的多态”31
- 15
软件自我保护
mate 模型 “Man-At-The-End 攻击 (MATE)”32
#### 代码重用攻击: Return to libc
危险库函数如 `system()`
攻击者构造合适的参数(在栈上, 返回指令指针的上方)
* 在x64架构上,还需要更多的工作:设置参数传递寄存器的值
函数返回,库函数得到执行
* 例如:`execve(“/bin/sh”)`
甚至可以链接两个库函数调用
![Image](https://i-blog.csdnimg.cn/blog_migrate/ff4d09f4b2239f3f34b98895290589c6.png)
==**具体地**==
* 攻击者用一个溢出填充buffer:
* 更改栈上保存的ebp为一个合适地址
* 更改返回指令指针为一个欲执行的库函数的地址
* 写一个占位符值(库函数会认为其是返回地址,如果想利用它调用第二个库函数, 应写入第二个库函数的地址)
* 写一个或多个要传递给此库函数的参数
* 当被攻击的函数返回时, 恢复(更改过的)ebp, 然后pop更改后的返回地址到eip, 从而开始执行库函数代码
* 因为库函数相信它已被调用, 故会将栈顶当前值(占位符)作为它自己栈帧的返回指令指针, 之上是参数
* 最终会在占位符位置的下方创建起一个新的栈帧 (对应于库函数的执行)
* 根据库函数参数类型以及库函数对参数的解释方式, 攻击者可能需要准确地知道参数地址以做溢出写
### 代码注入 vs 代码重用
![Image](https://i-blog.csdnimg.cn/blog_migrate/8979b0db04b8467e4bb5f3c578006818.png)
**代码重用与代码注入的协同**
在很多攻击中, 代码重用攻击用来作为禁用DEP的第一步
* 目标是允许对栈内存进行执行
* 有一个系统调用可以更改栈的读/写/执行属性
* int mprotect(void *addr, size_t len, int prot);
* 设置对于起始于addr的内存区域的保护
* 调用此系统调用, 允许在栈上的“执行”属性, 然后开始执行被注入的代码
威胁模型
威胁模型(Attack model):对于影响系统安全的所有信息的结构化表示
本质上, 是从安全的视角解读系统与其环境
用于理解攻击者
- 什么可信、什么不可信
- 攻击者的动机、资源、能力; 攻击造成的影响
威胁建模
捕获、组织和分析这些影响系统安全的信息的过程
威胁模型使得 结构化地推理 攻击面 成为可能
-
确定入口点
-
从攻击者的视角审视系统
- 分解、识别系统结构
- 确定各种威胁及其严重程度
- 确定对策和缓解措施
漏洞、攻击与危害
漏洞(vulnerability):可以被对缺陷具有利用能力的攻击者访问并利用的缺陷, 要素:
- 缺陷 (flaw)
- 攻击者能访问缺陷
- 攻击者有利用缺陷的能力
攻击(attack): 指攻击者尝试利用漏洞,例如 主动、被动、DoS…
危害(compromise):攻击成功则危害发生 ↩︎
安全策略与策略执行
安全策略
- 允许什么/不允许什么
- 谁被允许做什么
策略执行
-
为了安全策略被遵循, 我们要做什么
-
策略执行的方法:利用某些“机制”(mechanism)
- 说服
- 监控和威慑
- 技术上禁止 (这是我们最感兴趣的)
- 激励管理
安全策略是一个系统所应具备的 安全属性 的高层次规约
安全策略模型是安全策略的简明(规范化)描述
安全目标是用户希望通过执行安全策略而获得的东西
安全属性与安全目标有时不做严格区分
安全策略的 CIA 模型
参照 网络与协议安全 中的 CIA 概念
- 机密性(Confidentiality)
- 完整性(Integrity)
- 可用性(Availability)
其他安全目标:
隐私(Privacy)
可以归类到机密性下
非否认性(Non-repudiation), 或可追责性(accountability):防止对消息传送或接收的否认
也叫可计量性
网络与协议安全 中还有 真实性
安全属性的另一种分类方法:
- Safety 属性:“坏事”不会发生
- Liveness 属性:“好事”终会发生
任何安全属性都可以被分解为一个 safety 属性和一个 liveness 属性
注意:实际上有一些安全策略无法用 safety 属性或 liveness 属性表示, 如信息流策略(2-safety 属性[2])——是“非属性”(Non-Property) ↩︎
x86 概念与内存模型
基本概念
x86: 基于 Intel 8086/8088 CPU 的一系列向后兼容的 ISA 的总称
-
IA-32: 32 位版本的 x86 指令集体系结构
-
三种主要操作模式:
- 实模式 (Real Mode)
- 保护模式 (Protected Mode)
- 系统管理模式 (System Management Mode) (大概就是 BIOS 下,对操作系统透明)
-
-
CISC(Complex Instruction Set Computer)体系结构
-
x64: 又称 x86-64, 是 x86 的扩展, 是与 x86 兼容的 64 位 ISA
字节序:多字节数据在内存中存储或在网络上传输时各字节的存储/传输顺序
小端序(little endian):低位字节存储在内存中低位地址, 效率较高(Intel CPU 使用)
大端序(big endian):低位字节存储在内存中高位地址, 符合思维逻辑。RISC 架构处理器(如 MIPS, PowerPC)采用
IA-32 内存模型
分段内存模型
程序内存由一系列独立的地址空间(称为“段”)组成。代码、数据和栈在不同的段中
逻辑地址=段选择器 + 偏移量
保护模式下的内存管理:分段(必须)+ 分页(可选)
程序线性地址空间 ≤4GB, 物理地址空间 ≤64GB
每个段最大 232 字节, IA-32 程序最多使用 16383 个段
x86 Linux 系统的线性地址空间分层
- 最大 3G ?
↩︎
-
四、内存破坏漏洞
2022-03-21 第四次课后半部分
mem-vul-part.pdf 内存破坏漏洞
与安全相关的语言问题(C 与 Java 比较)
回顾:
漏洞、攻击与危害
漏洞(vulnerability):可以被对缺陷具有利用能力的攻击者访问并利用的缺陷, 要素:
- 缺陷 (flaw)
- 攻击者能访问缺陷
- 攻击者有利用缺陷的能力
攻击(attack): 指攻击者尝试利用漏洞,例如 主动、被动、DoS…
危害(compromise):攻击成功则危害发生
- 软件错误(Software error):会导致软件无法满足预期的编程错误
- 软件漏洞(Software vulnerability):可能导致攻击的软件错误
C 字符串的用法与陷阱
在 C/C++ 程序中, 用 C 语言字符串编程时, 容易出现的错误
- 缓冲区溢出(Buffer overflows)
- Null 终止错误(null-termination errors)
- 差一错误(off-by-one errors)
差一错误 是在计数时由于边界条件判断失误导致结果多了一或少了一的错误
gets:无边界的字符串复制
strcpy 和 strcat
缓冲区溢出:栈溢出(stack smashing)
“x86 Linux 系统的线性地址空间分层”33
“System V AMD64 ABI 调用惯例”34
缓冲区溢出可被利用于修改:
- 栈上的: 返回指令指针, 函数指针, 局部变量. . .
- 堆数据结构
覆写返回指令指针
代码注入
将 ret 设置为被注入代码的起始地址, 被注入代码可以做任何事情,如下载和安装蠕虫
注入 Shell Code
shellcode: 注入的 payload 代码会执行起来一个 shell
在该 shell 中, 攻击者可以执行任何命令
该 shell 与当前进程具有相同的特权级
通常具有 root 权限的进程会被攻击如何使用被注入的代码调用“execve”?
-
将函数“execve”的地址注入栈上
-
“execve”是一个 libc 中的函数, 动态链接到进程地址空间中
-
为了使得我们的可执行程序能够调用 libc 中的函数,可执行程序必须能找到被调用函数的地址
- 如何做到? 当前程序通过一个 stub(PLT, Procedure Linkage Table )调用“execve”, PLT 在链接时检索 GOT (Global Offset Table)中的地址集合(请回忆动态链接过程)
“劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用”35
- 如何做到? 当前程序通过一个 stub(PLT, Procedure Linkage Table )调用“execve”, PLT 在链接时检索 GOT (Global Offset Table)中的地址集合(请回忆动态链接过程)
整数溢出
略
堆溢出
堆溢出: 在堆上开辟的缓冲区中的缓冲区溢出漏洞
溢出发生在堆缓冲区
溢出堆上的元数据
Heap allocators (又称内存管理器)
- 哪些内存区域已被开辟,它们的大小
- 哪些内存区域可以被开辟
Heap allocators 维护了元数据 (前块大小, 本块大小, previous 指针, next 指针)
- 堆元数据与堆数据是内联关系
- 内存管理函数(如 malloc()和 free())内部,会修改元数据
堆溢出攻击会篡改元数据,并等待内存管理函数把被篡改的元数据写入目标地址
Heap Allocator
- 维护一个已开辟的块的双向链表,以及一个空闲块的双向链表
- malloc()和 free()会修改双向链表
移除一个块
攻击 Heap Allocator
-
通过溢出 chunk2, 攻击者控制 chunk2 的 bk 和 fd 指针
- 实际上就控制了“向哪儿写数据”(where)以及 “写入什么数据”(what), 可以随意修改堆, 因此又称 “write-what-where” 漏洞
-
假设攻击者想将 value 写入内存地址 addr
- 攻击者将 chunk2->fd 设为 value
- 攻击者将 chunk2->bk 设为 addr - offset,其中 offset 为 fd 字段在 chuck 结构体内的偏移量
free()
:- chunk2->bk->fd = chunk2->fd
- chunk2->fd->bk = chunk2->bk
变为
- “(addr - offset)->fd = value”, 等同于
(*addr)=value
- “value->bk = addr - offset” (应确保value->bk有可写权限)
第一个写操作实现了攻击者的目标, 实现了任意的内存写操作
因 write-what-where 操作由free()完成, 故需从free()向上寻找潜在溢出
当溢出与代码注入/代码重用结合时,
- “
*(addr)=value
”的addr是预先选择的函数指针、返回指令指针、影响控制流的变量地址等, value是恶意的跳转目标 - fd字段在chunk结构体中的偏移量固定
- 因此, 决定被 free 的 chunk2 的元数据 (chunk2->fd, chunk2->bk) 应该被溢出修改的取值, 是可计算的
Use After Free
定义: 程序在堆上释放内存, 然后引用该内存, 就好像该内存位置仍然合法:攻击者可以控制使用已释放的指针进行的数据写入
又称作悬挂指针使用,也是一个 write-what-where 漏洞
大多数有效的 use-after-free 攻击利用另一类型的数据
struct A { void (*fnptr)(char *arg); char *buf; }; struct B { int B1; int B2; char info[32]; }; // 释放A, 开辟B, 实际做了什么? x = (struct A *)malloc(sizeof(struct A)); free(x); y = (struct B *)malloc(sizeof(struct B)); // 如何利用此漏洞? y->B1 = 0xDEADBEEF; x->fnptr(x->buf);
Use-after-free 是类型混淆的一个实例
禁止 Use After Free
如何以较简单的方式禁止use-after-free?
设置所有被释放的堆空间为NULL
这时在使用被释放的堆空间时产生一个 null-pointer dereference
目前, 操作系统对 null-pointer deference 有内建的防护机制
复杂度: 需要设置所有别名指针(aliased pointers)为NULL
Double Free
略
类型混淆
略
格式化字符串攻击
能够导致很灵活的恶意利用
- 代码注入: 被注入代码直接放入字符串
- 各种代码重用
int i; printf (“i = %d with address %08x\n", i, &i);
将格式化字符串的地址指针,
i
和&i
分别通过寄存器 rdi, rsi 和 rdx 传递, 并调用printf
当程序运行至printf内部, 会在这些寄存器中查找参数;如果参数超过6个,还会在栈上查找参数
格式指示符字母的含义: printf - C++ Reference (cplusplus.com)
格式化字符串中的“
%n
”能够将到“%n”位置为止已经由printf打印出的字节数写到一个我们选定的变量中
int i; printf ("foobar%n\n", (int *) &i); printf ("i = %d\n", i); // i 的值最终为6
对于“format-string.c”程序, 如果用户输入“foobar%n”会怎样?
- 从rsi获得一个地址, 且向该地址上的内存单元中写入整数6
- 如果输入的是“foobar%10u%n”呢? 可能会向该内存位置写入16
- 如何向一个任意地址写? 将该地址放在正确的位置 (寄存器或栈单元, 以作为printf的参数)
因此, 攻击者可以用任意内容更新任意内存。可否覆写一个函数指针并劫持控制流(且安装某些蠕虫代码)?
int main(int argc, char *argv[]) { char buf[512]; fgets(buf, sizeof(buf), stdin); // no buffer overflow here printf("The input is:"); printf(buf); // format string attacks here return 0 }
攻击者可以
- 查看/修改内存的任意部分
- 执行任意代码, 只需要把代码也放入buf
格式化字符串攻击的修复
通过提供一个特殊的格式化字符串, 可以规避“
%400s
”的限制:“
%497d\x3c\xd3\xff\xbf<nops><shellcode>
”。创建一个497字符长的字符串,加上错误字符串(“ERR Wrong command: ”),超过了outbuf的长度4字节。虽然“user”字符串只允许 400字节,可以通过滥用格式化字符串参数扩展其长度。因为第二个sprintf
不检查长度, 它可以用来突破outbuf的长度界限。此时我们写入了一个返回地址 (0xbfffd33c
), 并可以以之前栈溢出的利用方式进行攻击
防止格式化字符串漏洞
即限制攻击者控制格式化字符串的能力
- 如果有可能,硬编码字符串;且不用包含“
%*
”的格式化字符串 - 如果必须要用格式化字符串,至少不要用“
printf(arg)
” - 不要使用
%n
- 小心其他的引用:
%s
和sprintf
能够被用于构造栈内容披露攻击 - 编译器支持printf参数与格式化字符串的匹配检查
↩︎
缓冲区溢出:栈溢出(stack smashing)
“x86 Linux 系统的线性地址空间分层”33
“System V AMD64 ABI 调用惯例”34
缓冲区溢出可被利用于修改:
- 栈上的: 返回指令指针, 函数指针, 局部变量. . .
- 堆数据结构
覆写返回指令指针
代码注入
将 ret 设置为被注入代码的起始地址, 被注入代码可以做任何事情,如下载和安装蠕虫
注入 Shell Code
shellcode: 注入的 payload 代码会执行起来一个 shell
在该 shell 中, 攻击者可以执行任何命令
该 shell 与当前进程具有相同的特权级
通常具有 root 权限的进程会被攻击如何使用被注入的代码调用“execve”?
-
将函数“execve”的地址注入栈上
-
“execve”是一个 libc 中的函数, 动态链接到进程地址空间中
-
为了使得我们的可执行程序能够调用 libc 中的函数,可执行程序必须能找到被调用函数的地址
- 如何做到? 当前程序通过一个 stub(PLT, Procedure Linkage Table )调用“execve”, PLT 在链接时检索 GOT (Global Offset Table)中的地址集合(请回忆动态链接过程)
“劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用”35
- 如何做到? 当前程序通过一个 stub(PLT, Procedure Linkage Table )调用“execve”, PLT 在链接时检索 GOT (Global Offset Table)中的地址集合(请回忆动态链接过程)
↩︎
堆溢出
堆溢出: 在堆上开辟的缓冲区中的缓冲区溢出漏洞
溢出发生在堆缓冲区
溢出堆上的元数据
Heap allocators (又称内存管理器)
- 哪些内存区域已被开辟,它们的大小
- 哪些内存区域可以被开辟
Heap allocators 维护了元数据 (前块大小, 本块大小, previous 指针, next 指针)
- 堆元数据与堆数据是内联关系
- 内存管理函数(如 malloc()和 free())内部,会修改元数据
堆溢出攻击会篡改元数据,并等待内存管理函数把被篡改的元数据写入目标地址
Heap Allocator
- 维护一个已开辟的块的双向链表,以及一个空闲块的双向链表
- malloc()和 free()会修改双向链表
移除一个块
攻击 Heap Allocator
-
通过溢出 chunk2, 攻击者控制 chunk2 的 bk 和 fd 指针
- 实际上就控制了“向哪儿写数据”(where)以及 “写入什么数据”(what), 可以随意修改堆, 因此又称 “write-what-where” 漏洞
-
假设攻击者想将 value 写入内存地址 addr
- 攻击者将 chunk2->fd 设为 value
- 攻击者将 chunk2->bk 设为 addr - offset,其中 offset 为 fd 字段在 chuck 结构体内的偏移量
free()
:- chunk2->bk->fd = chunk2->fd
- chunk2->fd->bk = chunk2->bk
变为
- “(addr - offset)->fd = value”, 等同于
(*addr)=value
- “value->bk = addr - offset” (应确保value->bk有可写权限)
第一个写操作实现了攻击者的目标, 实现了任意的内存写操作
因 write-what-where 操作由free()完成, 故需从free()向上寻找潜在溢出
当溢出与代码注入/代码重用结合时,
- “
*(addr)=value
”的addr是预先选择的函数指针、返回指令指针、影响控制流的变量地址等, value是恶意的跳转目标 - fd字段在chunk结构体中的偏移量固定
- 因此, 决定被 free 的 chunk2 的元数据 (chunk2->fd, chunk2->bk) 应该被溢出修改的取值, 是可计算的
↩︎
Use After Free
定义: 程序在堆上释放内存, 然后引用该内存, 就好像该内存位置仍然合法:攻击者可以控制使用已释放的指针进行的数据写入
又称作悬挂指针使用,也是一个 write-what-where 漏洞
大多数有效的 use-after-free 攻击利用另一类型的数据
struct A { void (*fnptr)(char *arg); char *buf; }; struct B { int B1; int B2; char info[32]; }; // 释放A, 开辟B, 实际做了什么? x = (struct A *)malloc(sizeof(struct A)); free(x); y = (struct B *)malloc(sizeof(struct B)); // 如何利用此漏洞? y->B1 = 0xDEADBEEF; x->fnptr(x->buf);
Use-after-free 是类型混淆的一个实例
禁止 Use After Free
如何以较简单的方式禁止use-after-free?
设置所有被释放的堆空间为NULL
这时在使用被释放的堆空间时产生一个 null-pointer dereference
目前, 操作系统对 null-pointer deference 有内建的防护机制
复杂度: 需要设置所有别名指针(aliased pointers)为NULL
↩︎
格式化字符串攻击
能够导致很灵活的恶意利用
- 代码注入: 被注入代码直接放入字符串
- 各种代码重用
int i; printf (“i = %d with address %08x\n", i, &i);
将格式化字符串的地址指针,
i
和&i
分别通过寄存器 rdi, rsi 和 rdx 传递, 并调用printf
当程序运行至printf内部, 会在这些寄存器中查找参数;如果参数超过6个,还会在栈上查找参数
格式指示符字母的含义: printf - C++ Reference (cplusplus.com)
格式化字符串中的“
%n
”能够将到“%n”位置为止已经由printf打印出的字节数写到一个我们选定的变量中
int i; printf ("foobar%n\n", (int *) &i); printf ("i = %d\n", i); // i 的值最终为6
对于“format-string.c”程序, 如果用户输入“foobar%n”会怎样?
- 从rsi获得一个地址, 且向该地址上的内存单元中写入整数6
- 如果输入的是“foobar%10u%n”呢? 可能会向该内存位置写入16
- 如何向一个任意地址写? 将该地址放在正确的位置 (寄存器或栈单元, 以作为printf的参数)
因此, 攻击者可以用任意内容更新任意内存。可否覆写一个函数指针并劫持控制流(且安装某些蠕虫代码)?
int main(int argc, char *argv[]) { char buf[512]; fgets(buf, sizeof(buf), stdin); // no buffer overflow here printf("The input is:"); printf(buf); // format string attacks here return 0 }
攻击者可以
- 查看/修改内存的任意部分
- 执行任意代码, 只需要把代码也放入buf
格式化字符串攻击的修复
通过提供一个特殊的格式化字符串, 可以规避“
%400s
”的限制:“
%497d\x3c\xd3\xff\xbf<nops><shellcode>
”。创建一个497字符长的字符串,加上错误字符串(“ERR Wrong command: ”),超过了outbuf的长度4字节。虽然“user”字符串只允许 400字节,可以通过滥用格式化字符串参数扩展其长度。因为第二个sprintf
不检查长度, 它可以用来突破outbuf的长度界限。此时我们写入了一个返回地址 (0xbfffd33c
), 并可以以之前栈溢出的利用方式进行攻击
防止格式化字符串漏洞
即限制攻击者控制格式化字符串的能力
- 如果有可能,硬编码字符串;且不用包含“
%*
”的格式化字符串 - 如果必须要用格式化字符串,至少不要用“
printf(arg)
” - 不要使用
%n
- 小心其他的引用:
%s
和sprintf
能够被用于构造栈内容披露攻击 - 编译器支持printf参数与格式化字符串的匹配检查
↩︎
五、内存破坏防御
虽然对缓冲区溢出的认知已超过 40 年之久, 但缓冲区溢出仍未被消除。部分原因在于存在大量的利用选项:
- 多样的目标: 不仅仅可以利用返回地址, 实际上可以利用任意代码地址或数据
- 多样的使用: 可以利用“读”或“写”操作
- 多样的利用方式: 对代码可以“注入”或“重用”
- 当前对溢出的防御措施多样, 但不完全
防御可以在不同的时机进行
- 编程前
- 开发过程中 (防御性编程)
- 测试时 (fuzzing, . . .)
- 代码运行时 (检测和缓解: stack canaries, DEP, . . .)
Stack Canaries 栈 金丝雀
是栈溢出的检测机制, 又称“栈 cookies”,由 gcc 的 StackGuard 实现
原理:将一个 dummy 值(或随机值)写到栈上的返回地址之前,并在函数返回时检查该值。不小心构造的栈溢出(假定是顺序栈粉碎)会覆写该“canary”单元, 该行为将被探测到。
攻破 StackGuard 的基本方法
对 canary 单元, 用正确的值覆写
- 如果 canary 所使用的随机值范围很小, 则枚举每种可能性
- 或先实施一个 memory disclosure 攻击, 获知 canary 的值
无法抵御 disclosure 攻击是 StackGuard 的最大局限性
-
disclosure 攻击通过对缓冲区的“overread”实现
-
著名例子: 对 SSL 的 Heartbleed 攻击
-
以下程序为什么会对 Stackguard 的 canaries 造成威胁?
- 通过对缓冲区的“overread”, 攻击者读取超出栈缓冲区之外的值, 从而获取 canary 的值
char packet[10]; … // suppose len is adversary controlled strncpy(buf, packet, len); send(fd, buf, len);
有时不需要覆写返回地址, 可以溢出:
- 安全敏感的局部变量
- 堆数据
- 全局数据
- · · ·
- 本质上, 攻击者只需要劫持一个函数指针
劫持函数指针
void foo () {...} void bar () {...} int main() { char buf [16]; void (*f) () = &foo; gets(buf); f(); }
假定我们没有机会溢出返回地址
可溢出缓冲区, 使得函数指针被修改为 bar 的地址, 然后函数调用将调用 bar 而非 foo
劫持函数指针的其他方法
- 使用堆溢出,对堆上的函数指针进行劫持
- 劫持全局函数指针
- 劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用
攻破 StackGuard 的其他方法
有时不需要覆写返回地址, 可以溢出:
-
安全敏感的局部变量
-
堆数据
-
全局数据
- 全局数据溢出: 攻击位于全局数据区的缓冲区
-
· · ·
如何防御?
- 让函数指针位于其他类型数据的下方(更低地址)
- 在全局数据区和其他管理表结构之间使用守卫页
守卫页(Guard Pages)
也是一种运行时检测方法, 可以看作StackGuard的扩展
在一个进程地址空间中关键内存区域之间放置守卫页 (像一些gaps)
- 需借助CPU内存管理单元(MMU)的管理功能将它们标记为非法地址
- 任何对其的访问尝试都导致进程被终止
效果: 能失效缓冲区溢出攻击, 特别是对全局数据区的溢出攻击
甚至可以在栈帧之间、或者堆缓冲区之间放置守卫页
- 可以提供更进一步的保护, 防止栈溢出和堆溢出攻击
- 会导致执行时间和内存的很大开销, 因为要支持大量页映射
数据执行保护(DEP)
冯诺依曼体系结构
- 将代码作为数据存储
- 使得攻击者可以向栈或堆注入代码, 而栈和堆原本只应该存储数据
哈佛架构
- 虚拟地址空间切分为一个数据区和一个代码区
- 代码区可读且可执行
- 数据区可读且可写
- 没有区域是既可写又可执行的
Data Execution Prevention (数据执行保护): 是一种运行时缓解技术
DEP又称作Nx-bit (non executable bit), W⊕X
能够阻止代码注入攻击
很多缓冲区溢出攻击涉及将机器码复制到目标缓冲区, 然后将执行转移到这些缓冲区
- 一种防御方法就是阻止在栈/堆/全局数据区中执行代码, 并假定可执行代码只能出现在进程地址空间中除这些位置外的其他位置需要CPU内存管理单元(MMU)提供支持, 将虚拟内存的对应页标记为不可执行
- 对于每一个被映射的虚拟内存页, 都有这样额外的1个no-executebit, 置位时, 表示该页的数据不能作为代码执行, 一旦程序控制流到达该页, CPU会产生陷入
DEP 被绝大多数操作系统和指令集体系结构支持
- 一些CPU早有支持(如Solaris的SPARC), 只需修改Solaris内核参数即可启用
- x86系列后来才向MMU中加入no-execute位
- Linux/Unix类系统, Windows均已提供相应扩展, 支持使用DEP特性
对 Nx-bit 的不同叫法
- Intel: XD (eXecute Disable)
- AMD: Enhanced Virus Protection
- ARM: XN (eXecute Never)
如果CPU硬件支持, DEP可作为操作系统更新, 通过更改对进程虚拟地址空间的内存管理, 提供对现有漏洞程序的保护
DEP将栈和堆置为不可执行, 对多种缓冲区溢出攻击提供了一种高度的保护
但有一些合法程序需要将可执行代码放在栈上:
- 如Java运行时系统、运行时代码生成、Linux信号处理程序等
- 需要针对这些需求制定一些专门条款(Special provisions)
攻击 DEP——代码重用攻击
思路: 重用程序自身的代码
Return-to-libc: 用危险的库函数的地址替换返回地址
代码重用攻击: Return to libc
危险库函数如
system()
攻击者构造合适的参数(在栈上, 返回指令指针的上方)
- 在x64架构上,还需要更多的工作:设置参数传递寄存器的值
函数返回,库函数得到执行
- 例如:
execve(“/bin/sh”)
甚至可以链接两个库函数调用
具体地
-
攻击者用一个溢出填充buffer:
- 更改栈上保存的ebp为一个合适地址
- 更改返回指令指针为一个欲执行的库函数的地址
- 写一个占位符值(库函数会认为其是返回地址,如果想利用它调用第二个库函数, 应写入第二个库函数的地址)
- 写一个或多个要传递给此库函数的参数
-
当被攻击的函数返回时, 恢复(更改过的)ebp, 然后pop更改后的返回地址到eip, 从而开始执行库函数代码
-
因为库函数相信它已被调用, 故会将栈顶当前值(占位符)作为它自己栈帧的返回指令指针, 之上是参数
-
最终会在占位符位置的下方创建起一个新的栈帧 (对应于库函数的执行)
-
根据库函数参数类型以及库函数对参数的解释方式, 攻击者可能需要准确地知道参数地址以做溢出写
代码注入 vs 代码重用
代码重用与代码注入的协同
在很多攻击中, 代码重用攻击用来作为禁用DEP的第一步
-
目标是允许对栈内存进行执行
-
有一个系统调用可以更改栈的读/写/执行属性
- int mprotect(void *addr, size_t len, int prot);
-
设置对于起始于addr的内存区域的保护
-
调用此系统调用, 允许在栈上的“执行”属性, 然后开始执行被注入的代码
ROP 面向返回的编程
面向返回的编程
- 执行任意行为, 不需要注入代码
- 联合现有的代码片段 (gadgets)
- 一系列图灵完全的 gadgets, 及一种串联这些 gadgets 的方法
- 现有的展示已能针对小程序(如16KB)找到图灵完全的 gadgets 集合
正常机器指令序列
ROP执行
TODO
用ROP我们能做什么?
Turing completeness
一种语言是Turing complete的,如果其具有
- 条件分支(Conditional branching)
- 可以任意修改内存
这两点在ROP中均能实现
针对ROP的保护
ROP的工作基于对程序控制流的修改
控制流完整性 (Control-flow integrity, CFI)
-
预先决定被攻击程序的控制流图
-
向该程序中插入检测, 使得在程序运行时发生非法控制流跳转时,终止程序
- 通过编译器或二进制重写进行插入
ROP 的运行时缓解: 随机化
ROP利用要求攻击者对代码/数据地址的知识,例如
- 缓冲区的起始地址
- 库函数的地址
思路: 引入人为的多样性(随机化)
- 使得地址对于攻击者而言难以预测
- 如果攻击者不知道一段代码(或数据)在内存的什么位置,
- 他就没办法在攻击中重用它们
有很多方法能够实现随机化
- 对栈的位置进行随机化, 对堆上的关键数据结构进行随机化,
- 对库函数的位置进行随机化
- 随机地填充栈帧
- 在编译时, 随机化代码生成, 以抵御 ROP
实现随机化的时机
- 编译时
- 链接时
- 运行时(通过动态二进制重写, dynamic binary rewriting)
地址空间随机化的挑战
- 信息泄露(如通过边信道)
- 暴力破解秘密值
- 对于长时间运行的进程, 如何“再次随机化”
地址空间随机化的有效性取决于
- 每个被随机出的位置的熵值
- 随机化的完备性(completeness), 例如是否所有的对象都被随机化?
- 信息泄露的避免程度
ASLR Linux 的地址空间布局随机化
ASLR(Address space layout randomization)
-
对于位置无关的可执行程序(PIE), 随机化该可执行程序的基地址
- 所有库都是 PIE, 因此它们的基地址被随机化
- 主可执行程序可能不是 PIE, 故可能无法被 ASLR 保护
-
关注的是内存块的随机化
-
ASLR 是一种粗粒度的随机化形式
- 只有基地址被随机化
- 在内存对象之间的相对距离不变
攻破 ASLR 的方法
-
如果随机地址空间很小, 可以进行一个穷举搜索
- 例如, Linux 提供 16 位的随机化强度, 可以在约 200 秒以内被穷举搜索攻破
-
ASLR 经常被 memory disclosure (内存泄漏) 攻破
- 例如, 如果攻击者可以读取指向栈的指针值, 他就可以使用该指针值发现栈在哪里
防御性编程
使用更安全的编程语言
代码评审
费根检查(fagan inspection)
检查表
编译时防御
编写时
危险的 C 库函数
输入验证
所有输入都是恶意的
最小化攻击面
识别攻击面
防御性编程总结
-
好的实践
-
使用更安全的编程语言
-
进行代码评审
-
使用编译器的机制, 如 StackGuard
-
编写内存安全的代码
- 使用边界检查库函数,使用更安全的库
-
-
输入验证
- 识别攻击面: 程序从信道获得输入
- 最小化攻击面
- 将所有输入都看作潜在恶意的
↩︎
Stack Canaries 栈 金丝雀
是栈溢出的检测机制, 又称“栈 cookies”,由 gcc 的 StackGuard 实现
原理:将一个 dummy 值(或随机值)写到栈上的返回地址之前,并在函数返回时检查该值。不小心构造的栈溢出(假定是顺序栈粉碎)会覆写该“canary”单元, 该行为将被探测到。
攻破 StackGuard 的基本方法
对 canary 单元, 用正确的值覆写
- 如果 canary 所使用的随机值范围很小, 则枚举每种可能性
- 或先实施一个 memory disclosure 攻击, 获知 canary 的值
无法抵御 disclosure 攻击是 StackGuard 的最大局限性
-
disclosure 攻击通过对缓冲区的“overread”实现
-
著名例子: 对 SSL 的 Heartbleed 攻击
-
以下程序为什么会对 Stackguard 的 canaries 造成威胁?
- 通过对缓冲区的“overread”, 攻击者读取超出栈缓冲区之外的值, 从而获取 canary 的值
char packet[10]; … // suppose len is adversary controlled strncpy(buf, packet, len); send(fd, buf, len);
有时不需要覆写返回地址, 可以溢出:
- 安全敏感的局部变量
- 堆数据
- 全局数据
- · · ·
- 本质上, 攻击者只需要劫持一个函数指针
劫持函数指针
void foo () {...} void bar () {...} int main() { char buf [16]; void (*f) () = &foo; gets(buf); f(); }
假定我们没有机会溢出返回地址
可溢出缓冲区, 使得函数指针被修改为 bar 的地址, 然后函数调用将调用 bar 而非 foo
劫持函数指针的其他方法
- 使用堆溢出,对堆上的函数指针进行劫持
- 劫持全局函数指针
- 劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用
攻破 StackGuard 的其他方法
有时不需要覆写返回地址, 可以溢出:
-
安全敏感的局部变量
-
堆数据
-
全局数据
- 全局数据溢出: 攻击位于全局数据区的缓冲区
-
· · ·
如何防御?
- 让函数指针位于其他类型数据的下方(更低地址)
- 在全局数据区和其他管理表结构之间使用守卫页
守卫页(Guard Pages)
也是一种运行时检测方法, 可以看作StackGuard的扩展
在一个进程地址空间中关键内存区域之间放置守卫页 (像一些gaps)
- 需借助CPU内存管理单元(MMU)的管理功能将它们标记为非法地址
- 任何对其的访问尝试都导致进程被终止
效果: 能失效缓冲区溢出攻击, 特别是对全局数据区的溢出攻击
甚至可以在栈帧之间、或者堆缓冲区之间放置守卫页
- 可以提供更进一步的保护, 防止栈溢出和堆溢出攻击
- 会导致执行时间和内存的很大开销, 因为要支持大量页映射
↩︎
数据执行保护(DEP)
冯诺依曼体系结构
- 将代码作为数据存储
- 使得攻击者可以向栈或堆注入代码, 而栈和堆原本只应该存储数据
哈佛架构
- 虚拟地址空间切分为一个数据区和一个代码区
- 代码区可读且可执行
- 数据区可读且可写
- 没有区域是既可写又可执行的
Data Execution Prevention (数据执行保护): 是一种运行时缓解技术
DEP又称作Nx-bit (non executable bit), W⊕X
能够阻止代码注入攻击
很多缓冲区溢出攻击涉及将机器码复制到目标缓冲区, 然后将执行转移到这些缓冲区
- 一种防御方法就是阻止在栈/堆/全局数据区中执行代码, 并假定可执行代码只能出现在进程地址空间中除这些位置外的其他位置需要CPU内存管理单元(MMU)提供支持, 将虚拟内存的对应页标记为不可执行
- 对于每一个被映射的虚拟内存页, 都有这样额外的1个no-executebit, 置位时, 表示该页的数据不能作为代码执行, 一旦程序控制流到达该页, CPU会产生陷入
DEP 被绝大多数操作系统和指令集体系结构支持
- 一些CPU早有支持(如Solaris的SPARC), 只需修改Solaris内核参数即可启用
- x86系列后来才向MMU中加入no-execute位
- Linux/Unix类系统, Windows均已提供相应扩展, 支持使用DEP特性
对 Nx-bit 的不同叫法
- Intel: XD (eXecute Disable)
- AMD: Enhanced Virus Protection
- ARM: XN (eXecute Never)
如果CPU硬件支持, DEP可作为操作系统更新, 通过更改对进程虚拟地址空间的内存管理, 提供对现有漏洞程序的保护
DEP将栈和堆置为不可执行, 对多种缓冲区溢出攻击提供了一种高度的保护
但有一些合法程序需要将可执行代码放在栈上:
- 如Java运行时系统、运行时代码生成、Linux信号处理程序等
- 需要针对这些需求制定一些专门条款(Special provisions)
攻击 DEP——代码重用攻击
思路: 重用程序自身的代码
代码注入 vs 代码重用
代码重用与代码注入的协同
在很多攻击中, 代码重用攻击用来作为禁用DEP的第一步
-
目标是允许对栈内存进行执行
-
有一个系统调用可以更改栈的读/写/执行属性
- int mprotect(void *addr, size_t len, int prot);
-
设置对于起始于addr的内存区域的保护
-
调用此系统调用, 允许在栈上的“执行”属性, 然后开始执行被注入的代码
↩︎
-
ASLR Linux 的地址空间布局随机化
ASLR(Address space layout randomization)
-
对于位置无关的可执行程序(PIE), 随机化该可执行程序的基地址
- 所有库都是 PIE, 因此它们的基地址被随机化
- 主可执行程序可能不是 PIE, 故可能无法被 ASLR 保护
-
关注的是内存块的随机化
-
ASLR 是一种粗粒度的随机化形式
- 只有基地址被随机化
- 在内存对象之间的相对距离不变
攻破 ASLR 的方法
-
如果随机地址空间很小, 可以进行一个穷举搜索
- 例如, Linux 提供 16 位的随机化强度, 可以在约 200 秒以内被穷举搜索攻破
-
ASLR 经常被 memory disclosure (内存泄漏) 攻破
- 例如, 如果攻击者可以读取指向栈的指针值, 他就可以使用该指针值发现栈在哪里
-
ROP 面向返回的编程
面向返回的编程
- 执行任意行为, 不需要注入代码
- 联合现有的代码片段 (gadgets)
- 一系列图灵完全的 gadgets, 及一种串联这些 gadgets 的方法
- 现有的展示已能针对小程序(如16KB)找到图灵完全的 gadgets 集合
正常机器指令序列
ROP执行
TODO
用ROP我们能做什么?
Turing completeness
一种语言是Turing complete的,如果其具有
- 条件分支(Conditional branching)
- 可以任意修改内存
这两点在ROP中均能实现
针对ROP的保护
ROP的工作基于对程序控制流的修改
控制流完整性 (Control-flow integrity, CFI)
-
预先决定被攻击程序的控制流图
-
向该程序中插入检测, 使得在程序运行时发生非法控制流跳转时,终止程序
- 通过编译器或二进制重写进行插入
ROP 的运行时缓解: 随机化
ROP利用要求攻击者对代码/数据地址的知识,例如
- 缓冲区的起始地址
- 库函数的地址
思路: 引入人为的多样性(随机化)
- 使得地址对于攻击者而言难以预测
- 如果攻击者不知道一段代码(或数据)在内存的什么位置,
- 他就没办法在攻击中重用它们
有很多方法能够实现随机化
- 对栈的位置进行随机化, 对堆上的关键数据结构进行随机化,
- 对库函数的位置进行随机化
- 随机地填充栈帧
- 在编译时, 随机化代码生成, 以抵御 ROP
实现随机化的时机
- 编译时
- 链接时
- 运行时(通过动态二进制重写, dynamic binary rewriting)
地址空间随机化的挑战
- 信息泄露(如通过边信道)
- 暴力破解秘密值
- 对于长时间运行的进程, 如何“再次随机化”
地址空间随机化的有效性取决于
- 每个被随机出的位置的熵值
- 随机化的完备性(completeness), 例如是否所有的对象都被随机化?
- 信息泄露的避免程度
ASLR Linux 的地址空间布局随机化
ASLR(Address space layout randomization)
-
对于位置无关的可执行程序(PIE), 随机化该可执行程序的基地址
- 所有库都是 PIE, 因此它们的基地址被随机化
- 主可执行程序可能不是 PIE, 故可能无法被 ASLR 保护
-
关注的是内存块的随机化
-
ASLR 是一种粗粒度的随机化形式
- 只有基地址被随机化
- 在内存对象之间的相对距离不变
攻破 ASLR 的方法
-
如果随机地址空间很小, 可以进行一个穷举搜索
- 例如, Linux 提供 16 位的随机化强度, 可以在约 200 秒以内被穷举搜索攻破
-
ASLR 经常被 memory disclosure (内存泄漏) 攻破
- 例如, 如果攻击者可以读取指向栈的指针值, 他就可以使用该指针值发现栈在哪里
六、恶意代码的机理及其防护
病毒, 蠕虫, 木马, (侧重:差异, 共性, 如何隐藏)
木马
一个典型的特洛伊木马(程序)通常具有以下四个特点:
- 有效性
- 隐蔽性
- 顽固性
- 易植入性
此外,木马还具有以下辅助型特点:
- 自动运行
- 欺骗性
- 自动恢复
- 功能的特殊
木马的实现原理与攻击步骤
-
木马实现原理
-
本质上说,木马大多都是网络客户/服务(Client/Server)程序的组合。常由一个攻击者控制的客户端程序和一个运行在被控计算机端的服务端程序组成
-
当攻击者要利用“木马”进行网络入侵,一般都需完成如下环节:
- 向目标主机植入木马
- 启动和隐藏木马
- 服务器端(目标主机)和客户端建立连接
- 进行远程控制
-
-
植入技术
-
主动植入
-
本地安装
-
远程安装
- 利用系统自身漏洞植入
- 利用第三方软件漏洞植入
-
-
被动植入
- 网页浏览植入
- 利用电子邮件植入
- 利用网络下载植入
- 利用即时通工具植入
- 与其它程序捆绑
- 利用移动存储设备植入
-
-
自动加载技术
-
在Windows系统中木马程序的自动加载技术主要有:
- 修改系统文件
- 修改系统注册表
- 添加系统服务
- 修改文件打开关联属性
- 修改任务计划
- 修改组策略
- 利用系统自动运行的程序
- 修改启动文件夹
- 替换系统DLL
-
-
隐藏技术
- 隐蔽性是木马程序与其它程序的重要区别
- 伪隐藏、真隐藏
- 设置窗口不可见 (从任务栏中隐藏)
- 把木马程序注册为服务 (从进程列表中隐藏)
- 欺骗查看进程的函数 (从进程列表中隐藏)
- 使用可变的高端口 (端口隐藏技术)
- 使用系统服务端口 (端口隐藏技术)
- 替换系统驱动或系统DLL (真隐藏技术)
- 动态嵌入技术 (真隐藏技术)
-
连接技术
- 反弹窗口的连接技术:更容易通过防火墙
-
监控技术
木马的远程监控功能概括起来有以下几点:
- 获取目标机器信息
- 记录用户事件
- 远程操作
木马的防御技术
-
木马的检测
- 端口扫描和连接检查
- 检查系统进程
- 检查ini文件、注册表和服务
- 监视网络通讯
-
木马的清除与善后
-
木马的防范
- 及时修补漏洞,安装补丁
- 运行实时监控程序
- 培养风险意识,不使用来历不明的软件
- 即时发现,即时清除
木马的发展趋势
- 跨平台
- 模块化设计
- 无连接木马
- 主动植入
- 木马与病毒的融合
病毒
计算机病毒的定义
算机病毒一般依附于其他程序或文档,是能够自身复制,并且产生用户不知情或不希望、甚至恶意的操作的非正常程
计算机病毒的特点
- 隐藏性
- 传染性
- 潜伏性
- 破坏性
以上列举的定义指的是狭义上的病毒。
但是随着黑客技术的发展,病毒、木马、蠕虫往往交叉在一起相互借鉴技术,因此人们经常说的计算机病毒往往是指广义上的病毒,它是一切恶意程序的统称。
计算机病毒的破坏性
- 破坏系统数据
- 破坏目录/文件
- 修改内存
- 干扰系统运行
- 效率降低
- 破坏显示
- 干扰键盘操作
- 制造噪音
- 修改CMOS参数
- 影响打印
计算机病毒引起的异常状况
- 计算机系统运行速度明显降低
- 系统容易死机
- 文件改变
- 磁盘可用空间迅速减少
- 系统参数被修改
- 文件被破坏
- 频繁产生错误信息
- 系统异常频繁重启动
- Office宏病毒提
计算机病毒的分类
我们针对狭义上的病毒,按照不同的标准对它进行分类。
-
按照计算机病毒攻击的对象或系统平台分类
- 攻击DOS系统的病毒
- 攻击WINDOWS系统的病毒
- 攻击UNIX 系统的病毒
- 攻击OS/2系统的病毒
- 其它操作系统上的病毒:如手机病毒
-
按照计算病毒的攻击目标机类型分类:
- 攻击微型计算机的病毒
- 攻击小型计算机的病毒
- 攻击工作站的病毒
-
按照计算机病毒的链接方式分类
- 源码型病毒
- 嵌入型病毒
- 外壳型病毒:包围在主程序的四周
- 操作系统型病毒
-
按照计算机病毒的破坏情况分类
- 良性病毒:是不包含有对计算机系统产生直接破坏作用的代码的计算机病毒。
- 恶性病毒:指在代码中包含有损伤和破坏计算机系统的操作
-
按传播媒介来分类
- 单机病毒:单机病毒的载体是磁盘或光盘。常见的是通过从软盘传入硬盘,感染系统后,再传染其它软盘。软盘又感染其它系统。
- 网络病毒:网络为病毒提供了最好的传播途径。网络病毒利用计算机网络的协议或命令以及Email等进行传播,常见的是通过QQ、 BBS、Email、 FTP、 Web等传播
计算机病毒的命名
虽然每个反病毒公司的命名规则都不太一样,但大体都是采用一个统一的命名方法来命名的。一般格式为: <病毒前缀>.<病毒名>.<病毒后缀>
计算机病毒的工作机制
-
计算机病毒程序模块划分
-
感染模块
- 寻找一个可执行文件。
- 检查该文件中是否有感染标记。
- 如果没有感染标记,进行感染,将病毒代码放入宿主程序
-
触发模块
- 检查预定触发条件是否满足。
- 如果满足,返回真值。
- 如果不满足,返回假值
-
破坏模块(表现模块)
-
主控模块
- 调用感染模块,进行感染。
- 调用触发模块,接受其返回值。
- 如果返回真值,执行破坏模块。
- 如果返回假值,执行后续程序。
-
-
计算机病毒的生命周期
- 感染
- 潜伏
- 繁殖
- 发作
-
计算机病毒的传播机制
-
病毒入侵宿主程序的基本方式有两种: 替代方式和链接方式。
-
病毒的宿主程序可分为两类: 操作系统和应用程序。
-
交叉感染
-
寄生感染
- 插入感染
- 逆插入感染
-
没有入口点的感染
-
零长度感染
-
-
计算机病毒的触发机制
-
计算机病毒的破坏机制
典型的计算机病毒
DOS病毒
Win32 PE病毒
宏病毒
脚本病毒
HTML病毒
蠕虫
病毒的隐藏技术
隐藏技术主要有:
- 反跟踪技术
- 避开修改中断向量
- 请求在内存中的合法身份
- 维持宿主程序的外部特性
- 不使用明显的感染标志
花指令
病毒的多态
多态技术中的密钥和解密代码都变化多端,多态技术将对解密代码进行等价指令替换、寄存器替换、插入垃圾指令或者随机调换指令的前后位置(有些指令的前后位置调换之后不影响代码功能)等变化,以产生功能相同但是代码截然不同的解密代码
多态引擎的组成:
- 指令位置变换模块
- 寄存器变换模块
- 指令扩展模块
- 指令收缩模块
- 等价指令替换模块
- 无用指令随机插入
- 垃圾指令插
蠕虫
它与其他的病毒相比,具有传染的主动性
计算机病毒是一段代码,能把自身加到其它程序包括操作系统上。它不能独立运行,需要由它的宿主程序运行来激活它”。
Eugene H. Spafford对蠕虫的定义:“计算机蠕虫可以独立运行,并能把自身的一个包含所有功能的版本传播到另外的计算机上”
↩︎
病毒
计算机病毒的定义
算机病毒一般依附于其他程序或文档,是能够自身复制,并且产生用户不知情或不希望、甚至恶意的操作的非正常程
计算机病毒的特点
- 隐藏性
- 传染性
- 潜伏性
- 破坏性
以上列举的定义指的是狭义上的病毒。
但是随着黑客技术的发展,病毒、木马、蠕虫往往交叉在一起相互借鉴技术,因此人们经常说的计算机病毒往往是指广义上的病毒,它是一切恶意程序的统称。
计算机病毒的破坏性
- 破坏系统数据
- 破坏目录/文件
- 修改内存
- 干扰系统运行
- 效率降低
- 破坏显示
- 干扰键盘操作
- 制造噪音
- 修改CMOS参数
- 影响打印
计算机病毒引起的异常状况
- 计算机系统运行速度明显降低
- 系统容易死机
- 文件改变
- 磁盘可用空间迅速减少
- 系统参数被修改
- 文件被破坏
- 频繁产生错误信息
- 系统异常频繁重启动
- Office宏病毒提
计算机病毒的分类
我们针对狭义上的病毒,按照不同的标准对它进行分类。
-
按照计算机病毒攻击的对象或系统平台分类
- 攻击DOS系统的病毒
- 攻击WINDOWS系统的病毒
- 攻击UNIX 系统的病毒
- 攻击OS/2系统的病毒
- 其它操作系统上的病毒:如手机病毒
-
按照计算病毒的攻击目标机类型分类:
- 攻击微型计算机的病毒
- 攻击小型计算机的病毒
- 攻击工作站的病毒
-
按照计算机病毒的链接方式分类
- 源码型病毒
- 嵌入型病毒
- 外壳型病毒:包围在主程序的四周
- 操作系统型病毒
-
按照计算机病毒的破坏情况分类
- 良性病毒:是不包含有对计算机系统产生直接破坏作用的代码的计算机病毒。
- 恶性病毒:指在代码中包含有损伤和破坏计算机系统的操作
-
按传播媒介来分类
- 单机病毒:单机病毒的载体是磁盘或光盘。常见的是通过从软盘传入硬盘,感染系统后,再传染其它软盘。软盘又感染其它系统。
- 网络病毒:网络为病毒提供了最好的传播途径。网络病毒利用计算机网络的协议或命令以及Email等进行传播,常见的是通过QQ、 BBS、Email、 FTP、 Web等传播
计算机病毒的命名
虽然每个反病毒公司的命名规则都不太一样,但大体都是采用一个统一的命名方法来命名的。一般格式为: <病毒前缀>.<病毒名>.<病毒后缀>
计算机病毒的工作机制
-
计算机病毒程序模块划分
-
感染模块
- 寻找一个可执行文件。
- 检查该文件中是否有感染标记。
- 如果没有感染标记,进行感染,将病毒代码放入宿主程序
-
触发模块
- 检查预定触发条件是否满足。
- 如果满足,返回真值。
- 如果不满足,返回假值
-
破坏模块(表现模块)
-
主控模块
- 调用感染模块,进行感染。
- 调用触发模块,接受其返回值。
- 如果返回真值,执行破坏模块。
- 如果返回假值,执行后续程序。
-
-
计算机病毒的生命周期
- 感染
- 潜伏
- 繁殖
- 发作
-
计算机病毒的传播机制
-
病毒入侵宿主程序的基本方式有两种: 替代方式和链接方式。
-
病毒的宿主程序可分为两类: 操作系统和应用程序。
-
交叉感染
-
寄生感染
- 插入感染
- 逆插入感染
-
没有入口点的感染
-
零长度感染
-
-
计算机病毒的触发机制
-
计算机病毒的破坏机制
典型的计算机病毒
DOS病毒
Win32 PE病毒
宏病毒
脚本病毒
HTML病毒
蠕虫
病毒的隐藏技术
隐藏技术主要有:
- 反跟踪技术
- 避开修改中断向量
- 请求在内存中的合法身份
- 维持宿主程序的外部特性
- 不使用明显的感染标志
花指令
病毒的多态
多态技术中的密钥和解密代码都变化多端,多态技术将对解密代码进行等价指令替换、寄存器替换、插入垃圾指令或者随机调换指令的前后位置(有些指令的前后位置调换之后不影响代码功能)等变化,以产生功能相同但是代码截然不同的解密代码
多态引擎的组成:
- 指令位置变换模块
- 寄存器变换模块
- 指令扩展模块
- 指令收缩模块
- 等价指令替换模块
- 无用指令随机插入
- 垃圾指令插
↩︎
木马
一个典型的特洛伊木马(程序)通常具有以下四个特点:
- 有效性
- 隐蔽性
- 顽固性
- 易植入性
此外,木马还具有以下辅助型特点:
- 自动运行
- 欺骗性
- 自动恢复
- 功能的特殊
木马的实现原理与攻击步骤
-
木马实现原理
-
本质上说,木马大多都是网络客户/服务(Client/Server)程序的组合。常由一个攻击者控制的客户端程序和一个运行在被控计算机端的服务端程序组成
-
当攻击者要利用“木马”进行网络入侵,一般都需完成如下环节:
- 向目标主机植入木马
- 启动和隐藏木马
- 服务器端(目标主机)和客户端建立连接
- 进行远程控制
-
-
植入技术
-
主动植入
-
本地安装
-
远程安装
- 利用系统自身漏洞植入
- 利用第三方软件漏洞植入
-
-
被动植入
- 网页浏览植入
- 利用电子邮件植入
- 利用网络下载植入
- 利用即时通工具植入
- 与其它程序捆绑
- 利用移动存储设备植入
-
-
自动加载技术
-
在Windows系统中木马程序的自动加载技术主要有:
- 修改系统文件
- 修改系统注册表
- 添加系统服务
- 修改文件打开关联属性
- 修改任务计划
- 修改组策略
- 利用系统自动运行的程序
- 修改启动文件夹
- 替换系统DLL
-
-
隐藏技术
- 隐蔽性是木马程序与其它程序的重要区别
- 伪隐藏、真隐藏
- 设置窗口不可见 (从任务栏中隐藏)
- 把木马程序注册为服务 (从进程列表中隐藏)
- 欺骗查看进程的函数 (从进程列表中隐藏)
- 使用可变的高端口 (端口隐藏技术)
- 使用系统服务端口 (端口隐藏技术)
- 替换系统驱动或系统DLL (真隐藏技术)
- 动态嵌入技术 (真隐藏技术)
-
连接技术
- 反弹窗口的连接技术:更容易通过防火墙
-
监控技术
木马的远程监控功能概括起来有以下几点:
- 获取目标机器信息
- 记录用户事件
- 远程操作
木马的防御技术
-
木马的检测
- 端口扫描和连接检查
- 检查系统进程
- 检查ini文件、注册表和服务
- 监视网络通讯
-
木马的清除与善后
-
木马的防范
- 及时修补漏洞,安装补丁
- 运行实时监控程序
- 培养风险意识,不使用来历不明的软件
- 即时发现,即时清除
木马的发展趋势
- 跨平台
- 模块化设计
- 无连接木马
- 主动植入
- 木马与病毒的融合
↩︎
蠕虫
它与其他的病毒相比,具有传染的主动性
计算机病毒是一段代码,能把自身加到其它程序包括操作系统上。它不能独立运行,需要由它的宿主程序运行来激活它”。
Eugene H. Spafford对蠕虫的定义:“计算机蠕虫可以独立运行,并能把自身的一个包含所有功能的版本传播到另外的计算机上”
↩︎
模糊测试
模糊测试:在很多随机的、不正常的输入上运行程序,找出程序对这些输入进行响应时的错误行为(如崩溃、挂起)
黑盒 fuzzing
基于突变(mutation)的 fuzzing
基于生成的 Fuzzing
基于覆盖(Coverage)的Fuzzing
AFL:
白盒Fuzzing
↩︎软件保护技术 ↩︎
代码混淆
目标:阻止对软件实施非授权的逆向分析
核心方法: 语义保留的程序变换
代码混淆的可能性
借鉴密码学算法的安全模型。目标:可证明安全性
然而,“虚拟黑盒”式的混淆器真的可以实现吗?
研究结论: 不可能
核心因素: 程序执行与Oracle访问有着本质区别
- 程序是对函数的简明描述
- Oracle访问只给出函数的输入-输出映射关系
- 函数的功能往往无法通过Oracle访问的方式予以精确学习
代码混淆的实际能力
- 做不到: 让程序的执行逻辑变得不可知
- 做得到: 使程序的执行逻辑变得难以理
代码混淆的方法
不透明谓词( opaque predicate)
较强的构造方法:基于3SAT问题 通过3SAT证明支配集是NPC问题 | 骑士的个人主页 (samjjx.github.io)
- 问题:证明显示不透明谓词在计算上是不安全的
条件分支混淆
方法:
-
单向函数
-
利用分析技术弱点
-
利用神经网络
软件防篡改
目标:
- 使得软件的内部逻辑无法被篡改
- 当篡改发生时,完成自我诊断/修复
意义: 阻止对软件的破解
方法:
-
内省自检(introspection)
-
Oblivious Hashing 未察觉的哈希
新场景:对抗app重打包
↩︎
软件混淆
软件胎记
胎记”的实际含义
- 一类对象的本质特征
- 与生俱来
- 独一无二
软件胎记的广义安全价值
- 反代码剽窃(未经授权使用共享库,或违反协议使用开源代码)
- 检测恶意代码(特别是经过各种伪装保护的)
- 检测移动app的重包装
构造形式分类
- 静态/动态(取决于胎记所依赖的特征类型)
静态构造举例:基于JAVA的栈行为模式
动态构造1:基于执行路
动态构造2:基于程序内的系统调用
不懂呀
软件水印
目标:在软件中嵌入用于标识其版权归属的秘密信息
软件水印的应用形式
- 反盗版 – 通过声明版权
- 反盗版 – 通过追溯盗版母盘的来源
软件水印的形式分类
- 静态/动态(取决于水印的构造方式)
静态构造举例:基本块重排序
静态构造举例:寄存器占用重分配
传统动态构造1:基于动态生成的图对象
传统动态构造2:基于执行路径上的分支行为
传统动态构造3:基于多线程的同步
传统动态构造的问题1:
- 与主程序的关联性很弱
- 往往具有显著的模式/特征
- 很难予以隐藏或伪装
一个特别的设计:基于抽象解释的水印
- 在正常维度上,水印组件服务于载体软件的原本功能
- 在预设的秘密维度上,水印组件展示出隐藏的信息
改进动态构造1:利用返回导向编程
改进动态构造2:利用代码混淆
传统动态构造的问题2:
改进动态构造3:利用神经网络
软件水印仍然存在的不足之处
- 没有在真正意义上实现隐蔽性
- 缺乏有效的定性/定量评估标准(特别是隐蔽性这一安全度量)
- 数据嵌入率很差
- 没有解决工业化、自动化实施的问题
↩︎
九、Web 安全
输入验证: SQL注入攻击
看PPT吧
脆弱性: 任意编程语言实现的连接到SQL数据库的应用程序都可能存在
输入验证: 跨站脚本 (XSS, Cross‐Site Scripting)
看PPT吧
脆弱性:用户输入, 可能包含可执行内容 (JavaScript, VBscript, ActiveX, …)被弹回到一个web页面
↩︎
输入验证: SQL注入攻击
看PPT吧
脆弱性: 任意编程语言实现的连接到SQL数据库的应用程序都可能存在
↩︎
输入验证: 跨站脚本 (XSS, Cross‐Site Scripting)
看PPT吧
脆弱性:用户输入, 可能包含可执行内容 (JavaScript, VBscript, ActiveX, …)被弹回到一个web页面
↩︎
代码重用攻击: Return to libc
危险库函数如
system()
攻击者构造合适的参数(在栈上, 返回指令指针的上方)
- 在x64架构上,还需要更多的工作:设置参数传递寄存器的值
函数返回,库函数得到执行
- 例如:
execve(“/bin/sh”)
甚至可以链接两个库函数调用
具体地
-
攻击者用一个溢出填充buffer:
- 更改栈上保存的ebp为一个合适地址
- 更改返回指令指针为一个欲执行的库函数的地址
- 写一个占位符值(库函数会认为其是返回地址,如果想利用它调用第二个库函数, 应写入第二个库函数的地址)
- 写一个或多个要传递给此库函数的参数
-
当被攻击的函数返回时, 恢复(更改过的)ebp, 然后pop更改后的返回地址到eip, 从而开始执行库函数代码
-
因为库函数相信它已被调用, 故会将栈顶当前值(占位符)作为它自己栈帧的返回指令指针, 之上是参数
-
最终会在占位符位置的下方创建起一个新的栈帧 (对应于库函数的执行)
-
根据库函数参数类型以及库函数对参数的解释方式, 攻击者可能需要准确地知道参数地址以做溢出写
↩︎
木马的实现原理与攻击步骤
-
木马实现原理
-
本质上说,木马大多都是网络客户/服务(Client/Server)程序的组合。常由一个攻击者控制的客户端程序和一个运行在被控计算机端的服务端程序组成
-
当攻击者要利用“木马”进行网络入侵,一般都需完成如下环节:
- 向目标主机植入木马
- 启动和隐藏木马
- 服务器端(目标主机)和客户端建立连接
- 进行远程控制
-
-
植入技术
-
主动植入
-
本地安装
-
远程安装
- 利用系统自身漏洞植入
- 利用第三方软件漏洞植入
-
-
被动植入
- 网页浏览植入
- 利用电子邮件植入
- 利用网络下载植入
- 利用即时通工具植入
- 与其它程序捆绑
- 利用移动存储设备植入
-
-
自动加载技术
-
在Windows系统中木马程序的自动加载技术主要有:
- 修改系统文件
- 修改系统注册表
- 添加系统服务
- 修改文件打开关联属性
- 修改任务计划
- 修改组策略
- 利用系统自动运行的程序
- 修改启动文件夹
- 替换系统DLL
-
-
隐藏技术
- 隐蔽性是木马程序与其它程序的重要区别
- 伪隐藏、真隐藏
- 设置窗口不可见 (从任务栏中隐藏)
- 把木马程序注册为服务 (从进程列表中隐藏)
- 欺骗查看进程的函数 (从进程列表中隐藏)
- 使用可变的高端口 (端口隐藏技术)
- 使用系统服务端口 (端口隐藏技术)
- 替换系统驱动或系统DLL (真隐藏技术)
- 动态嵌入技术 (真隐藏技术)
-
连接技术
- 反弹窗口的连接技术:更容易通过防火墙
-
监控技术
木马的远程监控功能概括起来有以下几点:
- 获取目标机器信息
- 记录用户事件
- 远程操作
↩︎
-
计算机病毒的分类
我们针对狭义上的病毒,按照不同的标准对它进行分类。
-
按照计算机病毒攻击的对象或系统平台分类
- 攻击DOS系统的病毒
- 攻击WINDOWS系统的病毒
- 攻击UNIX 系统的病毒
- 攻击OS/2系统的病毒
- 其它操作系统上的病毒:如手机病毒
-
按照计算病毒的攻击目标机类型分类:
- 攻击微型计算机的病毒
- 攻击小型计算机的病毒
- 攻击工作站的病毒
-
按照计算机病毒的链接方式分类
- 源码型病毒
- 嵌入型病毒
- 外壳型病毒:包围在主程序的四周
- 操作系统型病毒
-
按照计算机病毒的破坏情况分类
- 良性病毒:是不包含有对计算机系统产生直接破坏作用的代码的计算机病毒。
- 恶性病毒:指在代码中包含有损伤和破坏计算机系统的操作
-
按传播媒介来分类
- 单机病毒:单机病毒的载体是磁盘或光盘。常见的是通过从软盘传入硬盘,感染系统后,再传染其它软盘。软盘又感染其它系统。
- 网络病毒:网络为病毒提供了最好的传播途径。网络病毒利用计算机网络的协议或命令以及Email等进行传播,常见的是通过QQ、 BBS、Email、 FTP、 Web等传播
计算机病毒的命名
虽然每个反病毒公司的命名规则都不太一样,但大体都是采用一个统一的命名方法来命名的。一般格式为: <病毒前缀>.<病毒名>.<病毒后缀>
↩︎
-
病毒的隐藏技术
隐藏技术主要有:
- 反跟踪技术
- 避开修改中断向量
- 请求在内存中的合法身份
- 维持宿主程序的外部特性
- 不使用明显的感染标志
病毒的多态
多态技术中的密钥和解密代码都变化多端,多态技术将对解密代码进行等价指令替换、寄存器替换、插入垃圾指令或者随机调换指令的前后位置(有些指令的前后位置调换之后不影响代码功能)等变化,以产生功能相同但是代码截然不同的解密代码
多态引擎的组成:
- 指令位置变换模块
- 寄存器变换模块
- 指令扩展模块
- 指令收缩模块
- 等价指令替换模块
- 无用指令随机插入
- 垃圾指令插
↩︎
Man-At-The-End 攻击 (MATE)
攻击者: 位于终端,对终端计算资源有最高控制权限
攻击对象:安装在受控终端上的软件程序
攻击目的: 获悉、篡改软件的内部逻辑
↩︎
x86 Linux 系统的线性地址空间分层
- 最大 3G ?