陈学军
(重庆川仪自动化股份有限公司,重庆 401121)
串口通信以其成本低廉、互联方式简洁有效等特点被众多智能设备所采用,它在工业过程控制、科学试验分析等领域的设备互联过程中得到大规模的应用。而微软的Windows操作平台也以其良好的通用性、图形用户界面(graphical user interface,GUI)以及众多的技术支持基础而成为主要的上位监控应用平台。
目前,智能设备的串口通信协议标准众多,同时还存在多种自定义协议。要实现与其信息互联,通过分析其固有的上位平台与设备的通信流程,获得交换数据,从而逆向推断出通信协议的方式不失为一种好的解决途径。
相对于传统的串口通信分析与监测方法(主要有增加示波器或并联串行接口)[1],本文采用Windows平台下的纯底层软件设计技术。该技术不需要增加另外硬件设备或终端,达到了实时获取监测完整通信数据的目的。
Windows平台的最大特征之一是设备无关性,通过设备驱动程序将Windows应用程序和不同的设备相隔离,使得Windows程序访问设备时,不需要直接对相应的硬件端口进行操作,而只需要通过Windows操作系统提供的设备驱动程序来进行数据交互即可[2]。
Windows对串行通信的通信机制也进行了封装,对串口的相应操作等同于普通文件的操作,同样有打开 (CreateFile)、读写 (ReadFile/WriteFile)、关闭(CloseHandle)等功能支持。同时,针对串口通信的独特性,又增加了专用的API函数支持,如串口参数配置(SetCommState)等[3]。
通过以上分析可知,Windows平台下的串口通信实现较为容易,只需根据通信协议需要,调用相应的I/O函数与通信函数即可达到目的。而通过高级语言平台(如 VC/VB/Delphi等)调用串口通信控件(如MsCOMM)实现的串口通信也只是访问同样的I/O函数与通信函数封装而已。因此,这种调用底层的归一化正是Windows平台下串口通信的重要特点。这也是本文能够实现串口通信监控的基础。只要采用API钩子技术对这些主要API函数调用Hook并进行相应的功能扩展,就能达到实时获取相应的串口交换数据信息的目的。
进程是Windows平台中正在执行的应用程序。一个进程指的是一个执行中的文件使用资源的总和,包括虚拟地址空间、代码、数据、对象句柄、环境变量和执行单元等[4]。在Windows系统中,不同进程之间的地址空间是隔离的,用指令直接存取其他进程地址空间中的代码和数据是不允许的,甚至连在自己的代码段中写数据都是不合法的,这是由Windows中所设置的安全机制中所保障的。
为了实现对关注的串口通信数据进行实时获取与监测,必须对其所在的应用进程进行程序注入和API钩子安装。Robert Kuster提供了三种进程注入方法,用来实现将我们编制的串口监测程序注入到我们关注的进程中[5]。
本文选择采用CreateRemoteThread方式并根据实际需要作了适当改变。其基本流程具体如下。
①调用GetOpenFileName,选定需要运行的上位运行平台可执行文件。
②采用CREATE_SUSPENDED模式调用CreateProcess加载选定的进程,获得其相应的进程HANDLE。
③在远程进程中为DLL文件名分配内存(VirtualAllocEx)。
④把DLL的文件名(全路径)写到分配的内存中(WriteProcessMemory)。
⑤使用CreateRemoteThread和LoadLibrary把具备串口监测功能的DLL映射到远程进程。
⑥等待远程线程结束(WaitForSingleObject),即等待LoadLibrary返回。也就是说当我们的DllMain(以DLL_PROCESS_ATTACH为参数调用)返回时,远程线程也随之结束了。
⑦取回远程线程的结束码(GetExitCodeThread),即LoadLibrary的返回值。
⑧释放第③步分配的内存(VirtualFreeEx)。
⑨采用CreateRemoteThread和FreeLibrary把DLL从远程进程中卸载。调用时,将第⑦步取得的HMODULE传递给FreeLibrary(通过CreateRemoteThread的lpParameter参数)。
⑩调用ResumeThread,继续远程进程的运行。
⑪等待线程的结束(WaitForSingleObject)。
以上基本流程实现了将具备串口监测功能的DLL注入到需要监测的上位运行软件进程中,而API钩子的工作则在DLL初始化调用时就实施。
主程序主要用来完成应用界面的生成、被监测分析应用程序的加载以及DLL程序的注入。
主程序功能流程如图1所示。
图1 主程序功能流程图Fig.1 Functional flowchart of the main program
监测DLL是本软件串口数据监测的核心,它主要包括API钩子的实施与恢复、串口数据的获取保存与记录等功能。
2.2.1 API钩子的实施与恢复
分析主程序功能流程可知,API钩子功能流程重点围绕串口通信流程所需要调用的API函数展开,主要包括串口初始化(CreateFile)、串口参数配置(SetCommState)、串口超时设置(SetCommTimeouts)、串口通信(ReadFile、WriteFile)、关闭串口(CloseHandle)。在具体 API钩子功能实施过程中,为保证在众多Windows平台(Windows7/XP/2003/2000/NT)运行的可靠性、稳定性与通用性,采用自编的X86指令反编译引擎对需要Hook的API函数入口指令进行完整性分析[6-7],计算得到不小于 5 B 的完整程序指令字节空间,从而可以替换成跳转到新的API函数的程序指令[8-9]。实践证明,这样的替换过程使得程序的通用性和稳定性更佳。
API钩子安装流程(DLL_PROCESS_ATTACH触发)如图2所示。
图2 API钩子安装流程图Fig.2 Flowchart of API hook installation
2.2.2 串口数据的获取、保存与记录
替换的API函数除了完成原API函数的功能外(通过调用原API函数),还能完成通信串口的捕获、串口数据的完整获取与转存,以实现实时监测的最终目的。
下面就关键的几个API函数的替换做简要分析,为了简单起见,没有包含相应的错误处理和支持Unicode的代码。
①扩展的CreateFile函数流程
CreatFile函数扩展流程增加了识别串口调用与一般的文件操作功能。
CreateFile申明原型语句如下。
CreateFile (lpFileName,dwDesiredAccess,dwShareMode,lpSecurityAttributes,dwCreationDistribution,dwFlagsAndAttributes,hTemplateFile);
在该流程中,关注的重点参数为lpFileName。
通过此指针即可获得需要打开的设备名,从而识别出调用者是要监控的串口还是一般的文件操作。标准的串口定义为“//./COMx”,通过判别关键字“COM”,可以识别出是否是串口操作。
CreateFile扩展流程如图3所示。
图3 CreateFile扩展流程图Fig.3 Flowchart of the expanded CreateFile
②扩展的ReadFile函数流程
ReadFile函数扩展流程扩展了获得串口的数据读入并转存的功能。
ReadFile申明原型语句如下。
ReadFile(hFile,lpBuffer,nNumberOfBytesToRead,lpNumberOfBytesRead,lpOverlapped);
在该流程中,关注的重点参数如下。
hFile:与保存的串口handle相比较,就可知道是否是串口读操作。
lpBuffer:串口读入的数据存放在此区域。
nNumberOfBytesToRead:串口要读入的字节数。
lpNumberOfBytesRead:指向串口实际读入的字节数变量。
ReadFile扩展流程如图4所示。
图4 ReadFile扩展流程图Fig.4 Flowchart of the expanded ReadFile
③扩展的WriteFile函数流程
WriteFile函数扩展流程扩展了获得写入串口的数据并转存的功能。
WriteFile申明原型语句如下。
WriteFile(hFile,lpBuffer,nNumberOfBytesToWrite,pNumberOfBytesWritten,lpOverlapped)。
该流程中需要关注的重点参数如下。
hFile:与保存的串口handle相比较,就可知道是否是串口写操作。
lpBuffer:写入串口的数据存放在此区域。
nNumberOfBytesToWrite:要写入串口的字节数。
lpNumberOfBytesWritten:指向实际写入串口的字节数变量。
其功能流程与ReadFile基本一致,只是写操作与读操作存在差别。
④扩展的CloseHandle函数流程
CloseHandle函数扩展流程扩展的功能是判断串口操作是否结束,如是,则清除相应的串口捕获标志及handle。
CloseHandle申明原型语句如下。
CloseHandle(hObject)。
关注的重点参数是hObject。
与保存的串口handle相比较,就可知道串口操作是否结束。
因其扩展功能比较简单,故流程图省略。
⑤串口获取数据的转换与保存
在新API函数扩展功能中,每个API函数的扩展目标明确。针对串口操作状态以及获得的输入输出数据进行了直接转存,其目的就是为了尽量减小运行扩展功能占用的时间,以保障原固有通信流程的实时性。但为了便于后续分析查看等的需要,还要对转存的数据进行标注、转换(如输入数据、输出数据的区分,十六进制数据到十进制的转换等)及保存。本文采用的是在被监控进程结束时一次性完成标注、转换、保存工作,具体实现过程不再详述。
按照本文分析的思路,设计了基于串口通信数据获取与监测的软件包 COMSPY,它包括运行于Windows平台上的可执行程序COMSPY.EXE以及串口监测功能的COM.DLL。通过产品研发、实际使用过程验证(智能仪表通信平台、科学仪器通信平台、过程控制通信平台)以及市面上常用串口通信调试,达到了预期的实时性、准确性、通用性效果,并实现了完整通信数据获取的目的。其准确性主要体现在通信串口选择的一致性、通信参数设置的一致性(包括Baud、Size、Parity、Stop bit等)、通信流程(读/写)和数据帧的一致性以及通信周期时间的一致性
本研究采用了进程注入和基于串口的API钩子等系统底层软件设计技术,将数据获取与监控同步并存于设备的正常工作过程中,不影响相关智能设备与上位监控平台串口固有通信流程[10],数据可靠准确。相对于传统的依靠增加硬件进行串口数据获取的方法,本研究更体现了其应用的便捷性,在串口通信协议机理分析、通信故障分析和改进等方面具有广泛的应用价值。
[1]谢菁.基于远程注入的串口监视程序[J].西南民族大学学报,2008(8):600-605.
[2]晁永生,樊军,申晓萍,等.浅谈Visual C++串口通信编程[J].科技广场,2007(1):73-75.
[3]孙达,罗海福.Windows API在串口通信中的应用[J].微计算机信息,2004(4):106-108.
[4]罗云彬.Windows环境下32位汇编语言程序设计[M].北京:电子工业出版社,2003:453-528.
[5]Robet K.Three ways to inject your code into another process[EB/OL].[2003- 08- 20].http://www.codeproject.com/KB/threads/winspy.aspx.
[6]Linxer.x86机器码识别及其反汇编算法[EB/OL].[2006-05-12].http://linxer.bokee.com/4277473.html.
[7]Egogg.打造自己的反汇编引擎[EB/OL].[2008-10-22].http://bbs.pediy.com/showthread.php?t=75094.
[8]Huwei.hook api技术心得[EB/OL].[2010-09-16].http://home.51.com/huwei765/diary/item/10047018.html.
[9]段钢.加密与解密[M].3版.北京:电子工业出版社,2008:471-483.
[10]段敬红,冯江,张发存.基于软件构件的表面缺陷检测软件开发[J].计算机工程与科学,2009,31(10):77-79,153.