Windows 編輯控件設定 ES_PASSWORD 樣式後,螢幕顯示為星號,但記憶體中仍保存明文。任何進程若取得該視窗控制代碼(HWND),即可透過 WM_GETTEXT 或 EM_GETLINE 直接讀取密碼。這是眾多「顯密碼」工具的核心原理。
WindowFromPoint() 獲取 HWNDGetClassName() 確認為 EDIT 類別GetWindowLong(GWL_STYLE) 驗證 ES_PASSWORD 位元SendMessage(hwnd, WM_GETTEXT, ...) 讀取明文全程無身分驗證,任何進程皆可為之。
核心思路:在 DefWindowProc 中攔截敏感訊息,僅允許自家進程合法呼叫。
class CPasswordEdit : public CEdit {
DECLARE_DYNAMIC(CPasswordEdit)
public:
static BOOL g_bSenderIdentity; // 全域身分旗標
protected:
virtual LRESULT DefWindowProc(UINT msg, WPARAM wp, LPARAM lp);
};
BOOL CPasswordEdit::g_bSenderIdentity = FALSE;
LRESULT CPasswordEdit::DefWindowProc(UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_GETTEXT || msg == EM_GETLINE) {
if (!g_bSenderIdentity) {
AfxMessageBox(_T("偵測到非法讀取密碼嘗試!"));
return 0; // 拒絕服務
}
g_bSenderIdentity = FALSE; // 一次性授權
}
return CEdit::DefWindowProc(msg, wp, lp);
}
// 成員變數
CPasswordEdit m_edtPassword;
// OnInitDialog 子類化
m_edtPassword.SubclassDlgItem(IDC_EDIT_PASSWORD, this);
// DoDataExchange 僅在合法取值時置旗
void CDlgInput::DoDataExchange(CDataExchange* pDX) {
if (pDX->m_bSaveAndValidate) // UpdateData(TRUE) 時
CPasswordEdit::g_bSenderIdentity = TRUE;
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_PASSWORD, m_sPassword);
}
若惡意程式同樣子類化並過濾 WM_GETTEXT,防禦方可動態替換視窗程序:
// 建立標準 EDIT 取得原始 WndProc
HWND hTmp = CreateWindow(_T("EDIT"), NULL, WS_CHILD|ES_PASSWORD,
0,0,0,0, GetParent(hTarget), (HMENU)123,
(HINSTANCE)GetWindowLong(hTarget, GWL_HINSTANCE), NULL);
LONG_PTR pStdProc = GetWindowLongPtr(hTmp, GWLP_WNDPROC);
DestroyWindow(hTmp);
// 熱換目標視窗程序
LONG_PTR pOld = SetWindowLongPtr(hTarget, GWLP_WNDPROC, pStdProc);
SendMessage(hTarget, WM_GETTEXT, size, (LPARAM)buf); // 讀取成功
SetWindowLongPtr(hTarget, GWLP_WNDPROC, pOld); // 還原
此技術屬「核武級」對抗,僅在遭遇主動反偷窺時啟用,平時保持輕量。
TextBox 衍生類,重寫 WndProc密碼編輯框防護的本質是「訊息來源驗證」。透過 MFC 子類化與全域旗標配合 DDX 機制,可在幾十行程式碼內構建有效防線。面對進階對抗,動態視窗程序替換提供最後防禦手段。