BinarySecurityResearcher,IoTSec,NFVSec,FuzzingTest,CTFer
第一种是传统的静态二进制匹配方式,提取目标函数的CFG特征或者sig等信息,将其与无符号二进制中的函数进行比较,并输出匹配结果的可信度。由于尝试了几个现成的工具后发现效果不尽人意,暂时也没想到优化措施,就暂时搁置了这个思路。
几个关键的前提:
该框架支持使用Ghidra的headless模式,利于命令行处理数据。并且提供了P-codevisitor,可以通过符号执行的方式遍历P-code,判断指令中某个操作数是否存在潜在的溢出。还提供了各种自带的Checker,每个Checker对应一种CWE。当程序分析完成后,该框架就可以调用指定Checker分析反编译后的程序:
可以发现其中本身就提供了CWE190——也就是整数溢出的检测模块,但是非常遗憾的是这个模块实现得较为简单,没有针对漏洞特点进行进一步处理,所以漏报率和误报率都很高。
这是原生的代码实现:
最终,基于BinAbsInspector框架,我们构思了以下的实现思路来实现整数溢出漏洞检测:
在Checker模块添加自定义Sink,并实现扫描程序SymbolTable自动提取Sink的功能(就是一暴力枚举):
SymbolTablesymbolTable=GlobalState.currentProgram.getSymbolTable();SymbolIteratorsi=symbolTable.getSymbolIterator();...while(si.hasNext()){Symbols=si.next();if((s.getSymbolType()==SymbolType.FUNCTION)&&(!s.isExternal())&&(!isSymbolThunk(s))){for(Referencereference:s.getReferences()){Logging.debug(s.getName()+":"+reference.getFromAddress()+"->"+reference.getToAddress());hasWarning|=checkCodeBlock(reference,s.getName());}}}...这里首先从符号表提取出所有符号,然后过滤出函数符号,过滤掉External符号,过滤掉Thunk符号剩下来的作为Sink。其实这样的过滤还是太粗略的,可以大致总结一些基本不可能成为Sink但是又高频使用的常见函数构成黑名单,提取Sink时从中过滤一下实测效果会好很多。
不再直接遍历CodeBlock中的Instruction,因为这样使用的是Raw-Pcode。与Raw-Pcode相对应的是High-Pcode。Raw-Pcode只是将返汇编指令直接抽象出来得到中间的表示方式,它的CALL指令无法表示函数调用的参数信息。而High-Pcode是经过AST分析后得到的,其包含的Varnode具有语法树上的关联关系,CALL指令也包含了传入的参数
先获取Sink函数的引用点所在函数,调用decompileFunction进行反编译,分析函数的AST结构,并得到HighFunction,由HighFunction可以获得PcodeOpAST,PcodeOpAST继承自PocdeOp类,也就是上面所说的High-Pcode
while(pCodeOps.hasNext()){if(found){break;}pCode=pCodeOps.next();if(pCode.getOpcode()==PcodeOp.INT_LEFT||pCode.getOpcode()==PcodeOp.INT_MULT||pCode.getOpcode()==PcodeOp.INT_ADD||pCode.getOpcode()==PcodeOp.INT_SUB){if(PcodeVisitor.sink_address.contains(Utils.getAddress(pCode))){foundWrapAround=true;//getpCode'saddressandstoreitinlastSinkAddresslastSinkAddress=Utils.getAddress(pCode);}else{Logging.debug("sink_addresssetdoesnotcontain:"+String.valueOf(Utils.getAddress(pCode).getOffset()));}}...}其中PcodeVisitor.sink_address是下文添加的一个用于保存潜在溢出指令的数据结构3.3.1.4CALL指令参数检查因为不能直接认为潜在整数溢出指令就一定会导致后续CALL所调用的Sink函数会受到整数溢出影响,所以还需要明确整数溢出的位置是否影响到了函数的参数。为了提高效率,可以只检查函数的size参数或者length参数的位置,将这些位置对应的Varnode的def地址和lastSinkAddress作比较来确定参数是否受到溢出影响(事实上这操作也有一些问题)。
switch(symbolName){...case"calloc":if(pCode.getInput(1)==null&&pCode.getInput(2)==null){Logging.debug("Input(1)&Input(2)isnull!");break;}found=true;if(Utils.getAddress(pCode.getInput(1).getDef())==lastSinkAddress||Utils.getAddress(pCode.getInput(2).getDef())==lastSinkAddress){found=true;}break;case"realloc":if(pCode.getInput(2)==null){Logging.debug("Input(2)isnull!");break;}found=true;if(Utils.getAddress(pCode.getInput(2).getDef())==lastSinkAddress){found=true;}break;...}3.3.2修改PcodeVisitor这个模块主要完成符号执行的功能,如果某条指令发生了潜在的整数溢出可以通过Kset的isTop()方法来检查3.3.2.1标记潜在整数溢出指令添加一个public的静态HashSet变量,用于保存那些被符号执行认为存在潜在整数溢出的指令
publicstaticHashSet
sink_address=newHashSet();3.3.2.2检查四种运算指令的整数溢出在PcodeVisitor对之前提到的四种运算指令进行符号执行时,通过isTop()检查Pcode的两个InputVarnode和一个OutputVarnode对应的符号值是否存在潜在的整数溢出,如果有则标记到HashSetsink_address中以便Checker访问主站
官网提供的CVE数据库
官网提供的产品文档,关于一些重要函数有详尽的解释
VxWorks固件解析插件脚本
宏观角度上的运行机制分析
讲了VxWorks的主要内存结构,初始化方式,基址确定手段以及符号表修复手段
但是搜索了一圈发现不管是新版还是旧版固件解压和提取都有一定的套路,只不过网上大部分分析比较倾向于某个特例
直接binwalk跑一下会得到类似以下结果:
uBoot通常由uImageheader和紧随其后的一块LZMAcompresseddata组成,先要将他们提取出来:
ddif=wdr7660gv1.binof=uboot.rawbs=1skip=512count=66048testbinwalkuboot.rawDECIMALHEXADECIMALDESCRIPTION--------------------------------------------------------------------------------00x0uImageheader,headersize:64bytes,headerCRC:0xDEFB3DA,created:2018-09-0507:32:57,imagesize:48928bytes,DataAddress:0x41C00000,EntryPoint:0x41C00000,dataCRC:0x2A36A3AD,OS:Firmware,CPU:ARM,imagetype:StandaloneProgram,compressiontype:lzma,imagename:"U-Boot2014.04-rc1-gdbb6e75-dirt]"640x40LZMAcompresseddata,properties:0x5D,dictionarysize:67108864bytes,uncompressedsize:-1bytes由于缺少符号等原因这里大小才64K左右,暂时不分析
在0x10400偏移也就是uImageheader之后的第二块LZMAcompresseddata的位置存放了1.3M左右特别大的数据,一般来说这也是主程序所在,将其用同样的方法提取出来...
一开始我用的是:
ddif=wdr7660gv1.binof=data_0x10400.lzmabs=1skip=66560count=1355840但是在解压的时候提示压缩数据已损坏,初步判断可能是文件尾部的位置不正确,尝试用16进制编辑器打开手动定位到文件结束的位置:
感觉确实不太对,可能是超了,于是一直上溯到这:
感觉这里才是真正的文件尾部,于是将count修改为0x15a477-66560大小后再次提取:
ddif=wdr7660gv1.binof=data_0x10400.lzmabs=1skip=66560count=1351799提取完毕尝试解压:
lzma-d./data_0x10400.lzma得到data_0x10400二进制文件再丢进binwalk分析一下:
这一步在研究的时候花了不少功夫,对比了几个不同型号旧固件之后总结出入口地址存放的大致规律:
此时主程序的函数列表还是一堆sub_xxx,得先找到外部符号存放的位置
_wdr7660gv1-cn-up_2019-08-30_10.37.02.bin.extractedgrep-rbzero.匹配到二进制文件./15CBBA前面是一堆8字节的符号信息:
后面是符号对应的字符串:
网上找到了现成的脚本(python2的,可以考虑移植一下?),效果还不错,注意修改以下几个变量:
symfile_path:刚刚搜索到的符号文件的路径
symbols_table_start:符号表起始偏移,从16进制编辑器看出来是8(前8字节作用不清楚,也不重要)
strings_table_start:字符串起始偏移,也是从16进制编辑器看出来
实测不同型号固件虽然修复效果不尽相同,但大致思路都是一样的,欢迎纠正和改进