基础概念 我在记录调试分析joern源码的过程中,学到了一些关于java的新知识,为了能够方便自己后面回顾,所以先记录下一些基础概念:
重载overload :重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载 。
重写override :重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写! 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
Statement :statement的例子有(for循环,if条件句)
Expression :expression的例子有(函数调用,变量之间的运算)
如果你能将一串代码作为参数传递,那么这就能被称为expression。
Statement和Expression之间的关系 :所有的expression都是Statement ,反之则不成立。expression可以作为右值,但是statement不可以。简单来说,expression是可以用eval来求值 的东西,statement则不一定。可以参考这篇问答:https://stackoverflow.com/questions/4728073/what-is-the-difference-between-an-expression-and-a-statement-in-python 。
1 2 3 4 >function foo(expression) { statement; }
条件句基本结构 最常见的条件句就是if(或者是if-else)条件语句。
官方手册 if条件句官方手册:https://www.php.net/manual/zh/control-structures.if.php
官方中给出的最常见的if条件句结构:
一个最简单的例子,下面的php代码if_normal.php
表示,如果firstName
不为空,那么$name
就设为$firstName
,否则就设为Guest
:
1 2 3 4 5 6 <?php if (!empty ($firstName)) $name = $firstName; else $name = "Guest" ; ?>
生成的代码属性图为:
从中抽取出CFG为:
程序的控制流一共有两条路径(数字表示id):
1 2 2 -> 7 -> 11 -> 22 -> 3 2 -> 7 -> 18 -> 22 -> 3
我们知道,在控制流图中,有CFG_FUNC_ENTRY
和CFG_FUNC_EXIT
,其中,CFG_FUNC_EXIT
结点的前一个node通常是一个null结点,控制流会汇聚在null node,最后通过null node流向CFG出口结点CFG_FUNC_EXIT
。
上图中id=7
的结点它的type = AST_UNARY_OP
,说明了这是一个一元操作运算符 ,flags = UNARY_BOOL_NOT
,说明该一元操作运算符是!
。
在joern/projects/extensions/joern-php/src/main/java/inputModules/csv/PHPCSVNodeTypes.java
的PHPCSVNodeTypes 类中,就定义了几种flags来区分不同的操作运算符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class PHPCSVNodeTypes { public static final String FLAG_UNARY_BOOL_NOT = "UNARY_BOOL_NOT" ; public static final String FLAG_UNARY_BITWISE_NOT = "UNARY_BITWISE_NOT" ; public static final String FLAG_UNARY_MINUS = "UNARY_MINUS" ; public static final String FLAG_UNARY_PLUS = "UNARY_PLUS" ; public static final String FLAG_UNARY_SILENCE = "UNARY_SILENCE" ; }
nodes.csv
rels.csv
cpg_edges.csv 根据nodes.csv
和rels.csv
生成的对应的cpg_edges.csv
为:
根据rels.csv
得到的ast结构为,严格来说,[0]
号结点类型是Artificial
,并不是AST
类型的:
根据该AST树转成的CFG图为:
if条件句解析流程分析 Main.java 首先,从AST树的id=1
的结点开始,调用ASTToCFGConverter.convert
将AST对象转化为CFG对象:
因为是PHP源码,所以ASTToCFGConverter.setFactory
会将ASTToCFGConverter.factory
设置为PHPCFGFactory
,那么第23行调用的就是PHPCFGFactory.convert()
:
并且传入的参数node对象的类型为FunctionDef
,但上面第21行的node类型是FUnctionDefBase
,因为后者是前者的父类:
我们可以看到,无论是子类FunctionDef
还是父类FunctionDefBase
,其实归根结底都是一种ASTNode
。
PHPCFGFactory & CFGFactory PHPCFGFactory
类中有重载(overload)方法 newInstance()
来处理不同的statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package cfg;import ...public class PHPCFGFactory extends CFGFactory { private static final int CFG_ENTRY_OFFSET = 1 ; private static final int CFG_EXIT_OFFSET = 2 ; static { structuredFlowVisitior = new PHPStructuredFlowVisitor(); } public PHPCFGFactory () { structuredFlowVisitior = new PHPStructuredFlowVisitor(); } @Override public CFG newInstance (FunctionDefBase functionDefinition) { ... } public static CFG newInstance (IfStatementBase ifStatement) { ... } private static CFG convertIfElem (Iterator<IfElement> iterator) { ... } public static CFG newInstance (SwitchStatement switchStatement) { ... } public static CFG newInstance (ForEachStatement forEachStatement) { ... } public static CFG newInstance (TryStatement tryStatement) { ... } }
其中newInstance(FunctionDefBase functionDefinition)
重写 了父类CFGFactory 的CFGFactory.newInstance(FunctionDefBase functionDefinition)
,区别在于,在返回父类CFGFactory
生成的cfg
之后,还需要设置entry node和exit node的id,分为设置为2和3,并将entry node的类型转为CFGEntryNode
,exit node的类型转为CFGExitNode
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class PHPCFGFactory extends CFGFactory { ...... @Override public CFG newInstance (FunctionDefBase functionDefinition) { CFG cfg = super .newInstance(functionDefinition); Long id = functionDefinition.getNodeId(); ((CFGEntryNode)cfg.getEntryNode()).setNodeId( id + CFG_ENTRY_OFFSET); ((CFGExitNode)cfg.getExitNode()).setNodeId( id + CFG_EXIT_OFFSET); return cfg; } }
和PHPCFGFactory
一样,CFGFactory
也存在对cfg实例化方法newInstance()
的重载(overload) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package cfg;import ...public class CFGFactory { protected static StructuredFlowVisitor structuredFlowVisitior; public CFG newInstance (FunctionDefBase functionDefinition) { ... } public static CFG newInstance (ASTNode... nodes) { ... } public static CFG newErrorInstance () n { ... } public static CFG newInstance (WhileStatement whileStatement) { ... } public static CFG newInstance (ForStatement forStatement) { ... } public static CFG newInstance (DoStatement doStatement) { ... } public static CFG newInstance (TryStatement tryStatement) { ... } public static CFG newInstance (SwitchStatement switchStatement) { ... } public static CFG newInstance (ParameterList paramList) { ... } public static CFG newInstance (CompoundStatement content) { ... } public static CFG newInstance (ReturnStatement returnStatement) { ... } public static CFG newInstance (GotoStatement gotoStatement) { ... } public static CFG newInstance (Label labelStatement) { ... } public static CFG newInstance (ContinueStatement continueStatement) { ... } public static CFG newInstance (BreakStatement breakStatement) { ... } public static CFG newInstance (ThrowStatement throwStatement) { ... } public static CFG convert (ASTNode node) { ... } public static void fixBreakStatements (CFG thisCFG, CFGNode target) { ... } public static void fixContinueStatement (CFG thisCFG, CFGNode target) { ... } private static void fixBreakOrContinueStatements (CFG thisCFG, CFGNode target, Iterator<CFGNode> it) { ... } public static void fixGotoStatements (CFG thisCFG) { ... } public static void fixReturnStatements (CFG thisCFG) { ... } }
CFGFactory.newInstance(FunctionDefBase functionDefinition) 我们跟入到CFGFactory.newInstance(FunctionDefBase functionDefinition)
:
上图中第44行的断点处:
1 CFG function = newInstance();
它的作用是生成一个最小单位的CFG,一个最小单位的CFG只包含两个结点,一个entry node,一个exit node,表示为:
1 2 [ENTRY] { [ENTRY] ==[]==> [EXIT] , } [EXIT] { }
前面我们说到,无论是子类FunctionDef
对象,还是它的父类FunctionDefBase
对象,其实它们都是一种ASTNode
。一般来说,下面几种类型的ASTNode会成为FunctionDefBase functionDefinition
:
1 2 3 4 5 6 7 8 public class PHPCSVNodeTypes { public static final String TYPE_TOPLEVEL = "AST_TOPLEVEL" ; public static final String TYPE_FUNC_DECL = "AST_FUNC_DECL" ; public static final String TYPE_METHOD = "AST_METHOD" ; public static final String TYPE_CLOSURE = "AST_CLOSURE" ; }
在单php文件中,只要存在多个函数定义,就有可能存在多个FunctionDefBase
类型结点,因为该类型就是用来区分函数块的。
FunctionDefBase 接着继续分析,程序执行到下一行,此时已经执行了functionDefinition.getParameterList()
,但未执行convert()
函数:
functionDefinition.getParameterList()
返回的结果是null
。
这是什么意思?
我们来看一下FunctionDefBase
类,它有两个成员变量:
1 2 3 4 5 public abstract class FunctionDefBase extends ASTNode { protected ParameterList parameterList = null ; protected CompoundStatement content = null ; }
分别为类ParameterList
和类CompoundStatement
。
FunctionDeBase.ParameterList 其中ParameterList
是一种ASTNode List
:
ParameterList
类有一个成员变量parameters
,是一个LinkedList,它用来存储类型为ParameterBase
的结点:
1 2 3 4 public class ParameterList extends ASTNode implements Iterable <ParameterBase > { private LinkedList<ParameterBase> parameters = new LinkedList<ParameterBase>(); }
ParameterBase
类是Parameter
的父类,一般函数中的参数类型就是ParameterBase
(更具象来说是Parameter
),那ParameterList
的作用主要维护一个函数中的类型为ParameterBase
的函数参数。它们的关系如下图所示:
FunctionDefBase.CompoundStatement 然后是CompoundStatement
,简单来说就是一种复合类型的Statement。CompoundStatement通常包含更小的CompoundStatement。
用一个最简单的例子来描述ParamterList
和CompoundStatement
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public CFG newInstance (FunctionDefBase functionDefinition) { try { CFG function = newInstance(); CFG parameterBlock = convert( functionDefinition.getParameterList()); CFG functionBody = convert(functionDefinition.getContent()); parameterBlock.appendCFG(functionBody); function.appendCFG(parameterBlock); fixGotoStatements(function); fixReturnStatements(function); if (!function.getBreakStatements().isEmpty()) { System.err.println("warning: unresolved break statement" ); fixBreakStatements(function, function.getErrorNode()); } if (!function.getContinueStatements().isEmpty()) { System.err.println("warning: unresolved continue statement" ); fixContinueStatement(function, function.getErrorNode()); } if (function.hasExceptionNode()) { function.addEdge(function.getExceptionNode(), function.getExitNode(), CFGEdge.UNHANDLED_EXCEPT_LABEL); } return function; } catch (Exception e) { return newErrorInstance(); } }
functionBody生成 接着开始处理最主要的内容,我们可以较为简单的理解为开始处理函数里面的语句,对于无函数的php文件,就是<?php
后面对应的语句。
其中id=4
的结点就是type = AST_STMT_LIST
的结点,它通常是很多结点的父结点。
跟进CFGFactory.convert
函数,我们会发现这一步主要是根据传进convert函数的node类型来决定StructuredFlowVisitor
。
前面我们已经知道,CFG图是由CFGFactory
或是PHPCFGFactory
类中的重载方法newInstance
来决定的,但是不同的结点类型,它们的处理过程也有不同, 那么如何确定某个结点使用哪个newInstance
函数来生成该结点对应的CFG呢?这是由StructuredFlowVisitor
决定的。这里稍微提一下StructuredFlowVisitor
,它是PHPStructuredFlowVistor
的父类:
StructuredFlowVisitor
只处理两种类型的结点,CompoundStatement
和其他:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package cfg;import ...public class StructuredFlowVisitor extends ASTNodeVisitor { protected CFG returnCFG; public CFG getCFG () { return returnCFG; } public void visit (CompoundStatement content) { returnCFG = CFGFactory.newInstance(content); } public void visit (ASTNode expression) { returnCFG = CFGFactory.newInstance(expression); } }
而在PHPStructuredFlowVisitor
中,对StructuredFlowVisitor.visit(ASTNode expression)
进行了多次重写,进行了更加细致的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 package cfg;import ...public class PHPStructuredFlowVisitor extends StructuredFlowVisitor { @Override public void visit (ParameterList paramList) { returnCFG = CFGFactory.newInstance(paramList); } @Override public void visit (ParameterBase param) { returnCFG = CFGFactory.newInstance(param); for (CFGNode node : returnCFG.getVertices()) { if (!(node instanceof ASTNodeContainer)) continue ; returnCFG.registerParameter(node); } } @Override public void visit (IfStatementBase node) { returnCFG = PHPCFGFactory.newInstance(node); } @Override public void visit (Label node) { returnCFG = CFGFactory.newInstance(node); } @Override public void visit (WhileStatement node) { returnCFG = CFGFactory.newInstance(node); } @Override public void visit (DoStatement node) { returnCFG = CFGFactory.newInstance(node); } @Override public void visit (ForStatement node) { returnCFG = CFGFactory.newInstance(node); } @Override public void visit (ForEachStatement node) { returnCFG = PHPCFGFactory.newInstance(node); } @Override public void visit (ReturnStatement expression) { returnCFG = CFGFactory.newInstance(expression); } @Override public void visit (GotoStatement expression) { returnCFG = CFGFactory.newInstance(expression); } @Override public void visit (SwitchStatement node) { returnCFG = PHPCFGFactory.newInstance(node); } @Override public void visit (ContinueStatement expression) { returnCFG = CFGFactory.newInstance(expression); } @Override public void visit (BreakStatement expression) { returnCFG = CFGFactory.newInstance(expression); } @Override public void visit (TryStatement node) { returnCFG = PHPCFGFactory.newInstance(node); } @Override public void visit (ThrowStatement node) { returnCFG = CFGFactory.newInstance(node); } }
这段的执行流程为:
id=4, type=AST_STMT_LIST
的结点,它有一个id=5, type=AST_IF
的子结点,子结点类型为IfStatement
,在403行的代码由两部分组成:
1 2 CFG tmp_CFG = convert(statement); compoundBlock.appendCFG(tmp_CFG);
convert()
函数的作用和流程还和之前一样,不一样的地方只是PHPStructuredFlowVistitor.visit
会根据传入的结点类型进行不同的处理,目的就是找到正确的PHPCFGFactory.newInstance()
来处理结点:
PHPCFGFactory.newInstance(IfStatement ifStatement) 对于类型为IfStatement
的结点,对应的PHPCFGFactory.newInstance()
为newInstance(IfStatement ifStatement)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class PHPCFGFactory extends CFGFactory { ...... public static CFG newInstance (IfStatementBase ifStatement) { try { IfStatement phpIfStatement = (IfStatement)ifStatement; Iterator<IfElement> iterator = phpIfStatement.iterator(); if (!iterator.hasNext()) return newErrorInstance(); CFG ifElemCFG = convertIfElem(iterator); return ifElemCFG; } catch (Exception e) { return newErrorInstance(); } } ...... }
我们知道,在关系图中,type=AST_IF
的结点会根据分支语句的数量,有对应数量的type=AST_IF_ELEM
的子结点,而IfStatement.iterator()
会返回type=AST_IF
(结点类型为IfStatement
)对应个数的子结点,该子结点type=AST_IF_ELEM
,类型为IfElement
:
与普通结点不一样,IfElment
结点转化为CFG不是由PHPCFGFactory.convert
来处理,而是PHPCFGFactory.convertIfElem
来说处理。
PHPCFGFactory.convertIfElem: if-block 跟入PHPCFGFactory.convertIfElem(Iterator<IfElement> iterator)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class PHPCFGFactory extends CFGFactory { ...... private static CFG convertIfElem (Iterator<IfElement> iterator) { CFG block = new CFG(); IfElement ifElem = iterator.next(); Expression condition = ifElem.getCondition(); Statement statement = ifElem.getStatement(); CFG ifBlock = convert(statement); if (condition == null ) { return ifBlock; } CFGNode conditionContainer = new ASTNodeContainer( condition); block.addVertex(conditionContainer); block.addEdge(block.getEntryNode(), conditionContainer); block.mountCFG(conditionContainer, block.getExitNode(), ifBlock, CFGEdge.TRUE_LABEL); if (iterator.hasNext()) { CFG elseBlock = convertIfElem(iterator); block.mountCFG(conditionContainer, block.getExitNode(), elseBlock, CFGEdge.FALSE_LABEL); } else { block.addEdge(conditionContainer, block.getExitNode(), CFGEdge.FALSE_LABEL); } return block; } }
ifElem.getCondition()
和ifElem.getStatement()
分别会获取下面if条件句中的expr
和statement
:
1 2 3 4 <?php if (expr) statement ?>
对应我们的例子为:
1 2 3 4 5 6 <?php if (!empty ($firstName)) $name = $firstName; else $name = "Guest" ; ?>
如果$name = $firstName;
是更复杂一点的复合语句,比如:
1 2 $name = $firstName; echo $name;
那么[(11) ExpressionStatment]
就会变成CompoundStatement
,因为无论是CompoundStatement
还是ExpressionStatement
都是继承自Statement
:
接着将该[(11) ExpressionStatement]
转变成CFG
对象:
对应的ifBlock
为:
1 2 3 [ENTRY] { [ENTRY] ==[]==> [(11) ExpressionStatement] , } [EXIT] { } [(11) ExpressionStatement] { [(11) ExpressionStatement] ==[]==> [EXIT] , }
然后将type=AST_IF
的结点也转为CFG对象:
对应的block
为:
1 2 3 [ENTRY] { [ENTRY] ==[]==> [(7) UnaryOperationExpression] , } [EXIT] { } [(7) UnaryOperationExpression] { }
接着block
块调用CFG.mountCFG()
将AST_IF
和AST_IF_ELEM
等分支结点连接起来:
block
:代表AST_IF
结点对应的CFG对象
conditionContainer
:代表AST_IF
结点,是一个ASTNode对象
ifBlock
:代表AST_IF_ELEM
(是if分支语句中的一支)结点对应的CFG对象
CFG.mountCFG 跟入CFG.mountCFG
,注意调用该函数的是AST_IF
结点对应的CFG对象(下面注释中,我就称其为main if block):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 public class CFG extends IncidenceListGraph <CFGNode , CFGEdge > { ... public void mountCFG (CFGNode branchNode, CFGNode mergeNode, CFG cfg, String label) { if (!cfg.isEmpty()) { addCFG(cfg); for (CFGEdge edge : cfg.outgoingEdges(cfg.getEntryNode())) { addEdge(branchNode, edge.getDestination(), label); } for (CFGEdge edge : cfg.incomingEdges(cfg.getExitNode())) { addEdge(edge.getSource(), mergeNode, edge.getLabel()); } } else { addEdge(branchNode, mergeNode, label); } } public void addCFG (CFG otherCFG) { addVertices(otherCFG); addEdges(otherCFG); getParameters().addAll(otherCFG.getParameters()); getBreakStatements().addAll(otherCFG.getBreakStatements()); getContinueStatements().addAll(otherCFG.getContinueStatements()); getReturnStatements().addAll(otherCFG.getReturnStatements()); getGotoStatements().putAll(otherCFG.getGotoStatements()); getLabels().putAll(otherCFG.getLabels()); if (this .hasExceptionNode() && otherCFG.hasExceptionNode()) { CFGExceptionNode oldExceptionNode = getExceptionNode(); CFGExceptionNode newExceptionNode = new CFGExceptionNode(); setExceptionNode(newExceptionNode); addEdge(oldExceptionNode, newExceptionNode, CFGEdge.UNHANDLED_EXCEPT_LABEL); addEdge(otherCFG.getExceptionNode(), newExceptionNode, CFGEdge.UNHANDLED_EXCEPT_LABEL); } else if (otherCFG.hasExceptionNode()) { setExceptionNode(otherCFG.getExceptionNode()); } } private void addEdges (CFG cfg) { for (CFGNode vertex : cfg.getVertices()) { for (CFGEdge edge : cfg.outgoingEdges(vertex)) { if (!(edge.getSource().equals(cfg.getEntryNode()) || edge.getDestination().equals(cfg.getExitNode()))) { addEdge(edge); } } } } public void addEdge (CFGNode srcBlock, CFGNode dstBlock) { addEdge(srcBlock, dstBlock, CFGEdge.EMPTY_LABEL); } public void addEdge (CFGNode srcBlock, CFGNode dstBlock, String label) { CFGEdge edge = new CFGEdge(srcBlock, dstBlock, label); addEdge(edge); } ... }
经过CFG.addCFG()
之后,block
(即this
对象,因为是block
对象调用的mountCFG
(block.mountCFG()
))所代表的AST_IF
结点为变成了:
然后对block(this)
进行连边:
它是这样处理的:
最后得到的block(this)
为:
1 2 3 4 [ENTRY] { [ENTRY] ==[]==> [(7) UnaryOperationExpression] , } [EXIT] { } [(7) UnaryOperationExpression] { [(7) UnaryOperationExpression] ==[True]==> [(11) ExpressionStatement] , } [(11) ExpressionStatement] { [(11) ExpressionStatement] ==[]==> [EXIT] , }
PHPCFGFactory.convertIfElem: else-block 到上面这一步为止,源码中的下面部分的CFG已经处理了:
1 2 if (!empty ($firstName)) $name = $firstName;
但是还有else部分没有处理:
我们继续跟下去,else对应的type=AST_IF_ELEM
结点的类型为IfElement
,id为16。对于该else block,也是通过convertIfElement(iterator)
来处理的,所以我们看到在101行,调用了convertIfElem(iterator)
:
递归跟入convertIfElem()
,我们知道elseBlock并没有对应的condition,所以condition
对象的值肯定为null
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static CFG convertIfElem (Iterator<IfElement> iterator) { CFG block = new CFG(); IfElement ifElem = iterator.next(); Expression condition = ifElem.getCondition(); Statement statement = ifElem.getStatement(); CFG ifBlock = convert(statement); if (condition == null ) { return ifBlock; } ...... }
因为condition==null
,所以直接将ifBlock
返回给elseBlock
,然后调用block.mountCFG()
将elseBlock
拼接到main if block中,并且将边标记为CFGEdge.FalseLabel
:
经过block.mountCFG()
得到的CFG为:
1 2 3 4 5 [ENTRY] { [ENTRY] ==[]==> [(7) UnaryOperationExpression] , } [EXIT] { } [(7) UnaryOperationExpression] { [(7) UnaryOperationExpression] ==[True]==> [(11) ExpressionStatement] ,[(7) UnaryOperationExpression] ==[False]==> [(18) ExpressionStatement] , } [(11) ExpressionStatement] { [(11) ExpressionStatement] ==[]==> [EXIT] , } [(18) ExpressionStatement] { [(18) ExpressionStatement] ==[]==> [EXIT] , }
return to PHPCFGFactory.newInstance 现在if语句中的主要结点对应的CFG都已经形成了,所以接下来就是进行返回之前的递归调用,可进行收尾处理。
将PHPCFGFactory.convertIfElem(Iterator<IfElement> iterator)
的结果返回给PHPCFGFactory.newInstance(IfStatementBase ifStatement)
:
然后返回到处理id=4
的结点(这个结点type=AST_STMT_LIST
,是其他AST
结点的父亲节点):
type=4
的结点即compoundBlock
对象,它是最小的CFG单位,将if block([(5) IfStatement]
)添加上去后,还有CFG的汇流点,CFG_FUNC_EXIT
的父亲结点,[(22) NullNode]
需要处理:
利用compoundBlock.appendCFG()
将[(5) IFStatement]
对应的子CFG和[(22) NullNode]
对应的子CFG加入后,得到的CFG结构为:
1 2 3 4 5 6 [ENTRY] { [ENTRY] ==[]==> [(7) UnaryOperationExpression] , } [EXIT] { } [(7) UnaryOperationExpression] { [(7) UnaryOperationExpression] ==[True]==> [(11) ExpressionStatement] ,[(7) UnaryOperationExpression] ==[False]==> [(18) ExpressionStatement] , } [(11) ExpressionStatement] { [(11) ExpressionStatement] ==[]==> [(22) NullNode] , } [(18) ExpressionStatement] { [(18) ExpressionStatement] ==[]==> [(22) NullNode] , } [(22) NullNode] { [(22) NullNode] ==[]==> [EXIT] , }
到这一步,functionBody ,也就是函数体的内容语句部分对应的CFG就已经生成了:
然后后面还需要处理函数的parameter,因为此次解析的代码并不是函数式的,所以parameterBlock
其实也是仅包含enrty node和exit node的最小的CFG块,然后将functionBody
加到parameterBlock
上,最后将parameterBlock
用同样的方式添加到CFG起始块function
上。
最后,还需要为entry node和exit node分配id,并将它们的类型转为对应的CFGEntryNode
和CFGExitNode
(这两个类型都是继承自CFGNode
),这样,cfg就生成了。
现在得到的cfg为:
1 2 3 4 5 6 [(2) ENTRY] { [(2) ENTRY] ==[]==> [(7) UnaryOperationExpression] , } [(3) EXIT] { } [(7) UnaryOperationExpression] { [(7) UnaryOperationExpression] ==[True]==> [(11) ExpressionStatement] ,[(7) UnaryOperationExpression] ==[False]==> [(18) ExpressionStatement] , } [(11) ExpressionStatement] { [(11) ExpressionStatement] ==[]==> [(22) NullNode] , } [(18) ExpressionStatement] { [(18) ExpressionStatement] ==[]==> [(22) NullNode] , } [(22) NullNode] { [(22) NullNode] ==[]==> [(3) EXIT] , }
最后就是调用csvCFGExporter.writeCFGEdges(cfg)
将生成的cfg写入到cfg_edges.csv
中:
CFG生成小结 从上面的分析流程,其实我们可以看出在生成CFG的过程中,是通过前序遍历 的方式来访问每一个AST结点,并将符合要求的结点转为CFGNode
对象,添加到CFG中。
画一个比较粗略的执行流程:
后续问题 当然这是最简单的if条件语句,在很多情况下,if条件句可能会被其他运算符替代,比如php中唯一的三元运算符 ?:
和null合并运算符 ??
。
三元运算符 ?: 三元运算符可以用一行代码进行逻辑判断,从而代替常见的if-else变量赋值判断。
null合并运算符 ?? null合并运算符又称为空值合并运算符 ,它是用于执行isset()检测的三元运算符的快捷方式。NULL 合并运算符会判断变量是否存在且值不为NULL,如果是,它就会返回自身的值,否则返回它的第二个操作数。
这两种特殊的条件语句,它们生成的CPG,其实我们可以猜到,因为缺少if关键字,所以如果joern没有对其进行特殊的处理,是非常有可能无法生成正确的CPG的。这个在后续的学习中会进行测试。