返回顶部

水水水的博客

20199111 2019-2020-2 《网络攻防实践》第十周作业

20199111 2019-2020-2 《网络攻防实践》第十周作业

1.实践内容

1.1 软件安全概述

软件安全漏洞

  • 可以被攻击者利用并导致危害的安全缺陷被称为软件安全漏洞

  • 不限于软件安全漏洞,还包括硬件、个人与组织管理中存在的、能够被攻击者利用来破坏安全策略的弱点

软件安全困境

造成软件安全困境的三因素

  • 复杂性:现代软件已经非常复杂,且向着更复杂发展,越发庞大的规模也代表着更多的bug和漏洞等

  • 可扩展性:现代软件为了支持更优化的软件架构和更好的用户感受,会提供扩展和交互渠道,而攻击者可能会用难以预测的方式利用这些渠道进行攻击

  • 连通性:高度的连通性使得很小的软件缺陷也能影响巨大的范围

软件安全漏洞类型

  • 内存安全违规类:是指在处理RAM内存访问时引入的缺陷,主要出现在C/C++等编程语言所编写的软件程序中

    • 缓冲区溢出

    • 不安全指针:指在计算机程序中存在的并没有指向适当类型对象的非法指针。对其进行引用时导致内存访问错误,包括堆内存释放例程缺陷、两次释放、释放内存被重现引用等

  • 输入验证类:是指没有保证用户输入的正确性、合法性和安全性造成的漏洞

    • XSS、SQL注入、远程文件包含、HTTPHeader注入、HTTP相应分割错误等Web应用程序安全漏洞

    • 通过格式化字符串函数所提供的内存任意读/写能力,攻击者可以对栈中的函数返回地址、库函数地址等关键数据进行修改

  • 竞争条件类:是指处理进程的输出或结果无法预测,并依赖于其他进程导致的错误。常见于多进程、多线程程序

  • 限混淆与提升类:是指计算机程序由于自身编程疏忽或被第三方欺骗,从而滥用其权限,或赋予第三方不该给予的权限。权限混淆与提升类漏洞的具体技术形式主要有Web应用程序中的跨站请求伪造、Clickjacking、FTP反弹攻击、权限提升、越狱等

1.2 缓冲区溢出基础概念

缓冲区是一块连续的计算机内存区域,可保存相同数据类型的多个实例。缓冲区可以是堆栈(自动变量)、堆(动态内存)和静态数据区(全局或静态)。在C/C++语言中,通常使用字符数组和malloc/new之类内存分配函数实现缓冲区。溢出指数据被添加到分配给该缓冲区的内存块之外。缓冲区溢出是最常见的程序缺陷。

编译器与调试器的使用

  • C/C++等高级编程语言编写的源码,需要通过编译器和连接器才能生成可直接在操作系统平台上运行的可执行程序代码

  • 对于最常用的C/C++编程语言,最著名的编译与连接器是GCC。类UNIX平台上进行程序的调试经常使用GDB调试器

  • Windows常用的集成开发环境是Visual Studio、VS.net等。Windows常用的二进制可执行文件的调试器WinDbg、OllyObg、IDA Pro

汇编语言基础知识

  • 一般我们无法得到被分析软件的源代码,因此只能在反汇编技术的支持下,通过阅读和理解汇编代码,来对软件安全漏洞的机理进行分析

  • 从应用的角度一般将寄存器分为4类,即通用寄存器、段寄存器、控制寄存器和其他寄存器

IA32架构中的关键寄存器及功能

进程内存管理

  • “栈”是一种后进先出的数据结构,其地址空间从高地址向低地址增长,程序运行的环境变量env、运行参数argv、运行参数数量argc都被放置在“栈”底,然后是主函数及调用“栈”中各个函数的临时保存信息,栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。这种受限的运算使栈拥有“先进后出”的特性

栈的结构

  • “堆”是一种先进先出的数据结构,用于保存程序动态分配的数据和变量,其地址空间从低地址往高地址增长,与“栈”正好相反

  • 程序执行时,就会按照程序逻辑执行.text中的指令,并在“堆”和“栈”中保存和读取数据,然而程序并不能正确地区分指令和数据,所以当我们修改内存空间中影响程序执行逻辑的敏感位置,并将恶意数据作为指令提交给处理器时,它仍会执行

函数调用过程

  • 调用:将参数和下一条指令地址入栈并跳转到函数入口地址

  • 序言:对调用函数的栈基址入栈保存,创建函数自身栈结构等

  • 返回:恢复调用者栈顶栈底指针,执行下一条指令

缓冲区溢出原理

  • 由于C/C++语言没有数组越界检查机制,当向局部数组缓冲区里写入的数据超过为其分配的大小时,就会发生缓冲区溢出。攻击者可利用缓冲区溢出来窜改进程运行时栈,从而改变程序正常流向,轻则导致程序崩溃,重则系统特权被窃取。

  • 若将长度为16字节的字符串赋给acArrBuf数组,则系统会从acArrBuf[0]开始向高地址填充栈空间,导致覆盖EBP值和函数返回地址。若攻击者用一个有意义的地址(否则会出现段错误)覆盖返回地址的内容,函数返回时就会去执行该地址处事先安排好的攻击代码

1.3 Linux平台上的栈溢出与shellcode

栈溢出攻击技术

  • 按照攻击数据的构造方式不同,主要有NSR、RNS和RS三种模式。其中NSR和RNS模式适用于本地缓冲区溢出和远程栈溢出攻击,而RS模式只能用于本地

  • NSR模式:NSR模式主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造一堆Nop指令填充Shellcode,加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区

  • RNS模式:概括来说,RNS模式一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode

  • RS模式:RS模式下能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,因其位置是固定的,可以通过公式计算ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)

shellcode实现技术

  • 先用高级编程语言,通常用C,来编写Shellcode程序

  • 编译并反汇编调试这个Shellcode程序

  • 从汇编语言代码级别分析程序执行流程

  • 整理生成的汇编代码,尽量减小它的体积并使它可注入,并可通过嵌入C语言进行运行测试和调试

  • 提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组

C语言实现的Linux系统本地shell(使用execve()函数启动/bin/sh命令)

#include <stdio.h>
int main ( int argc, char * argv[] )
{
    char * name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve( name[0], name, NULL );
}

C语言代码对应的汇编代码为

int main()
{
   _asm_
   (
      xor    %edx,%edx// 原语句为(mov    $0x0,%edx),防止字符串中出现0x0出现使得产生截断
      push   %edx
      push   $0x68732f6e
      push   $0x69622f2f
      mov    %esp,%ebx
      push   %edx
      push   %ebx
      mov    %esp,%ecx
      mov    $0xb,%eax
      int    $0x80
    )
}

通过查表将shellcode汇编代码转换为opcode二进制指令代码

31 d2   // xor %edx,%edx
52      // push %edx
68 6e 2f 73 68   // push $0x68732f6e
68 2f 2f 62 69   // push $0x69622f2f
89 e3   // mov %esp,%ebx
52      // push %edx
53      // push %ebx
89 e1   // mov %esp,%ecx
8d 42 0b         // lea 0xb(%edx),%eax
cd 80   //  int $0x80
  • 远程Shellcode实现机制:Linux平台上的远程Shellcode实现机制与本地Shellcode实现机制是一样的,通过系统调用完成指定功能。Linux远程Shellcode需要让攻击目标程序创建socket监听指定的端口等待客户端连接,启动一个命令行Shell,并将命令行的输入输出与socket绑定,这样攻击者就可以通过socket客户端连接目标程序所在主机的开放端口,与服务端socket建立起通信通道,并获得远程访问Shell

1.4 Windows平台上的栈溢出与shellcode

与Linux平台实现机制(与栈溢出相关)差异

  • 对程序运行过程中废弃栈的处理方式差异:Windows会向废弃栈中写入一些随机的数据,而Linux则不进行任何的处理

  • 进程内存空间的分布导致RNS模式不适用,Linux栈0xC0000000附近,这些地址中没有空字节,Windows栈在0x00FFFFFF以下的用户空间,这些地址首字节均为0x00空字节

  • 系统功能调用实现方式差异:Linux系统中通过“int 80”中断处理来调用系统功能,而Windows系统则是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用

shellcode实现技术

  • shellcode必须可以找到所需要的Windows32 API函数,并生成函数调用表

  • 为了能够使用这些API函数,shellcode必须找到目标程序已加载的函数地址

  • shellcode需考虑消除空字节,以免在字符串操作函数中被截断

  • shellcode需确保自己可以正常退出,并使原来的目标程序进程继续运行或终止

  • 在目标系统环境存在异常处理和安全防护机制时,shellcode需进一步考虑如何应对这些机制

C语言版本的shellcode程序

#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC)(LPTSTR);
typedef void (*MYPROC2)(int);
void main()
{
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        MYPROC2 ProcAdd2;
        char dllbuf[11]  = "msvcrt.dll";
        char sysbuf[7] = "system";
        char cmdbuf[16] = "command.com";
        char sysbuf2[5] = "exit";
        LibHandle = LoadLibrary(dllbuf); //加载msvcrt.dll动态链接库
        ProcAdd = (MYPROC)GetProcAddress(
		LibHandle, sysbuf);  //获取system()函数的加载入口地址,赋值给ProcAdd函数指针
        (ProcAdd) (cmdbuf);

        ProcAdd2 = (MYPROC2) GetProcAddress(
			LibHandle, sysbuf2);
		(ProcAdd2)(0);
}

C语言代码对应的汇编代码为

#include <windows.h>
#include <winbase.h>
void main()
{
	LoadLibrary("msvcrt.dll");
	__asm{
		mov esp,ebp    //把esp的内容赋值为ebp
		push ebp       //保存ebp,esp-4
		mov ebp,esp    //给ebp赋新值,作为局部变量的基指针
    xor edi,edi    //
    push edi       //压入0,esp-4
    sub esp,08h    //一共12个字符,用来放command.com
    mov byte ptr [ebp-0ch],63h  //c
    mov byte ptr [ebp-0bh],6fh  //o
    mov byte ptr [ebp-0ah],6dh  //m
    mov byte ptr [ebp-09h],6Dh  //m
    mov byte ptr [ebp-08h],61h  //a
    mov byte ptr [ebp-07h],6eh  //n
    mov byte ptr [ebp-06h],64h  //d
    mov byte ptr [ebp-05h],2Eh  //.
    mov byte ptr [ebp-04h],63h  //c
    mov byte ptr [ebp-03h],6fh  //o
    mov byte ptr [ebp-02h],6dh  //m,生成command.com
    lea eax,[ebp-0ch]
    push eax                    //串地址作为参数入栈
    mov eax, 0x77bf8044         //API入口地址,根据调试获取
    call eax                    //调用system
		
	}

}

Windows远程shellcode

  • 创建一个服务器端socket,并在指定的端口上监听

  • 通过accept()接受客户端的网络连接

  • 创建子程序,运行“cmd.exe”,启动命令行

  • 创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端

1.5 堆溢出攻击

堆溢出是缓冲区溢出中第二种类型的攻击方式,由于堆中的内存分配与管理机制较栈更复杂,不同操作系统平台的实现机制具有显著的差异。堆中没有可以直接覆盖的返回地址,因此堆溢出攻击比栈溢出更难

  • 函数指针改写:此种攻击方式要求被溢出的缓冲区临近全局函数指针存储地址,且在其低地址方向上。如果向缓冲区填充数据的时候,如果没有边界控制和判断的话,缓冲区溢出就会自然的覆盖函数指针所在的内存区,从而改写函数指针的指向地址,则程序在使用这个函数指针调用原先的期望函数的时候就会转而执行shellcode

  • C++类对象虚函数表改写:使用了虚函数机制的C++类,如果它的类成员变量中存在可被溢出的缓冲区,那么就可以进行堆溢出攻击,通过覆盖类对象的虚函数指针,使其指向一个特殊构造的虚函数表,从而转向执行攻击者恶意注入的指令

  • Linux下堆管理glibc库free()函数本身漏洞:攻击者可以通过精心构造unlinkme内存块进行free()函数堆溢出攻击

1.6 缓冲区溢出攻击的防御技术

  • 尝试杜绝溢出的防御技术:解决缓冲区溢出攻击最根本的方法是编写正确的、不存在缓冲区溢出安全漏洞的软件代码。例如,通过Fuzz测试寻找安全漏洞并修复,在编译器引入针对缓冲区的边界保护检查机制

  • 允许溢出但不让程序改变运行流程的防御技术:允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击

  • 无法让攻代码执行的防御技术:尝试解决冯·诺依曼体系的本质缺陷,通过堆栈不可执行限制来防御缓冲区溢出攻击。IA64、AMD64、Alpha 等新的 CPU 硬件体系框架都引入对基于硬件 NX 保护机制,从硬件上支持对特定内存页设置成不可执行,Windows XP SP2、Linux 内核 2.6 及以后版本都支持硬件 NX 保护机制,与橾作系统配合来提升系统的安全性。

2.学习中遇到的问题及解决

操作系统、数据结构的知识很陌生

3.实践总结

越学到后面需要的基础知识也越多

参考资料

posted @ 2020-05-05 17:20  于淼9111  阅读(233)  评论(0编辑  收藏  举报