Lua编写Wireshark插件实战

标签(空格分隔): 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选项卡


    原码安装的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重新安装
    重装了Wireshark
    重装了Wireshark

3.3.2 启用Lua

  • 在About窗口中的Folders选项卡还可以查看各种文件夹的位置


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

  • init.lua脚本中有disable_lua = false,默认是启用Lua的(禁用是true),使用之前应确认一下

  • 但是我以往的习惯都是$ sudo wireshark运行的,这就会出现下面这个错误提示窗

    Lua错误提示
    Lua错误提示

  • 其实看一看init.lua代码就知道了,因为superuser运行对Lua的功能有潜在危害,所以init.lua禁用了Lua。因此这个错误只需运行时不加sudo即可避免。

    init.lua的46行处
    init.lua的46行处

  • 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!!!
    ScoreBoard测试
    ScoreBoard测试
  • 我编写的客户端发送了一个01(设置比分)报文,将比分设置为1:2
  • 因为没有编写服务器端接收客户短发送的数据包,所以出现了ICMP报文,请忽略==
  • 整个过程太折腾人了。。可以再尝试直接将数据写到pcap文件

3.4 插件的调试

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

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

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

3.5 插件的卸载

还没找到卸载的方式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容