Write WireShark script plug-in to parse custom messages in lua (full version)

Keywords: network Programming

0. Preface

WireShark is undoubtedly relying on the Sword of Heaven in the arsenal of ICT personnel for network analysis. Although it has a long history, its sharpness has not been reduced at all. Because of open source, it is easy for users to develop again, which makes the sword's appearance and function almost perfect.

For the current mainstream protocols, there are self-contained parsing plug-ins, such as IP, ARP, TCP, UDP, HTTP, DHCP and so on. But in practical application, these protocols are usually only the carriers of our data transmission process, and many communication protocols between software are private. For example, the interaction protocols between game client and server are usually private. Wireshark can not specifically interpret the meanings of various fields, and can only display the received ones. Binary data brings some difficulties to protocol analysis and problem checking, especially when the content of protocol is complex.

Starting with a custom simple message protocol, this paper analyses how to write WireShark plug-in through lua to parse the custom message protocol. Welcome God's correction and instruction.

1. Message structure

Suppose that the data structure MSG that needs to be transmitted through TCP is as follows:

#define DATA_LEN_MAX     1000
#define LISTEN_MAX       1024

#define SERV_PORT   8001
#define LOCAL_PORT  (SERV_PORT + 1)

#define MSG_BIT1_SYN    0
#define MSG_BIT1_ACK    1
#define MSG_BIT1_ERR    2
#define MSG_BIT1_UNKOWN 3

#define MSG_BIT2_RUN            1
#define MSG_BIT2_STOP           0
#define MSG_BIT2_SHUTDOWN       2
#define MSG_BIT2_REBOOT         3
#define MSG_BIT2_IDEL           4
#define MSG_BIT2_ERROR          7

#define MSG_BIT4_TRUE     1
#define MSG_BIT4_FALSE    0

typedef struct APP_MSG_HEADER{
    unsigned char msg_no;
    unsigned char msg_version; 
}MSG_HEADER;

typedef struct APP_MSG{
    MSG_HEADER msg_header;
    unsigned char msg_len;
    unsigned char msg_bit1:2;
    unsigned char msg_bit2:3;
    unsigned char msg_bit3:2;
    unsigned char msg_bit4:1;
    unsigned int local_id;
    unsigned int remote_id;
}MSG;

The message MSG (12Byte) +business data (988Byte) is transmitted together, and the captured message is as follows:

The content of each field can be known by wireshark, but if the message structure is complex, it needs to calculate the offset to get the specified field value; moreover, if the message structure contains the bit field field field field, it will be more complex and hurt the eyes; finally, it is impossible to use the specified field in the message field to filter the message. . Based on the above reasons, in order to help analysis, it is imperative to write plug-in script analysis.

2. Plug-in Writing

Through Lua language, plug-ins can be written to realize the analysis of MSG. The specific script msg_analysis.lua is as follows:

-- @brief Implementation of custom protocol parsing through Lua script programming
-- @author wang quan
-- @date 2019.09.06


	local VALS_BIT_1_3  = {[0x0] = "SYN", [0x1] = "ACK", [0x2] ="ERR", [0x3] = "UNKOWN"}
	local VALS_BIT_2  	= {[0x0] = "STOP", [0x1] = "RUN", [0x2] ="SHUTDOWN", [0x3] = "REBOOT",[0x4] = "IDLE",[0x7] = "ERR"}
	local VALS_BIT_4 = {[0] = "False", [1] = "True"}

	-- 1. Create parser objects
	local NAME = "CUST_MESS_PROTO"  --Custom protocol name
	local MsgProto = Proto(NAME, "Custom Message over TCP Protocol")

	-- MsgProto Resolution fields for defining protocols
	local fields = MsgProto.fields
    fields.msg_no = ProtoField.uint8(NAME .. "MSG_NO", "msg_no", base.DEC) 
	fields.msg_version = ProtoField.uint8(NAME .. "MSG_VERSION", "msg_version", base.DEC)
    fields.msg_len = ProtoField.uint8(NAME .. "MSG_LEN", "msg_len", base.DEC)
	
	fields.length = ProtoField.uint32(NAME .. "LENGTH", "[Length]", base.DEC) 	--Description of Total Data Length
	fields.data_length = ProtoField.uint32(NAME .. "DATA_LENGTH", "[DataLength]", base.DEC)--Application Data Length Description 
	
	fields.msg_bitx = ProtoField.uint8(NAME .. "MSG_BITX", "msg_bitx", base.HEX)	-- Location Definition
    fields.msg_bit1 = ProtoField.uint8("MSG_BIT1", "msg_bit1", base.DEC,VALS_BIT_1_3,0x3)
    fields.msg_bit2 = ProtoField.uint8("MSG_BIT2", "msg_bit2", base.DEC,VALS_BIT_2,0x1C)
    fields.msg_bit3 = ProtoField.uint8("MSG_BIT3", "msg_bit3", base.DEC,VALS_BIT_1_3,0x60)
	fields.msg_bit4 = ProtoField.uint8("MSG_BIT4", "msg_bit4", base.DEC,VALS_BIT_4,0x80)
	
	fields.local_id = ProtoField.uint32("LOCAL_ID", "local_id", base.DEC)
    fields.remote_id = ProtoField.uint32("REMOTE_ID", "remote_id", base.DEC)

	local data_dis = Dissector.get("data")

-- 2. Parser function dissect packet 
--[[
    //Next, define the main function of the foo parser, which is called by wireshark
    //The first parameter is the tvb type, which represents the data that needs to be parsed by this parser
    //The second parameter is the Pinfo type, which is the information on the protocol parsing tree, including the display on the UI.
    //The third parameter is the TreeItem type, which represents the upper parse tree.
--]]

function MsgProto.dissector (tvb, pinfo, tree)

   --Create a subtree from the root tree to print parsed message data
  local subtree = tree:add(MsgProto, tvb())
		subtree:append_text(", msg_no: " .. tvb(0, 1):uint())
  -- The protocol name displayed on the protocol line in the packet details
  pinfo.cols.protocol = MsgProto.name 

  tvb_length = tvb:len()
  -- dissect field one by one, and add to protocol tree
  --Include a header in the message and continue to create tree parsing
  local msg_head_tree = subtree:add(MsgProto, tvb(0,2),"MSG_HEADER") --"MSG_HEADER"The parameter replaces the protocol name display
	    msg_head_tree:add(fields.msg_no, tvb(0, 1))--Represents a byte starting from 0
	    msg_head_tree:add(fields.msg_version, tvb(1, 1))
  
  subtree:add(fields.msg_len, tvb(2,1)) 
  
  subtree:add(fields.length, tvb_length) --Display data slice length information without fetching data from slice memory
  subtree:add(fields.data_length, tvb_length-8)

  -- Bit Domain Continues to Create Tree Resolution
  local msg_bitx_tree = subtree:add( fields.msg_bitx, tvb(3,1) )       -- bitfield
  			msg_bitx_tree:add(fields.msg_bit1,tvb(3,1))
			msg_bitx_tree:add(fields.msg_bit2,tvb(3,1))
			msg_bitx_tree:add(fields.msg_bit3,tvb(3,1))
			msg_bitx_tree:add(fields.msg_bit4,tvb(3,1))

	subtree:add_le(fields.local_id,tvb(4,4))
	subtree:add_le(fields.remote_id,tvb(8,4))			

  data_dis:call(tvb(12):tvb(), pinfo, tree) --It is noteworthy to parse the data in the data stream after the message structure. call The parameter name must be tvb,???,I hope the big man will give me some advice.

end

-- 3 Register the parser to wireshark Analytical table register this dissector
local udp_port_table = DissectorTable.get("tcp.port")

--Adding parsed TCP Port, Identify Protocol Based on Port Number
for i,port in ipairs{8001,8002} do
  udp_port_table:add(port,MsgProto)
end

3. Plug-in usage

The Lua language is a weak language and does not need to be compiled. 1. Install the script file directly in the wireshar installation directory. 2. Find the init.lua file under the current path, and add the dofile (DATA_DIR. "msg_analysis.lua") statement at the end of the file, as follows:

4. Result presentation

4.1, CUST_MESS_PROTO Analytical Display

After loading the plug-in script, you can see that the original transmission data has been parsed into the custom protocol CUST_MESS_PROTO.

4.2. CUST_MESS_PROTO Tree Display

4.3. Message filtering display

The message is filtered through the field MSG_BIT3=== 1 in the message structure, as follows:

 

 

Posted by Desdinova on Fri, 06 Sep 2019 20:24:15 -0700