zoukankan      html  css  js  c++  java
  • C# 模拟键盘输入

    1. 使用.Net Framework的库函数

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

    namespace System.Windows.Forms命名空间下的SendKeys是.Net提供的模拟键盘输入的工具类。其中有Send()SendWait()这两个方法,都可以发送按键消息。区别在于SendWait()是会等待按键消息被处理完成才返回的,而Send()则不用。这就类似于SendMessagePostMessage的关系。
    上面代码中的{TAB}代表Tab键。键盘上一些特殊的按键都有对应的代码,具体的对照表可以参照微软MSDN上的介绍:SendKeys Class

    当然,还可以使用Windows API,API原型如下:

            /// <summary>
            /// 合成一次击键事件
            /// </summary>
            /// <param name="bVk">定义一个虚拟键码。键码值必须在1~254之间</param>
            /// <param name="bScan">定义该键的硬件扫描码</param>
            /// <param name="dwFlags">定义函数操作的各个方面的一个标志位集。应用程序可使用如下一些预定义常数的组合设置标志位</param>
            /// <param name="dwExtraInfo">定义与击键相关的附加的32位值</param>
            [DllImport("user32")]
            public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, 
    uint dwExtraInfo);
    

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

    但以上这些实现方法都是在Windows消息层面的,对于像记事本等常规应用程序是没问题的。但是对于一些游戏、QQ登录框、网银登录框等则无效。这是因为这些程序不是从Windows消息中获取按键信息的,而是直接从底层驱动层面获取按键信息的

    在一篇关于研究游戏外挂的博文里看到博主描述了这个问题:

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

    在解释更详细的原理之前,我们先来抓出幕后黑手,看看是哪个给游戏撑腰?让它有胆子违抗Windows消息命令。究竟是判断了真实键盘信息,还是有其他原因。结果在DirectX编程中发现了DirectInput这个API。就是它绕过了Windows的消息机制,它的目的是为了让游戏的实时性控制更好、更快。Windows消息是队列形式的,在传递过程中会有延时,比如格斗类游戏对实时性控制要求是非常高的,Window消息机制不能满足这个需求。而DirectInput直接和键盘驱动程序打交道,效率当然要高出一大截。我认为大部分游戏不响应消息的真正的原因在这里,而不是故意写了反作弊系统。

    2. 使用WinIO.dll

    由于上述方法只能模拟Windows消息层面的按键,以至于对一些应用程序无效,所以下面就采用直接在驱动层面模拟按键的方法。
    这里需要用到一个组件,那就是使用WinIO.dll,这是是国外大佬开发的一个dll。

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

    WinIO程序库允许在32位的Windows应用程序中直接对I/O端口和物理内存进行存取操作。通过使用一种内核模式的设备驱动器和其它几种底层编程技巧,它绕过了Windows系统的保护机制。

    使用此组件的环境要求:

    • 系统Win7或Win10均可。
    • 需要PS/2键盘(老式的针孔插头的键盘),USB键盘不行。
    • 正规使用的话需要官方授权签名,否则就得将Windows开启测试模式。
    • 使用此组件的应用程序需要以管理员的身份启动。
    • 此组件还有32位和64位的区分。
    • 与dll配套的还有个.sys的文件,要跟dll放在同一目录下。

    Windows开启测试模式的方法:
    以管理员身份打开cmd,输入开启测试模式的命令并执行。然后重启电脑,看到桌面右下角出现“测试模式”字样即可。

    开启测试模式的命令:

    bcdedit /set testsigning on
    

    关闭测试模式的命令:

    bcdedit /set testsigning off
    

    开启测试模式成功:


     
    开启测试模式成功

    调用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; }
    
    
    
            ///等待键盘缓冲区为空
    
            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,可以实现同样的效果。不需要授权签名,不需要开启测试模式,使用起来要方便很多。

    使用此组件的环境要求:

    • 系统Win7或Win10均可。
    • 需要PS/2键盘(老式的针孔插头的键盘),USB键盘不行。
    • 使用此组件的应用程序需要以管理员的身份启动。
    • 与dll配套的还有个.sys的文件,要跟dll放在同一目录下。
    • 此组件应该也是区分32位和64位的,只是我只找到64位的,没再去管32位的。

    调用此组件的示例代码有点长,这里就懒得贴了。

    对于2和3这两种方式,我写了一个完整、可行的Demo,放在GitHub上了。包括需要的组件都在里面。
    链接:
    模拟键盘输入的Demo
    URL地址:https://github.com/Zzz2333/TestKeyboard

    搜集的参考资料汇总:
    爬虫应对银行安全控件
    驱动级键盘模拟(C#)
    C#模拟鼠标和键盘操作
    Windows下对硬件端口的操作---WinIo库的使用
    WinIo使用笔记



    作者:白日l梦想家
    链接:https://www.jianshu.com/p/a0c88a765bfd
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    类的继承
    垃圾回收GC
    网络层
    数据链路层
    TCP/IP协议分层模型
    OSI参考模型
    浏览器访问一个域名的过程
    Thread&ThreadLocal
    设计模式---单例模式
    内存泄漏和内存溢出
  • 原文地址:https://www.cnblogs.com/soundcode/p/14031332.html
Copyright © 2011-2022 走看看