首页
社区
课程
招聘
[原创]逆向分析:修复老古董vs2005笔记
2022-3-19 16:32 11563

[原创]逆向分析:修复老古董vs2005笔记

2022-3-19 16:32
11563

由于工作需要,还在用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->...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:000:x86> kn 10
 # ChildEBP RetAddr 
00 000a3048 76cb9cae ntdll_77660000!RtlActivateActivationContextUnsafeFast+0x18
01 000a3118 76cb9577 USER32!UserCallWinProcCheckWow+0x14e
02 000a3150 76cb771b USER32!CallWindowProcAorW+0x7f
03 000a3168 059dea67 USER32!CallWindowProcW+0x1b //5007ea67
04 000a3188 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x29
05 000a31a4 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a
06 000a31d0 76cb9e9a USER32!_InternalCallWinProc+0x2b
07 000a32b4 76cb9577 USER32!UserCallWinProcCheckWow+0x33a
08 000a32ec 76cb771b USER32!CallWindowProcAorW+0x7f
09 000a3304 059dea67 USER32!CallWindowProcW+0x1b
0a 000a3324 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x29
0b 000a3340 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a
0c 000a336c 76cb9e9a USER32!_InternalCallWinProc+0x2b
0d 000a3450 76cb9577 USER32!UserCallWinProcCheckWow+0x33a
0e 000a3488 76cb771b USER32!CallWindowProcAorW+0x7f
0f 000a34a0 059dea67 USER32!CallWindowProcW+0x1b
...

打开IDA,简单看看msenv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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是哪里设置的,找到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

 

赋值之前看到原始地址:

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

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

 

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

1
2
3
4
5
0:000:x86> u CAutoCompletionManagerCL::TargetSubclassProcSTATIC
msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC:
05aeea06 55              push    ebp
0:000:x86> bc 1
0:000:x86> bp USER32!SetWindowLongW ".if(poi(esp+c)==05aeea06 ){}.else{g};"

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
USER32!SetWindowLongW:
76cb7730 8bff            mov     edi,edi
0:000:x86> dd esp
00194a2c  05c6a3cc 00093096 fffffffc 05aeea06
00194a3c  07747120 00000000 05aeec46 00000000
 
0:000:x86> kn
 # ChildEBP RetAddr 
00 00194a28 05c6a3cc USER32!SetWindowLongW
01 00194a40 05aeec46 msenv!CAutoCompletionManagerCL::VerifyAttachment+0x69
02 00194a50 05aee7ca msenv!CAutoCompletionManagerCL::VerifyAllAttachments+0x31
03 00194a70 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x3f
04 00194abc 05ae1ceb msenv!CMsoDropdownUser::GetText+0x12f
...
18 001961b0 76cc2edb msenv!TBWndWindowProc+0x65
19 001961dc 76cb9e9a USER32!_InternalCallWinProc+0x2b
1a 001962c0 76cb9a9a USER32!UserCallWinProcCheckWow+0x33a
...
49 00198424 05a90d54 msenv!CVsAutoCompletion::InitUI+0x251
4a 00198440 05a90c6b msenv!CAutoCompletionManager::OnCreate+0x58 //这里是创建
4b 00198448 05a90ae1 msenv!CAutoCompletionManagerCL::AttachToCombo+0x12
4c 00198468 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x95
4d 001984b0 05a90a72 msenv!CMsoDropdownUser::GetText+0x12f
4e 001984c8 05a9096b msenv!TBCDD::ResetEditText+0x12
4f 0019c6d8 05b018aa msenv!TBCDD::FDraw+0x684
50 0019c84c 05b013cc msenv!TB::FDraw+0xbb7
51 0019c8c8 76cc2edb msenv!TBWndProc+0xfa
52 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被谁调用了:

1
2
3
4
5
6
7
8
9
10
11
12
Breakpoint 0 hit
msenv!CAutoCompletionManagerCL::AttachToCombo:
03a10c59 53              push    ebx
0:000:x86> kn
 # ChildEBP RetAddr 
00 00198448 03a10ae1 msenv!CAutoCompletionManagerCL::AttachToCombo
01 00198468 03a62021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x95
02 001984b0 03a10a72 msenv!CMsoDropdownUser::GetText+0x12f
03 001984c8 03a1096b msenv!TBCDD::ResetEditText+0x12
04 0019c6d8 03a818aa msenv!TBCDD::FDraw+0x684
05 0019c84c 03a813cc msenv!TB::FDraw+0xbb7
06 0019c8c8 76cc2edb msenv!TBWndProc+0xfa

看看CAutoCompletionManagerCL::UseAutocompletion的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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在哪里设置的。

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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> p
msenv!VBRegOpenKeyFromRoot+0xed:
05a54d9d ff1550109205    call    dword ptr [msenv!_imp__RegOpenKeyExW (05921050)] ds:002b:05921050={AcLayers!NS_VirtualRegistry::APIHook_RegOpenKeyExW (68d83ce0)}
0:000:x86> dd esp
0019d300  80000001 0019d31c 00000000 00020019
0019d310  0019d314 0019d364 000001e4 006f0053
0019d320  00740066 00610077 00650072 004d005c
0019d330  00630069 006f0072 006f0073 00740066
0019d340  0056005c 00730069 00610075 0053006c
0019d350  00750074 00690064 005c006f 002e0038
0019d360  005c0030 00650047 0065006e 00610072
0019d370  0000006c 007c0cf0 baadf00d 007ed3e0
0:000:x86> du 0019d31c
0019d31c  "Software\Microsoft\VisualStudio\"
0019d35c  "8.0\General"

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

1
2
计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\8.0\General
CommandWindowAutocompletion = 0;REG_DWORD

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

 

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

 

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

 

(完)


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2022-3-19 16:50 被anhkgg编辑 ,原因:
收藏
点赞10
打赏
分享
最新回复 (3)
雪    币: 222
活跃值: (1846)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lhglhg 1 2022-3-19 21:08
2
0
学习了.
雪    币: 3350
活跃值: (3372)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2022-3-25 00:04
4
0
感谢分享!
游客
登录 | 注册 方可回帖
返回