Windows `Edit` 控制項設定 `ES_PASSWORD` 樣式僅在視覺上以 `*` 遮罩字元,實際記憶體仍保存明文。惡意程式可透過 `SendMessage(hwnd, WM_GETTEXT, ...)` 或 `EM_GETLINE` 跨程式竊取密碼。本文實作 `CPasswordEdit` 繼承 `CEdit`,覆寫 `DefWindowProc` 攔截敏感訊息,**驗證呼叫者身分**(全域旗標 `g_bSenderLegit`),僅允許本程式合法呼叫(如 `DoDataExchange` 取值)通過,其他一律攔截並彈出警告。
核心漏洞:`Edit` 控制項**不驗證訊息發送者身分**,任何擁有句柄的程序均可讀取。
// 全域旗標:標記當前訊息是否來自合法內部呼叫
BOOL g_bSenderLegit = FALSE;
class CPasswordEdit : public CEdit
{
DECLARE_DYNAMIC(CPasswordEdit)
public:
CPasswordEdit();
virtual ~CPasswordEdit();
protected:
// 核心:攔截視窗程序
virtual LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
LRESULT CPasswordEdit::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// 僅攔截讀取文字的兩個訊息
if (message == WM_GETTEXT || message == EM_GETLINE)
{
if (!g_bSenderLegit)
{
// 非法讀取:記錄、警告、返回 0(表示無文字)
AfxMessageBox(_T("安全警告:偵測到嘗試竊取密碼的操作!"));
return 0;
}
// 合法讀取:用完即刻重置旗標,防止後續濫用
g_bSenderLegit = FALSE;
}
return CEdit::DefWindowProc(message, wParam, lParam);
}
class CLoginDlg : public CDialog
{
CPasswordEdit m_edtPassword;
CString m_strPassword;
// DDX 交換時設定旗標
virtual void DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
if (pDX->m_bSaveAndValidate) // 從控制項讀取資料時
{
g_bSenderLegit = TRUE; // 標記合法
}
DDX_Text(pDX, IDC_EDIT_PASSWORD, m_strPassword);
}
BOOL OnInitDialog()
{
CDialog::OnInitDialog();
// 動態子類化關聯
m_edtPassword.SubclassDlgItem(IDC_EDIT_PASSWORD, this);
return TRUE;
}
};
全域旗標在多執行緒/多密碼框場景下有競態風險。改用 `GetWindowThreadProcessId` 驗證呼叫者是否屬於同一進程/執行緒:
LRESULT CPasswordEdit::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_GETTEXT || message == EM_GETLINE)
{
DWORD dwCallerProcId = GetCurrentProcessId(); // 實際需從訊息來源取得,這裡簡化
// 更嚴格:檢查發送訊息的執行緒是否為 UI 執行緒
// 可用 Hook 或 WH_GETMESSAGE 記錄發送者,這裡示範旗標法
if (!g_bSenderLegit) { AfxMessageBox(_T("非法讀取")); return 0; }
g_bSenderLegit = FALSE;
}
return CEdit::DefWindowProc(message, wParam, lParam);
}
真正的跨程式 `SendMessage` 會在**目標執行緒**內執行 `DefWindowProc`,`GetCurrentProcessId()` 永遠返回目標進程 ID,無法直接區分發送者。旗標法利用「只有我們自己的代碼會在呼叫 `DDX_Text` 前設 `TRUE`」這一事實,工程上足夠實用。
`CPasswordEdit` 以覆寫 `DefWindowProc` + 全域身分旗標極低成本封鎖 `WM_GETTEXT`/`EM_GETLINE` 跨程式竊取。雖非絕對安全(記憶體掃描、鍵盤記錄器、DLL 注入仍可繞過),但能有效阻擋絕大多數腳本級/工具級密碼竊取器。配合 `ES_PASSWORD` 視覺遮罩、禁用複製、記憶體加密儲存、輸入法管理,構建多層防護。現代新專案建議優先採用系統級憑證 UI 或 Web 驗證,減少自建敏感輸入框風險。