赞
踩
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
将收集到的主机信息、漏洞信息、安全策略、网络配置等信息转换成相应谓词形式
输入的谓词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谓词
这些派生出来新的谓词既可以作为最终的攻击表示,也可以用作其他交互规则的启用条件
#创建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"; then cat >> run.P <<EOF :-load_dyn('$dynamic_file'). :-apply_dynamic_changes. EOF fi #如果设置了 TRIM,加载相应的Trim模块,并执行与Trim相关的操作 if test -n "$TRIM"; then cat >> 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 else cat >> run.P <<EOF :-load_dyn('$MULVALROOT/src/analyzer/advances_notrim.P'). EOF fi #如果未设置 CVSS,插入一个 cvss(_, none) 事实 if test -z "$CVSS"; then cat >> run.P <<EOF :-assert(cvss(_, none)). EOF fi #如果设置了 goal,插入一个 attackGoal 事实。 if test -n "$goal"; then cat >> run.P <<EOF :- assert(attackGoal($goal)). EOF fi cat 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. 关闭输出文件。 EOF cat 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
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.P
cat metric.P >> trace_output.P #追加内容到trace_output
fi
根据 $ATTACK_GRAPH_OPTS 中的选项(使用工具时输入)和输入文件 trace_output.P,将生成的攻击图输出到名为 AttackGraph.txt 的文件中。
#执行攻击图生成。
$MULVALROOT/bin/attack_graph $ATTACK_GRAPH_OPTS trace_output.P > AttackGraph.txt
trace_output.P部分内容
primitive(mitm,3).
primitive(noCheckAuth,3).
primitive(isDomainMember,3).
derived(execCode,2).
derived(netAccess,3).
derived(lanAccess,1).
meta(cvss,2).
meta(attackGoal,1).
attack(execCode(webServer,apache)).
possible_duplicate_trace_step(because(12,rule_desc('first access on the same LAN',1.0),netAccess(workstation,tcp,445),[vlanInterface(workstation,homeNetwork),attackerLocated(homeNetwork)])).
possible_duplicate_trace_step(because(0,rule_desc('remote exploit of a server program',1.0),execCode(workstation,root),[netAccess(workstation,tcp,445),networkServiceInfo(workstation,_h4499,smb,tcp,445,root),vulExists(workstation,'CVE-2020-0796',smb,remoteExploit,privEscalation)])).
possible_duplicate_trace_step(because(0,rule_desc('remote exploit of a server program',1.0),execCode(workstation,root),[netAccess(workstation,tcp,445),networkServiceInfo(workstation,_h4046,smb,tcp,445,root),vulExists(workstation,'CVE-2020-0796',smb,remoteExploit,privEscalation)])).
possible_duplicate_trace_step(because(10,rule_desc('multi-hop access',1.0),netAccess(webServer,tcp,80),[vlanInterface(webServer,serviceLAN),vlanInterface(workstation,userLAN),firewallRule(workstation,userLAN,webServer,serviceLAN,tcp,80),execCode(workstation,root)])).
possible_duplicate_trace_step(because(8,rule_desc('LAN access',1.0),lanAccess(homeNetwork),[vlanInterface(workstation,homeNetwork),execCode(workstation,root)])).
possible_duplicate_trace_step(because(8,rule_desc('LAN access',1.0),lanAccess(userLAN),[vlanInterface(workstation,userLAN),execCode(workstation,root)])).
/bin/attack_graph 该文件由 src/attack_graph/下的attack_graph.cpp、attack_graph.h、Queue.h、graphit.l、graphit.y共同编译而成。
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 LINUX yyin = 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; } #endif if (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; }
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; } ... ... ...
int build_graph(void) { // 循环遍历所有唯一的traceStep traceStepMap::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;//获取事实的key
Node *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);
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);//调用Render()渲染弧
}
}
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;
}
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.P filePrimitiveFacts.open("primitive_facts.P"); for(int i = 1; i <= primitiveCounter; i++) { filePrimitiveFacts << mapPrimitives[i] << "." << endl; } filePrimitiveFacts.close(); // 写入派生事实 derived_facts. fileDerivedFacts.open("derived_facts.P"); for(int i = 1; i <= derivedCounter; i++) { fileDerivedFacts << mapDerived[i] << "." << endl; } fileDerivedFacts.close(); // 写入cnf子句 clauses.cnf fileCNF.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; }
#检查是否需要生成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)
调用render.sh脚本可视化攻击图
#检查是否需要可视化攻击图。
if test -n "$VISUALIZE"; then
render.sh $VISUALIZATION_OPTS
else
echo "The attack graph data can be found in AttackGraph.txt."
fi
ac_prev= for ac_option #迭代命令行参数 do # 如果 ac_prev 非空表示需要参数,将当前选项ac_option(当前选项)的值分配给ac_prev(前一个选项)。 if test -n "$ac_prev"; then eval "$ac_prev=\$ac_option" ac_prev= #将ac_prev置空,表示前一个选项已经处理完毕 continue fi case "$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] EOF exit ;; esac done echo "通过GraphViz生成攻击图" echo digraph G { > AttackGraph.dot #根据参数选择不同sed文件处理CSV文件 if test -n "$simple"; then if test -n "$nometric"; then vertice_sed_file=$MULVALROOT/utils/VERTICES_simple_no_metric.sed else vertice_sed_file=$MULVALROOT/utils/VERTICES_simple.sed fi else if test -n "$nometric"; then vertice_sed_file=$MULVALROOT/utils/VERTICES_no_metric.sed else vertice_sed_file=$MULVALROOT/utils/VERTICES.sed fi fi sed -f $vertice_sed_file VERTICES.CSV >> AttackGraph.dot if test -n "$reverse"; then if test -n "$arclabel"; then sed -f $MULVALROOT/utils/ARCS_reverse.sed ARCS.CSV >> AttackGraph.dot else sed -f $MULVALROOT/utils/ARCS_reverse_noLabel.sed ARCS.CSV >> AttackGraph.dot fi else if test -n "$arclabel"; then sed -f $MULVALROOT/utils/ARCS.sed ARCS.CSV >> AttackGraph.dot else sed -f $MULVALROOT/utils/ARCS_noLabel.sed ARCS.CSV >> AttackGraph.dot fi fi echo } >> AttackGraph.dot #使用 GraphViz 工具的 dot 命令将 AttackGraph.dot 转换为 PostScript 格式,并将结果存储在 AttackGraph.eps 文件中。 dot -Tps AttackGraph.dot > AttackGraph.eps epstopdf AttackGraph.eps # 转换生成的 EPS 文件为 PDF 格式。 echo "如果成功生成, 攻击图将在AttackGraph.pdf中" if test -n "$PDF_READER"; then $PDF_READER AttackGraph.pdf& fi
#检查 VERTICES.CSV 和 ARCS.CSV 文件是否存在且可读。
if [ -r VERTICES.CSV ] && [ -r ARCS.CSV ]; then
#设置 CLASSPATH 变量来包含MULVAL适配器的路径。
CLASSPATH=$CLASSPATH:$MULVALROOT/bin/adapter
#执行Java程序 XMLConstructor 来创建XML文件
java -cp $CLASSPATH XMLConstructor
else
exit 1
fi
public class XMLConstructor { public static void main(String[] args) { // TODO Auto-generated method stub constructXML(); } private static void constructXML() { String node1 =""; String node2 = ""; String line = ""; //节点在VERTICES.CSV String id = ""; String fact = ""; String type = ""; String metric=""; String line_items []; int line_len = 0; try { FileWriter fr = new FileWriter("AttackGraph.xml"); BufferedReader arcs= new BufferedReader(new FileReader("ARCS.CSV")); fr.write("<attack_graph>\n"); fr.write("<arcs>\n"); //第一步,收集所有节点的前驱 while ((line = arcs.readLine()) != null) { fr.write("<arc>\n"); //node here is the key node1 = line.split(",")[0]; node2 = line.split(",")[1]; fr.write("<src>"+node1+"</src>\n"); fr.write("<dst>"+node2+"</dst>\n"); fr.write("</arc>\n"); } fr.write("</arcs>\n"); BufferedReader vertices= new BufferedReader(new FileReader("VERTICES.CSV")); fr.write("<vertices>\n"); while ((line = vertices.readLine()) != null) { id = line.split(",")[0]; fact = line.split("\"")[1]; type = line.split("\"")[3]; line_items = line.split(","); line_len = line_items.length; //System.out.println(line); metric = line_items[line_len-1]; // System.out.println(metric); fr.write("<vertex>\n"); fr.write("<id>"+id+"</id>\n"); fr.write("<fact>"+fact+"</fact>\n"); fr.write("<metric>"+metric+"</metric>\n"); fr.write("<type>"+type+"</type>\n"); fr.write("</vertex>\n"); } fr.write("</vertices>\n"); fr.write("</attack_graph>\n"); fr.close(); } catch (Exception e) { e.printStackTrace(); } } }
MulVAL作为一款05年开发的攻击图生成工具,至13年不再更新。但基于MulVAL的可扩展性,仍有许多研究人员在其基础上做出新的拓展,例如根据不同风险场景的需求,开发出新的推理规则,或者框架改进等等。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。