积极答复者
发现一个问题,不知道是不是微软的BUG

问题
-
我测试的环境是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"这里有其它的回复过的,是我发错了地方,才转到这里来了。
答案
-
问题终于找到,元凶在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
全部回复
-
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; }
-
<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); }
-
问题终于找到,元凶在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