wireshark/epan/tvbuff_rdp.c

527 lines
13 KiB
C

/* 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 "config.h"
#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;
b->remainingBits -= (nbytes * 8);
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];
};
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 guint32 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 guint32 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;
zgfx->outputCount += count;
/* 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;
}
static 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);
tvb_memcpy(tvb, zgfx->outputSegment, 1, len);
zgfx->outputCount += 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;
}
}