今天下午花了很久去调试这个bug,因为自己一个dll函数参数的疏忽,导致浪费很多的时间去调试。
这个参数直接使用了CString类型,后面会造成两次析构而引发ESP出错,直接崩掉...
回来的时候,抽空分析了一下,大致过程是这样的,先写个简单的伪代码来调试吧:
Dll源码:
- //
- // MyDll.h
- #include <afx.h>
- #ifndef MY_API
- #define MY_API _declspec(dllimport)
- #endif
- CString g_strTitle = "";
- MY_API void MySetTitle(CString strTitle);
- MY_API CString MyGetTitle();
- //
- // MyDll.cpp
- #include "MyDll.h"
- void MySetTitle(CString strTitle)
- {
- g_strTitle = strTitle;
- }
- CString MyGetTitle()
- {
- return g_strTitle;
- }
主工程为对话框,添加两个按钮,消息响应如下:
- void CTestDlg::OnBtnDefault()
- {
- // TODO: Add your control notification handler code here
- CString str = "Hello World !";
- }
- void CTestDlg::OnBtnTest()
- {
- // TODO: Add your control notification handler code here
- CString str = "Hello World !";
- MySetTitle(str);
- str = MyGetTitle();
- }
点击测试,再关闭队话框,即出现常见的对话框(如果在调试时,将会出现上图结果):
Microsoft Visual C++ Debug Library:
Debug Error:
Program: ...
Module:
file: i386/chkesp.c
Line: xx
The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
(Press Retry to Debug the Application)
于是载入IDA,查看两个函数反汇编代码:
- ; Attributes: bp-based frame
- ; protected: void __thiscall CTestDlg::OnBtnDefault(void)
- ?OnBtnDefault@CTestDlg@@IAEXXZ proc near
- var_48= byte ptr -48h
- var_8= byte ptr -8
- var_4= dword ptr -4
- push ebp
- mov ebp, esp
- sub esp, 48h
- push ebx
- push esi
- push edi
- push ecx
- lea edi, [ebp+var_48]
- mov ecx, 12h
- mov eax, 0CCCCCCCCh
- rep stosd
- pop ecx
- mov [ebp+var_4], ecx
- push offset aHelloWorld ; "Hello World !"
- lea ecx, [ebp+var_8]
- call ??0CString@@QAE@PBD@Z ; CString::CString(char const *) ; 构造函数
- lea ecx, [ebp+var_8]
- call ??1CString@@QAE@XZ ; CString::~CString(void) ; 调用析构函数,这里是正常的
- pop edi
- pop esi
- pop ebx
- add esp, 48h
- cmp ebp, esp
- call __chkesp
- mov esp, ebp
- pop ebp
- retn
- ?OnBtnDefault@CTestDlg@@IAEXXZ endp
- ; Attributes: bp-based frame
- ; protected: void __thiscall CTestDlg::OnBtnTest(void)
- ?OnBtnTest@CTestDlg@@IAEXXZ proc near
- var_68= byte ptr -68h
- var_28= dword ptr -28h
- var_24= dword ptr -24h
- var_20= dword ptr -20h
- var_1C= byte ptr -1Ch
- var_18= dword ptr -18h
- var_14= byte ptr -14h
- var_10= dword ptr -10h
- var_C= dword ptr -0Ch
- var_4= dword ptr -4
- push ebp
- mov ebp, esp
- push 0FFFFFFFFh
- push offset __ehhandler$?OnBtnTest@CTestDlg@@IAEXXZ
- mov eax, large fs:0
- push eax
- mov large fs:0, esp
- sub esp, 5Ch
- push ebx
- push esi
- push edi
- push ecx
- lea edi, [ebp+var_68]
- mov ecx, 17h
- mov eax, 0CCCCCCCCh
- rep stosd
- pop ecx
- mov [ebp+var_10], ecx
- push offset aHelloWorld ; "Hello World !"
- lea ecx, [ebp+var_14]
- call ??0CString@@QAE@PBD@Z ; CString::CString(char const *)
- mov [ebp+var_4], 0
- push ecx
- mov ecx, esp
- mov [ebp+var_18], esp
- lea eax, [ebp+var_14]
- push eax
- call ??0CString@@QAE@ABV0@@Z ; CString::CString(CString const &) ; 构造函数
- mov [ebp+var_20], eax
- call ds:__imp_?MySetTitle@@YAXVCString@@@Z ; MySetTitle(CString)
- add esp, 4
- mov esi, esp
- lea ecx, [ebp+var_1C]
- push ecx
- call ds:__imp_?MyGetTitle@@YA?AVCString@@XZ ; MyGetTitle(void)
- add esp, 4
- cmp esi, esp
- call __chkesp
- mov [ebp+var_24], eax
- mov edx, [ebp+var_24]
- mov [ebp+var_28], edx
- mov byte ptr [ebp+var_4], 1
- mov eax, [ebp+var_28]
- push eax
- lea ecx, [ebp+var_14]
- call ??4CString@@QAEABV0@ABV0@@Z ; CString::operator=(CString const &); 这里拷贝构造函数,所以才引起两次析构造
- mov byte ptr [ebp+var_4], 0
- lea ecx, [ebp+var_1C]
- call ??1CString@@QAE@XZ ; CString::~CString(void) ; 第一次调用析构函数
- mov [ebp+var_4], 0FFFFFFFFh
- lea ecx, [ebp+var_14]
- call ??1CString@@QAE@XZ ; CString::~CString(void) ; 第二次调用析构函数
- mov ecx, [ebp+var_C]
- mov large fs:0, ecx
- pop edi
- pop esi
- pop ebx
- add esp, 68h
- cmp ebp, esp
- call __chkesp
- mov esp, ebp
- pop ebp
- retn
- ?OnBtnTest@CTestDlg@@IAEXXZ endp
于是,这个bug就立刻展现眼前,原来是CString拷贝构造函数引起两次析构函数,而造成ESP出错!于是改这个bug也很简单了,在参数前加个&即可。感谢法师,加一个&符号即可修正之.....
突然,才发现MS SDK的MFC类库函数也很少用到这个类型的参数,只是有很少的一部分也只是加上&引用使用。
所以,建议大家在Dll中尽量不要直接使用CString作为参数,可以直接使用LPCSTR替代吧!
配套源码:http://download.csdn.net/source/2372247
另外,送一个相关的超级传送门...
- 上一篇:“蛋定”大叔...
- 下一篇:金山网盾爆出本地提权漏洞