首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 密碼編輯框防竊取:子類化攔截 WM_GETTEXT/EM_GETLINE 保護 ES_PASSWORD 內容

     編輯:VC 教學

前言

Windows `Edit` 控制項設定 `ES_PASSWORD` 樣式僅在視覺上以 `*` 遮罩字元,實際記憶體仍保存明文。惡意程式可透過 `SendMessage(hwnd, WM_GETTEXT, ...)` 或 `EM_GETLINE` 跨程式竊取密碼。本文實作 `CPasswordEdit` 繼承 `CEdit`,覆寫 `DefWindowProc` 攔截敏感訊息,**驗證呼叫者身分**(全域旗標 `g_bSenderLegit`),僅允許本程式合法呼叫(如 `DoDataExchange` 取值)通過,其他一律攔截並彈出警告。

一、攻擊原理回顧

  1. 駭客工具掃描視窗句柄,`GetClassName` 確認為 `EDIT`。
  2. `GetWindowLong(GWL_STYLE)` 檢測 `ES_PASSWORD` 樣式。
  3. `SendMessage(hwnd, WM_GETTEXT, 255, (LPARAM)buf)` 直接讀取緩衝區明文。
  4. 或 `SendMessage(hwnd, EM_GETLINE, 0, (LPARAM)buf)` 逐行讀取。

核心漏洞:`Edit` 控制項**不驗證訊息發送者身分**,任何擁有句柄的程序均可讀取。

二、防護策略:身分驗證旗標


// 全域旗標:標記當前訊息是否來自合法內部呼叫
BOOL g_bSenderLegit = FALSE;

三、CPasswordEdit 實作


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`」這一事實,工程上足夠實用。

六、其他敏感訊息補充

  • `WM_COPY` / `WM_CUT`:剪貼簿操作同樣可洩露密碼,建議同步攔截或設定 `ES_PASSWORD` 時系統自動禁用複製(新版 Windows 已預設禁用)。
  • `EM_SETPASSWORDCHAR`:惡意程式可改變遮罩字元為空字元,建議在 `OnInitDialog` 後鎖定遮罩字元。
  • 現代替代方案:使用 `CredUIPromptForCredentials` 系統憑證對話框、WebView2 載入受信任的登入頁、或 Windows Hello 生物辨識,避免自建密碼框。

七、結語

`CPasswordEdit` 以覆寫 `DefWindowProc` + 全域身分旗標極低成本封鎖 `WM_GETTEXT`/`EM_GETLINE` 跨程式竊取。雖非絕對安全(記憶體掃描、鍵盤記錄器、DLL 注入仍可繞過),但能有效阻擋絕大多數腳本級/工具級密碼竊取器。配合 `ES_PASSWORD` 視覺遮罩、禁用複製、記憶體加密儲存、輸入法管理,構建多層防護。現代新專案建議優先採用系統級憑證 UI 或 Web 驗證,減少自建敏感輸入框風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved