Exploit编写教程1:栈溢出

本文为 Exploit编写教程 的学习笔记,原文请点击这里
本文仅作以防御为目的的技术总结,所有操作均在实验环境下进行,请勿用于非法行为,否则后果自负。
如有侵权烦请告知,我们会立即删除并致歉。谢谢。

要点提示:

  • 复现环境首选 WinXP,因为没有 地址随机化ASLR,可以减少复现难度。ASLR在 Vista以上版本才有。
  • 用到的工具有:python3\pwntools\windbg\x32dbg\msfvenom
  • 造成崩溃后查看EIP是否为目标地址,使用 d esp 查看栈中的数据。
  • 直接使EIP指向ESP不会有效果,要使EIP指向 jmp esp 指令的地址。
  • 查看软件加载的DLL所在的地址范围,再使用 s 1a800000 l 1f200000 ff e4 查询该指令所在的地址。ff e4jmp esp 的OPcode。
  • 使用 msfvenom 生成playload,选择合适的编码器

0x00 初识漏洞利用

2009年7月17日,昵称为“Crazy_Hacker”的人通过 packetstormsecurity.org 报告了 Easy RM to MP3 Conversion Utility 的一个漏洞,是在 WinXP SP2上验证的。漏洞报告包括一个PoC(proof of concept),但是无法在 WinXP SP3 虚拟机上验证。稍后又发布了另一个漏洞

如果直接复制 PoC 代码并运行,会发现并不起作用(如果幸运的话也会有效),所以可以试着理解构建漏洞利用的过程,以便修复漏洞利用程序,或者干脆从头开始构建自己的漏洞利用。

顺便说一句:除非你能快速对shellcode进行反汇编、阅读和理解,否则我绝不建议你直接运行别人写的漏洞利用,尤其是可执行文件的情况下。假如它只是为了在你电脑里开个后门而构建的呢?

问题是:漏洞利用编写者如何构建他们的漏洞利用?从检测可能的漏洞到构建实际有效的漏洞利用的过程是什么样的?如何使用漏洞信息来构建自己的漏洞利用?

自从我开博客以来,写一篇关于缓冲区溢出的基本教程就一直在我的“待办事项”清单上,但我从来没有真正花时间去做(或者干脆忘记了)。

当我今天看到漏洞报告并查看了漏洞利用时,我认为这个漏洞报告可以作为解释编写漏洞利用基础知识的完美示例————它干净、简单,并且允许我演示一些用于编写可稳定运行的基于栈的缓冲区溢出的技术。

所以也许现在是个好时机,尽管前面提到的漏洞报告已经包含了一个漏洞利用(不管是否有效),我仍然会以 [Easy RM to MP3 Conversion Utility] 中的漏洞为例,我们将一步一步地构建有效漏洞利用,无需复制任何内容。我们将从头开始构建它,这次让它在 WinXP SP3 上运行 😃

在我们继续之前,我先声明一件事:

本文档纯粹用于教育目的。

我不希望任何人使用此信息(或此博客上的任何信息)来实际入侵计算机或做其他非法事情。因此,对于其他人获取本文档的部分内容并将其用于非法目的的行为,我不承担任何责任。如果您不同意,则不允许您继续访问本网站,请立即关闭网页。

无论如何,通常能从漏洞报告中获得漏洞的基础信息。漏洞报告指出“Easy RM to MP3 Converter 版本 2.7.3.700 通用缓冲区溢出漏洞利用程序会创建恶意的 .m3u 文件”。换句话说,您可以创建一个恶意的 .m3u 文件,将其输入程序并触发漏洞。这些报告可能不是每次都非常具体,但在大多数情况下,您可以了解到如何构建崩溃或使应用程序行为异常。如果报告中没有任何信息,那么说明安全研究人员可能已经向应用程序供应商披露了漏洞,从而使供应商有时间修复漏洞,再或者只是想自己保留这个情报。

0x01 验证BUG

首先,验证应用程序是否在打开格式错误的 .m3u 文件时崩溃。

获取易受攻击版本的 Easy RM to MP3 的副本,并将其安装在运行 Windows XP 的计算机上。漏洞报告指出,该漏洞可在 XP SP2上运行,但我将用 XP SP3。

您可以在以下位置找到易受攻击的应用程序的副本exploit-db

在这里插入图片描述

快速旁注:您可以在以下位置找到旧版本的应用程序oldapps.comoldversion.com,或者通过查看exploit-db.com 上的漏洞利用(通常也有易受攻击的应用程序的本地副本)

我们将使用以下简单的 python3 脚本创建一个 crash.m3u 文件,它可以帮助我们发现有关该漏洞的更多信息:

filename = 'crash.m3u'
junk = b'\x41' * 10000
file = open(filename, "wb")
file.write(junk)
file.close()

运行 python 脚本将创建 crash.m3u 文件,其中填充了 10000 个 A(\x41 是 A 的十六进制表示),使用 Easy RM to MP3 打开这个 .m3u 文件。应用程序抛出一个错误,但看起来错误得到了正确处理并且应用程序没有崩溃。修改脚本以在文件中写入 20000 个 A 并重试,结果相同。(异常被正确处理,所以我们仍然无法覆盖任何有用的东西)。现在将脚本改为写入 30000 个 A,创建文件并在程序中打开它。

嘣——程序崩溃了。

好的,所以如果我们给它提供一个包含 20000 到 30000 A 的文件,应用程序就会崩溃。但是我们能用这个做什么呢?

0x02 验证BUG,并看看它是否有趣

在许多情况下,应用程序崩溃不会导致漏洞利用,但有时会。对于“漏洞利用”,我的意思是希望应用程序做一些它不打算做的事情——比如运行你自己的代码。使应用程序做一些不同的事情的最简单方法是控制其应用程序流程(并将其重定向到其他地方)。方法是控制指令指针寄存器——即程序计数器,它是一个 CPU 寄存器,指向需要执行的下一条指令所在位置的地址。

假设应用程序调用带有参数的函数。在转到函数之前,它将当前位置保存在指令指针中(因此它知道函数完成时返回的位置)。如果您可以修改此指针中的值,并将其指向包含您自己的代码段的内存位置,那么您可以更改应用程序流程并使其执行不同的操作(除了返回到原始位置)。控制流程后要执行的代码通常称为shellcode。因此,如果我们让应用程序运行我们的 shellcode,我们可以称其为有效利用。在大多数情况下,此指针由 EIP 代替。该寄存器大小为 4 个字节。因此,如果您可以修改这 4 个字节,您就控制了应用程序(以及运行应用程序的计算机)

0x03 前置理论

您只需要几个术语:

每个 Windows 应用程序都使用部分内存。进程内存包含 3 个主要组件:

  • 代码段(处理器执行的指令。EIP 跟踪下一条指令)
  • 数据段(变量、动态缓冲区)
  • 堆栈段(用于将数据或参数传递给函数,并用作变量的空间。栈从页面的虚拟内存的最末端开始(=栈底部)并向下增长(到较低的地址)。 PUSH 将一些内容添加到栈顶部,POP 将从栈中删除一项(4 字节)并将其放入寄存器中。

如果要直接访问栈内存,可以使用ESP(栈指针),它指向栈的顶部(即最低内存地址)。

  • PUSH 后,ESP 指向较低的内存地址(地址随着入栈的数据大小而递减,在地址/指针的情况下为 4 个字节)。减量通常发生在项目被放入栈之前(取决于具体实现,如果 ESP 已经指向栈中的下一个空闲位置,则减量发生在将数据放入栈之后)
  • POP 后,ESP 指向更高的地址(地址递增(在地址/指针的情况下增加 4 个字节))。从栈中删除项目后会发生增量。

当输入一个函数/子程序时,会创建一个栈帧。该栈帧将父过程的参数保存在一起,并用于将参数传递给子程序。栈的当前位置可以通过栈顶指针(ESP)访问,函数的当前基址包含在基址指针EBP(也即栈帧指针)中。

CPU的通用寄存器(Intel x86)是:

  • EAX : accumulator : 用于执行计算,用于存储函数调用的返回值。加、减、比较等基本操作均使用该通用寄存器
  • EBX : base(与EBP没有任何关系)。没有通用用途,可用于存储数据。
  • ECX : counter : 用于迭代。ECX 向下计数。
  • EDX : data : 这是 EAX 寄存器的扩展。它允许存储额外的数据以促进这些计算,从而允许进行更复杂的计算(乘法、除法)。
  • ESP :栈顶指针
  • EBP : 栈基指针
  • ESI :源索引:保存输入数据的位置
  • EDI :目标索引:指向存储数据操作结果的位置
  • EIP : 指令指针

0x04 进程内存

当应用程序在win32环境下启动时,会创建进程和分配虚拟内存。在一个32位进程中,地址范围是0x000000000xFFFFFFFF,其中从0x000000000x7FFFFFFF是用户空间,0x800000000xFFFFFFFF是内核空间。Windows系统使用平坦存储模式,CPU可以直接/顺序/线性的方式访问所有可用内存地址,而不必使用分段/分页方式。

内核空间内存只能由操作系统访问。

当一个进程被创建时,一个PEB(进程执行块)和TEB(线程环境块)被创建。

PEB包含所有与当前进程相关的用户地参数:

  • 主执行文件的位置
  • 指向加载器数据的指针(可用于列出所有可加载到进程中的DLL/模块)
  • 指向堆空间指针

TEB描述了线程状态,包括以下:

  • PEB在内存中的地址
  • 所属线程的栈的地址
  • 指向SHE链的第一个入口

进程中的每个线程都有一个TEB

Win32进程的内存图如下:

在这里插入图片描述

程序Image/DLL的代码段是只读的,仅包含应用程序代码,可以防止被修改。代码段的大小是固定的。

数据段用于存储全局变量和静态变量,用于初始化全局变量、字符串和其他常量。数据段是可写的,并且大小固定。

堆段用于存放其余的程序变量。它可以根据需要变大或变小。 堆中的所有内存都由内存分配器(和内存回收器)算法管理。这些算法保留了一个内存区域。堆会向高地址增长。

在DLL中,代码、导入表(DLL使用的函数列表,来自另一个DLL或应用程序)和导出表(它向其他DLL的应用程序提供的函数)是代码段的一部分

0x05 栈

栈是进程内存的一部分,是一个后进先出(Last in first out)的数据结构。操作系统为每个线程分配一个栈(当线程被创建时)。 当线程结束时,栈也会被清空。栈的大小在创建时就已经定义好了,不会改变。结合LIFO和它不需要复杂的管理结构/机制来得到管理的事实,栈读写速度很快,但大小有限。

LIFO意味着最近放置的数据(PUSH指令的结果)是第一个将被再次从栈中移除的。(通过POP指令)。
当一个栈被创建时,栈指针指向栈的顶部(栈上的最高地址)。当信息被PUSH入栈时,这个栈指针会递减(到一个较低的地址)。 因此,从本质上讲,栈会增长到一个更低的地址。

栈包含了局部变量、函数调用和其他不需要存储较长时间的信息。 随着更多的数据被添加到栈中(入栈),栈指针被递减,并指向一个较低的地址值。

每次函数被调用时,函数参数被入栈,以及寄存器的保存值(EBP、EIP)。 当一个函数返回时,EIP的保存值被从栈中取回并放回EIP中,因此可以恢复正常的应用流程。

让我们用几行简单的代码来演示这个行为。

#include <stdio.h>

void do_something(char *Buffer)
{
   
     char MyVar[128];
     strcpy(MyVar,Buffer);
}

int main (int argc, char **argv)
{
   
     do_something(argv[1]);
}

创建一个Win32的C语言命令行项目,粘贴以上代码进行编译,生成 stacktest.exe ,运行 stacktest.exe AAAA

这个应用程序接受一个参数 argv[1] ,并将该参数传递给函数 do_something()。 在该函数中,参数被复制到一个最大容量为128字节的局部变量中。所以如果参数的长度超过127字节(末尾加一个空字节来终止字符串),缓冲区可能会被溢出。

当函数 do_something()main() 中被调用时,会发生以下情况。

一个新的栈帧被创建,在“父”栈的顶部。栈顶指针(ESP)指向新创建的栈的最高地址。这就是 “栈顶”。

在这里插入图片描述

do_something()函数调用之前,先将指向参数的指针入栈。

在这里插入图片描述

接下来 do_something() 函数被调用。CALL指令首先把当前EIP入栈,这样当函数结束时就能知道应该返回到哪里,然后跳转到函数代码。

CALL指令之后的栈

在这里插入图片描述

PUSH指令后,ESP减少4字节,然后指向低地址

在这里插入图片描述

在调试器中,ESP指在 0013FF70,这个地址是已入栈的EIP(00401057),后面是参数的指针(指向 AAAA),在CALL指令完成之前,这个指针保存在栈上。

在这里插入图片描述

接下来,函数的 Prolog 开始执行,栈帧指针 EBP 保存在栈上,可以在函数返回时取回。保存EBP的指令是 push ebp,ESP再次减4字节。

在这里插入图片描述

push ebp 之后,当前ESP被放入EBP。此时,ESP和EBP都指向当前栈顶。从那时起,栈通常会被ESP(栈顶)和EBP(当前栈基指针)引用。这样,应用程序可以通过使用到EBP的偏移量来引用变量。

大多数函数以这个顺序开始: PUSH EBP; MOV EBP,ESP;

因此,如果您将4个字节入栈,ESP将减少4个字节,而EBP仍将停留在原来的位置。然后,您可以使用 EBP-0x4 引用这4个字节。

接下来,我们可以看到变量 MyVar(128字节) 的堆栈空间是如何声明/分配的。为了保存数据,在栈上分配了一些空间来保存这个变量中的数据。ESP减去一些字节。这个字节数很可能超过128个字节,因为这是由编译器决定的分配例程。你会看到一个 SUB ESP,84 指令。这样,这个变量就有了可用的空间。

在这里插入图片描述

函数的反汇编结果如下:

00401000 / 55            | push ebp                      | Prolog
00401001 | 8BEC          | mov ebp,esp                   | Prolog
00401003 | 81EC 84000000 | sub esp,84                    | 空间分配
00401009 | A1 04304100   | mov eax,dword ptr ds:[413004] |
0040100E | 33C5          | xor eax,ebp                   |
00401010 | 8945 FC       | mov dword ptr ss:[ebp-4],eax  | [ebp-4]:"AAAA"
00401013 | 8B45 08       | mov eax,dword ptr ss:[ebp+8]  |
00401016 | 50            | push eax                      |
00401017 | 8D8D 7CFFFFFF | lea ecx,dword ptr ss:[ebp-84] |
0040101D | 51            | push ecx                      | ecx:&"C:\\DOCUME~1\\a\\MYDOCU~1\\STACKT~1.EXE"
0040101E | E8 9D1A0000   | call <stacktest.sub_402AC0>   |
00401023 | 83C4 08       | add esp,8                     |
00401026 | 8B4D FC       | mov ecx,dword ptr ss:[ebp-4]  | [ebp-4]:"AAAA"
00401029 | 33CD          | xor ecx,ebp            
Exploit 编写系列教程 译序............................................................................................................................................2 Exploit 编写系列教程第一篇:栈溢出...............................................................................3 Exploit 编写系列教程第二篇:跳至 ShellCode............................................................25 Exploit 编写系列教程第三篇 a:基亍 SEH 的 Exploit.................................................54 Exploit 编写系列教程第三篇 b:基亍 SEH 的 Exploit&mdash;又一个实例........................77 Exploit 编写系列教程第四篇:编写 Metasploit Exploit.............................................83 Exploit 编写系列教程第五篇:利用调试器模块及插件加速 exploit 开发.................94 Exploit 编写系列教程第六篇:绕过 Cookie,SafeSeh,HW DEP 和 ASLR..............126 Exploit 编写系列教程第七篇:编写 Unicode Exploit................................................218 Exploit 编写系列教程第八篇:Win32 Egg Hunting..................................................256 Exploit 编写系列教程第九篇:Win32 Shellcode 编写入门......................................316 Exploit 编写系列教程第十篇:利用 ROP 绕过 DEP.....................................................432 附录 A:对《基亍栈的溢出》一文的补充.......................................................................509 附录 B:对《编写 unicode exploit》一文的补充........................................................511
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值