软件安全实验5:虚函数攻击
软件安全第五次实验报告
其实是第四次实验,但是老师给的名称叫实验五。
实验目标
1.要求A
(1)了解虚函数攻击的基本原理
(2)调试虚函数攻击代码,理解虚函数工作机制与内存分布方式,掌握基本的虚函数攻击与计算方式,并可以用OllyDbg/x32Dbg/IDA Pro追踪观察。
(3)我们已经将shellcode直接写成变量在程序中,通过修改虚函数表指针,指向我们伪造的虚函数表,运行我们的shellcode。(注意程序中的shellcode的某些函数地址,可能需要修改,可直接在程序运行时直接在内存中修改。或者直接修改main.cpp,重新编译),如果你有自己编写shellcode的想法,也可以自行在main.cpp中重新编译。
2.要求B
(1)在不修改源代码的情况下,研究如何利用栈溢出的方式攻击目标代码,通过命令行的方式植入shellcode,弹出对话框。(利用虚函数的特性实现shellcode注入即可)
3.要求C
(1)详述调试及追踪过程
(2)实验结果需要截图证明(如果自己编写shellcode,则需要重新编写的main.cpp和生成的.exe文件)
实验步骤与结果
了解虚函数攻击的基本原理
1.了解虚函数
(1)虚函数是C++实现多态性的重要方式。关于多态,简而言之就是用父类型的指针指向其子类的 实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过虚函数来体现的。
(2)虚函数(Virtual Function)是通过一张虚函数表vtbl(Virtual Table)来实现的。当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。对于一个包含虚函数的类的对象,在它的其他成员变量前会有一个虚表指针vptr(virtual table pointer),vptr指向它自己的虚表vtbl。当调用一个虚函数时,首先通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用。
2.包含虚函数的类的对象的内存布局如下。
3.了解虚函数攻击
由于C++虚函数表vtbl本身不可写,而虚函数表指针vptr一般处于可读数据段是可写的,对于C++虚函数的攻击也的本质可以称之为C++虚函数表的劫持。我们可以在实例调用虚函数之前,修改虚函数表的指针,让它指向伪造的虚函数表,在伪造的虚函数表中让虚函数指针指向shellcode。
虚函数题目A
1.分析程序流程
(1)声明了一个类,具有一个成员变量buf和一个虚函数test,同时创建了一个它的实例overflow和一 个指向该类的指针
1 | class vf |
(2)加载了动态链接库”user32.dll”;通过实例overflow成员变量buf的地址,找到实例overflow的虚表指针vptr,修改虚表指针的地址。之后strcpy(overflow.buf,shellcode1)函数,利用缓冲区溢出的方式,植入shellcode;之后调用实例overflow的虚函数test();而这时它的虚表指针已经发生变化了,所以运行的应该是shellcode。
1 | void main(void) |
(3)shellcode关键代码分析
| 地址 | 机器码 | 汇编代码 | 作用 |
| 0042E39C | 33DB | xor ebx,ebx | 将ebx置零 |
| 0042E39E | 53 | push ebx | 将0入栈,作为截断字符 |
| 0042E39F | 6862757074 | push 0x74707562 | 将字符串”bupt”入栈 |
| 0042E3A4 | 6862757074 | push 0x74707562 | 将字符串”bupt”入栈 |
| 0042E3A9 | 8BC4 | mov eax,esp | eax中保存字符串”buptbupt” |
| 0042E3AB | 53 | push ebx | 0入栈,函数MessageBoxA()的参数 |
| 0042E3AC | 50 | push eax | 字符”buptbupt”入栈,函数MessageBoxA()的参数 |
| 0042E3AD | 50 | push eax | 字符”buptbupt”入栈,函数MessageBoxA()的参数 |
| 0042E3AE | 53 | push ebx | 0入栈,函数MessageBoxA()的参数 |
| 0042E3AF | B8683DE277 | mov eax,0x77E23D68 | |
| 0042E3B4 | FFD0 | call eax | 调用函数MessageBoxA() |
2.利用x32dbg进行动态调试
(1)定位到修改虚表指针的代码,首先利用overflow实例的成员变量buf定位到虚表指针,其中vf.42e35c-4就是虚表指针的位置,因为overflow实例是一个全局变量,所以这个指向虚函数表 的指针存在于全局变量区,是可以修改的。
(2)在修改前,在内存vf.42e35c-4处可以看到原本的虚表指针,它指向的虚表是在只读数据段的。修改前,在内存vf.42e35c-4处可以看到原本的虚表指针0042E358,它指向的虚表是在只读数据段的。这段代码将这个虚表指针修改了,修改成一个指向0042e430的指针,在0042e430处保存新的虚表。
(3)继续跟进,程序之后调用strcpy函数将shellcode复制到了overflow实例的成员变量buf 上,而在这段内存中可以看到,修改之后的新的虚表的前四个字节是class vf的第一个虚函数,它指向了shellcode的开头,所以当overflow 实例调用虚函数时,就会自动跳转到shellcode之中。
(4)跟进代码到程序对虚函数test()的调用处:
①mov dword ptr ds:[0x42E350],vf.42E358:将overflow实例的地址赋值给一个指针,这个指针既表示overflow实例,也表示overflow实例的虚表指针
②mov edx,dword ptr ds:[0x42E350] mov eax,dword ptr ds:[edx]:通过 overflow实例的虚表指针找到它的虚表,虚表在eax中
③call dword ptr ds:[eax]:直接调用虚表中的第一个函数,也就是程序以为的test()函数
(5)这段代码跑完之后,函数自动跳转到0042e35c,也就是虚表中的第一个函数的入口点
(6)接下来执行shellcode,这里对MessageBoxA()函数的地址有问题,可以通过动态查询MessageBoxA的函数地址,修改程序之后执行。
(7)执行完毕之后,程序可以成功弹出弹窗。
虚函数题目B
1.分析程序流程
(1)声明了两个类,他们各自拥有一个成员变量和一个虚函数。之后各自创建了一个实例overflow、overflow1和一个指向各自实例的指针。
1 | class vf |
(2)首先加载了动态链接库user32.dll,之后对主函数的参数个数进行判断,如果参数个数不为3就输出并退出,否则就将参数argv[1]和argv[2]赋值给overflow.buf和overflow1.buf。此处 就存在shellcode植入的机会,之后调用overflow.test()。
1 | void main(int argc, char* argv[]) |
2.利用x32dbg进行动态调试
(1)随意输入两段利于定位的字符串,在程序执行完两端strcpy操作之后打个断点,观察内存情况。
(2)可以利用args[2]复写overflow实例虚表,让他指向shellcode,并通过arg[1]构造shellcode。
# args[1]
‘’’
66 81 EC 40 04 33 DB 53
68 62 75 70 74 68 62 75
70 74 8B C4 53 50 50 53
B8 00 49 F5 75 FF D0 53
B8 00 B1 E6 76 FF D0 66
5C EB 42
‘’’
# args[2]
“””
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 84 EB 42
“””
(3)其中,两个函数MessageBoxA和ExitProcess通过查找得到地址。
(4)当两个字符串通过strcpy传入之后,内存情况如下。
(5)当程序调用overflow实例的虚函数test()时,就会跳转到shellcode上,然后成功输出弹窗,并且程序可以正常运行。
实验结论
本次虚函数攻击实验让我对C++底层机制的安全风险有了颠覆性的认识。在调试过程中,当我第一次看到虚函数表指针在内存中的具体位置,并成功通过计算偏移量修改其指向伪造的虚函数表时,我深刻意识到面向对象编程的”封装”和”多态”特性在内存层面竟是如此脆弱。通过单步跟踪虚函数调用过程,我观察到程序在调用虚函数时,会先通过对象内存起始地址的vptr指针找到虚函数表,再根据函数索引跳转到对应地址执行。这个原本用于实现多态的正常机制,一旦被恶意利用,就能成为攻击的突破口。
在栈溢出攻击实践中,我尝试通过精心构造的输入数据覆盖返回地址,并将shellcode植入栈中。这个过程让我体会到内存布局的精确计算的重要性。不仅需要准确计算虚函数表指针的偏移,还要考虑栈帧的对齐和地址随机化等现代防护机制的影响。当我成功通过命令行参数注入shellcode并弹出对话框时,我既为攻击的成功感到兴奋,也为这类漏洞的潜在危害感到担忧。这让我认识到,软件开发中任何一个看似微小的内存操作失误,都可能成为安全漏洞的根源。
通过这次实验,我深刻理解了软件安全课程的现实意义。安全不是可以事后添加的功能,而应该从程序设计之初就深入每一个环节。无论是虚函数表的安全验证,还是栈溢出的防护措施,都需要开发人员具备系统性的安全思维。这次实验不仅锻炼了我的逆向工程和漏洞分析能力,更重要的是让我建立起”攻击者思维”,这将帮助我在未来的开发工作中更好地预见和防范安全风险。理论与实践的结合,让我真正体会到安全研究的价值所在,只有深入理解攻击原理,才能构建更可靠的软件防护体系。
实验报告持续更新中…





















