NetAnalyzer笔记 之 十一 打造本身的协议分析语言(1)初衷与语法构想

回头看看NetAnalyzer开发系文档上次一篇居然是2016年,老脸一红。不过这几年墨云成功过的讨到一个温柔贤淑的老婆,有了一个幸福的家庭,去年9月又有了一个大胖儿子,想一想也就释然了^_^java

其实这几年NetAnalyzer的开发一直也没有中断过,上一篇的NetAnalyzer仍是3.x系列的版本,如今最新的版本已是 5.6.0.38 版本了,去年8月份更新的node

NetAnalyzer官网地址: http://twzy.sinaapp.com/编程

废话很少说了,回到今天的主题--打造本身的协议分析语言。数组


1. 初衷

《道德经》中有“道生一,一辈子二,二生三,三生万物”的说法,描述了万物从少到多,从简单到复杂的一个过程。在计算机中咱们所面对的各类各样的文件,如:图片,文本,音乐甚至最基本的程序文件其实都是经过二进制数据也就是大量的0或1的方式存储在硬盘或内存中的。可是如何从0和1转换为咱们熟知的各类媒体数据呢,这就须要根据0和1不一样的排列顺来完成,这就是编码方案,而这种编码方案更通俗的来讲就是一种协议,这种协议来约束不一样的设备,不一样的系统当遇到对应的数据是应该将其解析为何文件。浏览器

当今网络做为与咱们生活朝夕相关的事物,给咱们带来了便利的生活体验,有些应用甚至能够作到计算机与智能手机之间的无缝切换,这就得益于网络中各个层次的协议完美对接。目前的互联网模型大部分都是基于经典的TCP/IP协议,虽然其安全性、传输效率等问题在这些年逐步暴露出来,可是其拥有的完整协议体系倒是其余协议体系不具有的。从物理层使用的CSMA/CD(载波监听多路访问冲突检测)协议实现端到端的数据传输,再到网络层中IP通讯协议,RIP、OSPF网络路径发现协议,实现从主机与主机实现跨网数据传输的功能,在而到保证让主机接口能够获取到无差异数据的TCP协议、实现最终数据呈现应用层协议,如http协议。这些协议都是公共开放的协议类型,而有部分软件就是基于这些公共协议进行工做的,如基于http的各类浏览器、基于FTP的各类文件传输 软件,虽然基于公共协议的软件不少,可是咱们大部分状况下使用的更多的是专属软件,这部分软件具备本身独立的协议,并且很大的一部分是在TCP协议之上创建起来的私有独立应用协议。安全

2.MangoScript

这里打造本身的协议分析语言的初衷就是为了解析这部分协议,而我给它起了一个名字MangoScript,私有协议是一个公司或一个组织定义的一套专属于内部的数据交流方案、这些协议可能由于涉密或是团体影响力太小并不能被外部人员获取到。而想要分析这些数据,箱借助协议分析工具进行分析是不可能的,而手动从各类二进制数据中获取信息,效率又极其低下。MangoScript的思想就是经过将数据方案转换为对应的脚本代码,将代码绑定到NetAnalyzer,经过NetAnalyzer实现与解析公共协议无差异的数据分析。网络

MangoScript做为NetAnalyzer扩展协议分析的专职语言,区别于现有流行的C\C++ java C# 之类的语言,设计的更像一种配置文件,能够经过不一样的配置方式,实现对数据流的解析。脚本使用协议分析树的逻辑方法,脚本编辑方式就是协议树的呈现方式,便是没有接触过编程的人也能够轻松进行代码编写。app

固然,由于MangoScript正处于测试开发阶段,所提供的功能也不近完善,这须要读者的体谅,也很但愿读者能够提供一些好的建议与意见。目前脚本采起宽泛执行的方式,即对于一些语法错误会自动忽略,以保证尽量的完成数据分析。函数

 3.MangoScript简单语法规则

经过MangoScript能够快速的对数据的结构进行描述与呈现。而且语法很是简单,适合快速入手使用。 在MangoScript中大小写不敏感(部分函数提供的参数除外),支持定义中文字段。工具

代码总体能够认为有两部分:

  •  对代码总体结构的约束定义(block代码)
  •  对具体数据呈现方式的定义(node代码)

从某种意义上来讲MangoScript更像是一种配置文件,由于该语言目前还不支持判断、循环等逻辑,只支持一种简单的分支,此外还不能自定义数据处理函数。 这也是墨云一直以来称其为语言有迟疑的地方。 然而从解析数据来看却要比真正所谓的脚本语言要快捷的多。

这是一段最简单的MangoScript代码:

 1 /*
 2 定义block(结构块)
 3 */
 4 
 5 block main
 6 {
 7      //定义标题
 8      title "我是标题";
 9      //定义一个node(节点)
10      node node1= select(0,1/*注释 选则长度*/);
11 }
12 
13  

block

block 我称之为结构块。用于定义一组数据呈现的结构体。block中包含一整块数据的规划与处理,代码定义方式为

1  block <name>
2  {
3 4  }

其中代码中name为必填项。

如在的上面的代码中就定义了一个名字叫main的block。

在结果呈现上,大部分状况下表现为父级节点。 在一段代码中能够定义多个block,显式定义中,不容许存在嵌套(在switch函数下能够定义匿名block,这种定义方式为隐式定义。具体请看该函数的功能说明)。而且在该段代码中必须存在一个名称为main的block做为代码起点,结构块的前后顺序不受影响数据解析方式。

node

node是MangoScript用于描述数据呈现方式的最基本部分。node一般用来描述一个子节点由数据转为本身制定类型过程和数据的呈现方式,具体的呈现内容则是经过一系列函数链进行不断演进的结果。 函数部分经过内部定义(MangoScript不支持自定义函数)MangoScipt经过对各类函数进行对应的选取排列来获取须要的值,而具体的函数方式能够经过函数API文档获得相关的帮助信息,以下代码为定义一个节点:

1 node data = select(2, 8).text("ascii");

首先使用node:做为前缀,而后定义节点名称对应的变量没成data ,定义完名称以后经过=开始函数部分的编写。

首先咱们须要选取数据区域,在这里咱们经过选取函数select获取从数据块中第2个开始8个字节的数据块。

当完成数据选取之后,再执行text将选取到的8个字节转为文本编码为“ASCII”的字符串。

最后将结果转换后的结果赋予变量data ,node定义以分号“;”结束。部分node还有方法体,使用大括号包裹起来,仍是以分号“;”结尾。

函数

在node中用于描述数据转换的方式,就是这里要说的函数。函数一般使用 “.”符号开始,如上面的代码,其中的select函数和text的函数都是经过 “.” 符号开始的,接下来就是函数名称,而且在括号中输入相关的控制参数。由于不一样的函数输入的参数类型和内容不同,而且随后还会不断的扩展函数库, 经过多个函数一块儿连接,node就造成列函数链,函数链从左往右,前一个的函数输出数据是后一个函数的输入数据,对于第一个函数,默认为所有的待处理数据,这就是为大节点都是以select函数做为开始的,对于最后一个函数则统一处理为文本进行输出。

一些特殊函数的说明

在MangoScript中有一些特殊的函数须要单独的说明一下。这些函数为脚本提供了最基本的数据访问和结构控制的功能,整个MangoScript都是创建在这些功能上面的。

select(offset,length) select函数,数据选择函数,是MangoScript中核心函数之一,主要功能是从待分析数据块中根据offset参数和length参数获取到须要处理的数据,如上面的代码中从第3个字节开始(索引都是从0开始的,因此offset=2)找到8个字节(length=8)做为待处理的子字节数组。对于select函数,除了能够进行正常的选择转换以外,还能够进行细节扩展,对该字段进行进一步的描述,经过以子节点的方式呈现出来。代码以下:

 

1   node 时间戳= select([帧长度]-12,14).text("ascii")
2   {
3          node 年=select(0,4).text("ascii");
4          node 月=select(4,2).text("ascii");
5          node 日=select(6,2).text("ascii");
6          node 时=select(8,2).text("ascii");
7          node 分=select(10,2).text("ascii");
8          node 秒=select(12,2).text("ascii");
9    };

 

如上面的时间戳节点,呈现的只是简单的将选中的数据转为以ASCII编码的文本,可是若是咱们想要知道其内部的具体细节,则须要借助子节点功能。在主节点最后一个函数后面添加大括号,再在大括号中定义子节点,这里的子节点选择的数据为主节点选中的数据,因此须要将索引值置为0从新开始。如:

   node 年= select(0,4).text("ascii");

最后须要注意的是,在大括号后面加须要有一个分号,结束对该节点的定义。

while(offset,length) while函数,结构循环函数。在数据分析过程当中,须要特定的结构,对数据块依次进行相同的分析,不少状况下,咱们并不知道该循环须要执行的次数,这就须要经过脚原本自行判断,这时候就用到了while函数,该函数是有方法和select带子节点结构一致,可是解析方式是不同的,该函数只须要定义开始分析位置,以及所要分析的长度,以后再在函数后面添加须要循环的分析块,该函数会自动根据填写的内容自动判断须要循环的次数。示例代码以下:

1 node 序号= while(4,16)
2 {
3     node sub=select(0,2); 
4     node sub2=select(2,6); 
5 };

 

该代码会根据偏移量最大的一个节点(该节点的偏移量加选择长度)做为一次循环的结束的标志,进行自我判断,对数据进行循环解析,直到所选数据结束为止。如上面代码,会被解析两次,由于sub2节点结束时候数据偏移到8(2+6),当前选择的数据为16,因此能够再次进行一次循环。

switch(offset,length) switch函数,转换函数。在一些业务分析过程当中,经过会有根据不一样字段,后续所要分析协议格式不一样的问题。这种状况下就会用到switch函数,在switch中有两个参数,和select同样用来选择数据,可是与select不一样的是,当switch选择完数据后,会直接转为数字类型(也就是说length最大为4), 并在switch对应的函数体内进行判断,在改函数体内,经过case关键字列出不一样的值,而且指向不一样的block,若是switch选择的数据与其中一个case的值相对应,则会指向对应的block代码, 在case中指向的block有两种方式:1.经过>方式的指向,该种指向为外部定义的block,后面只须要输入对应的block名称便可;2.经过:方式的指向,这种指向使用匿名方式创建一个block,不须要是使用block关键字,不须要定义名称,只要在后面输入大括号,就能够进行代码输入了,和平时定义block同样。

以下代码:

node date = switch(0,2)  //switch 自动将其转为无符号整数
{
      case 0x0101>Test,//指向一个block
      case 0x0102:  //该种结构又叫作匿名block
      {
             node test=select(3,34);
      }
 };
    ……

//定义的另一个节点
block Test
{
      node name = select(0,1).num(4,"hex");   
}

 

 

对于输入数据[0x01 0x01 0x03 ……],咱们经过switch(0,2) 获取到数字0x0101(十六进制方式),则会跳转到block名称为Test的结构块进行分析,注意咱们这里使用 case 0x0101>Test 这是使用外部block调用的方式。

若是输入数据为[0x01 0x02 0x03 ……] 获得的数字为0x0102,在这里使用的方式是内建匿名block:

1 case 0x0102:
2 {
3       node test= select(3,34);
4 }

 

这两种方式均可以按照普通的block同样使用。 当分析完数据后,并不会显示switch所在的节点内容,而是使用对应case所指向的block中的全部节点来代替。

ifblock(flag) ifblock函数,结构断定函数,在处理一些协议总会看到Magic字段,如某款IM软件协议中第一个字节就是0x02,这些协议一般是和其余服务共用了某些特征,如某款IM软件使用8000端口号,可是有好多应用都会使用这个端口,为了正确的识别这些协议,因而有了ifblock方法。 该方法的flag为数字类型,因此前面select所输入的长度不能超过4。好比咱们在断定是否为某款IM软件协议的时候,输入代码:

  

1 node flag=select(0,4).ifblock(0x02);

若是待测数据第一个字节为0x02 则继续进行下面的分析,若是不是则直接跳出,返回空的block。

display(flag) display,显示函数,在一些协议中,有些字段自己其表明的是一种类型,如:icmp中的类型字段,对应每一个字段都有不一样的意义。可是在作协议分析的时候,咱们拿到的仅仅是代码,若是能将代码对应的字段使用文本方式呈现出来,则更加具备可读性。 display函数正是基于这种思想实现的。在使用display以前,咱们首先须要定义对应关系。display的对应关系咱们这里叫作enum,该结构被定义在block外部,主要是为了共享enum,如下以某款IM软件协议(精简过)为例,定义方式以下:

 1 block main
 2 {
 3      …… //nodes
 4 }
 5 
 6 enum imcomond
 7 {
 8        case 0x0001 > "注销登陆",
 9        case 0x0002 > "心跳信息",
10        case 0x0004 > "更新用户信息",
11        case 0x0005 > "搜索用户"
12     ………
13  }

在block中定义display Node

1 node 命令= select(3,2).display(imcomond);

当待测数据为[0x01 0x01 0x01 0x00 0x02 0x00],输出为:

命令:心跳信息

其余功能点

MangoScript还包含了enum 等功能项以及函数信息,详细内容能够参考官网的NetAnalyzer开发文档。

这里有个针对某协议的示例:

 1 block main
 2 { 
 3     title "协议测试";
 4     node 前缀= select(0,2);
 5     node 序号= select(2,2).num();
 6     node 版本= select(4,3).eachbyte(".","num");//num text  
 7     node 数据类型= select(7,1).display(FrameType);
 8     node 帧长度= select(8,2).num();
 9     node 数据类型=select(10,1).display(mtype);
10     node 代码= select(11,3).reverse().num();
11     node 数据块= select(14,[帧长度]-26);
12     node 时间戳= select([帧长度]-12,14).text("ascii")
13                  {
14                       node 年= select(0,4).text("ascii");
15                       node 月= select(6,2).text("ascii");
16                       node 日= select(6,2).text("ascii");
17                       node 时= select(8,2).text("ascii");
18                       node 分= select(10,2).text("ascii");
19                       node 秒= select(12,2).text("ascii");
20                  };
21     node 校验和=select([帧长度]+2,2).num();
22     node 后缀=select([帧长度]+4,2);
23    
24 }
25   enum FrameType
26   {
27        case 0x00 > "帧类型1",
28        case 0x01 > "帧类型2"
29   }
30    
31   enum mtype
32   {
33        case 0x00 > "测试类型1",
34        case 0x01 > "测试类型2",
35        case 0x02 > "测试类型3"
36   }

在该代码中,能够看到只定义了一个数据块main和enum块,main数块的title 为“协议测试”而后定义了11个Node,都是常规定义定义方法,这里须要注意一点,从数据块开始在select函数的参数中有一个用中括号括起来的帧长度字段。这是一种引用字段数据的方式,可是使用引用字段的字段必须定义在被引用字段以后。除了引用字段还有找一种叫作公共参数的变量、虽然目前在MangoScript中只定义了一个也就是END 表示一直到数据末尾,因此我能能够这样使用它:

1 node 数据块= select(0,END);

经过改代码能够获取到整个数据块。

最后咱们来看看代码的运行状况吧:

在下面的一篇中咱们将会详细说明MangoScript编译器

相关文章
相关标签/搜索