1.目标
(1)了解SEH攻击及虚函数攻击的基本原理。
(2)通过调试SEH攻击代码,理解Windows异常处理机制,掌握针对SEH的攻击方式,并利用OllyDbg跟踪异常状态。
(3)调试虚函数攻击代码,理解虚函数工作机制与内存分布方式,掌握基本的虚函数攻击与计算方式,并可以用OllyDbg追踪。
在这一过程中,我需要详述跟踪调试过程,在实验关键处进行截图说明。
(4)解决思考题,即:针对Arrayindexerror数组索引思考题程序,在不修改源代码的情况下,研究如何攻击目标代码,调用bar函数。
2.测试步骤与结果
2.1 SEH实验
(1)阅读并理解代码:
通过分析上图所示代码,结合所学内容,可以获取以下信息:
- 函数MyExceptionhandler()是异常处理函数;
- test()函数的strcpy处是典型栈溢出漏洞;
- _try{}在test函数栈帧中安装一个S.E.H结构,其中除0操作会产生异常。strcpy操作没有产生溢出时,除0操作产生的异常会被异常处理函数处理,而当strcpy操作产生溢出时,会将栈帧中S.E.H异常处理句柄改为shellcode入口地址,代码植入成功。
(2)设置VC6的build版本为release:
(3)为了能触发int 3断点时启动OllyDbg,我们选择选项中的实时调试设置选择设置OllyDbg为实时调试器,然后当我们运行exe文件时,int 3 断点触发后就会启动OllyDbg:
(4)运行刚刚创建的SEH.exe,发现要求创建UDD目录:
(5)按照要求创建UDD目录:
(6)两个路径设置成功后,重新运行SEH.exe程序,成功在int 3上启动OllyDbg:
(7)在strcpy函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy函数之前,shellcode的起始地址为0x0012FE48:
(8)执行完strcpy,确认shellcode的起始位置是0x0012FE48:
(9)点击查看菜单中的S.E.H链,可以看到S.E.H链的情况:
可以看到地址为0x0012FF18。
(10)查看地址0x0012FF18的记录,发现其指向下一个SEH指针,接着是异常处理程序, 只需要把0x0012FF1C这个地址的内容改成shellcode起始地址即可:
(11)由于shellcode的起始地址为 0x0012FE48,第一个S.E.H地址为 0x0012FF18(指向下一个S.E.H的指针) 0x0012FF1C(异常处理地址),因此shellcode需要使用0x0012FF1C-0x0012FE48=212个字节进行填充,且注释掉__asm int 3。修改后的程序如下:
(12)运行程序,成功出现弹框:
这里注意到,shellcode已经被执行,但是点击确定却没有反应,这是因为shellcode已经被当作系统异常处理来进行了,所以点击确定不会退出程序。
2.2 虚函数实验
(1)代码如下:
(2)与SEH实验相同,运行attack.exe程序,成功在int 3上启动OllyDbg:
(3)在strcpy函数处设置断点,程序运行到此处时观察右下角缓冲区数据,可以看到在执行strcpy函数之前,shellcode的起始地址为0x0042E27C:
(4)根据shellcode起始地址0042E27C改写shellcode,shellCode长度为 216 Bytes,换算成十六进制为D8,故shellcode的末尾后四个字节地址是0x0042E27C+0xD8–0x4=0x0042E350。修改后的程序如下:
(5)启动程序,成功出现弹框,shellcode植入成功:
3.测试结论
可以利用栈溢出数据把S.E.H的异常处理函数地址替换为shellcode入口地址,让程序跳转去执行shellcode来实现我们自己的目的。
也可以通过利用虚函数原理达到攻击目的,让程序按照我们预先伪造的虚函数指针去寻找虚表,而在此处填上shellcode的起始地址作为伪造的虚函数入口地址,让程序跳转去执行shellcode。虚函数攻击原理如下:
通过这次测试,我对软件安全有了更深刻的认识,也意识到软件面临着极大的安全隐患,需要我们去保护和守卫。而要达到这一点,我们要去主动学习更多的知识、熟悉更加强有力的工具。
4.思考题
针对
Arrayindexerror
数组索引思考题程序,在不修改源代码的情况下,研究如何攻击目标代码,调用
bar
函数。
(1)源代码如下:
(2)通过分析可得,源代码中存在ArrayIndex error,即数组下标错误。
首先编译程序,然后在命令行执行程序,如下所示:
可以从上图中看到,bar函数的地址为0x00401000,数组是从0x00410048开始,写入的数值的位置为0x00410054。(该值随输入的第一个参数的值的变化而变化,如上图中我输入的第一个参数是3,则此时是在0x00410054写入数值AAAA)。
进一步分析,若要实现bar函数的调用,在命令行输入的2个参数就十分关键。通过研究代码逻辑,可以发现,在调用InsertInt(index, value)后,将返回main函数。那么只需要找到这个过程中保存栈的返回值的位置,且将返回值修改为bar函数的地址,并根据这两项信息确定2个参数的值,再运行程序,就可以调用bar函数了。
(3)下面的方程式描述了一个单独数组元素的地址是怎样通过数组的基址、下标以及数组元素大小来确定的:
数组元素的地址=数组元素的基址+下标*数组元素的大小。由(2)中的分析可得,输入的第一个参数便是下标的值,而输入的第二个参数是bar函数的地址。另外需要注意,输入的参数均为十进制格式,因此在获得十六进制格式的值之后需要进行简单的转换。
保存栈的返回值的位置
,可以通过OllyDbg调试获取:
首先在OllyDbg点击调试—>参数,并将2个参数随意设置为3和aa:
找到InsertInt函数,并在合适的位置下断点:
运行到断点处,并继续往下运行至0x00401038:
继续F8运行至0x004010FC处,此时观察右下角:
则可以确定0x0012FF84为保存栈的返回值的位置。
需要注意的是,将0x0012FF84代入前面提到的方程式的左端时,应该代入0x10012FF84,这里参考的解释如下:
(4)下面按照方程式计算:
0x10012FF84=0x00410048+4*下标,则下标为0x3FF47FCF即十进制的1072988111。
而bar函数的地址为0x00401000,即十进制的4198400。
注意到原代码中有对下标index的值进行限制:
要实现调用bar函数,得先注释掉上图所示的判断语句:
(5)在命令行输入两个参数1072988111和4198400:
结果如下,可以看到成功调用bar函数: