經過半天的分析和了解,大致明白了這個工具的使用方法和原理。
這個工具,會將一個源文件(目前我是用單一源文件測試的,沒有使用目錄測試),
每一個有效符號或者元素都解析出來,之后儲存在一個大list里面,供后續模塊檢測時使用,
但是一些特殊的元素,不會被列入list,如調用約定(__stdcall 此類等等),其他應該還有,但是還沒使用到,
目前看到的情況是,整個文件所有內容全部都被放到了一個list 里面,挺痛苦的。
內置模塊部分,其實它內置了很多功能
一堆check,很方便我來學習,據此我也寫出了一個簡單的check模塊,代碼如下
1 #pragma once 2 3 #include "check.h" 4 #include "ctu.h" 5 6 #include <cstddef> 7 #include <list> 8 #include <map> 9 #include <string> 10 #include <vector> 11 12 class CPPCHECKLIB CheckZooFrame : public Check { 13 public: 14 15 /** This constructor is used when registering the CheckClass */ 16 CheckZooFrame() : Check(myName()) { 17 } 18 19 /** This constructor is used when running checks. */ 20 CheckZooFrame(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) 21 : Check(myName(), tokenizer, settings, errorLogger) { 22 } 23 24 void runChecks(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) OVERRIDE; 25 26 void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const OVERRIDE; 27 28 /** @brief Parse current TU and extract file info */ 29 Check::FileInfo* getFileInfo(const Tokenizer* tokenizer, const Settings* settings) const OVERRIDE; 30 31 /** @brief Analyse all file infos for all TU */ 32 bool analyseWholeProgram(const CTU::FileInfo* ctu, const std::list<Check::FileInfo*>& fileInfo, const Settings& settings, ErrorLogger& errorLogger) OVERRIDE; 33 34 private: 35 36 /** data for multifile checking */ 37 class MyFileInfo : public Check::FileInfo { 38 public: 39 /** unsafe array index usage */ 40 std::list<CTU::FileInfo::UnsafeUsage> unsafeArrayIndex; 41 42 /** unsafe pointer arithmetics */ 43 std::list<CTU::FileInfo::UnsafeUsage> unsafePointerArith; 44 45 /** Convert MyFileInfo data into xml string */ 46 std::string toString() const OVERRIDE { 47 return "Zoos"; 48 } 49 }; 50 51 Check::FileInfo* loadFileInfoFromXml(const tinyxml2::XMLElement* xmlElement) const OVERRIDE; 52 53 54 static std::string myName() { 55 return "Zoo checking"; 56 } 57 58 std::string classInfo() const OVERRIDE { 59 return "Zoo Check Frame\n"; 60 } 61 };
1 #include "check_zoo.h" 2 #include "check.h" 3 4 namespace { 5 CheckZooFrame instance; 6 } 7 8 9 10 static const CWE CWE_POINTER_ARITHMETIC_OVERFLOW(758U); 11 12 13 void CheckZooFrame::runChecks(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) { 14 printf("FILE [%s] LINE [%d] Function [%s] \n", __FILE__, __LINE__, tokenizer->tokens()->next()->str().c_str()); 15 CheckZooFrame c(tokenizer, settings, errorLogger); 16 c.reportError(tokenizer->tokens(), Severity::portability, "Zoo檢測點", "Zoo框架", CWE_POINTER_ARITHMETIC_OVERFLOW, false); 17 18 for (const Token* tok = tokenizer->tokens(); tok; tok = tok->next()) 19 { 20 printf("%s ", tok->str().c_str()); 21 if (tok->str() == "{" || tok->str() == "}" || tok->str() == ";") 22 { 23 printf("\n"); 24 } 25 } 26 } 27 28 void CheckZooFrame::getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const { 29 printf("FILE [%s] LINE [%d] \n", __FILE__, __LINE__); 30 } 31 32 /** @brief Parse current TU and extract file info */ 33 Check::FileInfo* CheckZooFrame::getFileInfo(const Tokenizer* tokenizer, const Settings* settings) const { 34 printf("FILE [%s] LINE [%d] \n", __FILE__, __LINE__); 35 return NULL; 36 } 37 38 /** @brief Analyse all file infos for all TU */ 39 bool CheckZooFrame::analyseWholeProgram(const CTU::FileInfo* ctu, const std::list<Check::FileInfo*>& fileInfo, const Settings& settings, ErrorLogger& errorLogger) { 40 printf("FILE [%s] LINE [%d] \n", __FILE__, __LINE__); 41 return true; 42 } 43 44 45 Check::FileInfo* CheckZooFrame::loadFileInfoFromXml(const tinyxml2::XMLElement* xmlElement) const { 46 printf("FILE [%s] LINE [%d] \n", __FILE__, __LINE__); 47 return NULL; 48 }
擴展性還是非常強的,我的代碼,幾乎全部都是對照其他模塊來寫的,
而且接口也非常簡單,很友好。
這里簡單地解釋一下吧。
cppcheck的模塊系統屬於構造時直接靠全局變量串起來的這種模式(相似的可以看llvm,clang的命令行系統,也是這么串起來的),
優點是簡單方便,只要繼承了父類,然后實例化一個自己,就解決了問題,
缺點是,很多關鍵信息無法在初次構造時就獲取到,所以使用時有點麻煩。
本人在編寫這個簡單模塊的時候,就遇到了這個小麻煩,不過還是解決了。
主要體現在,錯誤上報的部分。
整個cppcheck框架,其實比較強大,它實際上真的僅僅是作為一個普通框架來執行的,
內部功能通過一個個模塊,一次次傳遞參數,一個個reportError來列出。
以 CheckZooFrame 為例,
首先需要實現若干虛函數,繼承於 check 父類,
然后定義一個全局變量,供初始化的時候,掛接自己,
但是這時候,由於自己無法獲取到任何關鍵信息,所以內部變量初始化全部都是空,
即CPP代碼中第五行。
掛接了自己之后,最重要的函數,其實只有一個,是runcheck,
這個函數會被外面check管理器直接調用,會傳入一系列的參數,
讓當前模塊來檢測是否有需要檢測的異常,
如果有的話,則通過reportError上報。
這時候就有問題了,reportError上報需要知道當前的代碼list,但是初始化的時候已經全都是空了,沒有list,
怎么辦,那就只能在自己內部重新創建個局部變量,構造時讓它有list,
即CPP代碼中15行的作用。
然后就可以盡情地玩耍了。
還有個問題,就是CWE是什么,其實CWE是一個索引ID,這個ID遵守一套XXXX的規則,
這個規則里面定義了若干種代碼可能出現的問題,通過這個ID,大家就可以統一使用並查詢出代碼出現了什么問題。
具體這個ID去哪查,就去下面的網站。
https://cwe.mitre.org/data/downloads.html
例子中的CWE是我隨便寫的,並不影響。
其實挺容易的,沒有那么難,只不過,任重而道遠啊。比如
下圖,指定位置有一個非常明顯的內存越界寫操作,可能會導致出現 0xC0000005,但是呢,cppcheck內置的模塊沒有發現這個問題,
這說明了一個事情,就是它內置模塊的檢測范圍可能還是以函數為單位的,只能檢測到當前函數內的事情,無法朔源檢測前面或者后面函數內的事情。
這只是一個點吧,我希望基於對它的學習,能多少再了解一些代碼審計相關的知識。
就到這吧
其實,由於它的模塊化是如此地出色,甚至可以插入一個python或者lua腳本解釋器之類的,讓它支持通過腳本來加模塊,那么應該會更完美。