20199324 2019-2020-2 《网络攻防实践》第10周作业
作业说明
- 这个作业属于哪个课程:课程链接
- 这个作业的要求在哪里:作业要求链接
- 我在这个课程的目标是 :学习网络攻防相关技术并进行实践
- 这个作业在哪个具体方面帮助我实现目标:学习缓冲区溢出漏洞和Shellcode的相关知识
作业正文
1.实践内容+知识点总结
软件安全概述
- 安全漏洞:包括软件安全漏洞(被攻击者利用并导致危害的安全缺陷),还包括硬件、个人与组织管理中存在的、能够被攻击者利用来破坏安全策略的弱点。软件安全漏洞是目前最常见,也是影响范围最大的安全漏洞类型。
- 软件安全的困境(三要素):
- 复杂性:软件规模膨胀更快更复杂,意味着软件的bug越来越多,虽然大多数bug并不会造成安全问题,或者无法被攻击者所利用,但是只要攻击者能够从中发现少数几个可利用的安全漏洞,就可以利用这些安全漏洞来危害软件的使用者。
- 可扩展性:难以阻止攻击者和恶意代码以不可预测的扩展方式来入侵软件和系统;分析可扩展性软件的安全性要比分析一个完全不能被更改的软件要困难得多。
- 连通性:高强度的连通性使一个小小的软件缺陷就可能造成巨大损失:蠕虫的大规模传播就验证了连通性对软件安全的影响作用;使得网络攻击能够引发现实世界中的故障,如电话网故障、电力系统遭遇攻击造成大规模停电事故。
- 软件安全漏洞类型:
- 内存安全违规类:
- 定义:是在软件开发过程中在处理RAM内存访问时所引入的安全缺陷。
- 类型:如缓冲区溢出漏洞(是最基础的内存安全问题)和Double Free、Use-after-Free等不安全指针(指在在计算机程序中存在的并没有指向适当类型对象的非法指针)问题。
- 输入验证类:
- 定义:指软件程序在对用户输入进行数据验证存在的错误。有保证输入数据的正确性、合法性和安全性。从而导致可能被恶意攻击与利用。
- 类型:包含格式化字符串、SQL注入、代码注入、远程文件包含、目录遍历、XSS、HTTP Header注入、HTTP响应分割错误等多种安全漏洞技术形式。
- 竞争条件类:
- 定义:是系统或进程中一类比较特殊的错误,通常在涉及多进程或者多线程处理的程序中出现,是指处理程序的输出或者结果无法预测,并且依赖于其他进程事件发生的次序或时间时导致的错误。
- 类型:Time-of-check-to-time-of-use(TOCTTOU)类漏洞;符号链接竞争问题。
- 权限混淆与提升类:
- 定义:是指计算机程序由于自身编程疏忽或被第三方欺骗从而滥用其权限,或赋予第三方不该给予的权限。
- 类型:具体技术形式有Web应用程序中的跨站请求伪造(Cross-Site Request Forgery,CSRF)、Clickjacking、FTP反弹攻击、权限提升、“越狱”(jailbreak)等。
- 内存安全违规类:
缓冲区溢出基础概念
-
基本概念:是计算机程序中存在的一类内存安全违规类漏洞,再算计程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。常见于C/C++语言程序中memcpy()、strcpy()等内存与字符串复制函数的引用位置(这些函数并不检查内存越界问题,程序员缺乏安全编程意识、经验与技巧)。
-
根本原因:现在计算机系统的基础架构——冯·诺依曼体系存在本质的安全缺陷,采用了“存储程序”的原理,计算机程序的数据和指令在同一内存中进行存储,没有严格的分离。使得攻击者可以将输入的输入通过利用缓冲区溢出漏洞,入股程序在内存空间中与数据区相邻存储的关键指令,从而达到程序执行恶意注入指令的攻击目的。
-
背景知识:
- 编译器与调试器的使用:
- 使用C/C++等高级编程语言编写的源码,需要通过编译器(Compiler)和连接器(Linker)才能生成可直接在操作系统平台上运行的可执行程序代码。调试器(Debugger)是开发人员在运行时刻调试与分析程序行为的基本工具。
- 对于C/C++编程语言最著名的编译与连接器是
GCC
。GCC
最基本的用法是执行gcc –c test.c
命令进行源码编译,生成test.o
;执行gcc –o test test.o
进行连接,生成test可执行程序,可以使用gcc test.c –o test
同时完成编译和连接过程。 - 类UNIX平台上进行程序的调试使用
GDB
调试器。GDB提供程序断点管理、执行控制、信息查看等多种类型的功能指令。
- 汇编语言基础知识:
- 汇编语言,尤其是IA32(Intel 32位)架构下的汇编语言,汇编语言是理解软件安全漏洞机理,掌握软件渗透攻击代码技术的底层基础。
- 从应用的角度将寄存器分为以下4类:
- 通用寄存器:如eax、ebx、ecx、edx等。主要用于普通的算术运算,保存数据、地址、偏移量、计数值等;
- 段寄存器:在IA32构架中是16位,用作段基址寄存器;
- 控制寄存器:用来控制处理器的执行流程,其中最关键的是指令指针eip,它保存下一条即将执行的机器指令的地址;
- 其他寄存器:“扩展标志”eflags寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。
- IA32架构中的关键寄存器及功能:
- 在IA32构架汇编语言中,分为Intel(DOS/Windows平台)和AT&T(类UNIX平台)两种汇编格式。Intel汇编格式与AT&T汇编格式:
- 进程内存管理:
- Linux系统进程内存空间布局:
- 3GB(0xc0000000)以下为用户态空间;3GB-4GB为内核态空间;操作系统将可执行程序加载到新创建的内存空间中,程序一般包含
.text
(包含程序指令,在内存中只被映射为只读)、.bss
(主要包含未经初始化的数据,被映射到可写的内存空间中)和.data
(主要包含静态初始化数据,被映射到可写的内存空间中)三种类型的段。 - 程序不能正确区分指令和数据,所以当通过修改内存空间中影响程序执行逻辑的铭感位置,并将恶意数据作为指令交给处理器时,仍然会执行这些“指令”,正是冯·诺依曼体系的本质缺陷和软件中存在的安全漏洞,才使得破解目标程序控制其执行流程成为可能。
- 3GB(0xc0000000)以下为用户态空间;3GB-4GB为内核态空间;操作系统将可执行程序加载到新创建的内存空间中,程序一般包含
- Windows系统进程内存空间布局:
- 2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象;0GB-2GB为用户态地址空间,高地址段映射大量程序共用系统DLL;在1GB地址位置装载一些进程本身引用的DLL文件,可执行代码段从0x400000开始,同样有栈和堆存储各进程的执行数据。
- Linux系统进程内存空间布局:
- 函数调用过程:
- 栈溢出攻击就是针对函数调用过程中返冋地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转至攻击者指定位置执行恶意代码的目的;
- 程序进行函数调用过程有如下三个步骤:
- 调用(call):调用者将函数调用参数、函数调用下一条指令的返回地址压栈,并跳转至被调用函数入口地址;
- 序言(prologue):被调用函数开始执行首先会进 入序言阶段,将对调用函数的栈基址进行压栈保存,并创建自身函数的栈结构,具体包括将ebp寄存器赋值为当前栈基址,为本地函数局部变量分配栈地址空间,更新esp寄存器为当前栈顶指针等;
- 返回(return):被调用函数执行完功能将指令控制权返回给调用者之前,会进行返回阶段的操作,通常执行
leave
和ret
指令,即恢复调用者的栈顶与栈底指针,并将之前压栈的返回地址装载至指令寄存器eip中,继续执行调用者在函数调用之后的下一条指令。
- 编译器与调试器的使用:
-
攻击原理:
- 缓冲区漏洞根据缓冲区在进程内存空间中的位置不同,分为:
- 栈溢出:指存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息(通常是返回地址),从而导致程序流程的改变。
- 堆溢出:存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题。
- 内核溢出:存在于内核模块或程序中,是由于进程内存空间内核态中存储的缓冲区变量被溢出造成的。
- Linux系统可能会采取对抗缓冲区溢出的防范措施,需要先把这些措施取消后,才能重现基础的缓冲区溢出攻击过程:
- 取消“栈上数据不可执行”保护:
echo 0 > /proc/sys/kerne/exec-shield
; - 取消“地址空间随机化”保护:
echo 0 > /proc/sys/kernel/randomize_va_space
; - 编译时取消“/GS”保护:
加上gcc编译选项 –fno-stack-protecto
。
- 取消“栈上数据不可执行”保护:
- 缓冲区漏洞根据缓冲区在进程内存空间中的位置不同,分为:
Linux平台上的栈溢出与Shellcode
Linux平台栈溢出攻击技术
Linux平台中的栈溢出攻击按照攻击数据的构造方式不同,主要有NSR、RNS和RS三种模式。
-
NSR模式:主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造方式是一堆Nop指令(即空操作指令)之后填充Shellcode,再加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区。
-
RNS模式:一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode。
-
RS模式:在这种模式下能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,由于环境变量是位于Linux进程空间的栈底位置,因而不会受到各种变量内存分配与对齐因素的影响,其位置是固定的,可以通过如下公式进行计算:
ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)
-
Linux平台上的远程栈溢出与本地栈溢出:
- Linux平台上的远程栈溢出攻击原理与本地栈溢出一样,区别在于用户输入传递的途径不同,以及Shellcode的编写方式不同。
- 本地栈溢出攻击中的Shellcode主要包含提升至较当前运行用户权限更高的权限,并给出本地Shell访问;而远程栈溢出攻击的Shellcode需要将Shell访问与网络连接起来,给出一个远程的Shell访问。
Linux平台的Shellcode实现技术
-
Shellcode是一段机器指令,对于我们通常接触的IA32构架平台,Shellcode就是符合Intel 32位指令规范的一串CPU指令,被用于溢出之后改变系统正常流程,转而执行Shellcode以完成渗透测试者的攻击目的,通常是为他提供一个访问系统的本地或远程命令行访问(即shell)。
- 在Linux操作系统中,程序通过“int 0x80”软中断来执行系统调用;
- 在Windows操作系统中,则通过核心DLL中提供的API接口来完成系统调用。
-
按照在本地溢出攻击和远程溢出攻击使用场景的不同,linux操作系统中的 Shellcode的实现与编写机制分为本地Shellcode和远程Shellcode。
-
Linux本地Shellcode实现机制:Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。
- linux系统本地Shellcode的生产过程:
- 先用高级编程语言,通常用C,来编写Shellcode程序;
- 编译并反汇编调试这个Shellcode程序;
- 从汇编语言代码级别分析程序执行流程;
- 整理生成的汇编代码,尽量减小它的体积并使它可注入,并可通过嵌入C语言进行运行测试和调试;
- 提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组。
- 在Linux本地Shellcode中,往往还会在运行
execve()
启动shell之前,调用setreuid(0)
将程序运行权限提升至Root用户,这样才能利用本地溢出攻击来提升权限,在执行execve()
函数之后还需要执行exit()
函数,从而在溢出攻击之后能够使程序正常退出。
- linux系统本地Shellcode的生产过程:
-
linux远程Shellcode实现机制:
- linux系统上的远程Shellcode的实现原理与本地Shellcode完全一致,也是通过执行一系列的系统调用来完成指定的功能。
- 实现方法步骤:首先给出高级语言的功能代码实现,然后通过反汇编调试编译后的二进制程序,提取、优化和整理所获得的汇编代码,并最终产生opcode二进制指令代码
- Linux远程Shellcode需要让攻击目标程序创建socket监听指定的端口等待客户端连接,启动一个命令行Shell,并将命令行的输入输出与socket绑定,这样攻击者就可以通过socket客户端连接目标程序所在初级的开放端口,与服务端socket建立起通信通道,并获得远程访问Shell。
- 在Linux系统中,
dup2()
函数能够将标准输入输出与socket的网络通信通道进行绑定,使得socket的远程输入连接至命令行标准输入,将命令行标准输出连接至远程网络输出,因而完成远程Shell的功能。
Windows平台上的栈溢出与Shellcode
-
Windows操作系统与Linux操作系统实现机制的不同(与成功攻击应用程序中栈溢出漏洞密切相关的):
- 对程序运行过程中废弃栈的处理方式差异:长须运行过程拥有大量的函数调用,当一个函数调用完成返回至调用者,执行下一条指令之前,会有恢复栈基和栈顶指针的过程,Windows平台会向废弃栈中写入一些随机的数据,而Linux则不进行任何处理。
- 进程内存空间的布局差异:Linux进程内存空间中栈底指针在0xc0000000之下,即一般栈中变量的位置都在0xbfff****地址附近,在这些地址中没有空字节。Windows平台的栈位置处于0x00FFFFFF以下的用户内存空间,一般为0x0012****地址附近,而这些内存地址的首字节均为0x00空字节。
- 系统功能调用的实现方式差异:Linux系统通过“int 80”中断处理来调用系统功能,而Windows系统是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用,对应用程序直接可见的是应用层中如kernel32.dll、User32.dll等系统动态链接库中导出的一些系统API接口函数。
-
Windows平台shellcode实现技术:
- 为使Windows中的Shellcode调用操作系统功能以完成攻击目标,并在期望注入的不同程序中乘车运行,需考虑:
- Shellcode找到所需的Win32 API函数,生成函数调用表;
- 为加载所需API函数库,Shellcode必须找出目标程序已加载函数地址;
- Shellcode需消除空字节,避免在字符串操作函数中被截断,编码以通过过滤;
- Shellcode需确保自己可以正常退出,使目标程序进程继续运行或终止;
- 在目标系统环境存在异常处理和安全防护机制时,shellcode还需进一步考虑如何对抗这些机制。
- Windows本地Shellcode:
- 在Windows平台上,典型的本地Shellcode同样也是启动一个命令行Shell,即
command.com
或cmd.exe
。 - 编写shellcode最简单的方式是使用硬编码的函数地址,如
system()
函数在WindowsXP特定版本的目标程序内存空间加载地址为0x77bf93c7
,在shellcode中使用Call 0x77bf93c7
指令,让EIP指令寄存器跳转至硬编码的函数入口地址执行。 - 为了确保Shellcode能正确调用所需函数,需要将所需函数的动态链接库庄仔倒目标程序内存,然后查询该函数的加载地址。Kernel32.dll中
LoadLibrary()
和GetProcAddress()
提供运行时加载指定动态链接库,及查询加载地址的功能。
- 在Windows平台上,典型的本地Shellcode同样也是启动一个命令行Shell,即
- Windows远程Shellcode:
- 实现步骤:
- 创建一个服务器端socket,并在指定的端口上监听;
- 通过
accept()
接受客户端的网络连接; - 创建子程序,运行
cmd.exe
,启动命令行; - 创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至
cmd.exe
的标准输入;然后输出管道将cmd.exe
的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端
- 实现步骤:
- 为使Windows中的Shellcode调用操作系统功能以完成攻击目标,并在期望注入的不同程序中乘车运行,需考虑:
堆溢出攻击
堆溢出是缓冲区溢出中第二种类型的攻击方式,由于堆中的内存分配与管理机制较栈更为复杂,不同操作系统平台的实现机制具有显著的差异,同时通过堆中的缓冲区溢出控制目标程序执行流程需要更加精妙的构造,因此堆溢出攻击难度较栈溢出更复杂。
-
函数指针改写:需要被溢出的缓冲区临近全局函数指针存储地址,且在其低地址方向。这符合这种变量布局的条件下,当向缓冲区填充数据时,如果没有边界判断和控制的话,那么缓冲区溢出之后就会自然地覆盖函数指针所在的内存区,从而改写函数指针的指向地址,攻击者只要能够将该函数指针指向恶意构造的Shellcode入口地址,在程序使用函数指针调用原先期望的函数时,就会转而执行Shellcode。
-
C++类对象虚函数表改写:C++类通过虚函数提供了一种Late binding运行过程绑定的机制,编译器为每个包含虚函数的类建立起虚函数表(vtable)、存放虚函数的地址,并在每个类对象的内存区中放入一个指向虚函数表的指针,通常称为虚函数指针vptr。
-
Linux下堆管理glibc库free()函数本身漏洞:通过glibc库来实现的,glibc 2.2.4及以下版本的堆内存管理算法是使用了Doug Lea的实现方式,称为dlmallo,而glibe 2.2.5及以上版本采用Wolfram Gloger的ptmalloc/ptmalloc2代码,ptmalloc代码是从dlmalloc代码移植过来的,主要目的是增加对多线程环境的支持,同时进一步优化了内存分配和回收的算法。
-
缓冲区溢出攻击的防御技术:
- 尝试杜绝缓冲区溢出的防御技术:解决缓冲区溢出攻击最根本的方法是编写正确的、不存在缓冲区溢出安全漏洞的软件代码,但由于C/C++语言作为效率优先的语言,很容易就会出现缓冲区溢出;
- 允许溢出但不让程序改变运行流程的防御技术:允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击。
- 无法让攻代码执行的防御技术:试解决冯·诺依曼体系的本质缺陷,通过堆栈不可执行限制来防御缓冲区溢出攻击。
2.学习中遇到的问题及解决
- 问题:课本中有许多汇编的部分看起来比较费劲
- 解决方案:不会就百度,基本上都可以找到,但是理解起来还是。。。
3.实践总结
本章的学习涉及缓冲区溢出漏洞和Shellcode的相关知识,虽然取消了实践部分,但是看课本的过程中还是发现了很多不懂的地方,尤其是涉及到的汇编部分,看起来很吃力,仅仅停留在简单的基础理论知识上,还是没有深入理解。