Some protocols do clever things with data. They might possibly encrypt the data, or compress data, or part of it. If you know how these steps are taken it is possible to reverse them within the dissector.
As encryption can be tricky, let’s consider the case of compression. These techniques can also work for other transformations of data, where some step is required before the data can be examined.
What basically needs to happen here, is to identify the data that needs conversion, take that data and transform it into a new stream, and then call a dissector on it. Often this needs to be done "on-the-fly" based on clues in the packet. Sometimes this needs to be used in conjunction with other techniques, such as packet reassembly. The following shows a technique to achieve this effect.
Decompressing data packets for dissection.
uint8_t flags = tvb_get_uint8(tvb, offset);
offset ++;
if (flags & FLAG_COMPRESSED) { /* the remainder of the packet is compressed */
uint16_t orig_size = tvb_get_ntohs(tvb, offset);
unsigned char *decompressed_buffer = (unsigned char*)wmem_alloc(pinfo->pool, orig_size);
offset += 2;
unsigned compr_size = tvb_captured_length_remaining(tvb, offset);
decompress_packet(tvb_memdup(pinfo->pool, tvb, offset, compr_size),
compr_size, decompressed_buffer, orig_size);
/* Now re-setup the tvb buffer to have the new data */
next_tvb = tvb_new_child_real_data(tvb, decompressed_buffer, orig_size, orig_size);
add_new_data_source(pinfo, next_tvb, "Decompressed Data");
} else {
next_tvb = tvb_new_subset_remaining(tvb, offset);
}
offset = 0;
/* process next_tvb from here on */
The first step here is to recognise the compression. In this case a flag byte alerts us to the fact the remainder of the packet is compressed. Next we retrieve the original size of the packet, which in this case is conveniently within the protocol. If it’s not, it may be part of the compression routine to work it out for you, in which case the logic would be different.
So armed with the size, a buffer is allocated to receive the uncompressed data
using wmem_alloc() in pinfo→pool memory, and the packet is decompressed into
it. The tvb_memdup() function gets a copy of the raw data of the packet from
the offset onwards. The length of the array of bytes has to be specified to
tvb_memdup() and the decompression function; in this case we want the rest of
the bytes present in tvbuffer, which is given by the tvb_captured_length_remaining()
function. (Using tvb_reported_length_remaining() would throw an exception
on a truncated packet. Sometimes that is desired, but here we assume the
decompression function can partially decompress truncated data.)
Next we build a new tvb buffer from this data, using the
tvb_new_child_real_data() call. This data is a child of our original data, so
calling this function also acknowledges that. No need to call
tvb_set_free_cb() as the pinfo→pool was used (the memory block will be
automatically freed when the pinfo pool lifetime expires). Finally we add this
tvb as a new data source, so that the detailed display can show the
decompressed bytes as well as the original.
After this has been set up the remainder of the dissector can dissect the buffer
next_tvb, as it’s a new buffer the offset needs to be 0 as we start again from
the beginning of this buffer. To make the rest of the dissector work regardless
of whether compression was involved or not, in the case that compression was not
signaled, we use tvb_new_subset_remaining() to deliver us a new buffer based
on the old one but starting at the current offset, and extending to the end.
This makes dissecting the packet from this point on exactly the same regardless
of compression.