C# 模擬鍵盤輸入

1. 使用.Net Framework的庫函數(shù)

SendKeys.SendWait("123{TAB}abc");

namespace System.Windows.Forms命名空間下的SendKeys是.Net提供的模擬鍵盤輸入的工具類。其中有Send()SendWait()這兩個方法,都可以發(fā)送按鍵消息。區(qū)別在于SendWait()是會等待按鍵消息被處理完成才返回的,而Send()則不用。這就類似于SendMessagePostMessage的關(guān)系。
上面代碼中的{TAB}代表Tab鍵。鍵盤上一些特殊的按鍵都有對應(yīng)的代碼,具體的對照表可以參照微軟MSDN上的介紹:SendKeys Class

當(dāng)然,還可以使用Windows API,API原型如下:

        /// <summary>
        /// 合成一次擊鍵事件
        /// </summary>
        /// <param name="bVk">定義一個虛擬鍵碼。鍵碼值必須在1~254之間</param>
        /// <param name="bScan">定義該鍵的硬件掃描碼</param>
        /// <param name="dwFlags">定義函數(shù)操作的各個方面的一個標志位集。應(yīng)用程序可使用如下一些預(yù)定義常數(shù)的組合設(shè)置標志位</param>
        /// <param name="dwExtraInfo">定義與擊鍵相關(guān)的附加的32位值</param>
        [DllImport("user32")]
        public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, 
uint dwExtraInfo);

甚至用SendMessagePostMessage也是可以做到的。

但以上這些實現(xiàn)方法都是在Windows消息層面的,對于像記事本等常規(guī)應(yīng)用程序是沒問題的。但是對于一些游戲、QQ登錄框、網(wǎng)銀登錄框等則無效。這是因為這些程序不是從Windows消息中獲取按鍵信息的,而是直接從底層驅(qū)動層面獲取按鍵信息的。

在一篇關(guān)于研究游戲外掛的博文里看到博主描述了這個問題:

引自:https://blog.csdn.net/zdrl/article/details/12707835

在解釋更詳細的原理之前,我們先來抓出幕后黑手,看看是哪個給游戲撐腰?讓它有膽子違抗Windows消息命令。究竟是判斷了真實鍵盤信息,還是有其他原因。結(jié)果在DirectX編程中發(fā)現(xiàn)了DirectInput這個API。就是它繞過了Windows的消息機制,它的目的是為了讓游戲的實時性控制更好、更快。Windows消息是隊列形式的,在傳遞過程中會有延時,比如格斗類游戲?qū)崟r性控制要求是非常高的,Window消息機制不能滿足這個需求。而DirectInput直接和鍵盤驅(qū)動程序打交道,效率當(dāng)然要高出一大截。我認為大部分游戲不響應(yīng)消息的真正的原因在這里,而不是故意寫了反作弊系統(tǒng)。

2. 使用WinIO.dll

由于上述方法只能模擬Windows消息層面的按鍵,以至于對一些應(yīng)用程序無效,所以下面就采用直接在驅(qū)動層面模擬按鍵的方法。
這里需要用到一個組件,那就是使用WinIO.dll,這是是國外大佬開發(fā)的一個dll。

引自百度百科:https://baike.baidu.com/item/winio/2877240?fr=aladdin

WinIO程序庫允許在32位的Windows應(yīng)用程序中直接對I/O端口和物理內(nèi)存進行存取操作。通過使用一種內(nèi)核模式的設(shè)備驅(qū)動器和其它幾種底層編程技巧,它繞過了Windows系統(tǒng)的保護機制。

使用此組件的環(huán)境要求:

  • 系統(tǒng)Win7或Win10均可。
  • 需要PS/2鍵盤(老式的針孔插頭的鍵盤),USB鍵盤不行。
  • 正規(guī)使用的話需要官方授權(quán)簽名,否則就得將Windows開啟測試模式。
  • 使用此組件的應(yīng)用程序需要以管理員的身份啟動。
  • 此組件還有32位和64位的區(qū)分。
  • 與dll配套的還有個.sys的文件,要跟dll放在同一目錄下。

Windows開啟測試模式的方法:
以管理員身份打開cmd,輸入開啟測試模式的命令并執(zhí)行。然后重啟電腦,看到桌面右下角出現(xiàn)“測試模式”字樣即可。

開啟測試模式的命令:

bcdedit /set testsigning on

關(guān)閉測試模式的命令:

bcdedit /set testsigning off

開啟測試模式成功:


開啟測試模式成功

調(diào)用WinIO64.dll的示例代碼:

    public class WinIO64

    {

        private const int KBC_KEY_CMD = 0x64;

        private const int KBC_KEY_DATA = 0x60;



        #region WinIo64.dll

        [DllImport("WinIo64.dll")]

        public static extern bool InitializeWinIo();



        [DllImport("WinIo64.dll")]

        public static extern bool GetPortVal(IntPtr wPortAddr, out int pdwPortVal, 
byte bSize);



        [DllImport("WinIo64.dll")]

        public static extern bool SetPortVal(uint wPortAddr, IntPtr dwPortVal, byte 
bSize);



        [DllImport("WinIo64.dll")]

        public static extern byte MapPhysToLin(byte pbPhysAddr, uint dwPhysSize, 
IntPtr PhysicalMemoryHandle);



        [DllImport("WinIo64.dll")]

        public static extern bool UnmapPhysicalMemory(IntPtr PhysicalMemoryHandle, 
byte pbLinAddr);



        [DllImport("WinIo64.dll")]

        public static extern bool GetPhysLong(IntPtr pbPhysAddr, byte pdwPhysVal);



        [DllImport("WinIo64.dll")]

        public static extern bool SetPhysLong(IntPtr pbPhysAddr, byte dwPhysVal);



        [DllImport("WinIo64.dll")]

        public static extern void ShutdownWinIo();

        #endregion



        [DllImport("user32.dll")]

        public static extern int MapVirtualKey(uint Ucode, uint uMapType);





        private WinIO64()

        {

            IsInitialize = true;

        }

        public static void Initialize()

        {

            if (InitializeWinIo())

            {

                KBCWait4IBE();

                IsInitialize = true;

            }

            else

                MessageBox.Show("Load WinIO Failed!");

        }

        public static void Shutdown()

        {

            if (IsInitialize)

                ShutdownWinIo();

            IsInitialize = false;

        }



        private static bool IsInitialize { get; set; }



        ///等待鍵盤緩沖區(qū)為空

        private static void KBCWait4IBE()

        {

            int dwVal = 0;

            do

            {

                bool flag = GetPortVal((IntPtr)0x64, out dwVal, 1);

            }

            while ((dwVal & 0x2) > 0);

        }

        /// 模擬鍵盤標按下

        public static void KeyDown(Keys vKeyCoad)

        {

            if (!IsInitialize) return;



            int btScancode = 0;

            btScancode = MapVirtualKey((uint)vKeyCoad, 0);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_DATA, (IntPtr)0x60, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_DATA, (IntPtr)btScancode, 1);

        }

        /// 模擬鍵盤彈出

        public static void KeyUp(Keys vKeyCoad)

        {

            if (!IsInitialize) return;



            int btScancode = 0;

            btScancode = MapVirtualKey((uint)vKeyCoad, 0);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_DATA, (IntPtr)0x60, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);

            KBCWait4IBE();

            SetPortVal(KBC_KEY_DATA, (IntPtr)(btScancode | 0x80), 1);

        }

    }

}

3. 使用WinRing0x64.dll

這里還有另外一個組件WinRing0x64.dll,可以實現(xiàn)同樣的效果。不需要授權(quán)簽名,不需要開啟測試模式,使用起來要方便很多。

使用此組件的環(huán)境要求:

  • 系統(tǒng)Win7或Win10均可。
  • 需要PS/2鍵盤(老式的針孔插頭的鍵盤),USB鍵盤不行。
  • 使用此組件的應(yīng)用程序需要以管理員的身份啟動。
  • 與dll配套的還有個.sys的文件,要跟dll放在同一目錄下。
  • 此組件應(yīng)該也是區(qū)分32位和64位的,只是我只找到64位的,沒再去管32位的。

調(diào)用此組件的示例代碼有點長,這里就懶得貼了。

對于2和3這兩種方式,我寫了一個完整、可行的Demo,放在GitHub上了。包括需要的組件都在里面。
鏈接:
模擬鍵盤輸入的Demo
URL地址:https://github.com/Zzz2333/TestKeyboard

搜集的參考資料匯總:
爬蟲應(yīng)對銀行安全控件
驅(qū)動級鍵盤模擬(C#)
C#模擬鼠標和鍵盤操作
Windows下對硬件端口的操作---WinIo庫的使用
WinIo使用筆記

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ??JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。 ??事件,就是文檔或瀏覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,696評論 1 11
  • 膽大心細,不怕問題,穩(wěn)! 遇到問題只能靠自己時,并發(fā)出的力量是很強大的。 故障的產(chǎn)生: 應(yīng)該是電腦被我踢了...
    哈森森閱讀 23,587評論 0 1
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 6,335評論 0 10
  • 多年以后才發(fā)現(xiàn)彈奏中蘊藏的人生哲學(xué): 示強是容易的,容易到我們大聲說話就是了,就像彈奏ff,我們用上大臂的力量,順...
    Regina521閱讀 450評論 0 1
  • 待到秋來九月八,我花開后百花殺。 沖天香陣透長安,滿城盡帶黃金甲。
    東北花孔雀閱讀 329評論 0 0

友情鏈接更多精彩內(nèi)容