陳學軍
(重慶川儀自動化股份有限公司,重慶 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.