vs2005无法启动分析笔记
2022-3-19 16:12:13 Author: mp.weixin.qq.com(查看原文) 阅读量:1 收藏

由于工作需要,还在用vs2005这个老古董,虽然很不喜欢。

虽然很轻,但有两个原因不喜欢:

  1. 调试总要加载符号,不让加非加,慢的无语

  2. 时不时总是无缘无故无法启动

无法启动这个事已经无数次出现了,重装,重启,屏蔽Assist均是无效。

后来无意间点击了C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE\devenv.com,可以启动了。

但这次这个方法也不行了,实在是忍无可忍。

决定干它。

上调试器,启动devenv.exe

看到崩溃原因 c00000fd,就是栈溢出,看来应该是函数调用无限循环了。

不要问我为啥知道,因为之前遇到过。

看看栈,果然如此,user32->msdev!xxx->user32->msdev!xxx->...

0:000:x86> kn 10 # ChildEBP RetAddr  00 000a3048 76cb9cae ntdll_77660000!RtlActivateActivationContextUnsafeFast+0x1801 000a3118 76cb9577 USER32!UserCallWinProcCheckWow+0x14e02 000a3150 76cb771b USER32!CallWindowProcAorW+0x7f03 000a3168 059dea67 USER32!CallWindowProcW+0x1b //5007ea6704 000a3188 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x2905 000a31a4 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a06 000a31d0 76cb9e9a USER32!_InternalCallWinProc+0x2b07 000a32b4 76cb9577 USER32!UserCallWinProcCheckWow+0x33a08 000a32ec 76cb771b USER32!CallWindowProcAorW+0x7f09 000a3304 059dea67 USER32!CallWindowProcW+0x1b0a 000a3324 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x290b 000a3340 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a0c 000a336c 76cb9e9a USER32!_InternalCallWinProc+0x2b0d 000a3450 76cb9577 USER32!UserCallWinProcCheckWow+0x33a0e 000a3488 76cb771b USER32!CallWindowProcAorW+0x7f0f 000a34a0 059dea67 USER32!CallWindowProcW+0x1b...

打开IDA,简单看看msdev

int __userpurge CAutoCompletionManagerCL::TargetSubclassProcSTATIC@<eax>(int a1@<esi>, HWND a2, unsigned int a3, unsigned int a4, unsigned int a5){  CAutoCompletionManagerCL *v5; // ecx  int result; // eax
if ( GetPropW(a2, L"MSENV_ACMgr") ) result = CAutoCompletionManagerCL::TargetSubclassProc(v5, a2, a4, a5, a1); else result = DefWindowProcAW(a2, a3, a4, a5); return result;}
text:5007EA3A 000 push ebp.text:5007EA3B 004 mov ebp, esp.text:5007EA3D 004 cmp ebx, 0Eh.text:5007EA40 004 push edi.text:5007EA41 008 jnz loc_500FEDCF.text:5007EA47 008 mov dword ptr [esi+34h], 1.text:5007EA4E.text:5007EA4E loc_5007EA4E: ; CODE XREF: CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+803C6↓j.text:5007EA4E ; CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+17B9CA↓j ....text:5007EA4E 008 mov eax, [esi+30h] //=0 不会再调用CallWindowProcAW.text:5007EA51 008 xor edi, edi.text:5007EA53 008 test eax, eax.text:5007EA55 008 jz short loc_5007EA69.text:5007EA57 008 push [ebp+arg_8].text:5007EA5A 00C push [ebp+arg_4].text:5007EA5D 010 push ebx.text:5007EA5E 014 push [ebp+arg_0].text:5007EA61 018 push eax //调用原始函数.text:5007EA62 01C call ?CallWindowProcAW@@YGJP6GJPAUHWND__@@IIJ@Z0IIJ@Z ; CallWindowProcAW(long (*)(HWND__ *,uint,uint,long),HWND__ *,uint,uint,long).text:5007EA67 008 mov edi, eax.text:5007EA69.text:5007EA69 loc_5007EA69: ; CODE XREF: CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+1B↑j.text:5007EA69 008 cmp ebx, 82h.text:5007EA6F 008 jz loc_501B3877.text:5007EA75

CAutoCompletionManagerCL::TargetSubclassProcSTATIC应该是个wndproc,本来逻辑应该是在内部CallWindowProcAW调用原始wndproc,但是CallWindowProcAW又调用了CAutoCompletionManagerCL::TargetSubclassProcSTATIC,这样死循环,一直到栈移除。

看看CAutoCompletionManagerCL::TargetSubclassProcSTATIC是哪里设置的,找到了:

int __userpurge CAutoCompletionManagerCL::AttachToCombo@<eax>(CAutoCompletionManagerCL *this@<ecx>, int a2@<eax>, struct IMsoControl *a3, HWND a4){  result = CAutoCompletionManager::OnCreate(this); //创建控件  if ( result >= 0 && !result )  {       v11 = (HWND)*((_DWORD *)this + 10);    if ( v6 )      v7 = SetWindowLongW(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);    else      v7 = SetWindowLongA(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);    v13 = (HWND)*((_DWORD *)this + 10);    *((_DWORD *)this + 12) = v7; //+30 = 1
   

所以问题应该基本清晰了,SetWindowLongW(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);被重复设置了,*((_DWORD *)this + 12) = v7,保存的值被覆盖成了CAutoCompletionManagerCL::TargetSubclassProcSTATIC

所以这样死循环,导致调用栈溢出

第一次进入msenv!CAutoCompletionManagerCL::AttachToCombo设置SetWindowLongW返回就已经是msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC

赋值之前看到原始地址:

msenv!CAutoCompletionManagerCL::AttachToCombo+0x60:05910cb5 894330          mov     dword ptr [ebx+30h],eax ds:002b:06e03228={msenv!_FakeEditProc (0596eab8)}

猜测CAutoCompletionManager::OnCreate中就已经调用SetWindowLongW了

尝试使用条件断点看看能不能找到。

0:000:x86> u CAutoCompletionManagerCL::TargetSubclassProcSTATICmsenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC:05aeea06 55              push    ebp0:000:x86> bc 10:000:x86> bp USER32!SetWindowLongW ".if(poi(esp+c)==05aeea06 ){}.else{g};"

断下后,确认函数没问题,看看调用栈。

可以看到msenv!CAutoCompletionManagerCL::VerifyAttachmentOnCreate内部设置了一次。

USER32!SetWindowLongW:76cb7730 8bff            mov     edi,edi0:000:x86> dd esp00194a2c  05c6a3cc 00093096 fffffffc 05aeea0600194a3c  07747120 00000000 05aeec46 00000000
0:000:x86> kn # ChildEBP RetAddr 00 00194a28 05c6a3cc USER32!SetWindowLongW01 00194a40 05aeec46 msenv!CAutoCompletionManagerCL::VerifyAttachment+0x6902 00194a50 05aee7ca msenv!CAutoCompletionManagerCL::VerifyAllAttachments+0x3103 00194a70 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x3f04 00194abc 05ae1ceb msenv!CMsoDropdownUser::GetText+0x12f...18 001961b0 76cc2edb msenv!TBWndWindowProc+0x6519 001961dc 76cb9e9a USER32!_InternalCallWinProc+0x2b1a 001962c0 76cb9a9a USER32!UserCallWinProcCheckWow+0x33a...49 00198424 05a90d54 msenv!CVsAutoCompletion::InitUI+0x2514a 00198440 05a90c6b msenv!CAutoCompletionManager::OnCreate+0x58 //这里是创建4b 00198448 05a90ae1 msenv!CAutoCompletionManagerCL::AttachToCombo+0x124c 00198468 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x95 4d 001984b0 05a90a72 msenv!CMsoDropdownUser::GetText+0x12f4e 001984c8 05a9096b msenv!TBCDD::ResetEditText+0x124f 0019c6d8 05b018aa msenv!TBCDD::FDraw+0x68450 0019c84c 05b013cc msenv!TB::FDraw+0xbb751 0019c8c8 76cc2edb msenv!TBWndProc+0xfa52 0019c8f4 76cb9e9a USER32!_InternalCallWinProc+0x2b
int __usercall CAutoCompletionManagerCL::VerifyAttachment@<eax>(int a1@<esi>){
v7 = *(HWND *)(a1 + 40); if ( v4 ) v5 = SetWindowLongW(v7, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC); else v5 = SetWindowLongA(v7, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC); *(_DWORD *)(a1 + 48) = v5; }}

所以应该是msevn逻辑出现了问题,应该加上控件创建成功后,才能SetWindowsLong,或者直接在SetWindowLong之前判断是否已经设置过。

我可以给他改改代码,但是挺麻烦的。

再看看是不是有什么可以控制的条件,有更简单的修改方法。

看看CAutoCompletionManagerCL::AttachToCombo被谁调用了:

Breakpoint 0 hitmsenv!CAutoCompletionManagerCL::AttachToCombo:03a10c59 53              push    ebx0:000:x86> kn # ChildEBP RetAddr  00 00198448 03a10ae1 msenv!CAutoCompletionManagerCL::AttachToCombo01 00198468 03a62021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x9502 001984b0 03a10a72 msenv!CMsoDropdownUser::GetText+0x12f03 001984c8 03a1096b msenv!TBCDD::ResetEditText+0x1204 0019c6d8 03a818aa msenv!TBCDD::FDraw+0x68405 0019c84c 03a813cc msenv!TB::FDraw+0xbb706 0019c8c8 76cc2edb msenv!TBWndProc+0xfa

看看CAutoCompletionManagerCL::UseAutocompletion的逻辑:

int __userpurge CAutoCompletionManagerCL::UseAutocompletion@<eax>(CVSShellMenu *a1@<ecx>, struct IMsoControl *a2@<edi>, struct IMsoControl *a3){  v6 = 0;  if ( !CAutoCompletionManagerCL::ms_picLastUsed )    CAutoCompletionManagerCL::ms_picLastUsed = a3;  if ( CCmdWindow::ms_fEnableAutocompletion ) //这个状态为0,应该就不会出现了  {    v7 = CVSShellMenu::GetHwndByPic(a1, a2);    v10 = (struct IMsoControl *)v7;    if ( !v7 )      return 1;    CAutoCompletionManagerCL::VerifyAllAttachments(a3); //重复设置的点    if ( CAutoCompletionManagerCL::GetManager(a3, v7) )      return v6;    if ( CAutoCompletionManagerCL::GetManagerByTargetHwnd(v7) )      return 1;    v6 = ATL::CComObject<CAutoCompletionManagerCL>::CreateInstance(&hData);    if ( v6 >= 0 )    {      x = hData;      (*(void (__stdcall **)(HANDLE))(*(_DWORD *)hData + 4))(hData);      v4 = dword_5074D4FC;      if ( safe::CEnvArray<CAutoCompletionManagerCL *,CAutoCompletionManagerCL *>::SetSize() )      {        *((_DWORD *)CAutoCompletionManagerCL::ms_rgManagers + v4) = x;        v6 = CAutoCompletionManagerCL::AttachToCombo(x, (int)a3, v10, v9); //初始化        if ( v6 )          CAutoCompletionManagerCL::OnDestroy(v5);      }      else      {        v6 = -2147024882;      }    }  }  return v6;}

猜测跟代码自动完成有关,这个CCmdWindow::ms_fEnableAutocompletion变量好像可以弄弄,如果设置为假,就完全不会进入后面的逻辑。

现在看看CCmdWindow::ms_fEnableAutocompletion在哪里设置的。

void __stdcall PrefInitPart2(){   if ( GetUserOption(0, L"CommandWindowAutocompletion", Data, 4u, (unsigned int)v72) < 0 )    *(_DWORD *)Data = 1; //没配置默认为1  v32 = 0;  hKey = *(HKEY *)Data;  CCmdWindow::ms_fEnableAutocompletion = *(_DWORD *)Data; //设置状态

好像有戏,CommandWindowAutocompletion是用户配置相关的。CommandWindowAutocompletion未配置默认开启ms_fEnableAutocompletion

int __userpurge GetUserOption@<eax>(int a1@<eax>, LPCWSTR lpValueName, LPBYTE lpData, DWORD cbData, unsigned int a5){
hKey = 0; if ( a1 ) VBRegOpenTwoKeysFromRoot( HKEY_CURRENT_USER, (const unsigned __int16 *)a1, L"General", (HKEY)&hKey, v7, v10, (unsigned int)v11, (HKEY *)hKey); else VBRegOpenKeyFromRoot(&hKey, HKEY_CURRENT_USER, L"General", (HKEY)0x20019, v7, (unsigned int)v10, v11);//a1=0 v5 = HrFromRegError(v8); if ( v5 >= 0 ) { RegQueryValueExW(hKey, lpValueName, 0, 0, lpData, &cbData); v5 = HrFromRegError(v9); if ( hKey ) RegCloseKey(hKey); } return v5;}int __userpurge VBRegOpenTwoKeysFromRoot@<eax>(HKEY a1@<ebx>, const unsigned __int16 *a2@<edi>, const unsigned __int16 *a3@<esi>, HKEY a4, const unsigned __int16 *a5, const unsigned __int16 *a6, unsigned int a7, HKEY *a8){
if ( !g_pMainParam || (v7 = (const wchar_t *)*((_DWORD *)g_pMainParam + 31)) == 0 || !*v7 ) v7 = L"Software\\Microsoft\\VisualStudio\\8.0";}
0:000:x86> pmsenv!VBRegOpenKeyFromRoot+0xed:05a54d9d ff1550109205 call dword ptr [msenv!_imp__RegOpenKeyExW (05921050)] ds:002b:05921050={AcLayers!NS_VirtualRegistry::APIHook_RegOpenKeyExW (68d83ce0)}0:000:x86> dd esp0019d300 80000001 0019d31c 00000000 000200190019d310 0019d314 0019d364 000001e4 006f00530019d320 00740066 00610077 00650072 004d005c0019d330 00630069 006f0072 006f0073 007400660019d340 0056005c 00730069 00610075 0053006c0019d350 00750074 00690064 005c006f 002e00380019d360 005c0030 00650047 0065006e 006100720019d370 0000006c 007c0cf0 baadf00d 007ed3e00:000:x86> du 0019d31c 0019d31c "Software\Microsoft\VisualStudio\"0019d35c "8.0\General"

通过调试确认,最终找到配置位置:

计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\8.0\GeneralCommandWindowAutocompletion = 0;REG_DWORD

增加注册表CommandWindowAutocompletion,设置为0,这样ms_fEnableAutocompletion就是0,问题解决。

看名字这个有点像代码自动完成功能的,但不知道ms_fEnableAutocompletion配置为0之后会不会影响该功能。

哈哈,经过验证没有影响,终于又可以正常使用这个老古董了。

(完)


文章来源: https://mp.weixin.qq.com/s?__biz=MzU4NjY5ODM3Nw==&mid=2247483832&idx=1&sn=b5e983ba6ba951b580fdd6cf7d44373e&chksm=fdf61a5eca819348abfffd0c0d779cb0ed2f617aa7cd60b4a8a359b7d49e3470a30616be39f0&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh