本文主要是介绍[更新中]MulVAL:基于逻辑推理的攻击图生成及风险评估工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 介绍
- 架构流程
- 核心文件
- 技术细节
- MulVAL的输入
- graph_gen.sh:启动XSB环境进行推理
- graph_gen.sh:调用攻击图生成
- attack_graph:main()
- attack_graph:process_args()
- attack_graph:build_graph()
- attack_graph:build_visual()
- graph_gen.sh:CSV形式输出
- graph_gen.sh:可视化攻击图
- render.sh:基于CSV文件可视化攻击图
- 总结
介绍
MulVAL是一个可以描述多主机、多阶段的基于逻辑推理的攻击图生成工具
官方网页
主要特点和功能:
- 多视图分析:MulVAL使用多视图方法,可以同时分析网络拓扑、系统配置和访问控制策略等多方面,以识别潜在的威胁和攻击路径。
- 自定义规则:可以定义自己的安全规则和策略,以适应其特定场景。
- 威胁建模:基于攻击图对网络攻击建模,可以帮助用户了解各种攻击威胁如何传播和影响后果。
- 漏洞分析:可以帮助用户识别网络和系统中的漏洞,以及这些漏洞可能被利用的方式。
- 可视化:MulVAL提供可视化工具,以便用户更容易地理解网络和系统的安全状况。
架构流程
核心文件
MulVAL目录:
├─bin/(src目录下功能文件编译后存放该目录下)├─adapter/├─metrics/
├─doc/
├─kb/(默认规则目录)├─interaction_rules.P├─interaction_rules_with_metrics.P├─interaction_rules_with_metrics_artifacts.P
├─lib/(存放库文件)├─libmulval.P├─dom4j-1.6.1.jar├─jaxen-1.1.1.jar├─mysql-connector-java-5.1.8-bin.jar
├─src/(部分核心功能文件)├─adapter/(一些java编写功能脚本,用于初始化数据库,连接数据库,获取漏洞信息等)├─analyzer/(用于XSB推理的Prolog功能函数)├─attack_graph/(绘制攻击图脚本文件)├─attack_graph.cpp├─attack_graph.h├─Queue.h├─graphit.l├─graphit.y├─metrics/(用于计算节点概率值)├─independentAlgoSumm.java├─node.java
├─testcases/(测试案例)
├─utils/(部分调用脚本,功能脚本)├─compute_metrics.sh├─dom.py(查找节点间支配与后支配关系)├─graph_gen.sh(启动脚本)├─load_policy.sh├─render.sh(生成可视化攻击图)├─riskAssess.sh├─runRiskAssess.sh├─trim.py(对图数据处理修剪)├─risk_assessment.py
LICENSE
Makefile
README
技术细节
MulVAL的输入
将收集到的主机信息、漏洞信息、安全策略、网络配置等信息转换成相应谓词形式
输入的谓词predicate分为三类primitive(初始)、meta(元)、derived(派生)
例:
漏洞信息(primitive)
vulExists(_host, _vulID, _program).
//_host 主机/服务器上的 _program 存在漏洞,编号为 _vulID
漏洞影响(primitive)
vulProperty(_vulID, _range, _consequence)
//编号为 _vulID 的漏洞,利用方式为 _range ,影响后果为 _consequence
主机配置(primitive)
networkServiceInfo(_host, _program, _protocol, _port, _user)
//程序 _program 以用户权限 _user 在 _host 上运行,使用协议 _protocol,侦听端口 _port。
部分工具自带的三种类型谓词
primitive(clientProgram(_host, _programname)).
primitive(vulExists(_host, _vulID, _program)).
primitive(vulProperty(_vulID, _range, _consequence)).
primitive(hacl(_src, _dst, _prot, _port)).
primitive(networkServiceInfo(_host, _program, _protocol, _port, _perm)).derived(execCode(_host, _perm)).
derived(netAccess(_machine,_protocol,_port)).
derived(canAccessHost(_host)).
derived(accessFile(_machine,_access,_filepath)).meta(attackGoal(_)).
meta(cvss(_vulID, _ac)).
交互行为表示
对于derived类型的谓词,需要制定对应的交互规则(rule),将其编写为 Horn子句,其中第一行是结论,其余行是启用条件,例:
execCode(H, Perm) :-vulExists(H, _VulID, Software, remoteExploit, privEscalation),networkServiceInfo(H, Software, Protocol, Port, Perm),netAccess(H, Protocol, Port)),rule_desc('remote exploit of a server program',1.0)).
/**如果在主机 H 上运行的程序 Software,存在一个可远程利用(remoteExploit)的漏洞(VulID),
该漏洞的影响是权限提升(privEscalation),并且该程序 Software 在权限 Perm 下使用协议 Protocol 并且侦听端口 Port,
通过网络连接netAccess,则攻击者可以以权限 Perm 在机器 Host 上执行任意代码(execCode(Attacker, Host, Priv))。
此规则可应用于任何与模式匹配的漏洞。**/
由XSB推理环境根据输入的谓词文件以及定义的交互规则推理生成出新的derived谓词
这些派生出来新的谓词既可以作为最终的攻击表示,也可以用作其他交互规则的启用条件
graph_gen.sh:启动XSB环境进行推理
#创建XSB运行脚本
#这是一个Here文档(Here Document)的开始。
#它允许在脚本中嵌入多行文本,直到遇到结束标记 EOF 为止。
#所以整个 run.P 文件的内容将在 EOF 处结束。
cat > run.P <<EOF
:-['$MULVALROOT/lib/libmulval']. 导入Prolog库文件
:-['$MULVALROOT/src/analyzer/translate'].
:-['$MULVALROOT/src/analyzer/attack_trace'].
:-['$MULVALROOT/src/analyzer/auxiliary'].:-dynamic meta/1. 创建动态事实:-load_dyn('running_rules.P'). 加载规则:-load_dyn('$INPUT'). 加载输入文件 :-assert(traceMode($trace_option)). 使用 assert 谓词在Prolog中插入一个 traceMode 事实,其值取自 $trace_option 变量。EOF#如果设置了 dynamic_file,则加载该文件,并执行 apply_dynamic_changes。
if test -n "$dynamic_file"; thencat >> run.P <<EOF
:-load_dyn('$dynamic_file').:-apply_dynamic_changes.EOF
fi#如果设置了 TRIM,加载相应的Trim模块,并执行与Trim相关的操作
if test -n "$TRIM"; thencat >> run.P <<EOF
:-load_dyn('$MULVALROOT/src/analyzer/advances_trim.P').:-tell('edges').:-writeEdges.:-told.:-shell('rm -f dominators.P').:-shell('dom.py edges dominators.P').:-loadDominators('dominators.P').EOF
elsecat >> run.P <<EOF
:-load_dyn('$MULVALROOT/src/analyzer/advances_notrim.P').EOF
fi#如果未设置 CVSS,插入一个 cvss(_, none) 事实
if test -z "$CVSS"; thencat >> run.P <<EOF
:-assert(cvss(_, none)).EOF
fi#如果设置了 goal,插入一个 attackGoal 事实。
if test -n "$goal"; thencat >> run.P <<EOF
:- assert(attackGoal($goal)).EOF
ficat run.P > environment.P#启动XSB Prolog系统,将其标准错误输出(2>)和标准输出(1>&2)重定向到名为 xsb_log.txt 的文件中,并使用Here文档传递Prolog脚本
xsb 2>xsb_log.txt 1>&2 <<EOF
[environment]. XSB环境中加载 environment.P 文件
tell('goals.txt'). 创建
writeln('Goal:').
iterate(attackGoal(G), 输出文件中列出攻击目标。(write(' '), write_canonical(G), nl)).
told. 关闭输出文件。
EOFcat goals.txt; rm goals.txt #读取并删除cat >> run.P <<EOF
:-mulval_run.
EOF
#该条规则在调用的libmulval.P中,实现在XSB Prolog环境中运行攻击图生成过程。
#“:-” 用于表示执行一个查询或目标,
#mulval_run :-#mulval_preprocess,#writeln('Running attack simulation...'),#attack_simulation_trace('trace_output.P'),#mulval_postprocess.# 在XSB执行正在运行的脚本
#[run]. 是一种在Prolog中运行脚本的方式
#在run.P文件中,已经定义了一系列的规则和查询,用于执行攻击图生成操作。
#执行[run].时,XSB环境会加载 run.P 文件并开始执行其中的规则和查询,这些规则和查询将调用其他规则,递归地生成攻击图,计算攻击目标等。
xsb 2>xsb_log.txt 1>&2 <<EOF
[run].EOF
XSB推理原理
XSB是逻辑编程系统,使用基于逻辑的语言Prolog。
在逻辑编程中,程序由一组事实(facts)和规则(rules)组成,这些规则描述了问题的逻辑关系和约束。
程序中的查询会被推理引擎自动解释和求解,从而得出答案。
程序通常表示为Horn子句的形式,即:
A :- B, C, D 当B,C,D三种事实都为True时,即可得出A为True
graph_gen.sh:调用攻击图生成
mulval_run :-mulval_preprocess,writeln('Running attack simulation...'),attack_simulation_trace('trace_output.P'),mulval_postprocess.
由该条规则,XSB执行推理后会生成trace_output.P文件,检查是否存在后会开始生成攻击图
if [ -f trace_output.P ]; then #检查trace_output.P是否存在if [ -f metric.P ]; then #检查metric.Pcat metric.P >> trace_output.P #追加内容到trace_outputfi
根据 $ATTACK_GRAPH_OPTS 中的选项(使用工具时输入)和输入文件 trace_output.P,将生成的攻击图输出到名为 AttackGraph.txt 的文件中。
#执行攻击图生成。$MULVALROOT/bin/attack_graph $ATTACK_GRAPH_OPTS trace_output.P > AttackGraph.txt
/bin/attack_graph 该文件由 src/attack_graph/下的attack_graph.cpp、attack_graph.h、Queue.h、graphit.l、graphit.y共同编译而成。
- attack_graph.cpp为主文件,attack_graph.h和Queue.h为头文件,提供部分功能函数。
- graphit.l、graphit.y主要用于处理输入文件
- graphit.l用于将输入文本分解成标记,然后将这些标记传递给与之相关的Bison规则文件进行语法分析。它的目的是将输入文本中的各个元素(如关键字、标识符、数值等)转化为一系列标识符,以便进行后续语法分析。
- graphit.y 文件的主要功能是定义输入文件的语法结构,并将不同元素关联到相应的数据结构中。它是与词法分析器(如graphit.l)协同工作的一部分,用于完成整个编译过程。
attack_graph:main()
int main(int argc, char *argv[] )
{if (argc < 2){cout << "Usage attack_graph trace_file.\n";return -1;}else{process_args(argc, argv);}// 解析输入, 填充 facts, traceSteps and ruleList objects#ifdef LINUXyyin = fopen( tracefile_name,"r");if (yyin == NULL) {cout << "Cannot open trace file " << tracefile_name << endl;return -1;}#else*my_ptr = fopen( tracefile_name,"r");if (*my_ptr == NULL) {cout << "Cannot open trace file " << tracefile_name << endl;return -1;}#endifif (yyparse() != 0){cerr << "Error in parsing trace_output.P" << endl;return -1;}//如果没有攻击目标,打印找不到攻击路径if (data.goals.size() == 0){cerr << "No attack paths found.\n";return 1;}if (build_graph())return -1;//调用build_visual()函数根据参数arc_and_node可视化攻击图if (build_visual(arc_and_node))return -1;// If SAT-solver option selected and valid attack graph has been generated, write to files//如果选择了SAT-solver选项并且生成了有效的攻击图,写入文件//对于攻击图分析,SAT求解器通常用于验证攻击路径是否可行,即检查是否存在一种攻击方式,使得一组条件都满足。//如果SAT求解器能够找到一组满足条件的变量赋值,那么攻击路径是可行的if (buildCNF) {cerr << "Convert graph nodes into CNF clauses, then write to clauses.cnf" << endl;build_cnf();}return 0;
}
attack_graph:process_args()
- 调用process_args函数检查处理命令行参数并打开输入文件,例如是否输出节点和边的列表、是否只输出简单路径、是否运行测试模式、是否构建CNF表示等
void process_args(int argc, char *argv[]){for (int i=1; i < argc; i++){if (*argv[i] == '-'){if (!strcmp(argv[i], "-l")){arc_and_node = true;}else if (!strcmp(argv[i], "--arcNum")){arc_mode = NUMBER;}else if (!strcmp(argv[i], "--arcMetric")){arc_mode = METRICMODE;}else if(!strcmp(argv[i], "-h")){print_usage();}else if(!strcmp(argv[i], "-s")){buildCNF = true;}
...
...
...
- 然后调用yyparse/yyin(linux)函数解析(由graphit.y graphit.l提供)有关facts、traceSteps、ruleList信息的输入文件并储存。
attack_graph:build_graph()
- 调用build_graph()函数构建攻击图,利用队列,循环遍历所有tracestep,根据谓词和事件关系创建相应的节点以及它们之间的边。
int build_graph(void)
{// 循环遍历所有唯一的traceSteptraceStepMap::iterator i,j; traceStepMap *Map;Map = &data.all_trace_steps.traceSteps;for( i=Map->begin(); i != Map->end(); ){string ts_key = i->first;TraceStep *ts = i->second;int num = ts->ruleNum;Conjunct *c = ts->conjunct;//合取项Fact *f = ts->fact;float metric = ts->metric;//释放TraceStep对象所占用的内存delete ts;j=i;i++;Map->erase( j );
创建一个fact_key用来获取事实节点的key属性
创建一个orNode的指针,指向OrNode类型对象,接受两个参数 事实节点的key和事实节点的label。
创建了一个andNode 指向 AndNode 类型对象的指针。并且将其添加到nodeList中
string fact_key = f->key;OrNode *orNode = data.all_or_nodes.addOrNode(fact_key, f);//addOrNode(string &key, Fact *label); AndNode *andNode = new AndNode(num, metric);//AndNode(int rulenum, float metric)if( andNode == NULL || orNode == NULL) {cerr << "Failed to create new node\n";return -1;}data.all_and_nodes.nodeList.add( *andNode );graph_data::nodeCount++;
接着将推理规则和推断节点之间建立边
andNode->nodeNum = graph_data::nodeCount;//将新创建的 AndNode 的节点编号设置为当前节点计数器的值。andNode->parentNodeNum = orNode->nodeNum;//将当前 AndNode(规则)的父节点编号设置为关联的 OrNode(推断节点)的节点编号。orNode->outGoing.add(*(new Arc(orNode, andNode)));//连边for( Fact *fa= c->factList.gethead(); fa >0; fa = c->factList.getnext()) {//取事实fact_key = fa->key; Node *newNode;Type factType = fa->predicate->type; //取事实的类型if( factType == primitive) {//判断原始还是推断 创建节点 分类newNode = data.all_leaf_nodes.addLeafNode(fact_key, fa); }else if( factType == derived) {newNode = data.all_or_nodes.addOrNode(fact_key, fa); }if (factType == primitive || factType == derived){andNode->outGoing.add(*(new Arc(andNode, newNode)));//将新创建的节点(newNode)与当前的 AndNode 相连接,表示从当前的 AndNode 到新节点的边。}}// 释放合取式c的空间delete c;}
攻击路径的终点为攻击目标goal,将goal作为头节点 反向处理
//为头节点添加数据NodeMap::iterator k;for (k = data.goals.begin(); k != data.goals.end(); k++) {//遍历数据结构 data.goals 中的所有攻击目标 data.goals为map映射string fact_key = k->first;//获取事实的keyNode *headNode = data.all_or_nodes.nodes[fact_key];//OrNode推断结点中找if (headNode != NULL){data.goals[fact_key] = headNode;}else{cerr << "Warning: attack goal "<<fact_key<<" was not computed."<<endl;}}
因为headNode以 Node *headNode = data.all_or_nodes.nodes[fact_key] 创建,故调用OrNode对应的函数
//执行修剪,删除非最短路径或非必要switch(prune_option){case noPrune: break;case nonSimple:for (k = data.goals.begin(); k != data.goals.end(); k++) {Node *headNode = k->second;if (headNode != NULL){headNode->allSimplePaths();}}for (k = data.goals.begin(); k != data.goals.end(); k++) {Node *headNode = k->second;if (headNode != NULL){headNode->pruneUselessEdges();}}default:break;}//修剪后重新分配节点编号currentCounter++;currentNodeNum=1;currentArcNum = 1;for (k = data.goals.begin(); k != data.goals.end(); k++) {Node *headNode = k->second;if (headNode != NULL){headNode->dfs(reAssignNodeNum);}}
allSimplePaths()查找最短简单路径长度
查找从当前 OrNode 到攻击目标的所有简单路径中的最短路径长度。
如果节点已经在路径中(inPath 标志为 true),函数会返回 -1,表示存在循环。
函数首先将当前节点标记为在路径中,并初始化最短路径长度为 -1。
然后,它递归调用所有子节点的 allSimplePaths 函数,以查找从子节点到目标的最短路径长度。
如果子节点的路径长度大于等于 0,表示存在简单路径,函数会记录路径长度并更新最短路径长度。
最后,函数将当前节点标记为不在路径中,并返回最短路径长度。
int OrNode::allSimplePaths()
{if (inPath){return -1;}// 扩展DFS路径inPath = true;int shortestLength = -1;// 递归调用所有子进程for (Arc *arc=outGoing.gethead(); arc != NULL; arc=outGoing.getnext()) {// 如果存在简单路径 返回该路径长度int length = arc->getDst()->allSimplePaths();if (length >= 0) {if (arc->weight < 0 || length + 1 < arc->weight){arc->weight = length + 1;}if (shortestLength < 0 || length + 1 < shortestLength){shortestLength = length + 1;}}}inPath = false;return shortestLength;
}
pruneUselessEdges()修剪无用边
剪除 OrNode 节点相连的无用边,以减少无效路径。
如果节点已经处理过(pruned 标志为 Useless),函数会直接返回。
函数首先将当前节点标记为已处理,然后遍历与当前节点关联的出边。
对于每个出边,如果边的权重小于 0,函数会从出边列表中删除该边。
否则,函数递归地调用子节点的 pruneUselessEdges 函数,以处理子节点的无用边。
void OrNode::pruneUselessEdges()
{// 如果已经处理过该节点 返回if (pruned == Useless){return;}pruned = Useless;QueueItem<Arc> *arcItemNext = NULL;for (QueueItem<Arc> *arcItem = outGoing.getheadQitem(); arcItem != NULL ; arcItem = arcItemNext) {arcItemNext = outGoing.getnextQitem(arcItem);Arc *arc = outGoing.getitem(arcItem);if (arc->weight < 0) {outGoing.remove(arcItem);}else{arc->getDst()->pruneUselessEdges(); }}
}
为AssetRank分配metrics
if (useMetrics){cerr << "Computing metrics..." << endl;for (k = data.goals.begin(); k != data.goals.end(); k++) {Node *headNode = k->second;if (headNode != NULL){headNode->bestMetric();}}}return 0;
}
bestMetric()计算节点最佳度量值
查找从当前OrNode节点开始的所有简单路径中的最佳度量值。
如果节点已经在路径中(inPath 标志为 true),函数会返回 -1,表示存在循环。
如果节点的度量值已经计算过(nodeMetric >= 0),则直接返回存储的值。
函数首先将当前节点标记为在路径中,并初始化最佳度量值为 -1。
然后,它递归调用所有子节点的 bestMetric 函数,以查找从子节点到目标的最佳度量值。
如果子节点的度量值大于等于 0,函数会记录该值,并与当前边的度量值进行比较,保留较大的度量值。
最后,函数将当前节点标记为不在路径中,并返回最佳度量值。
float OrNode::bestMetric()
{if (inPath){return -1;}// 如果节点的度量已经计算过,则返回存储的值。if (nodeMetric >= 0){return nodeMetric;}// 扩展DFS路径inPath = true;float bestMetric = -1;// 递归调用所有子项for (Arc *arc=outGoing.gethead(); arc != NULL; arc=outGoing.getnext()) {// 如果存在度量,记录。float metric = arc->getDst()->bestMetric();if (metric >= 0) {if (arc->metric < 0 || betterMetric(metric, arc->metric)){arc->metric = metric;}if (bestMetric < 0 || betterMetric(metric, bestMetric)){bestMetric = metric;}}}inPath = false;nodeMetric = bestMetric;return bestMetric;
}
攻击图节点处理
节点名称对应:OrNode-推断节点 AndNode-推理规则节点 LeafNode-事实节点
对于三种类型节点的处理有细微差别
OrNode 推断节点:
bool WellFounded(int level);//检查节点是否是良好状态,表示节点是否可达且没有未建立攻击路径。void RemoveUnfoundedEdges();//移除不良状态的节点的边int allSimplePaths();//计算到达节点的最短攻击路径的长度。float bestMetric();//获取到达节点的最佳度量值,用于评估攻击图的不同路径。void pruneUselessEdges();//修剪不必要的边。int CountAndNodes();//计算AndNode数。void dfs(dfsAlgorithm alg);//执行深度优先搜索算法,根据传入的 alg 参数进行不同的深度优先搜索操作。int ReAssignNodeNum(int nodeNum);//重新分配节点的编号。void Render(renderMode mode, int indent); //渲染节点,根据指定的渲染模式和缩进输出。bool Render2(arcLabelMode mode);//渲染另一种模式。返回布尔值。void outputVertex(string description, float metric);//输出节点的描述信息和度量值。int TransformToCNF(int parent);//转换为 CNF 形式。
AndNode推理规则节点:
float getMetric() {return metric;}bool WellFounded(int level);void RemoveUnfoundedEdges();int allSimplePaths();float bestMetric();void pruneUselessEdges();int CountAndNodes();void dfs(dfsAlgorithm alg);void Render(renderMode mode, int indent); bool Render2(arcLabelMode mode);void outputVertex(string description, float metric);int TransformToCNF(int parent);
LeafNode事实节点:
bool WellFounded(int level);void RemoveUnfoundedEdges();int allSimplePaths();float bestMetric();void pruneUselessEdges();void dfs(dfsAlgorithm alg);void Render(renderMode mode, int indent); bool Render2(arcLabelMode mode);void outputVertex(string description, float metric);int TransformToCNF(int parent);
attack_graph:build_visual()
- 调用build_visual函数,该函数根据(arc_and_node)参数选择不同的可视化输出方式。如果 (arc_and_node)为真,调用Render2()和outputVertex()函数输出节点和边信息。
int build_visual(bool arc_and_node)//arc_and_node为调用时接收的参数值 -l
{NodeMap::iterator k;for (k = data.goals.begin(); k != data.goals.end(); k++) {string fact_key = k->first;Node *headNode = k->second;if (headNode != NULL){if (arc_and_node){//cout << "0," << headNode->nodeNum << ",1" << endl;headNode->Render2(arc_mode);//根据输入参数选择渲染类型//--arcNum arc_mode = NUMBER;//--arcMetric arc_mode = METRICMODE;}else{// 渲染图,并且使用 0 表示起始缩进。headNode->Render(TEXT, 0);//文本形式渲染cout << endl;}}}return 0;
}
Render(renderMode mode, int indent)
此函数用于渲染(输出)攻击图中的 OrNode 节点,根据给定的 renderMode 和缩进级别 indent 进行格式化输出。
如果节点已经被渲染过(rendered 标志为 true),调用draw_a_link()函数输出。
否则,标记节点为已渲染,调用输出节点的标签信息,包括标签的类型(label)和与该节点相连的出边数量。
然后,遍历所有与当前节点关联的出边,并递归调用子节点的 Render 函数,将 renderMode 和递增后的缩进级别传递给子节点。
void OrNode::Render(renderMode mode, int indent)
{if(rendered) {draw_a_link(mode, indent, nodeNum, label );return;}rendered = true;label->Render(mode, indent, nodeNum, outGoing.size());for(Arc *arc=outGoing.gethead(); arc != NULL; arc=outGoing.getnext()) {arc->getDst()->Render(mode, indent +1);}
}
draw_a_link()
此函数绘制连接的信息,根据给定的 renderMode、缩进级别 indent、节点编号 nodeNumber 和标签信息 label 进行格式化输出。
函数创建缩进字符串(indentation)。
根据 renderMode 的不同,在文本模式下输出连接信息,包括标签的键值和目标节点的编号。
作者只给出了TEXT模式
void draw_a_link( renderMode mode, int indent, int nodeNumber, Fact *label)
{string indentation ;for ( int i =0; i< indent; i++) { indentation += indentStep; }switch (mode) {case TEXT:cout << indentation << label->key << "==><" << nodeNumber << ">" << endl;break;case HTML:break;default:break;};
}
Render2(renderMode)
如果节点尚未被渲染过(rendered 为 false),将节点标记为已渲染(rendered = true)。
调用 outputVertex 函数,输出节点的标签(label->key)和度量值(label->metric)。
遍历所有与当前节点关联的出边,并递归调用子节点的 Render2 函数,传递给子节点相同的 arcLabelMode。
函数返回 true,表示已渲染。
bool OrNode::Render2(arcLabelMode mode)
{if(!rendered) {rendered = true;outputVertex(label->key, label->metric);for(Arc *arc=outGoing.gethead(); arc != NULL; arc=outGoing.getnext()){if (arc->getDst()->Render2(mode))//调用arc由getDst得到的目标节点的 Render2 方法arc->Render(mode);//渲染当前弧}}return true;
}
outputVertex(description,metric)
输出 包括节点编号、描述(description)、节点类型(“OR”)以及可选的度量值(metric)的信息。
如果 displayMetric 为真,会将度量值输出,否则只输出节点编号和描述。
输出样例:1,“execCode(webServer,apache)”,“OR”,0
void OrNode::outputVertex( string description, float metric )//outputVertex(label->key, label->metric)
{if (displayMetric){if (metric < 0){metric = 0;}cout << nodeNum << ",\"" << description << "\",\"OR\"," << metric << endl;}else{cout << nodeNum << ",\"" << description << "\",\"OR\"" << endl;}return;
}
- 如果buildCNF为true,程序将调用build_cnf函数,把图节点转换为CNF子句并写入文件 “clauses.cnf” 中。
CNF子句用于表示合取范式(Conjunctive Normal Form),通常用于描述布尔逻辑问题,特别是可满足性问题(SAT)的实例。在CNF文件中,每一行表示一个逻辑子句,子句由多个bool变量通过“与”、“或”连接而成。
例:(P ∨ Q) ∧ (R ∨ ¬Q)
如果选择了SAT-solver选项并且生成了有效的攻击图,将其写入相应文件
对于攻击图分析,SAT求解器用于验证攻击路径是否可行,检查是否存在一种攻击方式,使得一组条件都满足。
如果SAT求解器能够找到一组满足条件的变量赋值,那么攻击路径是可行的
int build_cnf()
{NodeMap::iterator k;//NodeMap k;//k["key1"] = new Node();//k["key2"] = new DerivedNode();//k为NodeMap的一个对象 储存了两个节点和key1 key2关联Node *headNode;for (k = data.goals.begin(); k != data.goals.end(); k++) {headNode = k->second;if(headNode != NULL) {headNode->TransformToCNF(0);}}//遍历头节点并执行以下操作://如果 headNode 不为空,调用 headNode->TransformToCNF(0) 转换为合取范式(CNF)表示// 写入原始事实 primitive_facts.PfilePrimitiveFacts.open("primitive_facts.P");//filePrimitiveFacts << "assert_primitive_facts :- " << endl;for(int i = 1; i <= primitiveCounter; i++) {filePrimitiveFacts << mapPrimitives[i] << "." << endl;}//filePrimitiveFacts << " " << mapPrimitives[primitiveCounter] << "." << endl;filePrimitiveFacts.close();// 写入派生事实 derived_facts.fileDerivedFacts.open("derived_facts.P");//fileDerivedFacts << "assert_derived_facts :- " << endl;for(int i = 1; i <= derivedCounter; i++) {fileDerivedFacts << mapDerived[i] << "." << endl;}//fileDerivedFacts << " " << mapDerived[derivedCounter] << "." << endl;fileDerivedFacts.close();// 写入cnf子句 clauses.cnffileCNF.open("clauses.cnf");fileCNF << "p cnf " << cnfCounter << " " << clauseCounter << endl; //写入 CNF 文件的头部。//其中 "p cnf" 表示 CNF 的格式,cnfCounter 是 CNF 子句的数量,clauseCounter 是子句中的文字数量。这个头信息是 SAT 求解器期望的文件格式for(int i = 1; i <= clauseCounter; i++) {fileCNF << mapClauses[i] << endl;//将 CNF 子句写入文件}fileCNF << "0" << endl;//"0",表示 CNF 文件的结束fileCNF.close();// 将 CNF 编号/谓词字符串映射写入 mapping.cnf 文件cerr << "Write mapping of node number to tuple to mapping.cnf" << endl;//节点号到元组的映射fileMap.open("mapping.cnf");for(int i = 1; i <= cnfCounter; i++) {fileMap << i << "<<>>" << mapCNF[i] << endl;}fileMap.close();return 0;
}
graph_gen.sh:CSV形式输出
#检查是否需要生成CSV格式的输出if test -n "$CSVOutput"then#将 AttackGraph.txt 中的AND、OR和LEAF节点筛选到 VERTICES.CSV 文件中。grep -E "AND|OR|LEAF" AttackGraph.txt > VERTICES.CSV# AttackGraph.txt 中非AND、OR和LEAF节点筛选到 ARCS.CSV 文件中。grep -Ev "AND|OR|LEAF" AttackGraph.txt > ARCS.CSV
含义:节点编号(Num),节点描述(Label),节点类型(Type),节点概率值(Metric)
含义:后继节点ID(successorID),前驱节点ID(predeccesorID),边的度量值(Metric)
graph_gen.sh:可视化攻击图
调用render.sh脚本可视化攻击图
#检查是否需要可视化攻击图。if test -n "$VISUALIZE"; thenrender.sh $VISUALIZATION_OPTSelseecho "The attack graph data can be found in AttackGraph.txt."fi
render.sh:基于CSV文件可视化攻击图
ac_prev=
for ac_option
do# 如果前一个选项需要参数,将其分配。if test -n "$ac_prev"; theneval "$ac_prev=\$ac_option"ac_prev=continueficase "$ac_option" in--arclabel)arclabel=true ;;--reverse)reverse=true ;;--nometric)nometric=true ;;--simple)simple=true ;;*)
# -h | --help)cat <<EOF
Usage:render.sh [--arclabel][--reverse][--simple][-h|--help]
EOFexit ;;esac
doneecho "通过GraphViz生成攻击图"echo digraph G { > AttackGraph.dotif test -n "$simple"; thenif test -n "$nometric"; thenvertice_sed_file=$MULVALROOT/utils/VERTICES_simple_no_metric.sedelsevertice_sed_file=$MULVALROOT/utils/VERTICES_simple.sedfi
elseif test -n "$nometric"; thenvertice_sed_file=$MULVALROOT/utils/VERTICES_no_metric.sedelsevertice_sed_file=$MULVALROOT/utils/VERTICES.sedfi
fi
sed -f $vertice_sed_file VERTICES.CSV >> AttackGraph.dotif test -n "$reverse"; thenif test -n "$arclabel"; thensed -f $MULVALROOT/utils/ARCS_reverse.sed ARCS.CSV >> AttackGraph.dotelsesed -f $MULVALROOT/utils/ARCS_reverse_noLabel.sed ARCS.CSV >> AttackGraph.dotfi
elseif test -n "$arclabel"; thensed -f $MULVALROOT/utils/ARCS.sed ARCS.CSV >> AttackGraph.dotelsesed -f $MULVALROOT/utils/ARCS_noLabel.sed ARCS.CSV >> AttackGraph.dotfi
fiecho } >> AttackGraph.dotdot -Tps AttackGraph.dot > AttackGraph.eps
epstopdf AttackGraph.epsecho "如果成功生成, 攻击图将在AttackGraph.pdf中"if test -n "$PDF_READER"; then$PDF_READER AttackGraph.pdf&
fi
总结
这篇关于[更新中]MulVAL:基于逻辑推理的攻击图生成及风险评估工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!