韓 靜,彭雙和,趙佳利
(北京交通大學 智能交通數據安全與隱私保護技術北京市重點實驗室,北京 100044)
20世紀計算機剛剛興起的時候,計算機內存較小,動態分配[1]的內存若不及時釋放,會極大地占用系統資源,從而導致一系列的未知錯誤。因此,編程人員對于內存動態分配有著較強的內存釋放意識。然而,隨著計算機的不斷更新發展,計算機內存不斷擴大,學生對內存泄漏[2]的感知越來越弱,很多時候,我們根本察覺不到內存泄漏的存在,但這并不代表內存泄漏的危害會逐漸消失。內存泄漏的堆積最終會耗盡系統所有的內存,給應用程序帶來極大的不穩定性。因此,內存分配與內存釋放往往是成對出現,這樣才能科學有效地利用內存空間。
同理,對文件進行操作時,打開文件與關閉文件也必須成對出現,這也是學生在使用C語言的文件I/O接口時經常出現的錯誤。不關閉文件會導致數據丟失,因為在向文件寫數據時,首先將數據傳輸到緩沖區,待緩沖區充滿后才正式輸出給文件。如果數據未充滿緩沖區而程序結束運行,緩沖區中的數據就會丟失。用fclose()函數關閉文件,先將緩沖區中的數據輸出到磁盤文件,然后才釋放文件指針變量,從而避免了數據丟失。
雖然在教學過程中老師一再強調malloc()與free()以及fopen()與fclose()函數的成對出現,但在實際編程過程中,仍有部分學生沒有養成良好的編程習慣,從而忽略了釋放內存或關閉文件操作,而且對于編譯器報錯信息不太敏感的學生很難發現自己的錯誤所在,從而會盲目修改程序,浪費大量時間。這一方面是由于學生對內存分配以及文件操作知識掌握得不充分,不熟練;另一方面是沒有意識到內存泄漏以及數據丟失造成的嚴重后果,沒有引起大家的重視。因此,對于學生編程習慣以及程序安全意識的培養也格外重要。
此外,檢測函數的參數及其返回值,在對危險函數檢測、內存泄漏檢測或者污點追蹤等方面都有廣泛的應用。Intel-Pin[3]作為一個強大的分析工具,在性能評估和漏洞檢測等方面有著重大貢獻。它提供一些接口(API),可以實現程序運行時各種信息的收集,利用這些 API,用戶可以根據需要開發出各種分析工具,從而實現對可執行程序的動態分析[4],處理程序運行過程中生成的代碼和屬性,從中獲取并將函數的調用、內存的訪問、指令的執行等信息并將其記錄下來,從而實現污點分析、指令路徑追蹤、協議逆向、漏洞挖掘等目的。這些工作都值得在此基礎上繼續深入挖掘研究,而且對于學生安全相關知識的培養也有很大幫助。
Intel-Pin是Intel公司推出的一款動態二進制分析框架, 支持IA-32和x86-64指令集架構,可用于創建動態程序分析工具,然后可以使用這些工具來監視和記錄程序運行時的行為。使用Pin創建的名為Pintool的工具可用于在用戶空間應用程序上執行程序分析。
為獲取malloc()函數返回值,我們通過Pin對程序進行插樁,在程序運行時獲取其相關信息。作為一個動態的二進制檢測工具,檢測是在編譯后的二進制文件的運行時執行的。 因此,它不需要重新編譯源代碼,并且支持動態生成代碼的測試程序。換句話說,Pintool能完全控制程序運行時的執行,我們可以根據需要改變程序執行流程。Pin包含4種不同粒度級[5]的插樁,分別為:①指令級插樁(instruction instrumentatio),通過函數INS_AddInstrumentFunctio實現;②軌跡級插裝(trace instrumentation),通過函數TRACE_AddInstrumentFunction實現;③鏡像級插裝(image instrumentation), 使 用 IMG_AddInstrumentFunction函數,由于其依賴于符號信息去確定函數邊界,因此必須在調用PIN_Init之前調用PIN_InitSymbols;④函數級的插裝(routine instrumentation),使用RTN_AddInstrumentFunction函數,函數級插裝比鏡像級插裝更有效,因為只有鏡像中的一小部分函數被執行。
此外,Pin提供了豐富的API記錄、修改或對現有的編譯二進制文件進行其他操作,表1列舉了一些關鍵API及其功能描述。在想要分析一個程序但沒有該程序的源代碼的情況下,Pin尤其有用,這一特征也與要進行的實驗十分吻合。
內存泄漏(memory leak)是指程序中己動態分配的堆內存由于某種原因(程序未釋放或無法釋放),造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。
沒有釋放動態分配的存儲空間而造成內存泄漏,是使用動態存儲變量的主要問題,尤其對于初學動態內存分配的學生而言,更容易犯這種錯誤,而且內存泄漏缺陷具有隱蔽性、積累性的特征,比其他內存非法訪問錯誤更難檢測。因為內存泄漏的產生原因是內存塊未被釋放,屬于遺漏型缺陷而不是過錯型缺陷。此外,內存泄漏通常不會直接產生可觀察的錯誤癥狀,而是逐漸積累,降低系統整體性能,極端的情況下可能使系統崩潰,這也是需要我們在日常編程中注意及時釋放內存的原因。
實驗的設計初衷及要實現的功能是在動態二進制插樁平臺Pin上,通過編寫插樁工具Pintool,對可執行程序進行動態插樁,從而成功檢測到可執行程序中的malloc()和free()函數的參數及malloc()函數的返回值,通過結果分析可以觀測到其成對出現,而且malloc()的返回值剛好是free()要釋放的地址。此外,fopen()與fclose()原理與之相似,這里一并進行研究探討,對于其他成對出現的函數,學生也可以此作為參考對其進行測試。

表1 調用函數功能描述
與普通程序一樣,PinTool的入口位置依然為main()函數,主函數流程如圖1所示。
Pintool插樁過程和分析過程的主要實現過程為:當代碼經由Pin時,Pintool能觸發Pin提供的回調例程,進而獲取被插樁函數的信息。Pintool實現流程如圖2所示。

圖1 主函數流程圖

圖2 Pintool實現流程圖
考慮到性能及開銷,這里選擇鏡像級插樁,一旦加載了程序中的鏡像,Pintool就可以在鏡像中找到需要的函數名并插入分析代碼,進而找到其參數。
要使用Pin API,必須在代碼中包含pin.h頭文件,Pintool會將結果寫入到一個輸出文件。然后需要定義代碼序列中特定點處執行的分析例程,分析例程決定了對測試程序進行的操作。在malloc和fopen執行后記錄返回地址,每次調用malloc和fopen時都會調用該函數。
此外,預先定義一個布爾型的全局變量Record,用來控制是否打印記錄,然后在分析函數中通過條件語句判斷,從而過濾掉一些不必要的輸出信息,使得輸出結果更加明了,便于觀察分析。

插樁例程的主要功能是告訴Pin何時執行分析例程。每次運行加載函數過程中都會調用該例程,加載程序函數時,Pin就可以在適當的點插入分析例程。
該模塊主要是對malloc()和free()函數進行插樁。 打印每次調用malloc()、free()的大小以及malloc()的返回值。通過RTN_InsertCall函數在適當的點插入分析例程,該函數包括3個強制參數:首先是想要插樁的函數;其次是枚舉類型IPOINT,用來指定插入分析例程的位置;最后是要插入的分析例程。參數必須由IARG_END終止,為了將fopen及malloc函數的返回值傳遞給分析例程,需要指定IARG_FUNCRET_EXITPOINT_VALUE。
例如,在對malloc函數的插樁過程中,第二個參數表示在malloc函數執行前執行 Arg1Before函數,并依次為 Arg1Before 函數傳遞兩個參數。IARG_ADDRINT 表示第一個參數是 IARG_ADDRINT 型的常量,其值是宏FOPEN;IARG_FUNCARG_ENTRYPOINT_VALUE 表示第二個參數是僅在函數入口處有效的函數本身參數,其值是0。其核心代碼如下:

結束例程在插樁程序終止時調用,它包含了兩個參數,一個是保存程序主函數返回值的代碼參數,另一個參數用來傳遞附加信息給檢測函數。
通過一個小程序來測試我們的Pintool,該程序中包含了打開文件與關閉文件的操作,并且通過malloc函數動態分配了一塊地址空間,隨后將其釋放,其核心代碼如下:

將該代碼編譯成可執行程序,即可在命令行中使用我們的Pintool工具對其進行檢測。在終端輸入如下命令,此時文件夾中不存在hh.txt文件,故打開失敗。在控制臺輸出打開文件失敗的信息,同時在Pintool創建的輸出文件中可以查看函數信息,如圖3所示。
創建hh.txt文件后,文件打開成功,此時控制臺信息提示文件打開成功,此時Pintool的函數輸出信息如圖4所示。

圖3 打開失敗輸出

圖4 打開成功輸出
實驗結果分析:由圖3和圖4文件輸出結果可見,若文件正常打開,可捕獲到fclose()函數,而且最終會通過free()函數釋放該地址空間,同樣,對于malloc()函數,在其返回地址處依然有對應的free()函數;若文件打開失敗,則不會調用關閉文件函數,而且函數也沒有正常結束,有一塊地址空間沒有正常釋放。通過對輸出文件的分析,可以清楚地觀測到malloc()函數與free()函數以及fopen()和fclose()函數的對應關系。
該工具通過鏡像級插樁實現了對malloc()函數分配內存大小及其返回值的獲取,同時監測其返回地址處是否調用free()函數來釋放地址空間,從而在沒有源文件的情況下得知其源代碼是否規范,malloc()函數與free()函數是否對應。此外,我們也對fopen()函數和fclose()函數進行了監測。通過這種實驗的方式,學生對這些函數有了更深入的理解,相較于老師在課堂上多次強調效果卻微乎其微,學生自己動手操作更加直觀、形象、深刻,能達到事半功倍的效果,同時也可以舉一反三,通過一些微小的修改檢測其他函數或者自定義函數的參數及返回值,在不斷嘗試中體會學習的樂趣,這對教學改革有著重大意義。該工具也可以應用在許多場景中,為后續工作帶來了許多便利,這也是我們要進一步研究探索的方向。