高端网站开发制作,企业网站制作是什么,百度云搜索引擎入口 百度网盘,二手域名by fanxiushu 2024-03-14 转载或引用请注明原作者 把Windows电脑模拟成蓝牙鼠标和蓝牙键盘#xff0c;简单的说#xff0c;就是把笨重的PC电脑当成鼠标键盘来使用。 这应该是一个挺小众的应用#xff0c;但有时感觉也应该算比较好玩吧#xff0c; 毕竟实现一种一般人都感觉… by fanxiushu 2024-03-14 转载或引用请注明原作者 把Windows电脑模拟成蓝牙鼠标和蓝牙键盘简单的说就是把笨重的PC电脑当成鼠标键盘来使用。 这应该是一个挺小众的应用但有时感觉也应该算比较好玩吧 毕竟实现一种一般人都感觉没戏的功能 尤其是windows平台中并不是简单实现蓝牙服务端还得做一个小小的破解。 windows电脑当成蓝牙鼠标键盘这个功能有什么用呢 可以用来控制手机啊我最初研究把windows当成蓝牙鼠标键盘确实是出于这个目的。 我们先来看看手机系统Android和iOS Android不知道从哪个版本开始已经可以提供用户层API可以直接调用对应接口 直接在程序中进行 touchmousekeyboard 等控制模拟。也就是说Android系统是可以通过编程实现被远程控制的。 但iOS比较特殊至今都没看到集成这样的用户层API来进行输入控制模拟。 因此iOS是没法像目前的通用远程控制软件那样直接远程控制iOS系统。当然只是投屏的话是没问题的。 以前开发iOS系统下的xdisp_virt远程控制程序苦于找不到对应的输入模拟接口只能把xdisp_virt当成投屏来使用。 后来发现iOS系统虽然不提供对应接口但是却支持蓝牙鼠标键盘。 于是就打起了模拟蓝牙鼠标键盘的主意。 当然蓝牙鼠标键盘模拟不等同于普通意义上的远程控制因为蓝牙传输距离有限 也就顶多几十米。 我们也可以使用一个笨办法 使用linux系统电脑因为linux下的蓝牙鼠标键盘更好模拟模拟出蓝牙鼠标键盘然后连上iOS苹果手机。 然后linux系统模拟的蓝牙鼠标键盘程序再通过普通网络把控制事件传输出去这样就能达到普通意义上的远程控制了。 就是麻烦了些。 回到正题如何在windows平台实现蓝牙鼠标键盘呢 一般桌面电脑模拟设备终端都有天然的障碍因为当初设计就不是为了实现设备功能的。 比如要把桌面电脑模拟成 USB 设备需要底层硬件支持需要有UDC硬件控制器。 有了UDC底层硬件还不够还得系统提供对应接口 好在linux内核很早前就提供了UDC对应系统接口windows10以上的系统也提供了UDC接口。 值得庆幸的是蓝牙设计的时候同时提供了服务端和客户端也就是同时提供了两端。 这也挺好理解蓝牙本质就是无线传输如果蓝牙传输堆栈只提供客户端或者只提供服务端就像缺胳膊少腿一样。 蓝牙鼠标和蓝牙键盘是作为蓝牙服务端对外提供服务的。而且是作为HID标准的输入设备。 因此本文也只阐述蓝牙服务端的实现过程至于蓝牙客户端如何实现可以去查阅WDK下的例子代码。 具体例子代码在bluetooth或者bth目录下的bthecho目录它同时演示了服务端和客户端以及如何安装。 例子代码是实现自己的上层传输ECHO回显但是作为蓝牙鼠标键盘其上层传输协议是固定和公开的。 这也是与例子不同的地方除此之外流程什么的都是一样的。 开发windows蓝牙服务端需要实现以下几个部分 1初始化驱动获取 BTH_PROFILE_DRIVER_INTERFACE BTHDDI_SDP_PARSE_INTERFACEBTHDDI_SDP_NODE_INTERFACE 等三个接口里边全是接口函数用于后面处理其中第一个接口主要是BRB分配和释放第二三个用于生成SDP信息 2注册PSM因为蓝牙鼠标键盘就是统一的HID输入设备的PSM是固定的 0x11 和 0x13 其中0x11用于传输控制信息 0x13传输具体的鼠标键盘事件。 但是windows系统把0x11和0x13作为保留值也就是说我们在自己的蓝牙驱动中是无法注册这两个值的。 这就是windows最大的坑而且是一开始就让你觉得没戏的坑。 因为它无法通过修改某些配置信息改变而是被硬编码到windows系统组件中。 3注册 L2CAP Server, 并且设置接收回调函数也就是说如果有蓝牙客户端连上来这个回调函数就会被调用 从而建立起连接传输数据。 4创建并且发布带有 HID 报告描述的 SDP 。 5从第3步骤注册的L2CAP Server的回调函数中接收到蓝牙客户端的连接请求然后回复之后连接就建立起来了。 客户端会发起 0x11 和 0x13 共两条连接根据 4 步骤的HID SDP配置会在0x11控制传输中收到某些控制命令 响应这些命令然后就可以通过 0x13这个连接发送固定格式的鼠标键盘事件数据。 通过已上步骤一个蓝牙鼠标键盘就模拟成功了。 通过以上我们也能发现这个跟socket网络编程的服务端很像 第1步骤就像是创建socket 第2步骤是bind绑定socket第3,4步骤在listen 第5步骤就是 accept了。最后就是send和recv 了。 当然为了更好的理解和开发蓝牙鼠标键盘驱动我们还得去网上下载 蓝牙的 HID规范文档 因为里边规定了蓝牙HID数据传输格式SDP协议格式等。 同时也得准备windows的WDK开发包中 tools目录下的 bluetooth 其中 sdpverify.exe 可以帮我们查看 SDP协议格式 而蓝牙HID 格式内容较多光看规范文档是很头大的所以还不如找个现成的蓝牙鼠标然后用sdpverify程序查看SDP格式 再然后仿照它建立自己的HID SDP 。 首先初始化蓝牙驱动这就按照一般的 即插即用wdm驱动开发就可以了 需要特别主意的是它的安装方式比较特别 我们需要使用 应用层WIN32API 函数 BluetoothSetLocalServiceInfo 创建一个底层设备然后再把我们的驱动安装上去 只有这样我们的驱动才是蓝牙驱动才能获取到BTH_PROFILE_DRIVER_INTERFACE等接口。 接着在驱动的AddDevice初始化函数中获取到BTH_PROFILE_DRIVER_INTERFACE等接口。 如果你是使用KMDF框架的微软例子里也是KMDF框架可以直接使用 WdfFdoQueryForInterface 函数获取。 而我的驱动是基于WDM的以下阐述的都是基于WDM实现的蓝牙驱动。所以得自己实现其实也不难。如下 NTSTATUS query_interface(PDEVICE_OBJECT device_object, LPCGUID InterfaceType, PINTERFACE Interface, USHORT Size, USHORT Version, PVOID InterfaceSpecificData) { NTSTATUS status STATUS_SUCCESS; KEVENT event; PIRP irp; IO_STATUS_BLOCK ioStatusBlock; PIO_STACK_LOCATION irpStack; KeInitializeEvent(event, NotificationEvent, FALSE); irp IoBuildSynchronousFsdRequest(IRP_MJ_PNP, device_object, NULL, 0, NULL, event, ioStatusBlock); if (irp NULL) { status STATUS_INSUFFICIENT_RESOURCES; return status; } irpStack IoGetNextIrpStackLocation( irp ); irpStack-MinorFunction IRP_MN_QUERY_INTERFACE; irpStack-Parameters.QueryInterface.InterfaceType (LPGUID)InterfaceType; irpStack-Parameters.QueryInterface.Size Size; irpStack-Parameters.QueryInterface.Version Version; irpStack-Parameters.QueryInterface.Interface (PINTERFACE)Interface; irpStack-Parameters.QueryInterface.InterfaceSpecificData InterfaceSpecificData; // // Initialize the status to error in case the bus driver does not // set it correctly. irp-IoStatus.Status STATUS_NOT_SUPPORTED ; status IoCallDriver( device_object, irp ); if (status STATUS_PENDING) { status KeWaitForSingleObject( event, Executive, KernelMode, FALSE, NULL); status ioStatusBlock.Status; } return status; } 然后如下调用 status query_interface(fdo-LowerDeviceObject, GUID_BTHDDI_SDP_PARSE_INTERFACE, (PINTERFACE)fdo-sdp_parse_interface, sizeof(fdo-sdp_parse_interface), BTHDDI_SDP_PARSE_INTERFACE_VERSION_FOR_QI, NULL); 就获取到了 BTH_PROFILE_DRIVER_INTERFACE 接口使用同样办法获取其他两个用于操作 SDP 的接口。 同USB驱动类似 蓝牙驱动使用 BRB 结构来传输数据因此我们先实现一些通用函数比如如下同步提交brb的函数 ///同步提交BRB NTSTATUS bth_sync_call_driver(PDEVICE_OBJECT device_object, PVOID Brb ) { NTSTATUS status STATUS_SUCCESS; KEVENT event; IO_STATUS_BLOCK ioStatus; KeInitializeEvent(event, NotificationEvent, FALSE); PIRP irp IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_BTH_SUBMIT_BRB, device_object, NULL, 0, NULL, 0, TRUE, event, ioStatus); /// if (!irp){ return STATUS_INSUFFICIENT_RESOURCES; } PIO_STACK_LOCATION nextStack IoGetNextIrpStackLocation(irp); nextStack-Parameters.Others.Argument1 Brb; status IoCallDriver(device_object, irp); if (status STATUS_PENDING){ KeWaitForSingleObject(event, Executive, KernelMode, FALSE, NULL); /// wait for ever status ioStatus.Status; } return status; } 然后还可以实现异步传输 NTSTATUS bth_async_call_driver( PDEVICE_OBJECT device_object, PVOID Brb, void(*brb_complete)(brb_async_context* brb_ctx), PVOID ctx) 其中BrB就是BRB指针brb_complete 是当完成这个brb请求时候的完成函数。brb_async_context是自己定义的结构体。 这里不再罗嗦列出代码了。 当初始化驱动完成我们就需要开始注册 PSM了。这些需要注册 0x11和0x13两个都需要注册。 PSM注册伪代码如下 struct _BRB_PSM * brb; brb (struct _BRB_PSM*)brb_alloc(fdo, bRegister ? BRB_REGISTER_PSM : BRB_UNREGISTER_PSM ); brb-Psm PSM; // 这里应该是 0x11和0x13两个都需要分别注册 status bth_sync_call_driver(fdo-LowerDeviceObject, brb); brb_free(fdo, brb); 是的注册PSM就这么简单上面代码把注册和注销放到一起了。 但是注册0x11和0x13肯定不会成功既然这个都不会成功那么接下来注册L2CAP等步骤也没有了意义。 这是因为在PSM注册过程中windows系统组件会对PSM值做判断如果是系统保留的自然就会失败。 因此为了保证注册成功我们就得想办法让这个判断失效 这等于是在做破解不过破解的不是应用层的dll 而是驱动sys文件具体完成这个注册的系统组件是 bthport.sys bthport.sys如同应用层的dll一样 是个扩展库主要是帮忙实现蓝牙驱动的核心功能比如注册传输等各类都是在bthport.sys中完成。 所以可以对bthport.sys做逆向比如使用 IDA工具软件进行分析然后就会发现 里边有个未公开的函数 BthIsSystemPSM 就是判断是否是系统保留的PSM,我们只要让这个函数失效自然就能成功注册0x11和0x13了。 下面的连接就有阐述如何破解BthIsSystemPSM函数Windows Kernel | Nadavs Blog 至于关于这方面的更多的内容以后的章节会详细阐述。 接着我们需要注册L2CAP Server注册这个不会有什么系统保留限制只要不出其他问题都会成功 如下伪代码注册L2CAP Server: struct _BRB_L2CA_REGISTER_SERVER *brb; brb (struct _BRB_L2CA_REGISTER_SERVER*)brb_alloc(fdo, BRB_L2CA_REGISTER_SERVER); brb-BtAddress BTH_ADDR_NULL; brb-PSM 0; //we have already registered the PSM brb-IndicationCallback SrvIndicationCallback; brb-IndicationCallbackContext userCtx; brb-IndicationFlags 0; brb-ReferenceObject fdo-DeviceObject; status bth_sync_call_driver(fdo-LowerDeviceObject, brb ); //调用同步提交BRB函数 fdo-L2CAPServerHandle brb-ServerHandle; /// save ptr保持此句柄再注销的时候会使用到 brb_free(fdo, brb); static void SrvIndicationCallback( __in PVOID Context, __in INDICATION_CODE Indication, __in PINDICATION_PARAMETERS Parameters { bth_hid_user_t* user (bth_hid_user_t*)Context; switch (Indication) { case IndicationRemoteConnect: 有客户端连接上来 { DPT( Ctrl Connect --- PSM0x%X; addr%p\n, Parameters-Parameters.Connect.Request.PSM, Parameters-BtAddress ); bth_connect_remote(user, Parameters); 调用我们的函数初始化有新客户端连上的各种结构并且回复客户端 break; } } } 以上就是注册L2CAP Server的过程接下来就是如何创建 HID SDP 和发布SDP了。 HID SDP 的内容有点多创建起来有点麻烦以下是我的HID SDP内容 包括的内容基本如上图所描述的那样其中红色框中的 HID Descriptor List 包含的就是HID REPORT DESC 这个就是 HID REPORT DESC 则是标准的HID报告描述符关于这个细节可以去查阅HID的规范文档。 至于如何生成 SDP报告 则是全程使用 BTHDDI_SDP_NODE_INTERFACE 来构建各种NODE 最终合并到 PSDP_TREE_ROOT_NODE 根 root tree 中代码调用细节可以去查阅 微软的bthecho例子代码。 然后调用 BTHDDI_SDP_PARSE_INTERFACE 接口中的 SdpConvertTreeToStream 函数把root tree序列化为 stream 假设序列化为Stream 大小为StreamSize再通过 IOCTL_BTH_SDP_SUBMIT_RECORD 把这个SDP Publish出去 让蓝牙客户端能够看到我们的蓝牙HID设备伪代码如下 KEVENT event; IO_STATUS_BLOCK ioStatus; KeInitializeEvent(event, NotificationEvent, FALSE); HANDLE_SDP handle HANDLE_SDP_NULL; PIRP irp IoBuildDeviceIoControlRequest(IOCTL_BTH_SDP_SUBMIT_RECORD, / 发布SDP的 IOCTL fdo-LowerDeviceObject, Stream, StreamSize, handle, sizeof(HANDLE_SDP), FALSE, event, ioStatus); status IoCallDriver(fdo-LowerDeviceObject, irp); if (status STATUS_PENDING) { KeWaitForSingleObject(event, Executive, KernelMode, FALSE, NULL); /// wait for ever status ioStatus.Status; } 至此之后我们就可以安心等待蓝牙客户端连上来。 当有蓝牙客户端连上来之后上面注册L2CAP Server时候设置的SrvIndicationCallback回调函数就会被调用。 我们响应 IndicationRemoteConnect 请求在这个请求中我们需要构建 _BRB_L2CA_OPEN_CHANNEL 的 BRB 然后回答给客户端只有这样才能真正建立起一条新的连接。 大致伪代码如下 NTSTATUS bth_connect_remote(bth_hid_user_t* user, PINDICATION_PARAMETERS Parameters) { 。。其他初始化代码 _BRB_L2CA_OPEN_CHANNEL* brb brb (_BRB_L2CA_OPEN_CHANNEL*)brb_alloc(user-fdo, BRB_L2CA_OPEN_CHANNEL_RESPONSE); ///init brb brb-Hdr.ClientContext[0] client; /client是我们新建的结构体代表一个蓝牙连接 brb-BtAddress Parameters-BtAddress; brb-Psm Parameters-Parameters.Connect.Request.PSM; brb-ChannelHandle Parameters-ConnectionHandle; brb-Response CONNECT_RSP_RESULT_SUCCESS; brb-ChannelFlags CF_ROLE_EITHER; brb-ConfigOut.Flags 0; brb-ConfigIn.Flags 0; brb-ConfigOut.Flags | CFG_MTU; brb-ConfigOut.Mtu.Max L2CAP_DEFAULT_MTU; brb-ConfigOut.Mtu.Min L2CAP_MIN_MTU; brb-ConfigOut.Mtu.Preferred L2CAP_DEFAULT_MTU; brb-ConfigIn.Flags CFG_MTU; brb-ConfigIn.Mtu.Max brb-ConfigOut.Mtu.Max; brb-ConfigIn.Mtu.Min brb-ConfigOut.Mtu.Min; brb-ConfigIn.Mtu.Preferred brb-ConfigOut.Mtu.Max; // // Get notifications about disconnect //设置对方断开连接的时候的回调函数 brb-CallbackFlags CALLBACK_DISCONNECT; brb-Callback BthSvrConnectionIndicationCallback; brb-CallbackContext client; brb-ReferenceObject user-fdo-DeviceObject; /// 采用异步方式调用BRB以免阻塞系统的回调函数 status bth_async_call_driver(user-fdo-LowerDeviceObject, brb, open_channel_response_complete, client); 。。。。 } static void BthSvrConnectionIndicationCallback( __in PVOID Context, __in INDICATION_CODE Indication, __in PINDICATION_PARAMETERS Parameters ) { 。。。。 switch(Indication) { case IndicationRemoteDisconnect: / 客户端已经关闭了此连接我们也需要关闭以及释放相关结构 。。。。 break; } } static void open_channel_response_complete( brb_async_context* brbctx) { NTSTATUS status brbctx-Irp-IoStatus.Status; 。。。。 回答客户端已经完成通过判断 status 来确定是否已经成功建立了连接。 if (NT_SUCCESS(status)) { /// response success.. client-handle brb-ChannelHandle; 需要使用此handle 来接收和发送蓝牙数据包 client-address brb-BtAddress; 远端蓝牙地址 client-OutMTU brb-OutResults.Params.Mtu; client-InMTU brb-InResults.Params.Mtu; 。。。。其他处理。。。。 } } 至此客户端发起的一个连接就建立了起来我们可以通过这个连接收和发送蓝牙数据。‘ 未完待续。。。