Wireshark lua插件

源地址:https://www.zybuluo.com/natsumi/note/77991

参考: 
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

相关: 
「转」非root用户使用wireshark捕捉网络数据包 
https://www.zybuluo.com/natsumi/note/81282

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,先解读一下: 
(“--”后面是注释啊啊啊。。这高亮有点不对劲)

   
   
   
   
  1. --这个dissector只是把几个协议组合起来而已,并不是识别一种新的协议
  2. do --do...endLua语言的语句块关键字,相当于C#语言的{..}
  3. --创建一个Proto类的对象,表示一种协议
  4. local p_multi = Proto("multi","MultiProto");
  5. local vs_protos = {
  6. [2] = "mtp2",
  7. [3] = "mtp3",
  8. [4] = "alcap",
  9. [5] = "h248",
  10. [6] = "ranap",
  11. [7] = "rnsap",
  12. [8] = "nbap"
  13. }
  14. --创建几个ProtoField对象,就是主界面中部Packet Details窗格中能显示的那些属性
  15. local f_proto = ProtoField.uint8("multi.protocol","Protocol",base.DEC,vs_protos)
  16. local f_dir = ProtoField.uint8("multi.direction","Direction",base.DEC,{ [1] = "incoming", [0] = "outgoing"})
  17. local f_text = ProtoField.string("multi.text","Text")
  18. --把ProtoField对象加到Proto对象上
  19. p_multi.fields = { f_proto, f_dir, f_text }
  20. --用Dissector.get函数可以获得另外一个协议的解析组件
  21. local data_dis = Dissector.get("data")
  22. local protos = {
  23. [2] = Dissector.get("mtp2"),
  24. [3] = Dissector.get("mtp3"),
  25. [4] = Dissector.get("alcap"),
  26. [5] = Dissector.get("h248"),
  27. [6] = Dissector.get("ranap"),
  28. [7] = Dissector.get("rnsap"),
  29. [8] = Dissector.get("nbap"),
  30. [9] = Dissector.get("rrc"),
  31. [10] = DissectorTable.get("sctp.ppi"):get_dissector(3), -- m3ua
  32. [11] = DissectorTable.get("ip.proto"):get_dissector(132), -- sctp
  33. }
  34. --为Proto对象添加一个名为dissector的函数,
  35. --Wireshark会对每个“相关”数据包调用这个函数
  36. function p_multi.dissector(buf,pkt,root)
  37. --root:add会在Packet Details窗格中增加一行协议
  38. local t = root:add(p_multi,buf(0,2))
  39. --t:add,在Packet Details窗格中增加一行属性,
  40. --并指定要鼠标点击该属性时Packet Bytes窗格中会选中哪些字节
  41. t:add(f_proto,buf(0,1))
  42. t:add(f_dir,buf(1,1))
  43. --这句是将数据的第一个字节转换成无符号整数
  44. local proto_id = buf(0,1):uint()
  45. local dissector = protos[proto_id]
  46. if dissector ~= nil then
  47. dissector:call(buf(2):tvb(),pkt,root)
  48. elseif proto_id < 2 then
  49. t:add(f_text,buf(2))
  50. -- pkt.cols.info:set(buf(2,buf:len() - 3):string())
  51. else
  52. --调用另外一个dissector
  53. data_dis:call(buf(2):tvb(),pkt,root)
  54. end
  55. end
  56. --所有的dissector都是以“table”的形式组织的,table表示上级协议
  57. local wtap_encap_table = DissectorTable.get("wtap_encap")
  58. --这个是获得udp协议的DissectorTable,并且以端口号排列
  59. local udp_encap_table = DissectorTable.get("udp.port")
  60. wtap_encap_table:add(wtap.USER15,p_multi)
  61. wtap_encap_table:add(wtap.USER12,p_multi)
  62. --为UDP7555端口注册这个Proto对象,
  63. --当遇到源或目的为UDP7555的数据包,就会调用上面的p_multi.dissector函数
  64. udp_encap_table:add(7555,p_multi)
  65. 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协议插件代码

   
   
   
   
  1. do
  2. --协议名称为ScoreBoard,在Packet Details窗格显示为yoursunny.P2008.IS409 ScoreBoard
  3. local p_ScoreBoard = Proto("ScoreBoard","yoursunny.P2008.IS409 ScoreBoard")
  4. --协议的各个字段
  5. local f_identifier = ProtoField.bytes("ScoreBoard.identifier","Identifier")
  6. local f_operator = ProtoField.uint8("ScoreBoard.operator","Operator",base.HEX,
  7. --这个字段的数字值都有相应的含义,可以自动对应成字符串
  8. { [0] = "get-value", [1] = "set-value", [128] = "resp-value",
  9. [16] = "get-color", [17] = "set-color", [144] = "resp-color"})
  10. --所有可能的字段都要定义,到时没有t:add就不会显示
  11. local f_left = ProtoField.uint32("ScoreBoard.left","Value Left",base.DEC)
  12. local f_right = ProtoField.uint32("ScoreBoard.right","Value Right",base.DEC)
  13. local f_red = ProtoField.uint8("ScoreBoard.red","Color Red",base.DEC)
  14. local f_green = ProtoField.uint8("ScoreBoard.green","Color Green",base.DEC)
  15. local f_blue = ProtoField.uint8("ScoreBoard.blue","Color Blue",base.DEC)
  16. p_ScoreBoard.fields = { f_identifier, f_operator, f_left, f_right, f_red, f_green, f_blue }
  17. local data_dis = Dissector.get("data")
  18. local function ScoreBoard_dissector(buf,pkt,root)
  19. local buf_len = buf:len();
  20. --先检查报文长度,太短的不是我的协议
  21. if buf_len < 17 then return false end
  22. --取得前16字节identifier字段的值
  23. local v_identifier = buf(0,16)
  24. --验证identifier是否正确
  25. if ((buf(0,1):uint()~=226) or (buf(1,1):uint()~=203) or (buf(2,1):uint()~=181)
  26. or (buf(3,1):uint()~=128) or (buf(4,1):uint()~=203) or (buf(5,1):uint()~=9)
  27. or (buf(6,1):uint()~=78) or (buf(7,1):uint()~=186) or (buf(8,1):uint()~=163)
  28. or (buf(9,1):uint()~=107) or (buf(10,1):uint()~=246) or (buf(11,1):uint()~=7)
  29. or (buf(12,1):uint()~=206) or (buf(13,1):uint()~=149) or (buf(14,1):uint()~=63)
  30. or (buf(15,1):uint()~=43))
  31. --不正确就不是我的协议
  32. then return false end
  33. --取得operator的值
  34. local v_operator = buf(16,1)
  35. local i_operator = v_operator:uint()
  36. --现在知道是我的协议了,放心大胆添加Packet Details
  37. local t = root:add(p_ScoreBoard,buf)
  38. --在Packet List窗格的Protocol列也可以“做个小广告”
  39. pkt.cols.protocol = "ScoreBoard"
  40. t:add(f_identifier,v_identifier)
  41. t:add(f_operator,v_operator)
  42. if ((i_operator == 1) or (i_operator == 128)) and (buf_len >= 25) then
  43. --把存在的字段逐个添加进去
  44. t:add(f_left,buf(17,4))
  45. t:add(f_right,buf(21,4))
  46. elseif ((i_operator == 17) or (i_operator == 144)) and (buf_len >= 20) then
  47. t:add(f_red,buf(17,1))
  48. t:add(f_green,buf(18,1))
  49. t:add(f_blue,buf(19,1))
  50. end
  51. return true
  52. end
  53. function p_ScoreBoard.dissector(buf,pkt,root)
  54. if ScoreBoard_dissector(buf,pkt,root) then
  55. --valid ScoreBoard diagram
  56. else
  57. --data这个dissector几乎是必不可少的;当发现不是我的协议时,就应该调用data
  58. data_dis:call(buf,pkt,root)
  59. end
  60. end
  61. local udp_encap_table = DissectorTable.get("udp.port")
  62. --只需要处理UDP1127端口就可以了
  63. udp_encap_table:add(1127,p_ScoreBoard)
  64. end

3.3 Lua插件代码怎么用?

3.3.1 确认Wireshark是否支持Lua

  • 菜单栏-->Help-->About Wireshark 
    注意看弹出的窗口中的Wireshark选项卡 
    Wireshark lua插件_第1张图片

  • 上图是我之前原码安装的Wireshark1.99.2 
    很可惜,上面写着without Lua 
    观察到安装Wireshark过程中的./configure步骤中回输出了一些Lua相关的语句,如

   
   
   
   
  1. check for Lua ... no
  2. ...

apt-get安装lua5.2后还是没有解决这个问题。。有待进一步研究> <

  • 为了能尽快试验下ScoreBoard协议的插件,在原码目录下$sudo make uninstall卸载了原码安装的Wireshark,用$ sudo apt-get install wireshark重新安装 
    Wireshark lua插件_第2张图片

3.3.2 启用Lua

  • 在About窗口中的Folders选项卡还可以查看各种文件夹的位置 
    Wireshark lua插件_第3张图片

  • 在Global configuration的位置有个init.lua,其实这是一个到/etc/wireshark/init.lua的连接。Wireshark开始运行时会执行init.lua脚本

  • init.lua脚本中有disable_lua = false,默认是启用Lua的(禁用是true),使用之前应确认一下
  • 但是我以往的习惯都是$ sudo wireshark运行的,这就会出现下面这个错误提示窗 
    Wireshark lua插件_第4张图片
  • 其实看一看init.lua代码就知道了,因为superuser运行对Lua的功能有潜在危害,所以init.lua禁用了Lua。因此这个错误只需运行时不加sudo即可避免。 
    Wireshark lua插件_第5张图片
  • 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客户端程序。
   
   
   
   
  1. /*scoreboard_test.c
  2. *
  3. * Example code for sending ScoreBoard frames over UDP
  4. *
  5. */
  6. #include
  7. #include
  8. #include
  9. #include
  10. #include
  11. #include
  12. #include
  13. #include
  14. #include
  15. #include
  16. typedef unsigned char guint8;
  17. typedef unsigned short guint16;
  18. typedef unsigned int guint32;
  19. typedef int gboolean;
  20. #define GET_VALUE 0x00 //获取比分数值
  21. #define SET_VALUE 0x01 //设置比分数值
  22. #define RESP_VALUE 0x80 //应答比分数值
  23. #define GET_COLOR 0x10 //获取背景色
  24. #define SET_COLOR 0x11 //设置背景色
  25. #define RESP_COLOR 0x90 //应答背景色
  26. /* Globals where each frame is composed before sending */
  27. static unsigned char g_PDUBuffer[16000];
  28. static unsigned int g_PDUOffset;
  29. static unsigned char g_frameBuffer[16000];
  30. static unsigned int g_frameOffset;
  31. /* UDP socket used for sending frames */
  32. static int g_sockfd;
  33. /* Remote serveraddress (where Wireshark is running) */
  34. static struct sockaddr_in g_serv_addr;
  35. /* Write a PDU */
  36. static void EncodeDummyPDU(void)
  37. {
  38. g_PDUOffset = 0;
  39. /*left score*/
  40. g_PDUBuffer[g_PDUOffset++] = 0x00;
  41. g_PDUBuffer[g_PDUOffset++] = 0x00;
  42. g_PDUBuffer[g_PDUOffset++] = 0x00;
  43. g_PDUBuffer[g_PDUOffset++] = 0x01;
  44. /*right score*/
  45. g_PDUBuffer[g_PDUOffset++] = 0x00;
  46. g_PDUBuffer[g_PDUOffset++] = 0x00;
  47. g_PDUBuffer[g_PDUOffset++] = 0x00;
  48. g_PDUBuffer[g_PDUOffset++] = 0x02;
  49. }
  50. /*******************************************/
  51. /* Add framing header to PDU and send. */
  52. void SendFrame(guint8 operator)
  53. {
  54. ssize_t bytesSent;
  55. g_frameOffset = 0;
  56. unsigned short tmp16;
  57. /*16Bytes identifier*/
  58. g_frameBuffer[g_frameOffset++] = 0xe2;
  59. g_frameBuffer[g_frameOffset++] = 0xcb;
  60. g_frameBuffer[g_frameOffset++] = 0xb5;
  61. g_frameBuffer[g_frameOffset++] = 0x80;
  62. g_frameBuffer[g_frameOffset++] = 0xcb;
  63. g_frameBuffer[g_frameOffset++] = 0x09;
  64. g_frameBuffer[g_frameOffset++] = 0x4e;
  65. g_frameBuffer[g_frameOffset++] = 0xba;
  66. g_frameBuffer[g_frameOffset++] = 0xa3;
  67. g_frameBuffer[g_frameOffset++] = 0x6b;
  68. g_frameBuffer[g_frameOffset++] = 0xf6;
  69. g_frameBuffer[g_frameOffset++] = 0x07;
  70. g_frameBuffer[g_frameOffset++] = 0xce;
  71. g_frameBuffer[g_frameOffset++] = 0x95;
  72. g_frameBuffer[g_frameOffset++] = 0x3f;
  73. g_frameBuffer[g_frameOffset++] = 0x2b;
  74. /*1Byte opreator*/
  75. g_frameBuffer[g_frameOffset++] = operator;
  76. /* Append actual PDU */
  77. memcpy(g_frameBuffer+g_frameOffset, g_PDUBuffer, g_PDUOffset);
  78. g_frameOffset += g_PDUOffset;
  79. /* Send out the data over the UDP socket */
  80. bytesSent = sendto(g_sockfd, g_frameBuffer, g_frameOffset, 0,
  81. (const struct sockaddr*)&g_serv_addr, sizeof(g_serv_addr));
  82. if (bytesSent != g_frameOffset) {
  83. fprintf(stderr, "sendto() failed - expected %d bytes, got %d (errno=%d)\n",
  84. g_frameOffset, bytesSent, errno);
  85. exit(1);
  86. }
  87. }
  88. /**************************************************************************/
  89. /* Main function */
  90. /* - set up socket + aserver address */
  91. /* - send example ScoreBoard frames using framing protocol */
  92. int main(int argc, char *argv[]){
  93. struct hostent *hp;
  94. if (argc < 3) {
  95. fprintf(stderr, "Usage: coclient \n");
  96. exit(1);
  97. }
  98. /***********************************/
  99. /* Create local socket */
  100. g_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  101. if (g_sockfd == -1) {
  102. fprintf(stderr, "Error trying to create socket (errno=%d)\n", errno);
  103. exit(1);
  104. }
  105. /***************************************************/
  106. /* Get remote IP address from 1st command-line arg */
  107. g_serv_addr.sin_family = AF_INET;
  108. hp = gethostbyname(argv[1]);
  109. if (hp == (struct hostent *)0) {
  110. fprintf(stderr, "Unknown host %s (h_errno=%d)\n", argv[1], h_errno);
  111. exit(1);
  112. }
  113. memcpy((void*)&g_serv_addr.sin_addr, (void*)hp->h_addr, hp->h_length);
  114. /****************************************************/
  115. /* Get remote port number from 2nd command-line arg */
  116. g_serv_addr.sin_port = htons(atoi(argv[2]));
  117. /****************************************************/
  118. /* Send some frame */
  119. EncodeDummyPDU();
  120. SendFrame(SET_VALUE);
  121. /* Close local socket */
  122. close(g_sockfd);
  123. return EXIT_SUCCESS;
  124. }
  • 编译gcc -g -o scoreboard_test scoreboard_test.c
  • $ sudo wireshark打开wireshark (因为只有superuser才能抓包。) 「非root用户使用wireshark捕捉网络数据包https://www.zybuluo.com/natsumi/note/81282」,选择loopback接口开始捕获。
  • 运行客户端程序./scoreboard_test 127.0.0.1 1127
  • 捕获之后,还看不出ScoreBoard协议的解析效果,将捕获的包保存成pcap或者pcapng格式的文件。
  • 用Wireshark打开捕获文件。注意不要superuser!!! 
    Wireshark lua插件_第6张图片
  • 我编写的客户端发送了一个01(设置比分)报文,将比分设置为1:2
  • 因为没有编写服务器端接收客户短发送的数据包,所以出现了ICMP报文,请忽略==
  • 整个过程太折腾人了。。可以再尝试直接将数据写到pcap文件

3.4 插件的调试

我直接是用了阳光男孩的代码,所以没有调试过程,以下引用原文的调试方法

我没有找到非常有效的插件调试方法。

  • 当Lua脚本中有语法错误时,Wireshark会在启动时弹出提示框;有调用错误时,会在相关数据包的Packet Details窗格中以红色显示。
  • 你可以把捕获的数据包保存为.pcap文件,每次修改Lua脚本后双击.pcap文件打开Wireshark即可。不必每次都进行Live Capture。

刚查到了一款Lua IDE,重点是It also supports general Lua debugging for Wireshark,可以尝试一下~ 
http://studio.zerobrane.com/

3.5 插件的卸载

还没找到卸载的方式


你可能感兴趣的:(TCP/IP)