讨论缓冲区溢出问题,顺便散分

broadoceans 2004-01-03 01:32:11
利用缓冲区溢出漏洞进行攻击是目前黑客进行攻击的主要手段。
我们有必要讨论一下缓冲区溢出的原理,以及我们在程序设计中
怎样才能避免,希望大家畅所欲言。
为编写健壮的程序,我们应该主动预防缓冲区溢出漏洞,不给黑
客利用之进行攻击的机会。

另外,我现在变成一个红星了,顺便散分,感谢大家的支持。
...全文
262 28 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
28 条回复
切换为时间正序
请发表友善的回复…
发表回复
happlyman 2004-01-04
  • 打赏
  • 举报
回复
看看高手们的发言!
changlele 2004-01-04
  • 打赏
  • 举报
回复
-----------------------------
回复:bm1408(嗔!我心己乱,万物皆沉,唯伊也!独醉!)
可以我还是不明白啊呀
我太菜了吧!这个堆栈溢出后首先执行11-20字节的程序,为什么?

--------------------------------
数据和程序的区别你应该能明白吧,11-20字节是一段程序,虽然他也是以数据的方式
压入堆栈,但是这段程序一旦是溢出的时候写入的数据,这时这段代码已经获得机器的控制权,那么这段数据(程序)就是可以获得执行的程序,所以你想要达到什么目的就能达到什么目的。
怎么就解释不明白呢。真的搞不懂,可能说了你还是不明白。
zhaolaoxin 2004-01-04
  • 打赏
  • 举报
回复
学习
fengge8ylf 2004-01-04
  • 打赏
  • 举报
回复
先MAKE
GR 2004-01-04
  • 打赏
  • 举报
回复
说白了就是把ret给换了。怎么换。换什么都要遵从 windows的规则。
jackwuwei 2004-01-03
  • 打赏
  • 举报
回复
目前还没想那么多,以后注意了
nonocast 2004-01-03
  • 打赏
  • 举报
回复
上次看到过 程序春秋 上专门讨论过这个问题
分析得很彻底
不妨找来看看
其实就是越界
wangk 2004-01-03
  • 打赏
  • 举报
回复
回复人: ross33123() ( ) 信誉:110 2004-01-03 16:05:00 得分:0


我泼点凉水可以吗?大家别打我啊:)

我认为

缓冲区溢出是在老板不切实际地要求赶进度

程序员干活干得昏天黑地的时候犯的错误

像 Window 那么多漏洞

M$ 的程序员这么牛

正常情况下不会犯这种错误的

同意。
还有数组越界和非法指针的使用也可能造成溢出。
darkread 2004-01-03
  • 打赏
  • 举报
回复
连java都会,c#怎么就不会呢,缓冲区溢出,不只是堆栈溢出一种,有很多情况的
卖弄了,老外写过一本书,忘了叫什么(废话,呵呵)
haiwangstar 2004-01-03
  • 打赏
  • 举报
回复
我理解溢出漏洞只所以会发生,是因为C的编译器本身不对越界做检查,如果程序员在程序中自己不去检查,就有漏出的危险,比如调用strcpy时,如果不做检查,源串如大于目标串,就会溢出,不一定会覆盖了什么数据。而在C#中,我们的程序是处于托管环境中运行的,如果发生越界错误,不会通过编译,所以在C#中,不会现越界错误。不知我的理解是否正确。欢迎高手给出评论。
darkread 2004-01-03
  • 打赏
  • 举报
回复
现在我再回过头来分析一下。我们刚才使用字符’a’(0x61)作为文本文件的填充内容,以确定存在缓冲区溢出。由于EIP=0x61616161,当我们的程序访问试图访问该地址处的指令时,会因为是无效指令而导致系统出错。但如果所指向的地址存在可执行代码时又如何呢?例如装入内存的DLL代码等。哈哈,这样的话就会执行这些指令,从而可能做一些别人想像不到的事!;)

好了,到目前为止,我们已经能控制EIP的数值,也知道ESP指向的堆栈位置,和能够向堆栈写入任意数据。那么下一步做什么呢?当然是找到使系统执行我们的溢出代码的方法了。如果你看过ipxodi所著的文章《Windows系统下的堆栈溢出》,就会知道采用跳转指令(jmp esp)是最好不过的了。原因在这里就不再多讲,请大家仔细阅读《Windows系统下的堆栈溢出》就清楚了。正如前面分析过的,这是因为执行完ret指令后ESP正好能够指向我们的溢出代码!(……哦,找不到,我没分析过?在本文中查找单词”Great”吧,呵呵。)现在我们就要在应用程序的内存空间中找到含有”jmp esp”指令的地址。首先当然是确定这条指令的机器码了。怎么确定?这也要教?好吧,教就教吧。仅此一次,下不违例。;)其实方法很简单,按以下步骤就可以了。先在Visual C++中创建新的应用程序。(当然还是控制台程序,还是支持MFC,这是我的习惯。呵呵。)输入以下代码:

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
return 0;
__asm jmp esp
}
return nRetCode;
}

好了,然后在Visual C++环境中设置正确的调试断点。哪里?对了,在“return 0;”处。接着运行程序,使其在断点处暂停运行。现在(选择“view”菜单——“Debug Windows”——“Disassembly”)打开反汇编窗口,并在反汇编窗口中单击鼠标右键,在右键弹出菜单中选择“Source Annotation”和“Code Bytes”。此时,在内存地址列右侧、(jmp esp)指令列左侧的"FF E4"就是指令"jmp esp"的机器码。如果需要找出其它汇编指令的机器码,基本上都可通过这种方法得到。

下一步是如何在我们的进程空间里找到这串机器码。也是非常简单的,只要修改一下代码即可:

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
#if 0
return 0;
__asm jmp esp

#else

bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");

h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
darkread 2004-01-03
  • 打赏
  • 举报
回复
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
char buff[10];
overflow(buff);
}
return nRetCode;
}

  现在先来分析一下上面这段C++代码,找一找哪里有漏洞。这是一个MFC控制台应用程序,”main”函数与其它程序会有些不同,但工作机制基本一致。我们主要分析该函数中”else”那段代码。首先是第一行”char buff[10]”,定义了一个10字符长的本地变量。我们都知道,本地变量的内存空间是在堆栈里分配的。(如果你连这个都不知道,建议不要继续往下看了。:))然后是将buff变量作为参数调用overflow函数。好了,现在让我们分析overflow函数。首先是一个Cfile对象,然后是一个CfileException对象。接下来会试图以读权限打开当前目录下的文件”overflow.txt”。如果打开成功,则将该文件中的所有内容读取到buff数组变量中。发现了问题没有?buff变量只有10字符长。如果读取的文件内容长度是100时会发生什么问题呢?对了,“缓冲区溢出”!而且是在堆栈中发生的缓冲区溢出。在后面的测试中就能看到,我们利用这个漏洞能做些什么!;)现在让我们创建文本文件”overflow.txt”,并将它放到这个应用程序的project目录下。

在进行下一步前,先让我们探讨一下关于Windows NT/2000的内存结构。NT/2000的每一个进程都在启动时分配了4GB(0xFFFFFFFF)的虚拟内存。其中的某些部份实际上是由所有进程共享的,例如核心和设备驱动程序区域。但它们都会被映射到每个进程的虚拟地址空间里。实际上没有进程分配到4GB的物理内存,而是仅当需要时才分配物理内存。因此每一个进程都有各自的4GB虚拟内存,编址范围从0x00000000到0xFFFFFFFF。其中,0x00000000-0x0000FFFF是为NULL指针分配而保留的。访问该区域内存将导致“非法访问”错误。0x00010000-0x7FFEFFFF是用户进程空间。EXE文件的映像被加载到其中(起始地址0x00400000),DLL(动态链接库)也被加载到这部份空间。如果DLL或EXE的代码被装入到该范围的某些地址,就能够被执行。访问该区域中没有代码装入的地址将导致“非法访问”错误。0x7FFF0000-0x7FFFFFFF是保留区域,对此区域的任何访问都将导致“非法访问”错误。0x80000000-0xFFFFFFFF仅供操作系统使用。用于加载设备驱动程序和其它核心级代码。从用户级应用程序(ring 3)访问此区域将导致“非法访问”错误。

现在回到”overflow.txt”文件。现在我们将向这个文本文件中不断添加字符,直到弹出应用程序非法访问的系统对话框。在这里,填充什么字符是很重要的(原因待会就知道了)。我选择小写字母”a”来填充文本文件。我们已经知道缓冲区只有10字符长,那么先填充11个字符。(注意:以debug方式编译应用程序,否则结果可能会有所不同。)咦?没反应。我们继续填充字符……直到填充了18个字符应用程序才崩溃。但这个崩溃对我们的用处还不大。继续填充!当字符串长度为24时,运行程序并观察弹出的对话框信息:“”0x61616161”指令引用的”0x61616161”内存。该内存不能为”written”。”我想大家都应该知道”0x61”所代表的ASCII码是什么吧?;)如果你的机器安装了Visual C++,单击“取消”按钮就能够调试该应用程序。进入调试环境后,选择”view”菜单――”debug windows”――”registers”,可打开寄存器窗口。如果你对汇编一窍不通,建议先去找本汇编的书看看。在寄存器窗口里会看到EAX、EBS和EIP等寄存器的内容。EIP当然是最重要的了。EIP的内容就是程序下一步所要执行指令的地址。我们注意到ESP寄存器的值未被破坏,而且似乎离我们的buff变量不远。下一步我们需要找出ESP的值是如何处理得到的。

现在开始会复杂些了(而这就是乐趣的源泉!:))。 在main函数的最后一行代码处设置断点,因为我们只关心这里所发生的事情。现在启动调试器,并让程序无故障运行到该断点。然后切换到反汇编窗口(按Alt+8,或单击”View”――”debug windows”――”disassembly”)。另外还要打开内存窗口和寄存器窗口。

0040155B 5F pop edi
0040155C 5E pop esi
0040155D 5B pop ebx
0040155E 83 C4 50 add esp,50h
00401561 3B EC cmp ebp,esp
00401563 E8 7E 00 00 00 call _chkesp (004015e6)
00401568 8B E5 mov esp,ebp
0040156A 5D pop ebp
0040156B C3 ret

以上这些东西是什么?汇编代码。如果你对汇编一点都不懂,我在这里做一些简单的说明。第一行是”pop edi”。指令pop用于将仅次于堆栈顶端的数据移到其后的指定寄存器中。需要注意的是ESP寄存器。ESP是32位堆栈指针。一个pop指令移动堆栈顶端的一个数据单元,在这里是DWORD(双字,4字节),到指定寄存器中,并将堆栈指针加4(因为共移动了4字节)。在执行下一步前,让我们看一下ESP寄存器。在内存窗口中输入ESP,就能得到ESP当前指向的地址和内容。看一下ESP指向的内存地址中4个字节的内容和EDI寄存器的内容。现在单步执行”pop.edi”,我们能够看到EDI寄存器中填入了ESP所指向的内存地址的数值,同时ESP的数值也增加了4。后面的两条指令是一样的,只不过寄存器不同罢了。单步执行它们。跟着的三行指令对本文没什么意义,所以在这里不作解释。单步执行到指令”mov esp, ebp”,该指令会将EBP的值赋给ESP寄存器。然后是指令”pop ebp”,这条指令很重要。先让我们在内存窗口输入ESP,可以看到该内存地址有一串”0x61”(’a’的16进制值)。因此0x61616161将被弹出到EBP寄存器中。单步执行该指令可以检验我说的没错吧?;)好了,虽然我说的没错,但好象我们还没能得到什么有用的东西?现在到了最后一条指令”ret”。指令”ret”在汇编中是返回指令。它是如何知道应该返回到哪里的呢?由当前位于堆栈顶端的数值决定。这条指令如果用pop指令表示的话可以表示为”pop eip”(虽然实际上你无法执行这条pop指令;))。它从ESP所指向内存地址处弹出4字节内容,并赋给EIP寄存器(EIP寄存器是32位指令指针)。这就意味着,不管EIP指向哪个内存地址,该地址处的指令将总会成为下一条指令。我们再次在内存窗口中输入ESP,看一下将要赋给EIP寄存器的地址的指令是什么。其实我想此时大家都应该知道是4个字节长的0x61串。现在让我们单步执行该指令,看到EIP的值为0x61616161,也就是说下一指令地址为0x61616161,但指令却显示为???(意为无效指令)。因此再单步执行指令将导致“访问非法”错误。现在再看看ESP寄存器。它正确地指向了堆栈中的下一个数值。也就是说,下一步工作是确定在使缓冲区成功溢出(EIP=0x61616161)时,ESP所指向的地址是否能够存放我们的溢出代码!我们在overflow.txt文件中再次增加4个’a’(共28个’a’),并再次调试程序,在执行到”ret”指令时观察内存窗口和寄存器窗口,会发现执行”ret”指令后ESP所指向内存地址的内容为4字节长的0x61串。Great!这意味着什么?!这个让大家自己想去吧。;)))

darkread 2004-01-03
  • 打赏
  • 举报
回复
Windows 2000缓冲区溢出入门


原著:Jason
翻译/整理/改编:backend <backend@yeah.net><http://www.isbase.com>


--[ 前言

我在互联网上阅读过许多关于缓冲区溢出的文章。其中的绝大多数都是基于*NIX操作系统平台的。后来有幸拜读了ipxodi所著的《Windows系统下的堆栈溢出》(已刊登在绿盟网络安全月刊2000年第三期中),又碰巧看到了Jason先生的《Windows NT Buffer Overflow’s From Start to Finish》,得益匪浅。在翻译Jason先生的文章时,由于我的机器安装了Windows 2000 Server,在调试原文程序时发现细节略有出入。因此本文提供的有关源程序、动态链接库、偏移量等是以我在自己机器上调试为准。(对不同版本的动态链接库,都需要编程者自己调试。)

这篇文章应该属入门级。虽然比较简单,但对于Windows系统下的缓冲区溢出具有一定的通用性。例如,堆栈溢出地址的确定,跳转指令的查找和使用,溢出执行代码的编写,等等。只要发现Windows系统下存在缓冲区溢出漏洞的程序,基本上都可通过这些步骤进行攻击测试。但正如ipxodi所指出的,由于Windows下动态链接库的版本更新较快,一定要根据编程者的实际平台进行调试。在发布此类安全漏洞公告或溢出攻击程序时,源代码、系统平台和动态链接库的版本号都应该尽量列清楚。否则别人调试起来可能会头疼得很厉害。;)



--[ 调试、测试环境

Microsoft Visual C++ 6.0
Microsoft Windows 2000 Server (中文版,内部版本号:2195)



--[ 调试、测试过程

首先,写一个存在缓冲区溢出漏洞的应用程序。该程序可读取文件的内容,这样我们就能通过修改被读取文件的内容来使程序溢出。;-) 在Visual C++开发环境中创建一个新的控制台应用程序,选择”An Application that supports MFC”并单击”Finish”。(注:其实并不一定非是MFC应用程序不可,只不过是我自己的习惯而已。;-)))向这个应用程序中添加一些必要的代码,如下:

CWinApp theApp;

using namespace std;

void overflow(char* buff);

void overflow(char* buff)
{
CFile file;
CFileException er;
if(!file.Open(_T("overflow.txt"),CFile::modeRead,&er))
{
er.ReportError();
return;
}

int x = file.GetLength();
file.Read(buff,x);
}
bm1408 2004-01-03
  • 打赏
  • 举报
回复
可以我还是不明白啊呀
我太菜了吧!这个堆栈溢出后首先执行11-20字节的程序,为什么?
changlele 2004-01-03
  • 打赏
  • 举报
回复
既然是讨论,希望大家看看我的回复,一定对你有帮助的
缓冲区溢出程序关键是找溢出点,以及Shellcode的编写,这就看你基础知识到底过不过关了
至于怎么实现,自己去看书吧,这些东西一个帖子都不能说明白的
尽量少用strcpy等函数,非得用这些函数的话,一定要进行边界检查,

下面讲讲他的原理,原理其实很简单
假如有个堆栈,是10个字节,如果你往这个堆栈中写入20个字节,这20个字节前面的10个字节是我们想要写入堆栈的数据,从第11个字节到20个字节中要是一段程序的话,这个堆栈溢出后首先执行11-20字节的程序,然后返回错误信息,这段程序就是我们所说的Shellcode程序的话,可以向你机器中植入木马,或者别的什么程序,这些程序就获得机器的控制权,这是一件非常危险的事情了,溢出漏洞就是这样子的,建议大家看看数据结构和编译原理方面的书籍
就会想明白其中的道理,原理很简单,即使是明白原理也不一定会利用。

就像我上面所说的关键是溢出点和Shellcode的编写,能编出这样的程序对汇编语言以及计算机的体系结构非常明白才行。说了那么多不知道明白没有,希望楼主的分数大部分给我,因为我确实把原理说了。
ross33123 2004-01-03
  • 打赏
  • 举报
回复
我泼点凉水可以吗?大家别打我啊:)

我认为

缓冲区溢出是在老板不切实际地要求赶进度

程序员干活干得昏天黑地的时候犯的错误

像 Window 那么多漏洞

M$ 的程序员这么牛

正常情况下不会犯这种错误的



smallfool 2004-01-03
  • 打赏
  • 举报
回复
深奥+关注。
lsaturn 2004-01-03
  • 打赏
  • 举报
回复
mme(dog) 你说的是为什么?
yuzhan 2004-01-03
  • 打赏
  • 举报
回复

关注ing....
lygfqy 2004-01-03
  • 打赏
  • 举报
回复
不妨去看看如何编写安全代码一书,
上面有比较详细的介绍
“缓冲区溢出是一种系统默认的异常”
加载更多回复(8)

16,548

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧