经过半天的分析和了解,大致明白了这个工具的使用方法和原理。
这个工具,会将一个源文件(目前我是用单一源文件测试的,没有使用目录测试),
每一个有效符号或者元素都解析出来,之后储存在一个大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 "; 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] ", __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(" "); 24 } 25 } 26 } 27 28 void CheckZooFrame::getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const { 29 printf("FILE [%s] LINE [%d] ", __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] ", __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] ", __FILE__, __LINE__); 41 return true; 42 } 43 44 45 Check::FileInfo* CheckZooFrame::loadFileInfoFromXml(const tinyxml2::XMLElement* xmlElement) const { 46 printf("FILE [%s] LINE [%d] ", __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脚本解释器之类的,让它支持通过脚本来加模块,那么应该会更完美。