none
发现一个问题,不知道是不是微软的BUG RRS feed

  • 问题

  • 我测试的环境是WinXP x86 SP3,当前登录的用户是Administrator(管理员权限),在“C:\Documents and Settings\Administrator\SendTo”中创建了我的应用程序的快捷方式。该应用程序运行起来后就调用系统API,调用如下:

     

    int argNum = 0;

    LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &argNum);

    结果是从桌面上发送一个短文件名的文件给该快捷方式时能正常解析,如果是长文件名时,会解析出很多参数出来。

     

    不是8.3的,是Windows的长文件名,

    我说的短文件名是指“C:\Documents and Settings\Administrator\桌面\abc.txt"
    长文件名是指:“C:\Documents and Settings\Administrator\桌面\abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc.txt"

     

    这里有其它的回复过的,是我发错了地方,才转到这里来了。

    2010年8月9日 9:16

答案

  • 问题终于找到,元凶在explorer的处理中,详细如下:

    windbg挂接explorer进程后,对SHELL32!ShellExecuteExW设置断点,当选中文件右键发送时,windbg断下,查看调用栈信息:

    0:000> kb
    ChildEBP RetAddr Args to Child       
    0007d5dc 7d6c3238 0007d608 0007eb94 0007eb60 SHELL32!ShellExecuteExW
    0007e8d0 77f97cc7 0012e128 001212f8 00000009 SHELL32!CExeDropTarget::Drop+0x330
    0007e904 7d6da148 0012e128 001212f8 00000009 SHLWAPI!SHSimulateDrop+0x63
    0007eb4c 77f97cc7 000f0048 001212f8 00000009 SHELL32!CShellLink::Drop+0xd8
    0007eb80 7d732170 000f0048 001212f8 00000009 SHLWAPI!SHSimulateDrop+0x63
    0007ebac 7d7321e7 00290080 000f0048 0007eca4 SHELL32!CSendToMenu::_DoDragDrop+0x58
    0007ebc8 7d5e5f3b 000f0048 0007ebe4 00174940 SHELL32!CSendToMenu::InvokeCommand+0x40
    0007ec28 7d5e5e8d 00120b80 0007ec48 00000000 SHELL32!HDXA_LetHandlerProcessCommandEx+0xa5
    0007eeb8 7d675fca 00174940 0007f204 0007f244 SHELL32!CDefFolderMenu::InvokeCommand+0x17f
    0007f1e8 7d678f71 00174940 0007f204 77d2929a SHELL32!CDefView::_InvokeContextMenu+0xb0
    0007f4f0 7d67bda6 00174940 00000010 00000108 SHELL32!CDefView::_DoContextMenuPopup+0x37e
    0007f53c 7d5d20ce 01020108 00000000 00127008 SHELL32!CDefView::ContextMenu+0x1e4
    0007f6b0 7d5c2f38 000a007a 0000007b 000700c0 SHELL32!CDefView::WndProc+0x840
    0007f6f4 77d18734 000a007a 0000007b 000700c0 SHELL32!CDefView::s_WndProc+0x72
    0007f720 77d18816 7d5c2ee2 000a007a 0000007b USER32!InternalCallWinProc+0x28
    0007f788 77d28ea0 0009f1d0 7d5c2ee2 000a007a USER32!UserCallWinProcCheckWow+0x150
    0007f7dc 77d28eec 0064a540 0000007b 000700c0 USER32!DispatchClientMessage+0xa3
    0007f804 7c92e473 0007f814 00000018 0064a540 USER32!__fnDWORD+0x24
    0007f828 77d194be 77d28e0d 000700c0 0000007b ntdll!KiUserCallbackDispatcher+0x13
    0007f87c 77d28dd9 000700c0 0000007b 000700c0 USER32!NtUserMessageCall+0xc
    0007f898 5ad71af6 000700c0 0000007b 000700c0 USER32!RealDefWindowProcW+0x47
    0007f8f0 5ad71b3d 00000000 00000000 000700c0 UxTheme!_ThemeDefWindowProc+0x16e
    0007f90c 77d294ed 000700c0 0000007b 000700c0 UxTheme!ThemeDefWindowProcW+0x18
    0007f954 771d0d82 000700c0 0000007b 000700c0 USER32!DefWindowProcW+0x6b
    0007fac0 77d18734 000700c0 0000007b 000700c0 comctl32!ListView_WndProc+0xf0
    0007faec 77d18816 771d0c92 000700c0 0000007b USER32!InternalCallWinProc+0x28
    0007fb54 77d2927b 0009f1d0 771d0c92 000700c0 USER32!UserCallWinProcCheckWow+0x150
    0007fb90 77d292e3 00656848 0061ad30 000700c0 USER32!SendMessageWorker+0x4a5
    0007fbb0 771d0a7e 000700c0 0000007b 000700c0 USER32!SendMessageW+0x7f
    0007fc30 771d0ab6 000700c0 00000002 00000108 comctl32!ListView_HandleMouse+0x57b
    0007fc50 771d14e9 00116618 00000000 00000108 comctl32!ListView_OnButtonDown+0x1b
    0007fdc0 77d18734 000700c0 00000204 00000002 comctl32!ListView_WndProc+0x857
    0007fdec 77d18816 771d0c92 000700c0 00000204 USER32!InternalCallWinProc+0x28
    0007fe54 77d189cd 0009f1d0 771d0c92 000700c0 USER32!UserCallWinProcCheckWow+0x150
    0007feb4 77d18a10 0007fed4 00000000 0007fef0 USER32!DispatchMessageWorker+0x306
    0007fec4 7d5b7b6d 0007fed4 000eafd8 000700c0 USER32!DispatchMessageW+0xf
    0007fef0 7d5b7a02 7c80934a 000eafd8 000eafd8 SHELL32!CDesktopBrowser::_PeekForAMessage+0x66
    0007ff08 7d5e3a7c 00000000 0007ff5c 01013256 SHELL32!CDesktopBrowser::_MessageLoop+0x14
    0007ff14 01013256 000eafd8 7ffdf000 0007ffc0 SHELL32!SHDesktopMessageLoop+0x24
    0007ff5c 0101a5c7 00000000 00000000 0002097e explorer!ExplorerWinMain+0x2db
    0007ffc0 7c817077 00fc2990 0006e890 7ffdf000 explorer!ModuleEntry+0x6d
    0007fff0 00000000 0101a55f 00000000 78746341 kernel32!BaseProcessStart+0x23
    

    发现SHELL32!CExeDropTarget::Drop函数内部处理了相应的参数,然后调用shellexcuteexW函数启动了对应进程,也就是说问题的根源在于SHELL32!CExeDropTarget::Drop函数中。

    对SHELL32!CExeDropTarget::Drop的执行流程跟踪如下:

    0:000> wt -l 1
    Tracing SHELL32!CExeDropTarget::Drop to return address 77f97cc7
      5   0 [ 0] SHELL32!CExeDropTarget::Drop
      1   0 [ 1]  SHELL32!_chkstk
      16   0 [ 1]  SHELL32!_alloca_probe
      31  17 [ 0] SHELL32!CExeDropTarget::Drop
      33   0 [ 1]  SHELL32!GetAppDropTarget
      58  50 [ 0] SHELL32!CExeDropTarget::Drop
      98   0 [ 1]  SHELL32!CExeDropTarget::_FillSEIFromLinkSite
      73  148 [ 0] SHELL32!CExeDropTarget::Drop
      36   0 [ 1]  SHELL32!CFSIDLData::GetData
      82  184 [ 0] SHELL32!CExeDropTarget::Drop
      38   0 [ 1]  SHELL32!App_IsLFNAware
      92  222 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
      99  234 [ 0] SHELL32!CExeDropTarget::Drop
      21   0 [ 1]  SHLWAPI!PathQuoteSpacesW
     103  255 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  kernel32!lstrlenW
     111  267 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     119  279 [ 0] SHELL32!CExeDropTarget::Drop
      25   0 [ 1]  kernel32!LocalAlloc
     133  304 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     140  316 [ 0] SHELL32!CExeDropTarget::Drop
      21   0 [ 1]  SHLWAPI!PathQuoteSpacesW
     148  337 [ 0] SHELL32!CExeDropTarget::Drop
      23   0 [ 1]  SHLWAPI!StrCatBuffW
     155  360 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     162  372 [ 0] SHELL32!CExeDropTarget::Drop
    ModLoad: 60820000 60827000  C:\WINDOWS\system32\MSISIP.DLL
    ModLoad: 7e940000 7e956000  C:\WINDOWS\system32\wshext.dll
    ModLoad: 36d30000 36d4a000  C:\PROGRA~1\MICROS~2\OFFICE11\MCPS.DLL
    ModLoad: 038e0000 03905000  C:\Program Files\360\360safe\Antispy.dll
      52   0 [ 1]  SHELL32!ShellExecuteExW
    >> No match on ret
      52   0 [ 1]  SHELL32!ShellExecuteExW
      6   0 [ 1]  SHELL32!_InvokePidl
    >> No match on ret
      6   0 [ 1]  SHELL32!_InvokePidl
      17   0 [ 1]  SHELL32!CShellExecMenu::_InvokeOne
    >> No match on ret
      17   0 [ 1]  SHELL32!CShellExecMenu::_InvokeOne
      17   0 [ 1]  SHELL32!CShellExecMenu::InvokeCommand
    >> No match on ret
      17   0 [ 1]  SHELL32!CShellExecMenu::InvokeCommand
      10   0 [ 1]  SHELL32!HDXA_LetHandlerProcessCommandEx
    >> No match on ret
      10   0 [ 1]  SHELL32!HDXA_LetHandlerProcessCommandEx
      13   0 [ 1]  SHELL32!CDefFolderMenu::InvokeCommand
    >> No match on ret
      13   0 [ 1]  SHELL32!CDefFolderMenu::InvokeCommand
      13   0 [ 1]  SHELL32!_InvokeInProcExec
    >> No match on ret
      13   0 [ 1]  SHELL32!_InvokeInProcExec
      15   0 [ 1]  SHELL32!CShellExecute::_ShellExecPidl
    >> No match on ret
      15   0 [ 1]  SHELL32!CShellExecute::_ShellExecPidl
      8   0 [ 1]  SHELL32!CShellExecute::_DoExecPidl
    >> No match on ret
      8   0 [ 1]  SHELL32!CShellExecute::_DoExecPidl
      8   0 [ 1]  SHELL32!CShellExecute::_TryExecPidl
    >> No match on ret
      8   0 [ 1]  SHELL32!CShellExecute::_TryExecPidl
      26   0 [ 1]  SHELL32!CShellExecute::ExecuteNormal
    >> No match on ret
      26   0 [ 1]  SHELL32!CShellExecute::ExecuteNormal
      11   0 [ 1]  SHELL32!ShellExecuteNormal
    >> No match on ret
      11   0 [ 1]  SHELL32!ShellExecuteNormal
      17   0 [ 1]  SHELL32!ShellExecuteExW
     164  585 [ 0] SHELL32!CExeDropTarget::Drop
      17   0 [ 1]  kernel32!LocalFree
     168  602 [ 0] SHELL32!CExeDropTarget::Drop
      36   0 [ 1]  ole32!ReleaseStgMedium
     175  638 [ 0] SHELL32!CExeDropTarget::Drop
      23   0 [ 1]  SHELL32!CExeDropTarget::_CleanupSEIFromLinkSite
     184  661 [ 0] SHELL32!CExeDropTarget::Drop
      5   0 [ 1]  SHELL32!__security_check_cookie
     186  666 [ 0] SHELL32!CExeDropTarget::Drop
    
    852 instructions were executed in 851 events (0 from other threads)
    
    Function Name                Invocations MinInst MaxInst AvgInst
    SHELL32!App_IsLFNAware                1   38   38   38
    SHELL32!CDefFolderMenu::InvokeCommand         1   13   13   13
    SHELL32!CExeDropTarget::Drop             1   186   186   186
    SHELL32!CExeDropTarget::_CleanupSEIFromLinkSite    1   23   23   23
    SHELL32!CExeDropTarget::_FillSEIFromLinkSite     1   98   98   98
    SHELL32!CFSIDLData::GetData              1   36   36   36
    SHELL32!CShellExecMenu::InvokeCommand         1   17   17   17
    SHELL32!CShellExecMenu::_InvokeOne          1   17   17   17
    SHELL32!CShellExecute::ExecuteNormal         1   26   26   26
    SHELL32!CShellExecute::_DoExecPidl          1    8    8    8
    SHELL32!CShellExecute::_ShellExecPidl         1   15   15   15
    SHELL32!CShellExecute::_TryExecPidl          1    8    8    8
    SHELL32!DragQueryFileW                4   12   12   12
    SHELL32!GetAppDropTarget               1   33   33   33
    SHELL32!HDXA_LetHandlerProcessCommandEx        1   10   10   10
    SHELL32!ShellExecuteExW                2   17   52   34
    SHELL32!ShellExecuteNormal              1   11   11   11
    SHELL32!_InvokeInProcExec               1   13   13   13
    SHELL32!_InvokePidl                  1    6    6    6
    SHELL32!__security_check_cookie            1    5    5    5
    SHELL32!_alloca_probe                 1   16   16   16
    SHELL32!_chkstk                    1    1    1    1
    SHLWAPI!PathQuoteSpacesW               2   21   21   21
    SHLWAPI!StrCatBuffW                  1   23   23   23
    kernel32!LocalAlloc                  1   25   25   25
    kernel32!LocalFree                  1   17   17   17
    kernel32!lstrlenW                   1   12   12   12
    ole32!ReleaseStgMedium                1   36   36   36
    
    0 system calls were executed
    
    eax=00000000 ebx=04a744f0 ecx=0000a398 edx=00000000 esi=0007eb60 edi=0007eb94
    eip=77f97cc7 esp=0007e8f0 ebp=0007e904 iopl=0     nv up ei pl zr na pe nc
    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000       efl=00000246
    SHLWAPI!SHSimulateDrop+0x63:
    77f97cc7 eb09      jmp   SHLWAPI!SHSimulateDrop+0x6e (77f97cd2)

    发现在函数内部调用了SHLWAPI!PathQuoteSpacesW函数来实现了对文件路径是否含有空格进行处理,如果文件路径中有空格,则在文件路径的开头和结尾添加双引号,详细分析了该函数的内部调用,发现,该函数处理长路径名时,出现了问题。贴出函数的汇编代码和IDA的F5代码:

    0:002> uf SHLWAPI!PathQuoteSpacesW
    SHLWAPI!PathQuoteSpacesW:
    77f91097 8bff      mov   edi,edi
    77f91099 55       push  ebp
    77f9109a 8bec      mov   ebp,esp
    77f9109c 56       push  esi
    77f9109d 8b7508     mov   esi,dword ptr [ebp+8]
    77f910a0 85f6      test  esi,esi
    77f910a2 7444      je   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0xd:
    77f910a4 6a20      push  20h
    77f910a6 56       push  esi
    77f910a7 e82156fbff   call  SHLWAPI!StrChrW (77f466cd)
    77f910ac 85c0      test  eax,eax
    77f910ae 7438      je   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0x19:
    77f910b0 56       push  esi
    77f910b1 ff157014f477  call  dword ptr [SHLWAPI!_imp__lstrlenW (77f41470)]
    77f910b7 40       inc   eax
    77f910b8 8d4801     lea   ecx,[eax+1]
    77f910bb 81f904010000  cmp   ecx,104h
    77f910c1 7d25      jge   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0x2c:
    77f910c3 57       push  edi
    77f910c4 8d3c00     lea   edi,[eax+eax]
    77f910c7 57       push  edi
    77f910c8 8d4602     lea   eax,[esi+2]
    77f910cb 56       push  esi
    77f910cc 50       push  eax
    77f910cd e8cf83fbff   call  SHLWAPI!memmove (77f494a1)
    77f910d2 8d0437     lea   eax,[edi+esi]
    77f910d5 83c40c     add   esp,0Ch
    77f910d8 66c7062200   mov   word ptr [esi],22h
    77f910dd 6683600200   and   word ptr [eax+2],0
    77f910e2 66c7002200   mov   word ptr [eax],22h
    77f910e7 5f       pop   edi
    
    SHLWAPI!PathQuoteSpacesW+0x51:
    77f910e8 5e       pop   esi
    77f910e9 5d       pop   ebp
    77f910ea c20400     ret   4

     IDA F5代码:

    BOOL __stdcall PathQuoteSpacesW(LPWSTR lpsz)
    {
     int v1; // edi@4
     BOOL result; // eax@5
    
     if ( lpsz )
     {
      result = (BOOL)StrChrW(lpsz, 0x20u);
      if ( result )
      {
       result = lstrlenW(lpsz) + 1;
       if ( result + 1 < 260 )
       {
        v1 = 2 * result;
        memmove(lpsz + 1, lpsz, 2 * result);
        result = (BOOL)((char *)lpsz + v1);
        *lpsz = 34;
        *(LPWSTR)((char *)lpsz + v1 + 2) = 0;
        *(LPWSTR)((char *)lpsz + v1) = 34;
       }
      }
     }
     return result;
    }
    
    
    

     分析到这儿,答案也就出来了。

    原因就在于 微软判断的时候,如果文件长度最大长度为MAX_PATH时,如果此时需要在两端添加双引号的话,仍然判断MAX_PATH为最大值的话,岂不是出问题了? 丢失了2个双引号啊。

    • 已建议为答案 xiaohuazi 2010年8月14日 6:19
    • 已标记为答案 lifestar2008 2010年8月16日 2:08
    2010年8月14日 6:18

全部回复

  • 最大长度超过MAX_PATH,惹的祸??
    2010年8月11日 6:02
  • 很久没有C++了,分配块干净的大点的内存给args呢?

     

    2010年8月11日 12:48
  • int APIENTRY WinMain(HINSTANCE hInstance,
               HINSTANCE hPrevInstance,
               LPSTR   lpCmdLine,
               int    nCmdShow)
    {
     	// TODO: Place code here.
    	int argNum = 0;
    
    	LPWSTR lpCommandLine = GetCommandLineW();
    
    	LPWSTR* args = CommandLineToArgvW(lpCommandLine, &argNum);
    	/*
    	比如: c:\test.exe 123.txt 我们认为:c:\test.exe 是一个参数,123.txt是一个参数;
    	中间以空格分隔;
    	如果 c:\test.exe C:\Documents and Settings\Administrator\桌面\123.txt ,该函数
    	仍然要以空格分隔,所以,就会出现你说的情况;
    	你可以 c:\test.exe "C:\Documents and Settings\Administrator\桌面\123.txt" 来使用。
    	*/
    
    	for (int i = 0 ; i < argNum; i ++)
    	{
    		OutputDebugStringW(args[i]);
    	}
    	
    	return 0;
    }
    
    2010年8月12日 1:27
  • 这个我知道,关键是我的EXE的命令行参数不是我写的,是Windows在右键菜单中的“发送到”为我的应用程序添加的快捷方式,没有添加引号导致的。

    垃圾MS。

    2010年8月12日 6:03
  • <p>真正的测试了一下,的确出现了你描述的问题;</p><p>测试结果如下:<br/>当文件路径长度 大于259时,就解析错误了;</p><p>通过windbg调试,发现 CommandLineToArgvW 函数内部调用了Parse_Cmdline函数来解析参数;Parse_Cmdline函数内部处理的比较麻烦,索性在nt4 Src 中搜索了一下,贴出代码:</p>
    
    /***
    *void Parse_Cmdline(cmdstart, argv, lpstr, numargs, numbytes)
    *
    *Purpose:
    *    Parses the command line and sets up the Unicode argv[] array.
    *    On entry, cmdstart should point to the command line,
    *    argv should point to memory for the argv array, lpstr
    *    points to memory to place the text of the arguments.
    *    If these are NULL, then no storing (only counting)
    *    is done. On exit, *numargs has the number of
    *    arguments (plus one for a final NULL argument),
    *    and *numbytes has the number of bytes used in the buffer
    *    pointed to by args.
    *
    *Entry:
    *    LPWSTR cmdstart - pointer to command line of the form
    *      <progname><nul><args><nul>
    *    TCHAR **argv - where to build argv array; NULL means don't
    *           build array
    *    LPWSTR lpstr - where to place argument text; NULL means don't
    *           store text
    *
    *Exit:
    *    no return value
    *    INT *numargs - returns number of argv entries created
    *    INT *numbytes - number of bytes used in args buffer
    *
    *Exceptions:
    *
    *******************************************************************************/
    
    void Parse_Cmdline (
    					LPWSTR cmdstart,
    					LPWSTR*argv,
    					LPWSTR lpstr,
    					INT *numargs,
    					INT *numbytes
    					)
    {
      LPWSTR p;
      WCHAR c;
      INT inquote;          /* 1 = inside quotes */
      INT copychar;          /* 1 = copy char to *args */
      WORD numslash;         /* num of backslashes seen */
    	
      *numbytes = 0;
      *numargs = 1;          /* the program name at least */
    	
      /* first scan the program name, copy it, and count the bytes */
      p = cmdstart;
      if (argv)
        *argv++ = lpstr;
    	
    		/* A quoted program name is handled here. The handling is much
    		simpler than for other arguments. Basically, whatever lies
    		between the leading double-quote and next one, or a terminal null
    		character is simply accepted. Fancier handling is not required
    		because the program name must be a legal NTFS/HPFS file name.
    		Note that the double-quote characters are not copied, nor do they
    	contribute to numbytes. */
      if (*p == TEXT('\"'))
      {
    	/* scan from just past the first double-quote through the next
    		double-quote, or up to a null, whichever comes first */
        while ((*(++p) != TEXT('\"')) && (*p != TEXT('\0')))
        {
          *numbytes += sizeof(WCHAR);
          if (lpstr)
            *lpstr++ = *p;
        }
        /* append the terminating null */
        *numbytes += sizeof(WCHAR);
        if (lpstr)
          *lpstr++ = TEXT('\0');
    		
        /* if we stopped on a double-quote (usual case), skip over it */
        if (*p == TEXT('\"'))
          p++;
      }
      else
      {
        /* Not a quoted program name */
        do {
          *numbytes += sizeof(WCHAR);
          if (lpstr)
            *lpstr++ = *p;
    			
          c = (WCHAR) *p++;
    			
        } while (c > TEXT(' '));
    		
        if (c == TEXT('\0'))
        {
          p--;
        }
        else
        {
          if (lpstr)
            *(lpstr - 1) = TEXT('\0');
        }
      }
    	
      inquote = 0;
    	
      /* loop on each argument */
      for ( ; ; )
      {
        if (*p)
        {
          while (*p == TEXT(' ') || *p == TEXT('\t'))
    			{
    				++p;
    			}
        }
    		
        if (*p == TEXT('\0'))
    		{
    			break;         /* end of args */
    		}
    		
        /* scan an argument */
        if (argv)
    		{
    			*argv++ = lpstr;     /* store ptr to arg */
    		}
    
        ++*numargs;
    		
        /* loop through scanning one argument */
        for ( ; ; )
        {
          copychar = 1;
          /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
    			2N+1 backslashes + " ==> N backslashes + literal "
    			N backslashes ==> N backslashes */
          numslash = 0;
          while (*p == TEXT('\\'))
          {
            /* count number of backslashes for use below */
            ++p;
            ++numslash;
          }
          if (*p == TEXT('\"'))
          {
    			/* if 2N backslashes before, start/end quote, otherwise
    				copy literally */
            if (numslash % 2 == 0)
            {
              if (inquote)
                if (p[1] == TEXT('\"'))
                  p++;  /* Double quote inside quoted string */
                else    /* skip first quote char and copy second */
                  copychar = 0;
    						else
    							copychar = 0;    /* don't copy quote */
    						
    						inquote = !inquote;
            }
            numslash /= 2;     /* divide numslash by two */
          }
    			
          /* copy slashes */
          while (numslash--)
          {
            if (lpstr)
    				{
    					*lpstr++ = TEXT('\\');
    				}
    
            *numbytes += sizeof(WCHAR);
          }
    			
          /* if at end of arg, break loop */
          if (*p == TEXT('\0') || (!inquote && (*p == TEXT(' ') || *p == TEXT('\t'))))
            break;
    			
          /* copy character into argument */
          if (copychar)
          {
            if (lpstr)
    					*lpstr++ = *p;
            *numbytes += sizeof(WCHAR);
          }
          ++p;
        }
    		
        /* null-terminate the argument */
    		
        if (lpstr)
    		{
    			*lpstr++ = TEXT('\0');     /* terminate string */
    		}
    
        *numbytes += sizeof(WCHAR);
      }
    	
    }
    
    
    /***
    *CommandLineToArgvW - set up Unicode "argv" for C programs
    *
    *Purpose:
    *    Read the command line and create the argv array for C
    *    programs.
    *
    *Entry:
    *    Arguments are retrieved from the program command line
    *
    *Exit:
    *    "argv" points to a null-terminated list of pointers to UNICODE
    *    strings, each of which is an argument from the command line.
    *    The list of pointers is also located on the heap or stack.
    *
    *Exceptions:
    *    Terminates with out of memory error if no memory to allocate.
    *
    *******************************************************************************/
    
    LPWSTR* _CommandLineToArgvW (LPCWSTR lpCmdLine, int*pNumArgs)
    {
      LPWSTR*argv_U;
      LPWSTR cmdstart;         /* start of command line to parse */
      INT   numbytes;
      WCHAR  pgmname[MAX_PATH];
    	
      if (pNumArgs == NULL) {
    		SetLastError(ERROR_INVALID_PARAMETER);
    		return NULL;
      }
    	
      /* Get the program name pointer from Win32 Base */
    	
      GetModuleFileNameW (NULL, pgmname, sizeof(pgmname) / sizeof(WCHAR));
    	
      /* if there's no command line at all (won't happen from cmd.exe, but
    	possibly another program), then we use pgmname as the command line
    	to parse, so that argv[0] is initialized to the program name */
      cmdstart = (LPWSTR)((*lpCmdLine == TEXT('\0')) ? (LPCWSTR)pgmname : lpCmdLine);
    	
      /* first find out how much space is needed to store args */
      Parse_Cmdline (cmdstart, NULL, NULL, pNumArgs, &numbytes);
    	
      /* allocate space for argv[] vector and strings */
    //   argv_U = (LPWSTR*) RtlAllocateHeap(RtlProcessHeap(),0,
    // 		*pNumArgs * sizeof(LPWSTR) + numbytes);
    
    	argv_U = (LPWSTR*)LocalAlloc(0x40, *pNumArgs * sizeof(LPWSTR) + numbytes);
    
      if (!argv_U) 
    	{
    		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return (NULL);
      }
    	
      /* store args and argv ptrs in just allocated block */
      Parse_Cmdline (cmdstart, argv_U,
    		(LPWSTR) (((LPBYTE)argv_U) + *pNumArgs * sizeof(LPWSTR)),
    		pNumArgs, &numbytes);
    	
      return (argv_U);
    }
    
    

     

     

    2010年8月12日 9:52
  • 问题终于找到,元凶在explorer的处理中,详细如下:

    windbg挂接explorer进程后,对SHELL32!ShellExecuteExW设置断点,当选中文件右键发送时,windbg断下,查看调用栈信息:

    0:000> kb
    ChildEBP RetAddr Args to Child       
    0007d5dc 7d6c3238 0007d608 0007eb94 0007eb60 SHELL32!ShellExecuteExW
    0007e8d0 77f97cc7 0012e128 001212f8 00000009 SHELL32!CExeDropTarget::Drop+0x330
    0007e904 7d6da148 0012e128 001212f8 00000009 SHLWAPI!SHSimulateDrop+0x63
    0007eb4c 77f97cc7 000f0048 001212f8 00000009 SHELL32!CShellLink::Drop+0xd8
    0007eb80 7d732170 000f0048 001212f8 00000009 SHLWAPI!SHSimulateDrop+0x63
    0007ebac 7d7321e7 00290080 000f0048 0007eca4 SHELL32!CSendToMenu::_DoDragDrop+0x58
    0007ebc8 7d5e5f3b 000f0048 0007ebe4 00174940 SHELL32!CSendToMenu::InvokeCommand+0x40
    0007ec28 7d5e5e8d 00120b80 0007ec48 00000000 SHELL32!HDXA_LetHandlerProcessCommandEx+0xa5
    0007eeb8 7d675fca 00174940 0007f204 0007f244 SHELL32!CDefFolderMenu::InvokeCommand+0x17f
    0007f1e8 7d678f71 00174940 0007f204 77d2929a SHELL32!CDefView::_InvokeContextMenu+0xb0
    0007f4f0 7d67bda6 00174940 00000010 00000108 SHELL32!CDefView::_DoContextMenuPopup+0x37e
    0007f53c 7d5d20ce 01020108 00000000 00127008 SHELL32!CDefView::ContextMenu+0x1e4
    0007f6b0 7d5c2f38 000a007a 0000007b 000700c0 SHELL32!CDefView::WndProc+0x840
    0007f6f4 77d18734 000a007a 0000007b 000700c0 SHELL32!CDefView::s_WndProc+0x72
    0007f720 77d18816 7d5c2ee2 000a007a 0000007b USER32!InternalCallWinProc+0x28
    0007f788 77d28ea0 0009f1d0 7d5c2ee2 000a007a USER32!UserCallWinProcCheckWow+0x150
    0007f7dc 77d28eec 0064a540 0000007b 000700c0 USER32!DispatchClientMessage+0xa3
    0007f804 7c92e473 0007f814 00000018 0064a540 USER32!__fnDWORD+0x24
    0007f828 77d194be 77d28e0d 000700c0 0000007b ntdll!KiUserCallbackDispatcher+0x13
    0007f87c 77d28dd9 000700c0 0000007b 000700c0 USER32!NtUserMessageCall+0xc
    0007f898 5ad71af6 000700c0 0000007b 000700c0 USER32!RealDefWindowProcW+0x47
    0007f8f0 5ad71b3d 00000000 00000000 000700c0 UxTheme!_ThemeDefWindowProc+0x16e
    0007f90c 77d294ed 000700c0 0000007b 000700c0 UxTheme!ThemeDefWindowProcW+0x18
    0007f954 771d0d82 000700c0 0000007b 000700c0 USER32!DefWindowProcW+0x6b
    0007fac0 77d18734 000700c0 0000007b 000700c0 comctl32!ListView_WndProc+0xf0
    0007faec 77d18816 771d0c92 000700c0 0000007b USER32!InternalCallWinProc+0x28
    0007fb54 77d2927b 0009f1d0 771d0c92 000700c0 USER32!UserCallWinProcCheckWow+0x150
    0007fb90 77d292e3 00656848 0061ad30 000700c0 USER32!SendMessageWorker+0x4a5
    0007fbb0 771d0a7e 000700c0 0000007b 000700c0 USER32!SendMessageW+0x7f
    0007fc30 771d0ab6 000700c0 00000002 00000108 comctl32!ListView_HandleMouse+0x57b
    0007fc50 771d14e9 00116618 00000000 00000108 comctl32!ListView_OnButtonDown+0x1b
    0007fdc0 77d18734 000700c0 00000204 00000002 comctl32!ListView_WndProc+0x857
    0007fdec 77d18816 771d0c92 000700c0 00000204 USER32!InternalCallWinProc+0x28
    0007fe54 77d189cd 0009f1d0 771d0c92 000700c0 USER32!UserCallWinProcCheckWow+0x150
    0007feb4 77d18a10 0007fed4 00000000 0007fef0 USER32!DispatchMessageWorker+0x306
    0007fec4 7d5b7b6d 0007fed4 000eafd8 000700c0 USER32!DispatchMessageW+0xf
    0007fef0 7d5b7a02 7c80934a 000eafd8 000eafd8 SHELL32!CDesktopBrowser::_PeekForAMessage+0x66
    0007ff08 7d5e3a7c 00000000 0007ff5c 01013256 SHELL32!CDesktopBrowser::_MessageLoop+0x14
    0007ff14 01013256 000eafd8 7ffdf000 0007ffc0 SHELL32!SHDesktopMessageLoop+0x24
    0007ff5c 0101a5c7 00000000 00000000 0002097e explorer!ExplorerWinMain+0x2db
    0007ffc0 7c817077 00fc2990 0006e890 7ffdf000 explorer!ModuleEntry+0x6d
    0007fff0 00000000 0101a55f 00000000 78746341 kernel32!BaseProcessStart+0x23
    

    发现SHELL32!CExeDropTarget::Drop函数内部处理了相应的参数,然后调用shellexcuteexW函数启动了对应进程,也就是说问题的根源在于SHELL32!CExeDropTarget::Drop函数中。

    对SHELL32!CExeDropTarget::Drop的执行流程跟踪如下:

    0:000> wt -l 1
    Tracing SHELL32!CExeDropTarget::Drop to return address 77f97cc7
      5   0 [ 0] SHELL32!CExeDropTarget::Drop
      1   0 [ 1]  SHELL32!_chkstk
      16   0 [ 1]  SHELL32!_alloca_probe
      31  17 [ 0] SHELL32!CExeDropTarget::Drop
      33   0 [ 1]  SHELL32!GetAppDropTarget
      58  50 [ 0] SHELL32!CExeDropTarget::Drop
      98   0 [ 1]  SHELL32!CExeDropTarget::_FillSEIFromLinkSite
      73  148 [ 0] SHELL32!CExeDropTarget::Drop
      36   0 [ 1]  SHELL32!CFSIDLData::GetData
      82  184 [ 0] SHELL32!CExeDropTarget::Drop
      38   0 [ 1]  SHELL32!App_IsLFNAware
      92  222 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
      99  234 [ 0] SHELL32!CExeDropTarget::Drop
      21   0 [ 1]  SHLWAPI!PathQuoteSpacesW
     103  255 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  kernel32!lstrlenW
     111  267 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     119  279 [ 0] SHELL32!CExeDropTarget::Drop
      25   0 [ 1]  kernel32!LocalAlloc
     133  304 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     140  316 [ 0] SHELL32!CExeDropTarget::Drop
      21   0 [ 1]  SHLWAPI!PathQuoteSpacesW
     148  337 [ 0] SHELL32!CExeDropTarget::Drop
      23   0 [ 1]  SHLWAPI!StrCatBuffW
     155  360 [ 0] SHELL32!CExeDropTarget::Drop
      12   0 [ 1]  SHELL32!DragQueryFileW
     162  372 [ 0] SHELL32!CExeDropTarget::Drop
    ModLoad: 60820000 60827000  C:\WINDOWS\system32\MSISIP.DLL
    ModLoad: 7e940000 7e956000  C:\WINDOWS\system32\wshext.dll
    ModLoad: 36d30000 36d4a000  C:\PROGRA~1\MICROS~2\OFFICE11\MCPS.DLL
    ModLoad: 038e0000 03905000  C:\Program Files\360\360safe\Antispy.dll
      52   0 [ 1]  SHELL32!ShellExecuteExW
    >> No match on ret
      52   0 [ 1]  SHELL32!ShellExecuteExW
      6   0 [ 1]  SHELL32!_InvokePidl
    >> No match on ret
      6   0 [ 1]  SHELL32!_InvokePidl
      17   0 [ 1]  SHELL32!CShellExecMenu::_InvokeOne
    >> No match on ret
      17   0 [ 1]  SHELL32!CShellExecMenu::_InvokeOne
      17   0 [ 1]  SHELL32!CShellExecMenu::InvokeCommand
    >> No match on ret
      17   0 [ 1]  SHELL32!CShellExecMenu::InvokeCommand
      10   0 [ 1]  SHELL32!HDXA_LetHandlerProcessCommandEx
    >> No match on ret
      10   0 [ 1]  SHELL32!HDXA_LetHandlerProcessCommandEx
      13   0 [ 1]  SHELL32!CDefFolderMenu::InvokeCommand
    >> No match on ret
      13   0 [ 1]  SHELL32!CDefFolderMenu::InvokeCommand
      13   0 [ 1]  SHELL32!_InvokeInProcExec
    >> No match on ret
      13   0 [ 1]  SHELL32!_InvokeInProcExec
      15   0 [ 1]  SHELL32!CShellExecute::_ShellExecPidl
    >> No match on ret
      15   0 [ 1]  SHELL32!CShellExecute::_ShellExecPidl
      8   0 [ 1]  SHELL32!CShellExecute::_DoExecPidl
    >> No match on ret
      8   0 [ 1]  SHELL32!CShellExecute::_DoExecPidl
      8   0 [ 1]  SHELL32!CShellExecute::_TryExecPidl
    >> No match on ret
      8   0 [ 1]  SHELL32!CShellExecute::_TryExecPidl
      26   0 [ 1]  SHELL32!CShellExecute::ExecuteNormal
    >> No match on ret
      26   0 [ 1]  SHELL32!CShellExecute::ExecuteNormal
      11   0 [ 1]  SHELL32!ShellExecuteNormal
    >> No match on ret
      11   0 [ 1]  SHELL32!ShellExecuteNormal
      17   0 [ 1]  SHELL32!ShellExecuteExW
     164  585 [ 0] SHELL32!CExeDropTarget::Drop
      17   0 [ 1]  kernel32!LocalFree
     168  602 [ 0] SHELL32!CExeDropTarget::Drop
      36   0 [ 1]  ole32!ReleaseStgMedium
     175  638 [ 0] SHELL32!CExeDropTarget::Drop
      23   0 [ 1]  SHELL32!CExeDropTarget::_CleanupSEIFromLinkSite
     184  661 [ 0] SHELL32!CExeDropTarget::Drop
      5   0 [ 1]  SHELL32!__security_check_cookie
     186  666 [ 0] SHELL32!CExeDropTarget::Drop
    
    852 instructions were executed in 851 events (0 from other threads)
    
    Function Name                Invocations MinInst MaxInst AvgInst
    SHELL32!App_IsLFNAware                1   38   38   38
    SHELL32!CDefFolderMenu::InvokeCommand         1   13   13   13
    SHELL32!CExeDropTarget::Drop             1   186   186   186
    SHELL32!CExeDropTarget::_CleanupSEIFromLinkSite    1   23   23   23
    SHELL32!CExeDropTarget::_FillSEIFromLinkSite     1   98   98   98
    SHELL32!CFSIDLData::GetData              1   36   36   36
    SHELL32!CShellExecMenu::InvokeCommand         1   17   17   17
    SHELL32!CShellExecMenu::_InvokeOne          1   17   17   17
    SHELL32!CShellExecute::ExecuteNormal         1   26   26   26
    SHELL32!CShellExecute::_DoExecPidl          1    8    8    8
    SHELL32!CShellExecute::_ShellExecPidl         1   15   15   15
    SHELL32!CShellExecute::_TryExecPidl          1    8    8    8
    SHELL32!DragQueryFileW                4   12   12   12
    SHELL32!GetAppDropTarget               1   33   33   33
    SHELL32!HDXA_LetHandlerProcessCommandEx        1   10   10   10
    SHELL32!ShellExecuteExW                2   17   52   34
    SHELL32!ShellExecuteNormal              1   11   11   11
    SHELL32!_InvokeInProcExec               1   13   13   13
    SHELL32!_InvokePidl                  1    6    6    6
    SHELL32!__security_check_cookie            1    5    5    5
    SHELL32!_alloca_probe                 1   16   16   16
    SHELL32!_chkstk                    1    1    1    1
    SHLWAPI!PathQuoteSpacesW               2   21   21   21
    SHLWAPI!StrCatBuffW                  1   23   23   23
    kernel32!LocalAlloc                  1   25   25   25
    kernel32!LocalFree                  1   17   17   17
    kernel32!lstrlenW                   1   12   12   12
    ole32!ReleaseStgMedium                1   36   36   36
    
    0 system calls were executed
    
    eax=00000000 ebx=04a744f0 ecx=0000a398 edx=00000000 esi=0007eb60 edi=0007eb94
    eip=77f97cc7 esp=0007e8f0 ebp=0007e904 iopl=0     nv up ei pl zr na pe nc
    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000       efl=00000246
    SHLWAPI!SHSimulateDrop+0x63:
    77f97cc7 eb09      jmp   SHLWAPI!SHSimulateDrop+0x6e (77f97cd2)

    发现在函数内部调用了SHLWAPI!PathQuoteSpacesW函数来实现了对文件路径是否含有空格进行处理,如果文件路径中有空格,则在文件路径的开头和结尾添加双引号,详细分析了该函数的内部调用,发现,该函数处理长路径名时,出现了问题。贴出函数的汇编代码和IDA的F5代码:

    0:002> uf SHLWAPI!PathQuoteSpacesW
    SHLWAPI!PathQuoteSpacesW:
    77f91097 8bff      mov   edi,edi
    77f91099 55       push  ebp
    77f9109a 8bec      mov   ebp,esp
    77f9109c 56       push  esi
    77f9109d 8b7508     mov   esi,dword ptr [ebp+8]
    77f910a0 85f6      test  esi,esi
    77f910a2 7444      je   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0xd:
    77f910a4 6a20      push  20h
    77f910a6 56       push  esi
    77f910a7 e82156fbff   call  SHLWAPI!StrChrW (77f466cd)
    77f910ac 85c0      test  eax,eax
    77f910ae 7438      je   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0x19:
    77f910b0 56       push  esi
    77f910b1 ff157014f477  call  dword ptr [SHLWAPI!_imp__lstrlenW (77f41470)]
    77f910b7 40       inc   eax
    77f910b8 8d4801     lea   ecx,[eax+1]
    77f910bb 81f904010000  cmp   ecx,104h
    77f910c1 7d25      jge   SHLWAPI!PathQuoteSpacesW+0x51 (77f910e8)
    
    SHLWAPI!PathQuoteSpacesW+0x2c:
    77f910c3 57       push  edi
    77f910c4 8d3c00     lea   edi,[eax+eax]
    77f910c7 57       push  edi
    77f910c8 8d4602     lea   eax,[esi+2]
    77f910cb 56       push  esi
    77f910cc 50       push  eax
    77f910cd e8cf83fbff   call  SHLWAPI!memmove (77f494a1)
    77f910d2 8d0437     lea   eax,[edi+esi]
    77f910d5 83c40c     add   esp,0Ch
    77f910d8 66c7062200   mov   word ptr [esi],22h
    77f910dd 6683600200   and   word ptr [eax+2],0
    77f910e2 66c7002200   mov   word ptr [eax],22h
    77f910e7 5f       pop   edi
    
    SHLWAPI!PathQuoteSpacesW+0x51:
    77f910e8 5e       pop   esi
    77f910e9 5d       pop   ebp
    77f910ea c20400     ret   4

     IDA F5代码:

    BOOL __stdcall PathQuoteSpacesW(LPWSTR lpsz)
    {
     int v1; // edi@4
     BOOL result; // eax@5
    
     if ( lpsz )
     {
      result = (BOOL)StrChrW(lpsz, 0x20u);
      if ( result )
      {
       result = lstrlenW(lpsz) + 1;
       if ( result + 1 < 260 )
       {
        v1 = 2 * result;
        memmove(lpsz + 1, lpsz, 2 * result);
        result = (BOOL)((char *)lpsz + v1);
        *lpsz = 34;
        *(LPWSTR)((char *)lpsz + v1 + 2) = 0;
        *(LPWSTR)((char *)lpsz + v1) = 34;
       }
      }
     }
     return result;
    }
    
    
    

     分析到这儿,答案也就出来了。

    原因就在于 微软判断的时候,如果文件长度最大长度为MAX_PATH时,如果此时需要在两端添加双引号的话,仍然判断MAX_PATH为最大值的话,岂不是出问题了? 丢失了2个双引号啊。

    • 已建议为答案 xiaohuazi 2010年8月14日 6:19
    • 已标记为答案 lifestar2008 2010年8月16日 2:08
    2010年8月14日 6:18
  • 谢谢,xiaohuazi 答复,微软的Bug

    2010年8月16日 2:10