先用一个POC来观察溢出发生的时候栈中的数据分布。首先打开WINWORD.EXE,然后用Immunity Debugger附加WINWORD.EXE,然后F9运行,之后将POC测试文档poc.doc拖进WINWORD.EXE,如图:程序在0x41414141这处地址断下,这是一个无效的内存地址,所以程序无法继续运行,根据栈溢出的利用方式判断,0x41414141这个位置应该是函数返回地址一类的数据。接着观察这个返回地址属于哪个函数,在右下角的栈区右键=>”Follow in Dump”,在左边的内存数据区出现虚拟地址对应的机器码,在该区右键=>”Disassemble”就看到了对应的汇编指令,向上回溯就看能到函数sub_275C89C7。
漏洞存在的位置是MSCOMCTL.OCX模块,因此sub_275C89C7也就对应于MSCOMCTL.OCX的位置,接下来下断点看看数据是怎么复制到缓冲区的,如果直接在这里下断点是没法断下的,对比书上的分析可知,MSCOMCTL模块是在打开poc.doc时才加载,这里先“Alt+E”找到MSCOMCTL对应的路径,再用Immunity Debugger加载运行,然后bp 0x275C89C7,之后在加载poc.doc就可以在sub_275C89C7断下。
这里一共调用了两次sub_275C876D:
同时将MSCOMCTL.OCX模块在IDA反编译用F5看看这段代码的基本用途:
IDA中的伪代码“if(V5 == 1784835907)”中,1784835907对应的十六进制为0x6A626F43,对应的ASCII码为“jboC”,按照小端存储规则逆过来就是“Cobj”,在Immunity Debugger中F7跟进sub_275C876D可以知道,第一次调用sub_275C876D是复制了“Cobjd”,这应该是判断程序接着要处理的对象名字是不是“Cobj”,由MSCOMCTL的结构可知,这是MSCOMCTL.OCX模块的第二个对象。Cobj::load用来读取数据。
之后再跟进第二个sub_275C876D:发现了溢出的现场就在这个函数,对比刚开始加载时的栈状态,0x275C87CB处的指令将数据复制到栈中,并覆盖了栈中0x00124C0C处的返回地址。
在IDA中的伪代码中,要执行第二个sub_275C876D,需要满足“if ( v5 == 0x6A626F43 && dwBytes >= 8 )”,如果dwByte小于8就不执行。此时在Immunity Debugger中可以看到:ss:[EBP-C]==0x00008282,这远远大于8,满足了这里判断条件,这样Cobj::load就可以读取多余的数据,这个意思好像就是非得让你溢出不可了,原本的判断条件应该是“if ( v5 == 0x6A626F43 && dwBytes <= 8 )”,正是这个误写的大于使得多余的数据也能被复制到栈中
接着用一个可以弹出计算器的word文档再看看溢出的发生和利用:
先来到溢出发生的地方,在调试器看到,是从DS:[ESI]这个位置开始复制,在内存中跟随看看,可以看到这段代码用了0x7FFA4512,即”jmp esp”作为跳板地址,那么shellcode的起始位置就是esp的值。
数据被通过溢出被复制到栈中:
通过“jmp esp”跳回栈中执行shellcode,ESP此时的值为0x00124C6C
栈中的shellcode被当作代码执行:
弹出计算器:
修复后的函数在IDA中如下,在新的判断条件中,“Cobj::load”读取的长度必须为8字节,否则不进行复制。