rdp: add dissector for the egfx channel

This patch adds basic dissection for the egfx channel. It also fixes fragmentation
in the dynamic channel, and also introduces some of the decompressors involved in RDP
traffic.
This commit is contained in:
David Fort 2021-09-24 11:13:54 +02:00
parent 7b5661dfe0
commit 09f762ba5e
6 changed files with 1183 additions and 3 deletions

View File

@ -256,6 +256,7 @@ set(LIBWIRESHARK_NONGENERATED_FILES
tvbuff_lz77.c
tvbuff_lz77huff.c
tvbuff_lznt1.c
tvbuff_rdp.c
uat.c
value_string.c
wscbor.c

View File

@ -1656,6 +1656,7 @@ set(DISSECTOR_SRC
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdp.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdp_multitransport.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdp_drdynvc.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdp_egfx.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdpudp.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-rdt.c
${CMAKE_CURRENT_SOURCE_DIR}/packet-redback.c

View File

@ -54,6 +54,7 @@ static int ett_rdp_drdynvc_softsync_channels = -1;
static int ett_rdp_drdynvc_softsync_channel = -1;
static int ett_rdp_drdynvc_softsync_dvc = -1;
dissector_handle_t egfx_handle;
#define PNAME "RDP Dynamic Channel Protocol"
#define PSNAME "DRDYNVC"
@ -86,10 +87,19 @@ typedef enum {
DRDYNVC_CHANNEL_AUTH_REDIR /* MS-RDPEAR */
} drdynvc_known_channel_t;
typedef struct {
wmem_array_t *packet;
guint32 pendingLen;
guint32 startFrame;
} drdynvc_pending_packet_t;
typedef struct {
drdynvc_known_channel_t type;
char *name;
guint32 channelId;
drdynvc_pending_packet_t pending_cs;
drdynvc_pending_packet_t pending_sc;
} drdynvc_channel_def_t;
#define DRDYNVC_MAX_CHANNELS 32
@ -237,6 +247,7 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
gboolean haveChannelId, havePri, haveLen;
gboolean isServerTarget = rdp_isServerAddressTarget(pinfo);
guint32 channelId = 0;
guint32 pduLen;
drdynvc_conv_info_t *info;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "DRDYNVC");
@ -285,13 +296,13 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
if (haveLen) {
Len = (cbIdSpCmd >> 2) & 0x3;
offset += dissect_rdp_vlength(tvb, hf_rdp_drdynvc_length, offset, Len, tree, NULL);
offset += dissect_rdp_vlength(tvb, hf_rdp_drdynvc_length, offset, Len, tree, &pduLen);
}
info = drdynvc_get_conversation_data(pinfo);
switch (cmdId) {
case DRDYNVC_CREATE_REQUEST_PDU:
if (isServerTarget) {
if (!isServerTarget) {
guint nameLen = tvb_strsize(tvb, offset);
col_set_str(pinfo->cinfo, COL_INFO, "CreateChannel Request");
@ -303,6 +314,13 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
channel->channelId = channelId;
channel->name = tvb_get_string_enc(NULL, tvb, offset, nameLen, ENC_ASCII);
channel->type = drdynvc_find_channel_type(channel->name);
channel->pending_cs.pendingLen = 0;
channel->pending_cs.packet = NULL;
channel->pending_cs.startFrame = pinfo->num;
channel->pending_sc.pendingLen = 0;
channel->pending_sc.packet = NULL;
channel->pending_sc.startFrame = pinfo->num;
info->maxChannels++;
}
@ -328,7 +346,7 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
offset += 2;
if (isServerTarget) {
if (!isServerTarget) {
col_set_str(pinfo->cinfo, COL_INFO, "Capabilities request");
proto_tree_add_item(tree, hf_rdp_drdynvc_capa_prio0, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
@ -348,8 +366,31 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
col_set_str(pinfo->cinfo, COL_INFO, "Data first");
if (channel) {
drdynvc_pending_packet_t *pendingPacket = isServerTarget ? &channel->pending_cs : &channel->pending_sc;
gint payloadLen = tvb_reported_length_remaining(tvb, offset);
proto_item *channelName = proto_tree_add_string_format_value(tree, hf_rdp_drdynvc_createresp_channelname, tvb, offset, 0, NULL, "%s", channel->name);
proto_item_set_generated(channelName);
pendingPacket->pendingLen = pduLen;
pendingPacket->pendingLen -= payloadLen;
pendingPacket->startFrame = pinfo->num;
if (!pendingPacket->pendingLen) {
switch (channel->type) {
case DRDYNVC_CHANNEL_EGFX:
call_dissector(egfx_handle, tvb_new_subset_remaining(tvb, offset), pinfo, tree);
break;
default:
proto_tree_add_item(tree, hf_rdp_drdynvc_data, tvb, offset, -1, ENC_NA);
break;
}
offset += payloadLen;
return offset;
} else {
pendingPacket->packet = wmem_array_sized_new(wmem_file_scope(), 1, pduLen);
wmem_array_append(pendingPacket->packet, tvb_get_ptr(tvb, offset, -1), payloadLen);
}
}
proto_tree_add_item(tree, hf_rdp_drdynvc_data, tvb, offset, -1, ENC_NA);
@ -361,8 +402,45 @@ dissect_rdp_drdynvc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree,
col_set_str(pinfo->cinfo, COL_INFO, "Data");
if (channel) {
drdynvc_pending_packet_t *pendingPacket = isServerTarget ? &channel->pending_cs : &channel->pending_sc;
gboolean fragmented = FALSE;
gint payloadLen = tvb_reported_length_remaining(tvb, offset);
proto_item *channelName = proto_tree_add_string_format_value(tree, hf_rdp_drdynvc_createresp_channelname, tvb, offset, 0, NULL, "%s", channel->name);
proto_item_set_generated(channelName);
if (pendingPacket->startFrame > pinfo->num) {
/* catch the case when we're on the second pass and the end of the capture
* contains a packet fragment */
pendingPacket->pendingLen = 0;
}
if (pendingPacket->pendingLen) {
if ((guint32)payloadLen > pendingPacket->pendingLen) {
// TODO: error
return offset;
}
pendingPacket->pendingLen -= payloadLen;
wmem_array_append(pendingPacket->packet, tvb_get_ptr(tvb, offset, -1), payloadLen);
fragmented = TRUE;
}
if (!pendingPacket->pendingLen) {
tvbuff_t *targetTvb;
if (!fragmented) {
targetTvb = tvb_new_subset_remaining(tvb, offset);
} else {
gint reassembled_len = wmem_array_get_count(pendingPacket->packet);
targetTvb = tvb_new_real_data(wmem_array_get_raw(pendingPacket->packet), reassembled_len, reassembled_len);
add_new_data_source(pinfo, targetTvb, "Reassembled DRDYNVC");
}
switch (channel->type) {
case DRDYNVC_CHANNEL_EGFX:
return call_dissector(egfx_handle, targetTvb, pinfo, tree);
default:
proto_tree_add_item(tree, hf_rdp_drdynvc_data, tvb, offset, -1, ENC_NA);
return offset;
}
}
}
proto_tree_add_item(tree, hf_rdp_drdynvc_data, tvb, offset, -1, ENC_NA);
@ -631,6 +709,7 @@ void proto_register_rdp_drdynvc(void) {
}
void proto_reg_handoff_drdynvc(void) {
egfx_handle = find_dissector("rdp_egfx");
}
/*

View File

@ -0,0 +1,552 @@
/* Packet-rdp_egfx.c
* Routines for the EGFX RDP channel
* Copyright 2021, David Fort <contact@hardening-consulting.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
* See: "[MS-RDPEGFX] "
*/
#include "config.h"
#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/conversation.h>
#include <epan/expert.h>
#include <epan/tvbuff_rdp.h>
#include "packet-rdp.h"
#include "packet-rdpudp.h"
void proto_register_rdp_egfx(void);
void proto_reg_handoff_rdp_egfx(void);
static int proto_rdp_egfx = -1;
static int hf_egfx_cmdId = -1;
static int hf_egfx_flags = -1;
static int hf_egfx_pduLength = -1;
static int hf_egfx_caps_capsSetCount = -1;
static int hf_egfx_cap_version = -1;
static int hf_egfx_cap_length = -1;
static int hf_egfx_reset_width = -1;
static int hf_egfx_reset_height = -1;
static int hf_egfx_reset_monitorCount = -1;
static int hf_egfx_reset_monitorDefLeft = -1;
static int hf_egfx_reset_monitorDefTop = -1;
static int hf_egfx_reset_monitorDefRight = -1;
static int hf_egfx_reset_monitorDefBottom = -1;
static int hf_egfx_reset_monitorDefFlags = -1;
static int hf_egfx_ack_queue_depth = -1;
static int hf_egfx_ack_frame_id = -1;
static int hf_egfx_ack_total_decoded = -1;
static int hf_egfx_ackqoe_frame_id = -1;
static int hf_egfx_ackqoe_timestamp = -1;
static int hf_egfx_ackqoe_timediffse = -1;
static int hf_egfx_ackqoe_timediffedr = -1;
static int hf_egfx_start_timestamp = -1;
static int hf_egfx_start_frameid = -1;
static int hf_egfx_end_frameid = -1;
static int ett_rdp_egfx = -1;
static int ett_egfx_caps = -1;
static int ett_egfx_capsconfirm = -1;
static int ett_egfx_cap = -1;
static int ett_egfx_ack = -1;
static int ett_egfx_ackqoe = -1;
static int ett_egfx_reset = -1;
static int ett_egfx_monitors = -1;
static int ett_egfx_monitordef = -1;
static expert_field ei_egfx_pdulen_invalid = EI_INIT;
static expert_field ei_egfx_invalid_compression = EI_INIT;
#define PNAME "RDP Graphic pipeline channel Protocol"
#define PSNAME "EGFX"
#define PFNAME "rdp_egfx"
enum {
RDPGFX_CMDID_WIRETOSURFACE_1 = 0x0001,
RDPGFX_CMDID_WIRETOSURFACE_2 = 0x0002,
RDPGFX_CMDID_DELETEENCODINGCONTEXT = 0x0003,
RDPGFX_CMDID_SOLIDFILL = 0x0004,
RDPGFX_CMDID_SURFACETOSURFACE = 0x0005,
RDPGFX_CMDID_SURFACETOCACHE = 0x0006,
RDPGFX_CMDID_CACHETOSURFACE = 0x0007,
RDPGFX_CMDID_EVICTCACHEENTRY = 0x0008,
RDPGFX_CMDID_CREATESURFACE = 0x0009,
RDPGFX_CMDID_DELETESURFACE = 0x000a,
RDPGFX_CMDID_STARTFRAME = 0x000b,
RDPGFX_CMDID_ENDFRAME = 0x000c,
RDPGFX_CMDID_FRAMEACKNOWLEDGE = 0x000d,
RDPGFX_CMDID_RESETGRAPHICS = 0x000e,
RDPGFX_CMDID_MAPSURFACETOOUTPUT = 0x000f,
RDPGFX_CMDID_CACHEIMPORTOFFER = 0x0010,
RDPGFX_CMDID_CACHEIMPORTREPLY = 0x0011,
RDPGFX_CMDID_CAPSADVERTISE = 0x0012,
RDPGFX_CMDID_CAPSCONFIRM = 0x0013,
RDPGFX_CMDID_MAPSURFACETOWINDOW = 0x0015,
RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE = 0x0016,
RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT = 0x0017,
RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW = 0x0018,
};
enum {
RDPGFX_CAPVERSION_8 = 0x00080004,
RDPGFX_CAPVERSION_81 = 0x00080105,
RDPGFX_CAPVERSION_101 = 0x000A0100,
RDPGFX_CAPVERSION_102 = 0x000A0200,
RDPGFX_CAPVERSION_103 = 0x000A0301,
RDPGFX_CAPVERSION_104 = 0x000A0400,
RDPGFX_CAPVERSION_105 = 0x000A0502,
RDPGFX_CAPVERSION_106 = 0x000A0600
};
static const value_string rdp_egfx_cmd_vals[] = {
{ RDPGFX_CMDID_WIRETOSURFACE_1, "Wire to surface 1" },
{ RDPGFX_CMDID_WIRETOSURFACE_2, "Wire to surface 2" },
{ RDPGFX_CMDID_DELETEENCODINGCONTEXT, "delete encoding context" },
{ RDPGFX_CMDID_SOLIDFILL, "Solid fill" },
{ RDPGFX_CMDID_SURFACETOSURFACE, "Surface to surface" },
{ RDPGFX_CMDID_SURFACETOCACHE, "Surface to cache" },
{ RDPGFX_CMDID_CACHETOSURFACE, "Cache to surface" },
{ RDPGFX_CMDID_EVICTCACHEENTRY, "Evict cache entry" },
{ RDPGFX_CMDID_CREATESURFACE, "Create surface" },
{ RDPGFX_CMDID_DELETESURFACE, "Delete surface" },
{ RDPGFX_CMDID_STARTFRAME, "Start frame" },
{ RDPGFX_CMDID_ENDFRAME, "End frame" },
{ RDPGFX_CMDID_FRAMEACKNOWLEDGE, "Frame acknowlegde" },
{ RDPGFX_CMDID_RESETGRAPHICS, "Reset graphics" },
{ RDPGFX_CMDID_MAPSURFACETOOUTPUT, "Map Surface to output" },
{ RDPGFX_CMDID_CACHEIMPORTOFFER, "Cache import offer" },
{ RDPGFX_CMDID_CACHEIMPORTREPLY, "Cache import reply" },
{ RDPGFX_CMDID_CAPSADVERTISE, "Caps advertise" },
{ RDPGFX_CMDID_CAPSCONFIRM, "Caps confirm" },
{ RDPGFX_CMDID_MAPSURFACETOWINDOW, "Map surface to window" },
{ RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE, "Qoe frame acknowlegde" },
{ RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT, "Map surface to scaled output" },
{ RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW, "Map surface to scaled window" },
{ 0x0, NULL },
};
static const value_string rdp_egfx_caps_version_vals[] = {
{ RDPGFX_CAPVERSION_8, "8" },
{ RDPGFX_CAPVERSION_81, "8.1" } ,
{ RDPGFX_CAPVERSION_101, "10.1" },
{ RDPGFX_CAPVERSION_102, "10.2" },
{ RDPGFX_CAPVERSION_103, "10.3" },
{ RDPGFX_CAPVERSION_104, "10.4" },
{ RDPGFX_CAPVERSION_105, "10.5" },
{ RDPGFX_CAPVERSION_106, "10.6" },
{ 0x0, NULL },
};
static const value_string rdp_egfx_monitor_flags_vals[] = {
{ 0x00000000, "is secondary" },
{ 0x00000001, "is primary" },
{ 0x0, NULL },
};
typedef struct {
zgfx_context_t *zgfx;
} egfx_conv_info_t;
static egfx_conv_info_t *
egfx_get_conversation_data(packet_info *pinfo)
{
conversation_t *conversation, *conversation_tcp;
egfx_conv_info_t *info;
conversation = find_or_create_conversation(pinfo);
info = (egfx_conv_info_t *)conversation_get_proto_data(conversation, proto_rdp_egfx);
if (!info) {
conversation_tcp = rdp_find_tcp_conversation_from_udp(conversation);
if (conversation_tcp)
info = (egfx_conv_info_t *)conversation_get_proto_data(conversation_tcp, proto_rdp_egfx);
}
if (info == NULL) {
info = wmem_new0(wmem_file_scope(), egfx_conv_info_t);
info->zgfx = zgfx_context_new(wmem_file_scope());
conversation_add_proto_data(conversation, proto_rdp_egfx, info);
}
return info;
}
static int
dissect_rdp_egfx_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void *data _U_)
{
proto_item *item;
proto_tree *tree;
proto_tree *subtree;
gint offset = 0;
guint16 cmdId = 0;
guint32 pduLength;
guint32 i;
parent_tree = proto_tree_get_root(parent_tree);
col_set_str(pinfo->cinfo, COL_PROTOCOL, "EGFX");
col_clear(pinfo->cinfo, COL_INFO);
while (tvb_captured_length_remaining(tvb, offset) > 8) {
pduLength = tvb_get_guint32(tvb, offset + 4, ENC_LITTLE_ENDIAN);
item = proto_tree_add_item(parent_tree, proto_rdp_egfx, tvb, offset, pduLength, ENC_NA);
tree = proto_item_add_subtree(item, ett_rdp_egfx);
cmdId = tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN);
proto_tree_add_item(tree, hf_egfx_cmdId, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
proto_tree_add_item(tree, hf_egfx_flags, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
proto_tree_add_item(tree, hf_egfx_pduLength, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
if (pduLength < 8) {
expert_add_info_format(pinfo, item, &ei_egfx_pdulen_invalid, "pduLength is %u, not < 8", pduLength);
return offset;
}
switch (cmdId) {
case RDPGFX_CMDID_CAPSADVERTISE: {
guint16 capsSetCount = tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN);
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Caps advertise");
proto_tree_add_item(tree, hf_egfx_caps_capsSetCount, tvb, offset, 2, ENC_LITTLE_ENDIAN);
subtree = proto_tree_add_subtree(tree, tvb, offset, pduLength-8, ett_egfx_caps, NULL, "Caps");
offset += 2;
for (i = 0; i < capsSetCount; i++) {
guint32 capsDataLength;
proto_tree_add_item(subtree, hf_egfx_cap_version, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_cap_length, tvb, offset, 4, ENC_LITTLE_ENDIAN);
capsDataLength = tvb_get_guint32(tvb, offset, ENC_LITTLE_ENDIAN);
offset += 4;
offset += capsDataLength;
}
break;
}
case RDPGFX_CMDID_CAPSCONFIRM: {
guint32 capsDataLength;
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Caps confirm");
subtree = proto_tree_add_subtree(tree, tvb, offset, pduLength-8, ett_egfx_capsconfirm, NULL, "Caps confirm");
proto_tree_add_item(subtree, hf_egfx_cap_version, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item_ret_uint(subtree, hf_egfx_cap_length, tvb, offset, 4, ENC_LITTLE_ENDIAN, &capsDataLength);
offset += 4 + capsDataLength;
break;
}
case RDPGFX_CMDID_RESETGRAPHICS: {
guint32 nmonitor;
proto_tree *monitors_tree;
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Reset graphics");
subtree = proto_tree_add_subtree(tree, tvb, offset, pduLength-4, ett_egfx_reset, NULL, "Reset graphics");
proto_tree_add_item(subtree, hf_egfx_reset_width, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_reset_height, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item_ret_uint(subtree, hf_egfx_reset_monitorCount, tvb, offset, 4, ENC_LITTLE_ENDIAN, &nmonitor);
offset += 4;
monitors_tree = proto_tree_add_subtree(subtree, tvb, offset, nmonitor * 20, ett_egfx_monitors, NULL, "Monitors");
for (i = 0; i < nmonitor; i++) {
proto_item *monitor_tree;
guint32 left, top, right, bottom;
left = tvb_get_guint32(tvb, offset, ENC_LITTLE_ENDIAN);
top = tvb_get_guint32(tvb, offset+4, ENC_LITTLE_ENDIAN);
right = tvb_get_guint32(tvb, offset+8, ENC_LITTLE_ENDIAN);
bottom = tvb_get_guint32(tvb, offset+12, ENC_LITTLE_ENDIAN);
monitor_tree = proto_tree_add_subtree_format(monitors_tree, tvb, offset, 20, ett_egfx_monitordef, NULL,
"(%d,%d) - (%d,%d)", left, top, right, bottom);
proto_tree_add_item(monitor_tree, hf_egfx_reset_monitorDefLeft, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(monitor_tree, hf_egfx_reset_monitorDefTop, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(monitor_tree, hf_egfx_reset_monitorDefRight, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(monitor_tree, hf_egfx_reset_monitorDefBottom, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(monitor_tree, hf_egfx_reset_monitorDefFlags, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
}
offset += (pduLength - 8);
break;
}
case RDPGFX_CMDID_STARTFRAME: {
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Start frame");
proto_tree_add_item(tree, hf_egfx_start_timestamp, tvb, offset, 4, ENC_LITTLE_ENDIAN);
// TODO: dissect timestamp
offset += 4;
proto_tree_add_item(tree, hf_egfx_start_frameid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
break;
}
case RDPGFX_CMDID_ENDFRAME:
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "End frame");
proto_tree_add_item(tree, hf_egfx_end_frameid, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
break;
case RDPGFX_CMDID_FRAMEACKNOWLEDGE:
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Frame acknowledge");
subtree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_egfx_ack, NULL, "Frame acknowledge");
proto_tree_add_item(subtree, hf_egfx_ack_queue_depth, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_ack_frame_id, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_ack_total_decoded, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
break;
case RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE:
col_append_sep_str(pinfo->cinfo, COL_INFO, ",", "Frame acknowledge QoE");
subtree = proto_tree_add_subtree(tree, tvb, offset, -1, ett_egfx_ackqoe, NULL, "Frame acknowledge QoE");
proto_tree_add_item(subtree, hf_egfx_ackqoe_frame_id, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_ackqoe_timestamp, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
proto_tree_add_item(subtree, hf_egfx_ackqoe_timediffse, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
proto_tree_add_item(subtree, hf_egfx_ackqoe_timediffedr, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
break;
default:
offset += (pduLength - 8);
break;
}
}
return offset;
}
static int
dissect_rdp_egfx(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void *data)
{
tvbuff_t *work_tvb = tvb;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "EGFX");
col_clear(pinfo->cinfo, COL_INFO);
parent_tree = proto_tree_get_root(parent_tree);
if (!rdp_isServerAddressTarget(pinfo)) {
egfx_conv_info_t *infos = egfx_get_conversation_data(pinfo);
work_tvb = rdp8_decompress(infos->zgfx, wmem_packet_scope(), tvb, 0);
if (!work_tvb && parent_tree) {
expert_add_info_format(pinfo, parent_tree->last_child, &ei_egfx_invalid_compression, "invalid compression");
return 0;
}
add_new_data_source(pinfo, work_tvb, "Uncompressed GFX");
}
dissect_rdp_egfx_payload(work_tvb, pinfo, parent_tree, data);
return tvb_reported_length(tvb);
}
void proto_register_rdp_egfx(void) {
static hf_register_info hf[] = {
{ &hf_egfx_cmdId,
{ "CmdId", "rdp_egfx.cmdid",
FT_UINT16, BASE_HEX, VALS(rdp_egfx_cmd_vals), 0x0,
NULL, HFILL }
},
{ &hf_egfx_flags,
{ "flags", "rdp_egfx.flags",
FT_UINT16, BASE_HEX, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_pduLength,
{ "pduLength", "rdp_egfx.pdulength",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_caps_capsSetCount,
{ "capsSetCount", "rdp_egfx.caps.setcount",
FT_UINT16, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_cap_version,
{ "Version", "rdp_egfx.cap.version",
FT_UINT32, BASE_HEX, VALS(rdp_egfx_caps_version_vals), 0x0,
NULL, HFILL }
},
{ &hf_egfx_cap_length,
{ "capsDataLength", "rdp_egfx.cap.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ack_queue_depth,
{ "queueDepth", "rdp_egfx.ack.queuedepth",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ack_frame_id,
{ "frameId", "rdp_egfx.ack.frameid",
FT_UINT32, BASE_HEX, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ack_total_decoded,
{ "Total frames decoded", "rdp_egfx.ack.totalframesdecoded",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ackqoe_frame_id,
{ "frameId", "rdp_egfx.ackqoe.frameid",
FT_UINT32, BASE_HEX, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ackqoe_timestamp,
{ "Timestamp", "rdp_egfx.ackqoe.timestamp",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ackqoe_timediffse,
{ "TimeDiffSE", "rdp_egfx.ackqoe.timediffse",
FT_UINT16, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_ackqoe_timediffedr,
{ "TimeDiffEDR", "rdp_egfx.ackqoe.timediffedr",
FT_UINT16, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_width,
{ "Width", "rdp_egfx.reset.width",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_height,
{ "Height", "rdp_egfx.reset.height",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorCount,
{ "Monitor count", "rdp_egfx.reset.monitorcount",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorDefLeft,
{ "Left", "rdp_egfx.monitor.left",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorDefTop,
{ "Top", "rdp_egfx.monitor.top",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorDefRight,
{ "Right", "rdp_egfx.monitor.right",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorDefBottom,
{ "Bottom", "rdp_egfx.monitor.bottom",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_reset_monitorDefFlags,
{ "Flags", "rdp_egfx.monitor.flags",
FT_UINT32, BASE_DEC, VALS(rdp_egfx_monitor_flags_vals), 0x0,
NULL, HFILL }
},
{ &hf_egfx_start_timestamp,
{ "Timestamp", "rdp_egfx.startframe.timestamp",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_start_frameid,
{ "Frame id", "rdp_egfx.startframe.frameid",
FT_UINT32, BASE_HEX, NULL, 0x0,
NULL, HFILL }
},
{ &hf_egfx_end_frameid,
{ "Frame id", "rdp_egfx.endframe.frameid",
FT_UINT32, BASE_HEX, NULL, 0x0,
NULL, HFILL }
},
};
static gint *ett[] = {
&ett_rdp_egfx,
&ett_egfx_caps,
&ett_egfx_cap,
&ett_egfx_ack,
&ett_egfx_ackqoe,
&ett_egfx_reset,
&ett_egfx_capsconfirm,
&ett_egfx_monitors,
&ett_egfx_monitordef,
};
static ei_register_info ei[] = {
{ &ei_egfx_pdulen_invalid, { "rdp_egfx.pdulength.invalid", PI_PROTOCOL, PI_ERROR, "Invalid length", EXPFILL }},
{ &ei_egfx_invalid_compression, { "rdp_egfx.compression.invalid", PI_PROTOCOL, PI_ERROR, "Invalid compression", EXPFILL }},
};
expert_module_t* expert_egfx;
proto_rdp_egfx = proto_register_protocol(PNAME, PSNAME, PFNAME);
/* Register fields and subtrees */
proto_register_field_array(proto_rdp_egfx, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
expert_egfx = expert_register_protocol(proto_rdp_egfx);
expert_register_field_array(expert_egfx, ei, array_length(ei));
register_dissector("rdp_egfx", dissect_rdp_egfx, proto_rdp_egfx);
}
void proto_reg_handoff_rdp_egfx(void) {
}

520
epan/tvbuff_rdp.c Normal file
View File

@ -0,0 +1,520 @@
/* tvbuff_rdp.c
* Decompression routines used in RDP
* Copyright 2021, David Fort
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <glib.h>
#include <stdbool.h>
#include <epan/tvbuff.h>
#include <epan/proto.h>
#include <epan/tvbuff_rdp.h>
enum {
ZGFX_SEGMENTED_SINGLE = 0xe0,
ZGFX_SEGMENTED_MULTIPART = 0xe1,
ZGX_PACKET_COMPRESSED = 0x20,
};
typedef struct {
tvbuff_t *input;
guint offset;
guint remainingBits;
guint32 currentValue;
guint currentBits;
} bitstream_t;
static void
bitstream_init(bitstream_t *b, tvbuff_t *input, guint blen) {
b->input = input;
b->offset = 0;
b->remainingBits = blen;
b->currentValue = 0;
b->currentBits = 0;
}
static guint32
bitstream_getbits(bitstream_t *b, guint8 nbits, gboolean *ok) {
guint32 ret = 0;
if (nbits > b->remainingBits) {
*ok = FALSE;
return 0;
}
while (b->currentBits < nbits) {
if (!tvb_reported_length_remaining(b->input, b->offset)) {
*ok = FALSE;
return 0;
}
b->currentValue <<= 8;
b->currentValue += tvb_get_guint8(b->input, b->offset++);
b->currentBits += 8;
}
*ok = TRUE;
ret = b->currentValue >> (b->currentBits-nbits);
b->currentBits -= nbits;
b->remainingBits -= nbits;
b->currentValue &= (1 << b->currentBits) - 1;
return ret;
}
static gboolean
bitstream_copyraw(bitstream_t *b, guint8 *dest, gint nbytes)
{
if (tvb_captured_length_remaining(b->input, b->offset) < nbytes)
return FALSE;
tvb_memcpy(b->input, dest, b->offset, nbytes);
return TRUE;
}
static gboolean
bitstream_copyraw_advance(bitstream_t *b, guint8 *dest, guint nbytes)
{
if (!bitstream_copyraw(b, dest, nbytes))
return FALSE;
b->offset += nbytes;
return TRUE;
}
static void
bitstream_realign(bitstream_t *b) {
b->remainingBits -= b->currentBits;
b->currentBits = 0;
b->currentValue = 0;
}
typedef struct {
guint32 prefixLength;
guint32 prefixCode;
guint32 valueBits;
guint32 valueBase;
} zgfx_token_t;
static const zgfx_token_t ZGFX_LITTERAL_TABLE[] = {
// prefixLength prefixCode valueBits valueBase
{ 5, 24, 0, 0x00 }, // 11000
{ 5, 25, 0, 0x01 }, // 11001
{ 6, 52, 0, 0x02 }, // 110100
{ 6, 53, 0, 0x03 }, // 110101
{ 6, 54, 0, 0xFF }, // 110110
{ 7, 110, 0, 0x04 }, // 1101110
{ 7, 111, 0, 0x05 }, // 1101111
{ 7, 112, 0, 0x06 }, // 1110000
{ 7, 113, 0, 0x07 }, // 1110001
{ 7, 114, 0, 0x08 }, // 1110010
{ 7, 115, 0, 0x09 }, // 1110011
{ 7, 116, 0, 0x0A }, // 1110100
{ 7, 117, 0, 0x0B }, // 1110101
{ 7, 118, 0, 0x3A }, // 1110110
{ 7, 119, 0, 0x3B }, // 1110111
{ 7, 120, 0, 0x3C }, // 1111000
{ 7, 121, 0, 0x3D }, // 1111001
{ 7, 122, 0, 0x3E }, // 1111010
{ 7, 123, 0, 0x3F }, // 1111011
{ 7, 124, 0, 0x40 }, // 1111100
{ 7, 125, 0, 0x80 }, // 1111101
{ 8, 252, 0, 0x0C }, // 11111100
{ 8, 253, 0, 0x38 }, // 11111101
{ 8, 254, 0, 0x39 }, // 11111110
{ 8, 255, 0, 0x66 }, // 11111111
};
static const zgfx_token_t ZGFX_MATCH_TABLE[] = {
// prefixLength prefixCode valueBits tokenType valueBase
{ 5, 17, 5, 0 }, // 10001
{ 5, 18, 7, 32 }, // 10010
{ 5, 19, 9, 160 }, // 10011
{ 5, 20, 10, 672 }, // 10100
{ 5, 21, 12, 1696 }, // 10101
{ 6, 44, 14, 5792 }, // 101100
{ 6, 45, 15, 22176 }, // 101101
{ 7, 92, 18, 54944 }, // 1011100
{ 7, 93, 20, 317088 }, // 1011101
{ 8, 188, 20, 1365664 }, // 10111100
{ 8, 189, 21, 2414240 }, // 10111101
{ 9, 380, 22, 4511392 }, // 101111100
{ 9, 381, 23, 8705696 }, // 101111101
{ 9, 382, 24, 17094304 }, // 101111110
};
struct _zgfx_context_t{
guint8 historyBuffer[2500000];
guint32 historyIndex;
guint32 historyBufferSize;
guint32 outputCount;
guint8 outputSegment[65536];
};
typedef struct _zgfx_context_t zgfx_context_t;
zgfx_context_t *zgfx_context_new(wmem_allocator_t *allocator) {
zgfx_context_t *ret = wmem_alloc0(allocator, sizeof(*ret));
ret->historyBufferSize = sizeof(ret->historyBuffer);
return ret;
}
static void
zgfx_write_history_litteral(zgfx_context_t *zgfx, guint8 c)
{
zgfx->historyBuffer[zgfx->historyIndex] = c;
zgfx->historyIndex = (zgfx->historyIndex + 1) % zgfx->historyBufferSize;
}
static void
zgfx_write_history_buffer_tvb(zgfx_context_t *zgfx, tvbuff_t *src, guint32 count)
{
gint src_offset = 0;
guint32 front;
if (count > zgfx->historyBufferSize) {
const size_t residue = count - zgfx->historyBufferSize;
count = zgfx->historyBufferSize;
src_offset += residue;
zgfx->historyIndex = (zgfx->historyIndex + residue) % zgfx->historyBufferSize;
}
if (zgfx->historyIndex + count <= zgfx->historyBufferSize)
{
tvb_memcpy(src, &(zgfx->historyBuffer[zgfx->historyIndex]), src_offset, count);
}
else
{
front = zgfx->historyBufferSize - zgfx->historyIndex;
tvb_memcpy(src, &(zgfx->historyBuffer[zgfx->historyIndex]), src_offset, front);
tvb_memcpy(src, &(zgfx->historyBuffer), src_offset + count, count - front);
}
zgfx->historyIndex = (zgfx->historyIndex + count) % zgfx->historyBufferSize;
}
static void
zgfx_write_history_buffer(zgfx_context_t *zgfx, const guint8 *src, guint32 count)
{
guint32 front;
if (count > zgfx->historyBufferSize) {
const size_t residue = count - zgfx->historyBufferSize;
count = zgfx->historyBufferSize;
zgfx->historyIndex = (zgfx->historyIndex + residue) % zgfx->historyBufferSize;
}
if (zgfx->historyIndex + count <= zgfx->historyBufferSize)
{
memcpy(&(zgfx->historyBuffer[zgfx->historyIndex]), src, count);
}
else
{
front = zgfx->historyBufferSize - zgfx->historyIndex;
memcpy(&(zgfx->historyBuffer[zgfx->historyIndex]), src, front);
memcpy(&(zgfx->historyBuffer), src + front, count - front);
}
zgfx->historyIndex = (zgfx->historyIndex + count) % zgfx->historyBufferSize;
}
static gboolean
zgfx_write_litteral(zgfx_context_t *zgfx, guint8 c)
{
if (zgfx->outputCount == 65535)
return FALSE;
zgfx->outputSegment[zgfx->outputCount++] = c;
zgfx_write_history_litteral(zgfx, c);
return TRUE;
}
static gboolean
zgfx_write_raw(zgfx_context_t *zgfx, bitstream_t *b, guint32 count)
{
guint32 rest, tocopy;
/* first copy in the output buffer */
if (zgfx->outputCount > 65535 - count)
return FALSE;
if (!bitstream_copyraw(b, &(zgfx->outputSegment[zgfx->outputCount]), count))
return FALSE;
/* then update the history buffer */
rest = (zgfx->historyBufferSize - zgfx->historyIndex);
tocopy = count;
if (rest < count)
tocopy = rest;
if (!bitstream_copyraw_advance(b, &(zgfx->historyBuffer[zgfx->historyIndex]), tocopy))
return FALSE;
zgfx->historyIndex = (zgfx->historyIndex + tocopy) % zgfx->historyBufferSize;
count -= tocopy;
if (count) {
if (!bitstream_copyraw_advance(b, &(zgfx->historyBuffer[zgfx->historyIndex]), tocopy))
return FALSE;
zgfx->historyIndex = (zgfx->historyIndex + tocopy) % zgfx->historyBufferSize;
}
return TRUE;
}
static gboolean
zgfx_write_from_history(zgfx_context_t *zgfx, guint32 distance, guint32 count)
{
guint idx;
guint32 remainingCount, copyTemplateSize, toCopy;
guint8 *outputPtr;
if (zgfx->outputCount > 65535 - count)
return FALSE;
remainingCount = count;
idx = (zgfx->historyIndex + zgfx->historyBufferSize - distance) % zgfx->historyBufferSize;
copyTemplateSize = (distance > count) ? count : distance;
/* first do copy a single copy in output */
outputPtr = &(zgfx->outputSegment[zgfx->outputCount]);
toCopy = copyTemplateSize;
if (idx + toCopy < zgfx->historyBufferSize) {
memcpy(outputPtr, &(zgfx->historyBuffer[idx]), toCopy);
} else {
guint32 partial = zgfx->historyBufferSize - idx;
memcpy(outputPtr, &(zgfx->historyBuffer[idx]), partial);
memcpy(outputPtr + partial, zgfx->historyBuffer, toCopy - partial);
}
outputPtr += toCopy;
remainingCount -= toCopy;
/* then duplicate output as much as needed by count, at each loop turn we double
* the size of the template we can copy */
while (remainingCount) {
toCopy = (remainingCount < copyTemplateSize) ? remainingCount : copyTemplateSize;
memcpy(outputPtr, &(zgfx->outputSegment[zgfx->outputCount]), toCopy);
outputPtr += toCopy;
remainingCount -= toCopy;
copyTemplateSize *= 2;
}
/* let's update the history from output and update counters */
zgfx_write_history_buffer(zgfx, &(zgfx->outputSegment[zgfx->outputCount]), count);
zgfx->outputCount += count;
return TRUE;
}
gboolean
rdp8_decompress_segment(zgfx_context_t *zgfx, tvbuff_t *tvb)
{
bitstream_t bitstream;
gint offset = 0;
gint len = tvb_reported_length(tvb);
guint8 flags = tvb_get_guint8(tvb, offset);
guint8 v;
offset++;
len--;
if (!(flags & ZGX_PACKET_COMPRESSED)) {
tvbuff_t *raw = tvb_new_subset_remaining(tvb, 1);
zgfx_write_history_buffer_tvb(zgfx, raw, len);
return TRUE;
}
v = tvb_get_guint8(tvb, offset + len - 1);
if (v > 7)
return FALSE;
len--;
bitstream_init(&bitstream, tvb_new_subset_length(tvb, offset, len), (len * 8) - v);
while (bitstream.remainingBits) {
gboolean ok, ismatch, found;
guint32 bits_val = bitstream_getbits(&bitstream, 1, &ok);
guint32 inPrefix;
const zgfx_token_t *tokens;
gint ntokens, i;
guint32 prefixBits;
if (!ok)
return FALSE;
// 0 - litteral
if (bits_val == 0) {
bits_val = bitstream_getbits(&bitstream, 8, &ok);
if (!zgfx_write_litteral(zgfx, bits_val))
return FALSE;
continue;
}
// 1x - match or litteral branch
bits_val = bitstream_getbits(&bitstream, 1, &ok);
if (bits_val == 0) {
// 10 - match
ismatch = true;
tokens = ZGFX_MATCH_TABLE;
ntokens = sizeof(ZGFX_MATCH_TABLE) / sizeof(ZGFX_MATCH_TABLE[0]);
inPrefix = 2;
} else {
// 11 - litteral
ismatch = false;
tokens = ZGFX_LITTERAL_TABLE;
ntokens = sizeof(ZGFX_LITTERAL_TABLE) / sizeof(ZGFX_LITTERAL_TABLE[0]);
inPrefix = 3;
}
prefixBits = 2;
found = FALSE;
for (i = 0; i < ntokens; i++) {
if (prefixBits != tokens[i].prefixLength) {
guint32 missingBits = (tokens[i].prefixLength - prefixBits);
inPrefix <<= missingBits;
inPrefix |= bitstream_getbits(&bitstream, missingBits, &ok);
if (!ok)
return FALSE;
prefixBits = tokens[i].prefixLength;
}
if (inPrefix == tokens[i].prefixCode) {
found = TRUE;
break;
}
}
if (!found) // TODO: is it an error ?
continue;
if (ismatch) {
/* It's a match */
guint32 count, distance, extra = 0;
distance = tokens[i].valueBase + bitstream_getbits(&bitstream, tokens[i].valueBits, &ok);
if (!ok)
return FALSE;
if (distance != 0) {
bits_val = bitstream_getbits(&bitstream, 1, &ok);
if (!ok)
return FALSE;
if (bits_val == 0) {
count = 3;
} else {
count = 4;
extra = 2;
bits_val = bitstream_getbits(&bitstream, 1, &ok);
if (!ok)
return FALSE;
while (bits_val == 1) {
count *= 2;
extra ++;
bits_val = bitstream_getbits(&bitstream, 1, &ok);
if (!ok)
return FALSE;
}
count += bitstream_getbits(&bitstream, extra, &ok);
if (!ok)
return FALSE;
}
if (!zgfx_write_from_history(zgfx, distance, count))
return FALSE;
} else {
/* Unencoded */
count = bitstream_getbits(&bitstream, 15, &ok);
if (!ok)
return FALSE;
bitstream_realign(&bitstream);
if (!zgfx_write_raw(zgfx, &bitstream, count))
return FALSE;
}
} else {
/* Litteral */
bits_val = tokens[i].valueBase;
if (!zgfx_write_litteral(zgfx, bits_val))
return FALSE;
}
}
return TRUE;
}
tvbuff_t *
rdp8_decompress(zgfx_context_t *zgfx, wmem_allocator_t *allocator, tvbuff_t *tvb, guint offset)
{
void *output;
guint8 descriptor;
descriptor = tvb_get_guint8(tvb, offset);
offset++;
switch (descriptor) {
case ZGFX_SEGMENTED_SINGLE:
zgfx->outputCount = 0;
if (!rdp8_decompress_segment(zgfx, tvb_new_subset_remaining(tvb, offset)))
return NULL;
output = wmem_alloc(allocator, zgfx->outputCount);
memcpy(output, zgfx->outputSegment, zgfx->outputCount);
return tvb_new_real_data(output, zgfx->outputCount, zgfx->outputCount);
case ZGFX_SEGMENTED_MULTIPART: {
guint16 segment_count, i;
guint32 output_consumed, uncompressed_size;
guint8 *output_ptr;
segment_count = tvb_get_guint16(tvb, offset, ENC_LITTLE_ENDIAN);
offset += 2;
uncompressed_size = tvb_get_guint32(tvb, offset, ENC_LITTLE_ENDIAN);
offset += 4;
output = output_ptr = wmem_alloc(allocator, uncompressed_size);
output_consumed = 0;
for (i = 0; i < segment_count; i++) {
guint32 segment_size = tvb_get_guint32(tvb, offset, ENC_LITTLE_ENDIAN);
offset += 4;
zgfx->outputCount = 0;
if (!rdp8_decompress_segment(zgfx, tvb_new_subset_length(tvb, offset, segment_size)))
return NULL;
output_consumed += zgfx->outputCount;
if (output_consumed > uncompressed_size) {
// TODO: error message ?
return NULL;
}
memcpy(output_ptr, zgfx->outputSegment, zgfx->outputCount);
offset += segment_size;
output_ptr += zgfx->outputCount;
}
return tvb_new_real_data(output, uncompressed_size, uncompressed_size);
}
default:
return tvb;
}
}

27
epan/tvbuff_rdp.h Normal file
View File

@ -0,0 +1,27 @@
/* tvbuff_rdp.h
* Various decompression routines used by RDP
*
* Copyright (c) 2021 by David Fort <contact@hardening-consulting.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __TVBUFF_RDP_H__
#define __TVBUFF_RDP_H__
#include <glib.h>
#include <epan/wmem_scopes.h>
#include <epan/tvbuff.h>
typedef struct _zgfx_context_t zgfx_context_t;
zgfx_context_t *zgfx_context_new(wmem_allocator_t *allocator);
tvbuff_t *rdp8_decompress(zgfx_context_t *zgfx, wmem_allocator_t *allocator, tvbuff_t *tvb, guint offset);
#endif /* __TVBUFF_RDP_H__ */