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选项卡


    Lua编写Wireshark插件实战_第1张图片
    原码安装的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重新安装
    Lua编写Wireshark插件实战_第2张图片
    重装了Wireshark

3.3.2 启用Lua

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


    Lua编写Wireshark插件实战_第3张图片
    About Wireshark Folders
  • 在Global configuration的位置有个init.lua,其实这是一个到/etc/wireshark/init.lua的连接。Wireshark开始运行时会执行init.lua脚本

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

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

    Lua编写Wireshark插件实战_第4张图片
    Lua错误提示

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

    Lua编写Wireshark插件实战_第5张图片
    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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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

3.4 插件的调试

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

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

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

3.5 插件的卸载

还没找到卸载的方式

你可能感兴趣的:(Lua编写Wireshark插件实战)