标签(空格分隔): Wireshark Lua
参考:
http://yoursunny.com/t/2008/Wireshark-Lua-dissector/
http://yoursunny.com/study/IS409/ScoreBoard.htm
http://www.360doc.com/content/13/1021/14/1317564_323017649.shtml
http://www.cnblogs.com/wendellyi/p/3475461.html
http://blog.csdn.net/fan_hai_ping/article/details/6703468
1. 理论部分
1.1 Wireshark
Wireshark已经支持数千种协议,对新协议的支持还在不断增加。今天,你发明了一个新的网络协议,也想让Wireshark识别,你该怎么办呢?你有两个选择:
- 发布你的网络协议,等到有1,000,000人每天使用你的协议时,Wireshark就会支持你的协议
- 编写一个Wireshark插件,自己动手、丰衣足食
(如果你选择了前者,请按下CTRL+D,然后在你改变主意的时候再回来。)
如果你还没有安装Wireshark,请下载并安装Wireshark。
(原码安装参考 https://www.zybuluo.com/natsumi/note/70150)
从功能看,Wireshark可以分为以下几个模块:
- 核心
- 用户界面
- 抓包:调用libpcap或winpcap实现
- 协议分析:支持的数千种协议,都有相应的分析组件,称为dissector
- 保存/读取文件
……
要让Wireshark识别你发明的协议,应该从“协议分析”部分入手,也就是编写一个Wireshark dissector。
1.2 编写dissector? C语言 VS. Lua
Wireshark本身是用C语言编写的,用C语言编写dissector是很自然的选择。但是,C语言并不简单,编译C语言的插件代码也并不容易。浏览一遍Wireshark Developer's Guide的目录,你会看到一章Lua Support in Wireshark,这就是编写Wireshark dissector插件的另一种选择。
Lua是一种功能强大的、快速的、轻量的、嵌入式的脚本语言。不需要复杂的makefile神码的==
Lua是一种嵌入式脚本语言,Wireshark嵌入了Lua脚本引擎,因此我们可以使用Lua脚本语言扩展Wireshark。
Wireshark有了Lua支持后,如虎添翼,大大方便了插件的开发。当你发明了一个网络协议,要让Wireshark支持它的最佳办法就是动手用Lua语言写一个dissector。只要你充分理解自己设计的网络协议,结合Lua语言参考和Wireshark文档(不过有些函数名不够准确、请参考Wireshark源码),写出一个Wireshark插件还是挺容易的。
2. Lua实例学习
Wireshark Developer's Guide中的10.2节给出了一段Wireshark插件代码Example of Dissector written in Lua
,先解读一下:
(“--”后面是注释啊啊啊。。这高亮有点不对劲)
--这个dissector只是把几个协议组合起来而已,并不是识别一种新的协议
do --do...end是Lua语言的语句块关键字,相当于C#语言的{..}
--创建一个Proto类的对象,表示一种协议
local p_multi = Proto("multi","MultiProto");
local vs_protos = {
[2] = "mtp2",
[3] = "mtp3",
[4] = "alcap",
[5] = "h248",
[6] = "ranap",
[7] = "rnsap",
[8] = "nbap"
}
--创建几个ProtoField对象,就是主界面中部Packet Details窗格中能显示的那些属性
local f_proto = ProtoField.uint8("multi.protocol","Protocol",base.DEC,vs_protos)
local f_dir = ProtoField.uint8("multi.direction","Direction",base.DEC,{ [1] = "incoming", [0] = "outgoing"})
local f_text = ProtoField.string("multi.text","Text")
--把ProtoField对象加到Proto对象上
p_multi.fields = { f_proto, f_dir, f_text }
--用Dissector.get函数可以获得另外一个协议的解析组件
local data_dis = Dissector.get("data")
local protos = {
[2] = Dissector.get("mtp2"),
[3] = Dissector.get("mtp3"),
[4] = Dissector.get("alcap"),
[5] = Dissector.get("h248"),
[6] = Dissector.get("ranap"),
[7] = Dissector.get("rnsap"),
[8] = Dissector.get("nbap"),
[9] = Dissector.get("rrc"),
[10] = DissectorTable.get("sctp.ppi"):get_dissector(3), -- m3ua
[11] = DissectorTable.get("ip.proto"):get_dissector(132), -- sctp
}
--为Proto对象添加一个名为dissector的函数,
--Wireshark会对每个“相关”数据包调用这个函数
function p_multi.dissector(buf,pkt,root)
--root:add会在Packet Details窗格中增加一行协议
local t = root:add(p_multi,buf(0,2))
--t:add,在Packet Details窗格中增加一行属性,
--并指定要鼠标点击该属性时Packet Bytes窗格中会选中哪些字节
t:add(f_proto,buf(0,1))
t:add(f_dir,buf(1,1))
--这句是将数据的第一个字节转换成无符号整数
local proto_id = buf(0,1):uint()
local dissector = protos[proto_id]
if dissector ~= nil then
dissector:call(buf(2):tvb(),pkt,root)
elseif proto_id < 2 then
t:add(f_text,buf(2))
-- pkt.cols.info:set(buf(2,buf:len() - 3):string())
else
--调用另外一个dissector
data_dis:call(buf(2):tvb(),pkt,root)
end
end
--所有的dissector都是以“table”的形式组织的,table表示上级协议
local wtap_encap_table = DissectorTable.get("wtap_encap")
--这个是获得udp协议的DissectorTable,并且以端口号排列
local udp_encap_table = DissectorTable.get("udp.port")
wtap_encap_table:add(wtap.USER15,p_multi)
wtap_encap_table:add(wtap.USER12,p_multi)
--为UDP的7555端口注册这个Proto对象,
--当遇到源或目的为UDP7555的数据包,就会调用上面的p_multi.dissector函数
udp_encap_table:add(7555,p_multi)
end
3. 用ScoreBoard协议来实战~
3.1 ScoreBoard协议
ScoreBoard协议用于更新比分牌的数值和背景颜色。服务端监听UDP1127端口,客户端端口任意。
3.1.1 报文格式
- 每个报文的前16字节是固定的识别符identifier:
e2 cb b5 80 cb 09 4e ba a3 6b f6 07 ce 95 3f 2b
- 第17字节表示报文类型operator:
00 get-value 获取比分数值
01 set-value 设置比分数值
80 resp-value 应答比分数值
10 get-color 获取背景色
11 set-color 设置背景色
90 resp-color 应答背景色
- 数据部分:
- 00、80类型的报文
第18~21字节为左边的比分数值(32位无符号整数,big endian)
第22~25字节为右边的比分数值(32位无符号整数,big endian)- 10、90类型的报文
第18字节为红色分量
第19字节为绿色分量
第20字节为蓝色分量
3.1.2 交互流程
客户端使用00、01类型的报文请求服务端,服务端应当回复80类型的报文
客户端使用10、11类型的报文请求服务端,服务端应当回复90类型的报文
3.2 ScoreBoard协议插件代码
do
--协议名称为ScoreBoard,在Packet Details窗格显示为yoursunny.P2008.IS409 ScoreBoard
local p_ScoreBoard = Proto("ScoreBoard","yoursunny.P2008.IS409 ScoreBoard")
--协议的各个字段
local f_identifier = ProtoField.bytes("ScoreBoard.identifier","Identifier")
local f_operator = ProtoField.uint8("ScoreBoard.operator","Operator",base.HEX,
--这个字段的数字值都有相应的含义,可以自动对应成字符串
{ [0] = "get-value", [1] = "set-value", [128] = "resp-value",
[16] = "get-color", [17] = "set-color", [144] = "resp-color"})
--所有可能的字段都要定义,到时没有t:add就不会显示
local f_left = ProtoField.uint32("ScoreBoard.left","Value Left",base.DEC)
local f_right = ProtoField.uint32("ScoreBoard.right","Value Right",base.DEC)
local f_red = ProtoField.uint8("ScoreBoard.red","Color Red",base.DEC)
local f_green = ProtoField.uint8("ScoreBoard.green","Color Green",base.DEC)
local f_blue = ProtoField.uint8("ScoreBoard.blue","Color Blue",base.DEC)
p_ScoreBoard.fields = { f_identifier, f_operator, f_left, f_right, f_red, f_green, f_blue }
local data_dis = Dissector.get("data")
local function ScoreBoard_dissector(buf,pkt,root)
local buf_len = buf:len();
--先检查报文长度,太短的不是我的协议
if buf_len < 17 then return false end
--取得前16字节identifier字段的值
local v_identifier = buf(0,16)
--验证identifier是否正确
if ((buf(0,1):uint()~=226) or (buf(1,1):uint()~=203) or (buf(2,1):uint()~=181)
or (buf(3,1):uint()~=128) or (buf(4,1):uint()~=203) or (buf(5,1):uint()~=9)
or (buf(6,1):uint()~=78) or (buf(7,1):uint()~=186) or (buf(8,1):uint()~=163)
or (buf(9,1):uint()~=107) or (buf(10,1):uint()~=246) or (buf(11,1):uint()~=7)
or (buf(12,1):uint()~=206) or (buf(13,1):uint()~=149) or (buf(14,1):uint()~=63)
or (buf(15,1):uint()~=43))
--不正确就不是我的协议
then return false end
--取得operator的值
local v_operator = buf(16,1)
local i_operator = v_operator:uint()
--现在知道是我的协议了,放心大胆添加Packet Details
local t = root:add(p_ScoreBoard,buf)
--在Packet List窗格的Protocol列也可以“做个小广告”
pkt.cols.protocol = "ScoreBoard"
t:add(f_identifier,v_identifier)
t:add(f_operator,v_operator)
if ((i_operator == 1) or (i_operator == 128)) and (buf_len >= 25) then
--把存在的字段逐个添加进去
t:add(f_left,buf(17,4))
t:add(f_right,buf(21,4))
elseif ((i_operator == 17) or (i_operator == 144)) and (buf_len >= 20) then
t:add(f_red,buf(17,1))
t:add(f_green,buf(18,1))
t:add(f_blue,buf(19,1))
end
return true
end
function p_ScoreBoard.dissector(buf,pkt,root)
if ScoreBoard_dissector(buf,pkt,root) then
--valid ScoreBoard diagram
else
--data这个dissector几乎是必不可少的;当发现不是我的协议时,就应该调用data
data_dis:call(buf,pkt,root)
end
end
local udp_encap_table = DissectorTable.get("udp.port")
--只需要处理UDP1127端口就可以了
udp_encap_table:add(1127,p_ScoreBoard)
end
3.3 Lua插件代码怎么用?
3.3.1 确认Wireshark是否支持Lua
-
菜单栏-->Help-->About Wireshark
注意看弹出的窗口中的Wireshark选项卡
上图是我之前原码安装的Wireshark1.99.2
很可惜,上面写着without Lua
观察到安装Wireshark过程中的./configure
步骤中回输出了一些Lua相关的语句,如
check for Lua ... no
...
apt-get安装lua5.2后还是没有解决这个问题。。有待进一步研究> <
- 为了能尽快试验下ScoreBoard协议的插件,在原码目录下
$sudo make uninstall
卸载了原码安装的Wireshark,用$ sudo apt-get install wireshark
重新安装
3.3.2 启用Lua
-
在About窗口中的Folders选项卡还可以查看各种文件夹的位置
在Global configuration的位置有个
init.lua
,其实这是一个到/etc/wireshark/init.lua
的连接。Wireshark开始运行时会执行init.lua
脚本init.lua
脚本中有disable_lua = false
,默认是启用Lua的(禁用是true),使用之前应确认一下-
但是我以往的习惯都是
$ sudo wireshark
运行的,这就会出现下面这个错误提示窗
-
其实看一看
init.lua
代码就知道了,因为superuser运行对Lua的功能有潜在危害,所以init.lua
禁用了Lua。因此这个错误只需运行时不加sudo
即可避免。
init.lua
文末有一句dofile(DATA_DIR.."console.lua")
,此句执行的console脚本也是Wireshark自带的,作用是在Tool菜单下创建子菜单Lua,成功执行后即可看到这个Lua子菜单。
3.3.3 加载Lua插件
- 在init.lua的最后调用你的Lua插件:dofile('路径\文件名.lua');
例如我选择把这个插件放到personal plugins文件夹中
dofile("/home/tiantian/.wireshark/plugins/scoreboard.lua")
- 保存后启动wireshark,即完成加载
还可以用tshark命令加载lua脚本
# tshark -x lua_script:hello.lua
,但是终端说“tshark尚未安装”,先记一笔,暂不深究
3.3.4 测试ScoreBoard协议
ScoreBoard协议的原创——阳光男孩的博客中提供了“ScoreBoard协议的样例客户端脚本、服务端程序、pcap抓包、Lua插件源码”的打包下载,但是好像下不了了
所以我自己用Wireshark给的pdcp_lte_logger.c
改写成了最简单的ScoreBoard客户端程序。
/*scoreboard_test.c
*
* Example code for sending ScoreBoard frames over UDP
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
typedef unsigned char guint8;
typedef unsigned short guint16;
typedef unsigned int guint32;
typedef int gboolean;
#define GET_VALUE 0x00 //获取比分数值
#define SET_VALUE 0x01 //设置比分数值
#define RESP_VALUE 0x80 //应答比分数值
#define GET_COLOR 0x10 //获取背景色
#define SET_COLOR 0x11 //设置背景色
#define RESP_COLOR 0x90 //应答背景色
/* Globals where each frame is composed before sending */
static unsigned char g_PDUBuffer[16000];
static unsigned int g_PDUOffset;
static unsigned char g_frameBuffer[16000];
static unsigned int g_frameOffset;
/* UDP socket used for sending frames */
static int g_sockfd;
/* Remote serveraddress (where Wireshark is running) */
static struct sockaddr_in g_serv_addr;
/* Write a PDU */
static void EncodeDummyPDU(void)
{
g_PDUOffset = 0;
/*left score*/
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x01;
/*right score*/
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x00;
g_PDUBuffer[g_PDUOffset++] = 0x02;
}
/*******************************************/
/* Add framing header to PDU and send. */
void SendFrame(guint8 operator)
{
ssize_t bytesSent;
g_frameOffset = 0;
unsigned short tmp16;
/*16Bytes identifier*/
g_frameBuffer[g_frameOffset++] = 0xe2;
g_frameBuffer[g_frameOffset++] = 0xcb;
g_frameBuffer[g_frameOffset++] = 0xb5;
g_frameBuffer[g_frameOffset++] = 0x80;
g_frameBuffer[g_frameOffset++] = 0xcb;
g_frameBuffer[g_frameOffset++] = 0x09;
g_frameBuffer[g_frameOffset++] = 0x4e;
g_frameBuffer[g_frameOffset++] = 0xba;
g_frameBuffer[g_frameOffset++] = 0xa3;
g_frameBuffer[g_frameOffset++] = 0x6b;
g_frameBuffer[g_frameOffset++] = 0xf6;
g_frameBuffer[g_frameOffset++] = 0x07;
g_frameBuffer[g_frameOffset++] = 0xce;
g_frameBuffer[g_frameOffset++] = 0x95;
g_frameBuffer[g_frameOffset++] = 0x3f;
g_frameBuffer[g_frameOffset++] = 0x2b;
/*1Byte opreator*/
g_frameBuffer[g_frameOffset++] = operator;
/* Append actual PDU */
memcpy(g_frameBuffer+g_frameOffset, g_PDUBuffer, g_PDUOffset);
g_frameOffset += g_PDUOffset;
/* Send out the data over the UDP socket */
bytesSent = sendto(g_sockfd, g_frameBuffer, g_frameOffset, 0,
(const struct sockaddr*)&g_serv_addr, sizeof(g_serv_addr));
if (bytesSent != g_frameOffset) {
fprintf(stderr, "sendto() failed - expected %d bytes, got %d (errno=%d)\n",
g_frameOffset, bytesSent, errno);
exit(1);
}
}
/**************************************************************************/
/* Main function */
/* - set up socket + aserver address */
/* - send example ScoreBoard frames using framing protocol */
int main(int argc, char *argv[]){
struct hostent *hp;
if (argc < 3) {
fprintf(stderr, "Usage: coclient <server-host> <server-port>\n");
exit(1);
}
/***********************************/
/* Create local socket */
g_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (g_sockfd == -1) {
fprintf(stderr, "Error trying to create socket (errno=%d)\n", errno);
exit(1);
}
/***************************************************/
/* Get remote IP address from 1st command-line arg */
g_serv_addr.sin_family = AF_INET;
hp = gethostbyname(argv[1]);
if (hp == (struct hostent *)0) {
fprintf(stderr, "Unknown host %s (h_errno=%d)\n", argv[1], h_errno);
exit(1);
}
memcpy((void*)&g_serv_addr.sin_addr, (void*)hp->h_addr, hp->h_length);
/****************************************************/
/* Get remote port number from 2nd command-line arg */
g_serv_addr.sin_port = htons(atoi(argv[2]));
/****************************************************/
/* Send some frame */
EncodeDummyPDU();
SendFrame(SET_VALUE);
/* Close local socket */
close(g_sockfd);
return EXIT_SUCCESS;
}
- 编译
gcc -g -o scoreboard_test scoreboard_test.c
-
$ sudo wireshark
打开wireshark(因为只有superuser才能抓包。这也是Lua编写插件的一个硬伤。目前我还没发现什么好方法),选择loopback接口开始捕获。 - 运行客户端程序
./scoreboard_test 127.0.0.1 1127
- 捕获之后,还看不出ScoreBoard协议的解析效果,将捕获的包保存成pcap或者pcapng格式的文件。
- 用Wireshark打开捕获文件。注意不要superuser!!!
- 我编写的客户端发送了一个01(设置比分)报文,将比分设置为1:2
- 因为没有编写服务器端接收客户短发送的数据包,所以出现了ICMP报文,请忽略==
- 整个过程太折腾人了。。可以再尝试直接将数据写到pcap文件
3.4 插件的调试
我直接是用了阳光男孩的代码,所以没有调试过程,以下引用原文的调试方法
我没有找到非常有效的插件调试方法。
- 当Lua脚本中有语法错误时,Wireshark会在启动时弹出提示框;有调用错误时,会在相关数据包的Packet Details窗格中以红色显示。
- 你可以把捕获的数据包保存为.pcap文件,每次修改Lua脚本后双击.pcap文件打开Wireshark即可。不必每次都进行Live Capture。
3.5 插件的卸载
还没找到卸载的方式