回頭看看NetAnalyzer開發系文檔上次一篇居然是2016年,老臉一紅。不過這幾年墨雲成功過的討到一個溫柔賢淑的老婆,有了一個幸福的家庭,去年9月又有了一個大胖兒子,想一想也就釋然了^_^java
其實這幾年NetAnalyzer的開發一直也沒有中斷過,上一篇的NetAnalyzer仍是3.x系列的版本,如今最新的版本已是 5.6.0.38 版本了,去年8月份更新的node
NetAnalyzer官網地址: http://twzy.sinaapp.com/編程
廢話很少說了,回到今天的主題--打造本身的協議分析語言。數組
《道德經》中有「道生一,一輩子二,二生三,三生萬物」的說法,描述了萬物從少到多,從簡單到複雜的一個過程。在計算機中咱們所面對的各類各樣的文件,如:圖片,文本,音樂甚至最基本的程序文件其實都是經過二進制數據也就是大量的0或1的方式存儲在硬盤或內存中的。可是如何從0和1轉換爲咱們熟知的各類媒體數據呢,這就須要根據0和1不一樣的排列順來完成,這就是編碼方案,而這種編碼方案更通俗的來講就是一種協議,這種協議來約束不一樣的設備,不一樣的系統當遇到對應的數據是應該將其解析爲何文件。瀏覽器
當今網絡做爲與咱們生活朝夕相關的事物,給咱們帶來了便利的生活體驗,有些應用甚至能夠作到計算機與智能手機之間的無縫切換,這就得益於網絡中各個層次的協議完美對接。目前的互聯網模型大部分都是基於經典的TCP/IP協議,雖然其安全性、傳輸效率等問題在這些年逐步暴露出來,可是其擁有的完整協議體系倒是其餘協議體系不具有的。從物理層使用的CSMA/CD(載波監聽多路訪問衝突檢測)協議實現端到端的數據傳輸,再到網絡層中IP通訊協議,RIP、OSPF網絡路徑發現協議,實現從主機與主機實現跨網數據傳輸的功能,在而到保證讓主機接口能夠獲取到無差異數據的TCP協議、實現最終數據呈現應用層協議,如http協議。這些協議都是公共開放的協議類型,而有部分軟件就是基於這些公共協議進行工做的,如基於http的各類瀏覽器、基於FTP的各類文件傳輸 軟件,雖然基於公共協議的軟件不少,可是咱們大部分狀況下使用的更多的是專屬軟件,這部分軟件具備本身獨立的協議,並且很大的一部分是在TCP協議之上創建起來的私有獨立應用協議。安全
這裏打造本身的協議分析語言的初衷就是爲了解析這部分協議,而我給它起了一個名字MangoScript,私有協議是一個公司或一個組織定義的一套專屬於內部的數據交流方案、這些協議可能由於涉密或是團體影響力太小並不能被外部人員獲取到。而想要分析這些數據,箱藉助協議分析工具進行分析是不可能的,而手動從各類二進制數據中獲取信息,效率又極其低下。MangoScript的思想就是經過將數據方案轉換爲對應的腳本代碼,將代碼綁定到NetAnalyzer,經過NetAnalyzer實現與解析公共協議無差異的數據分析。網絡
MangoScript做爲NetAnalyzer擴展協議分析的專職語言,區別於現有流行的C\C++ java C# 之類的語言,設計的更像一種配置文件,能夠經過不一樣的配置方式,實現對數據流的解析。腳本使用協議分析樹的邏輯方法,腳本編輯方式就是協議樹的呈現方式,便是沒有接觸過編程的人也能夠輕鬆進行代碼編寫。app
固然,由於MangoScript正處於測試開發階段,所提供的功能也不近完善,這須要讀者的體諒,也很但願讀者能夠提供一些好的建議與意見。目前腳本採起寬泛執行的方式,即對於一些語法錯誤會自動忽略,以保證儘量的完成數據分析。函數
經過MangoScript能夠快速的對數據的結構進行描述與呈現。而且語法很是簡單,適合快速入手使用。 在MangoScript中大小寫不敏感(部分函數提供的參數除外),支持定義中文字段。工具
代碼總體能夠認爲有兩部分:
從某種意義上來講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中包含一整塊數據的規劃與處理,代碼定義方式爲
1 block <name> 2 { 3 … 4 }
其中代碼中name爲必填項。
如在的上面的代碼中就定義了一個名字叫main的block。
在結果呈現上,大部分狀況下表現爲父級節點。 在一段代碼中能夠定義多個block,顯式定義中,不容許存在嵌套(在switch函數下能夠定義匿名block,這種定義方式爲隱式定義。具體請看該函數的功能說明)。而且在該段代碼中必須存在一個名稱爲main的block做爲代碼起點,結構塊的前後順序不受影響數據解析方式。
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編譯器