From 09f762ba5e0a471090d4df7462e04871d6cea204 Mon Sep 17 00:00:00 2001 From: David Fort Date: Fri, 24 Sep 2021 11:13:54 +0200 Subject: [PATCH] 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. --- epan/CMakeLists.txt | 1 + epan/dissectors/CMakeLists.txt | 1 + epan/dissectors/packet-rdp_drdynvc.c | 85 ++++- epan/dissectors/packet-rdp_egfx.c | 552 +++++++++++++++++++++++++++ epan/tvbuff_rdp.c | 520 +++++++++++++++++++++++++ epan/tvbuff_rdp.h | 27 ++ 6 files changed, 1183 insertions(+), 3 deletions(-) create mode 100644 epan/dissectors/packet-rdp_egfx.c create mode 100644 epan/tvbuff_rdp.c create mode 100644 epan/tvbuff_rdp.h diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt index 2839e57f1f..f0c4283ffa 100644 --- a/epan/CMakeLists.txt +++ b/epan/CMakeLists.txt @@ -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 diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 37dd6865bb..a275431aae 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -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 diff --git a/epan/dissectors/packet-rdp_drdynvc.c b/epan/dissectors/packet-rdp_drdynvc.c index 86fcf61b58..3c1297412a 100644 --- a/epan/dissectors/packet-rdp_drdynvc.c +++ b/epan/dissectors/packet-rdp_drdynvc.c @@ -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"); } /* diff --git a/epan/dissectors/packet-rdp_egfx.c b/epan/dissectors/packet-rdp_egfx.c new file mode 100644 index 0000000000..fc26a9f3b6 --- /dev/null +++ b/epan/dissectors/packet-rdp_egfx.c @@ -0,0 +1,552 @@ +/* Packet-rdp_egfx.c + * Routines for the EGFX RDP channel + * Copyright 2021, David Fort + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * See: "[MS-RDPEGFX] " + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#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) { +} diff --git a/epan/tvbuff_rdp.c b/epan/tvbuff_rdp.c new file mode 100644 index 0000000000..2234cbfbb7 --- /dev/null +++ b/epan/tvbuff_rdp.c @@ -0,0 +1,520 @@ +/* tvbuff_rdp.c + * Decompression routines used in RDP + * Copyright 2021, David Fort + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include +#include + +#include +#include +#include + +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; + } +} diff --git a/epan/tvbuff_rdp.h b/epan/tvbuff_rdp.h new file mode 100644 index 0000000000..ea5db33284 --- /dev/null +++ b/epan/tvbuff_rdp.h @@ -0,0 +1,27 @@ +/* tvbuff_rdp.h + * Various decompression routines used by RDP + * + * Copyright (c) 2021 by David Fort + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __TVBUFF_RDP_H__ +#define __TVBUFF_RDP_H__ + +#include +#include +#include + +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__ */