diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /tools | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-x | tools/build-clang.sh | 30 | ||||
-rw-r--r-- | tools/smartcard-interpreter.py | 572 | ||||
-rwxr-xr-x | tools/update-settings-tests | 349 | ||||
-rw-r--r-- | tools/wireshark/rdp-udp.lua | 709 |
4 files changed, 1660 insertions, 0 deletions
diff --git a/tools/build-clang.sh b/tools/build-clang.sh new file mode 100755 index 0000000..9f83cf5 --- /dev/null +++ b/tools/build-clang.sh @@ -0,0 +1,30 @@ +#!/bin/bash -xe +SCRIPT_PATH=$(dirname "${BASH_SOURCE[0]}") +SCRIPT_PATH=$(realpath "$SCRIPT_PATH") + +WARN_FLAGS="-Weverything -Wno-padded -Wno-assign-enum -Wno-switch-enum \ + -Wno-declaration-after-statement -Wno-c++98-compat -Wno-c++98-compat-pedantic \ + -Wno-cast-align -Wno-covered-switch-default -Wno-documentation-unknown-command \ + -Wno-documentation -Wno-documentation-html" + +cmake \ + -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$SCRIPT_PATH/../cmake/ClangToolchain.cmake" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_FIND_LIBRARY_SUFFIXES=".a;.so" \ + -DBUILD_SHARED_LIBS=OFF \ + -Bclang \ + -S"$SCRIPT_PATH/.." \ + -DCMAKE_INSTALL_PREFIX=/tmp/xxx \ + -DWITH_SERVER=ON \ + -DWITH_SAMPLE=ON \ + -DWITH_CAIRO=ON \ + -DWITH_FFMPEG=ON \ + -DWITH_DSP_FFMPEG=ON \ + -DWITH_PKCS11=ON \ + -DWITH_SOZR=ON \ + -DWITH_WAYLAND=ON \ + -DWITH_WEBVIEW=ON \ + -DWITH_SWSCALE=ON \ + -DCMAKE_C_FLAGS="$WARN_FLAGS" \ + -DCMAKE_CXX_FLAGS="$WARN_FLAGS" diff --git a/tools/smartcard-interpreter.py b/tools/smartcard-interpreter.py new file mode 100644 index 0000000..2fd1605 --- /dev/null +++ b/tools/smartcard-interpreter.py @@ -0,0 +1,572 @@ +#!/usr/bin/env python3 +# +# Copyright 2022 David Fort <contact@hardening-consulting.com> +# +# This script is meant to parse some FreeRDP logs in DEBUG mode (WLOG_LEVEL=DEBUG) and interpret the +# smartcard traffic, dissecting the PIV or GIDS commands +# +# usage: +# * live: WLOG_LEVEL=DEBUG xfreerdp <args with smartcard> | python3 smartcard-interpreter.py +# * on an existing log file: python3 smartcard-interpreter.py <log file> +# +import sys +import codecs + + +CMD_NAMES = { + 0x04: "DESACTIVATE FILE", + 0x0C: "ERASE RECORD", + 0x0E: "ERASE BINARY", + 0x0F: "ERASE BINARY", + 0x20: "VERIFY", + 0x21: "VERIFY", + 0x22: "MSE", + 0x24: "CHANGE REFERENCE DATA", + 0x25: "MSE", + 0x26: "DISABLE VERIFICATION REQUIREMENT", + 0x28: "ENABLE VERIFICATION REQUIREMENT", + 0x2A: "PSO", + 0x2C: "RESET RETRY COUNTER", + 0x2D: "RESET RETRY COUNTER", + 0x44: "ACTIVATE FILE", + 0x46: "GENERATE ASYMMETRIC KEY PAIR", + 0x47: "GENERATE ASYMMETRIC KEY PAIR", + 0x84: "GET CHALLENGE", + 0x86: "GENERAL AUTHENTICATE", + 0x87: "GENERAL AUTHENTICATE", + 0x88: "INTERNAL AUTHENTICATE", + 0xA0: "SEARCH BINARY", + 0xA1: "SEARCH BINARY", + 0xA2: "SEARCH RECORD", + 0xA4: "SELECT", + 0xB0: "READ BINARY", + 0xB1: "READ BINARY", + 0xB2: "READ RECORD", + 0xB3: "READ RECORD", + 0xC0: "GET RESPONSE", + 0xC2: "ENVELOPPE", + 0xC3: "ENVELOPPE", + 0xCA: "GET DATA", + 0xCB: "GET DATA", + 0xD0: "WRITE BINARY", + 0xD1: "WRITE BINARY", + 0xD2: "WRITE RECORD", + 0xD6: "UPDATE BINARY", + 0xD7: "UPDATE BINARY", + 0xDA: "PUT DATA", + 0xDB: "PUT DATA", + 0xDC: "UPDATE RECORD", + 0xDD: "UPDATE RECORD", + 0xE0: "CREATE FILE", + 0xE2: "APPEND RECORD", + 0xE4: "DELETE FILE", + 0xE6: "TERMINATE DF", + 0xE8: "TERMINATE EF", + 0xFE: "TERMINATE CARD USAGE", +} + +AIDs = { + "a00000039742544659": "MsGidsAID", + "a000000308": "PIV", + "a0000003974349445f0100": "SC PNP", +} + +FIDs = { + 0x0000: "Current EF", + 0x2F00: "EF.DIR", + 0x2F01: "EF.ATR", + 0x3FFF: "Current application(ADF)", +} + +DOs = { + "df1f": "DO_FILESYSTEMTABLE", + "df20": "DO_CARDID", + "df21": "DO_CARDAPPS", + "df22": "DO_CARDCF", + "df23": "DO_CMAPFILE", + "df24": "DO_KXC00", +} + +ERROR_CODES = { + 0x9000: "success", + 0x6282: "end of file or record", + 0x63C0: "warning counter 0", + 0x63C1: "warning counter 1", + 0x63C2: "warning counter 2", + 0x63C3: "warning counter 3", + 0x63C4: "warning counter 4", + 0x63C5: "warning counter 5", + 0x63C6: "warning counter 6", + 0x63C7: "warning counter 7", + 0x63C8: "warning counter 8", + 0x63C9: "warning counter 9", + 0x6982: "security status not satisfied", + 0x6985: "condition of use not satisfied", + 0x6A80: "incorrect parameter cmd data field", + 0x6A81: "function not suppported", + 0x6A82: "file or application not found", + 0x6A83: "record not found", + 0x6A88: "REFERENCE DATA NOT FOUND", + 0x6D00: "unsupported", +} + +PIV_OIDs = { + "5fc101": "X.509 Certificate for Card Authentication", + "5fc102": "Card Holder Unique Identifier", + "5fc103": "Cardholder Fingerprints", + "5fc105": "X.509 Certificate for PIV Authentication", + "5fc106": "Security Object", + "5fc107": "Card Capability Container", + "5fc108": "Cardholder Facial Image", + "5fc10a": "X.509 Certificate for Digital Signature", + "5fc10b": "X.509 Certificate for Key Management", + "5fc10d": "Retired X.509 Certificate for Key Management 1", + "5fc10e": "Retired X.509 Certificate for Key Management 2", + "5fc10f": "Retired X.509 Certificate for Key Management 3", +} + +class ApplicationDummy(object): + def __init__(self, aid): + self.aid = aid + + def getAID(self): + return self.aid + + def selectResult(self, fci, status, body): + return 'selectResult(%s, %s, %s)\n' %(fci, status, body.hex()) + + def getData(self, fileId, bytes): + return 'getData(0x%x, %s)\n' %(fileId, bytes.hex()) + + def getDataResult(self, status, body): + return 'getDataResult(0x%x, %s)\n' %(status, body.hex()) + + def mse(self, body): + return body.hex() + + def mseResult(self, status, body): + return body.hex() + + def pso(self, body): + return body.hex() + + def psoResult(self, status, body): + return body.hex() + + +class ApplicationPIV(object): + def __init__(self, aid): + self.lastGet = None + self.aid = aid + + def getAID(self): + return self.aid + + def selectResult(self, selectT, status, body): + ret = '' + appTag = body[0] + appLen = body[1] + + body = body[2:2+appLen] + while len(body) > 2: + tag = body[0] + tagLen = body[1] + if selectT == "FCI": + if tag == 0x4f: + ret += "\tpiv version: %s\n" % body[2:2 + tagLen].hex() + elif tag == 0x79: + subBody = body[2:2 + tagLen] + + subTag = subBody[0] + subLen = subBody[1] + + content = subBody.hex() + if subTag == 0x4f: + v = content[4:] + if v.startswith('a000000308'): + content = 'NIST RID' + ret += '\tCoexistent tag allocation authority: %s\n' % content + + elif tag == 0x50: + ret += '\tapplication label\n' + elif tag == 0xac: + ret += '\tCryptographic algorithms supported\n' + else: + rety += '\tunknown tag 0x%x\n' % tag + + else: + ret += "\tTODO: selectType %s\n" % selectT + + body = body[2+tagLen:] + + return ret + + def getData(self, fileId, bytes): + ret = "\tfileId=%s\n" % FIDs.get(fileId, "%0.4x" % fileId) + + lc = bytes[4] + tag = bytes[5] + tagLen = bytes[6] + + if lc == 4: + ret += "\tdoId=%0.4x\n"% (bytes[7] * 256 + bytes[8]) + + elif lc == 0xa: + keyStr = '' + # TLV + i = 7 + tag = bytes[i] + tagLen = bytes[i+1] + keyRef = bytes[i+3] + keyStr = "key(tag=0x%x len=%d ref=0x%x)=" % (tag, tagLen, keyRef) + i = i + 2 + tagLen + + tag = bytes[i] + tagLen = bytes[i+1] + keyStr += "value(tag=0x%x len=%d)" + elif lc == 5: + if tag == 0x5C: + tagStr = bytes[7:].hex() + ret += '\ttag: %s(%s)\n' % (tagStr, PIV_OIDs.get(tagStr, '<unknown>')) + self.lastGet = tagStr + else: + ret += "\tunknown key access\n" + + return ret + + def getDataResult(self, status, body): + ret = '' + if not len(body): + return '' + appTag = body[0] + appLen = body[1] + + body = body[2:2+appLen] + while len(body) > 2: + tag = body[0] + tagLen = body[1] + tagBody = body[2:2+tagLen] + + if self.lastGet in ('5fc102',): + # Card holder Unique Identifier + if tag == 0x30: + ret += '\tFASC-N: %s\n' % tagBody.hex() + elif tag == 0x34: + ret += '\tGUID: %s\n' % tagBody.hex() + elif tag == 0x35: + ret += '\texpirationDate: %s\n' % tagBody.decode('utf8') + elif tag == 0x3e: + ret += '\tIssuer Asymmetric Signature: %s\n' % tagBody.hex() + else: + ret += "\tunknown tag=0x%x len=%d content=%s\n" % (tag, tagLen, tagBody.hex()) + else: + ret += "\t%s: unknown tag=0x%x len=%d content=%s\n" % (self.lastGet, tag, tagLen, tagBody.hex()) + + body = body[2+tagLen:] + + return ret + + def mse(self, body): + return body.hex() + + def mseResult(self, status, body): + return body.hex() + + def pso(self, body): + return body.hex() + + def psoResult(self, status, body): + return body.hex() + + + +class ApplicationGids(object): + def __init__(self, aid): + self.aid = aid + self.lastDo = None + + def getAID(self): + return self.aid + + def parseFcp(self, bytes): + ret = '' + tag = bytes[0] + tagLen = bytes[1] + + body = bytes[2:2+tagLen] + + if tag == 0x62: + ret += '\tFCP\n' + + while len(body) > 2: + tag2 = body[0] + tag2Len = body[1] + tag2Body = body[2:2+tag2Len] + + if tag2 == 0x82: + ret += '\t\tFileDescriptor: %s\n' % tag2Body.hex() + elif tag2 == 0x8a: + ret += '\t\tLifeCycleByte: %s\n' % tag2Body.hex() + elif tag2 == 0x84: + ret += '\t\tDF name: %s\n' % tag2Body.encode('utf8') + elif tag2 == 0x8C: + ret += '\t\tSecurityAttributes: %s\n' % tag2Body.hex() + else: + ret += '\t\tunhandled tag=0x%x body=%s\n' % (tag2, tag2Body.hex()) + + body = body[2+tag2Len:] + + return ret + + def parseFci(self, bytes): + ret = '' + tag = bytes[0] + tagLen = bytes[1] + + body = bytes[2:2+tagLen] + + if tag == 0x61: + ret += '\tFCI\n' + + while len(body) > 2: + tag2 = body[0] + tag2Len = body[1] + tag2Body = body[2:2+tag2Len] + + if tag2 == 0x4F: + ret += '\t\tApplication AID: %s\n' % tag2Body.hex() + + elif tag2 == 0x50: + ret += '\t\tApplication label: %s\n' % tag2Body.encode('utf8') + + elif tag2 == 0x73: + body2 = tag2Body + tokens = [] + while len(body2) > 2: + tag3 = body2[0] + tag3Len = body2[1] + + if tag3 == 0x40: + v = body2[2] + if v & 0x80: + tokens.append('mutualAuthSymAlgo') + if v & 0x40: + tokens.append('extAuthSymAlgo') + if v & 0x20: + tokens.append('keyEstabIntAuthECC') + + + body2 = body2[2+tag3Len:] + + ret += '\t\tDiscretionary data objects: %s\n' % ",".join(tokens) + else: + ret += '\t\tunhandled tag=0x%x body=%s\n' % (tag2, tag2Body.hex()) + + body = body[2+tag2Len:] + + return ret + + + def selectResult(self, selectT, status, body): + if not len(body): + return '' + + if selectT == 'FCP': + return self.parseFcp(body) + elif selectT == 'FCI': + return self.parseFci(body) + else: + return '\tselectResult(%s, %s, %s)\n' % (selectT, status, body.hex()) + + def getData(self, fileId, bytes): + lc = bytes[4] + tag = bytes[5] + tagLen = bytes[6] + + if tag == 0x5c: + doStr = bytes[7:7+tagLen].hex() + ret = '\tDO=%s\n' % DOs.get(doStr, "<%s>" % doStr) + self.lastDo = doStr + else: + ret = '\tunknown tag=0%x len=%d v=%s' % (tag, tagLen, bytes[7:7+tagLen].hex()) + + return ret + + def getDataResult(self, status, body): + ret = '' + ''' + while len(body) > 2: + tag = body[0] + tagLen = body[1] + + ret += '\ttag=0x%x len=%d content=%s\n' % (tag, tagLen, body[2:2+tagLen].hex()) + + body = body[2+tagLen:] + ''' + return ret + + def mse(self, body): + return body.hex() + + def mseResult(self, status, body): + return body.hex() + + def pso(self, body): + return body.hex() + + def psoResult(self, status, body): + return body.hex() + + + +def createAppByAid(aid): + if aid == "a000000308": + return ApplicationPIV(aid) + + elif aid in ('a00000039742544659',): + return ApplicationGids(aid) + + return ApplicationDummy(aid) + + +if __name__ == '__main__': + if len(sys.argv) > 1: + fin = open(sys.argv[1], "r") + else: + fin = sys.stdin + + lineno = 0 + lastCmd = 0 + lastSelect = None + lastSelectFCI = False + lastGetItem = None + currentApp = None + + for l in fin.readlines(): + lineno += 1 + + if not len(l): + continue + + # smartcard loggers have changed + #if l.find("[DEBUG][com.freerdp.channels.smartcard.client]") == -1: + # continue + + body = '' + recvKey = 'pbRecvBuffer: { ' + + pos = l.find(recvKey) + if pos != -1: + toCard = False + + pos += len(recvKey) + pos2 = l.find(' }', pos) + if pos2 == -1: + print("line %d: invalid recvBuffer") + continue + + else: + toCard = True + sendKey = 'pbSendBuffer: { ' + pos = l.find(sendKey) + if pos == -1: + continue + pos += len(sendKey) + + pos2 = l.find(' }', pos) + if pos2 == -1: + print("line %d: invalid sendBuffer") + continue + + body = l[pos:pos2] + + print(l[0:-1]) + bytes = codecs.decode(body, 'hex') + if toCard: + (cla, ins, p1, p2) = bytes[0:4] + cmdName = CMD_NAMES.get(ins, "<COMMAND 0x%x>" % ins) + print(cmdName + ":") + + if cmdName == "SELECT": + lc = bytes[4] + i = 5 + + if p1 == 0x00: + print("\tselectByFID: %0.2x%0.2x" % (bytes[i], bytes[i+1])) + i = i + lc + + elif p1 == 0x4: + aid = bytes[i:i+lc].hex() + lastSelect = AIDs.get(aid, '') + print("\tselectByAID: %s(%s)" % (aid, lastSelect)) + + if p2 == 0x00: + lastSelectT = "FCI" + print('\tFCI') + elif p2 == 0x04: + print('\tFCP') + lastSelectT = "FCP" + elif p2 == 0x08: + print('\tFMD') + lastSelectT = "FMD" + + if not currentApp or currentApp.getAID() != aid: + currentApp = createAppByAid(aid) + + + elif cmdName == "VERIFY": + lc = bytes[4] + P2_DATA_QUALIFIER = { + 0x00: "Card global password", + 0x01: "RFU", + 0x80: "Application password", + 0x81: "Application resetting password", + 0x82: "Application security status resetting code", + } + + pin='' + if lc: + pin = ", pin='" + bytes[5:5+lc-2].decode('utf8)') + "'" + + print("\t%s%s" % (P2_DATA_QUALIFIER.get(p2, "<unknown>"), pin)) + + elif cmdName == "GET DATA": + lc = bytes[4] + fileId = p1 * 256 + p2 + + ret = currentApp.getData(fileId, bytes) + print("%s" % ret) + + elif cmdName == "MSE": + ret = currentApp.mse(bytes[5:5+lc]) + print("%s" % ret) + + elif cmdName == "PSO": + ret = currentApp.pso(bytes[5:5+lc]) + print("%s" % ret) + else: + print('handle %s' % cmdName) + + lastCmd = cmdName + + else: + # Responses + status = bytes[-1] + bytes[-2] * 256 + body = bytes[0:-2] + print("status=0x%0.4x(%s)" % (status, ERROR_CODES.get(status, "<unknown>"))) + + if not len(body): + continue + + ret = '' + if lastCmd == "SELECT": + ret = currentApp.selectResult(lastSelectT, status, body) + elif lastCmd == "GET DATA": + ret = currentApp.getDataResult(status, body) + elif lastCmd == "MSE": + ret = currentApp.mseResult(status, body) + elif lastCmd == "PSO": + ret = currentApp.psoResult(status, body) + + if ret: + print("%s" % ret)
\ No newline at end of file diff --git a/tools/update-settings-tests b/tools/update-settings-tests new file mode 100755 index 0000000..bd8e439 --- /dev/null +++ b/tools/update-settings-tests @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +import os +import sys + +def get_values(entry_dict, entry_type): + values = [] + if '*' == entry_type: + for key in list(entry_dict.keys()): + if entry_type in key: + values += entry_dict[key] + entry_dict.pop(key, None) + elif entry_type in dict(entry_dict): + values = entry_dict[entry_type] + entry_dict.pop(entry_type, None) + if values: + return sorted(values) + return values + +def write_entry(f, entry_dict, entry_type, entry_name): + values = get_values(entry_dict, entry_type) + if not values: + return + + f.write('#define have_' + entry_name.lower() + '_list_indices\n') + f.write('static const size_t ' + entry_name.lower() + '_list_indices[] =\n') + f.write('{\n') + + for val in values: + f.write('\tFreeRDP_' + val + ',\n') + + f.write('};\n\n') + +def write_str_case(f, entry_idx, val): + entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'STRING', 'POINTER'] + f.write('\t\t{FreeRDP_' + val + ', FREERDP_SETTINGS_TYPE_' + str(entry_types[entry_idx]) + ', "FreeRDP_' + val + '"},\n') + +def write_str(f, entry_dict): + f.write('typedef enum {\n') + f.write('\tFREERDP_SETTINGS_TYPE_BOOL,\n') + f.write('\tFREERDP_SETTINGS_TYPE_UINT16,\n') + f.write('\tFREERDP_SETTINGS_TYPE_INT16,\n') + f.write('\tFREERDP_SETTINGS_TYPE_UINT32,\n') + f.write('\tFREERDP_SETTINGS_TYPE_INT32,\n') + f.write('\tFREERDP_SETTINGS_TYPE_UINT64,\n') + f.write('\tFREERDP_SETTINGS_TYPE_INT64,\n') + f.write('\tFREERDP_SETTINGS_TYPE_STRING,\n') + f.write('\tFREERDP_SETTINGS_TYPE_POINTER\n') + f.write('} FREERDP_SETTINGS_TYPE;\n') + f.write('\n') + f.write('struct settings_str_entry {\n') + f.write('\tSSIZE_T id;\n') + f.write('\tSSIZE_T type;\n') + f.write('\tconst char* str;\n') + f.write('};\n') + f.write('static const struct settings_str_entry settings_map[] =\n') + f.write('{\n') + + entry_types = ['BOOL', 'UINT16', 'INT16', 'UINT32', 'INT32', 'UINT64', 'INT64', 'char*', '*'] + for entry_type in entry_types: + values = get_values(entry_dict, entry_type) + if values: + for val in values: + write_str_case(f, entry_types.index(entry_type), val) + f.write('};\n\n') + f.write('\n') + +def write_getter_case(f, val): + f.write('\t\tcase FreeRDP_' + val + ':\n') + f.write('\t\t\treturn settings->' + val + ';\n\n') + +def write_getter_body(f, values, ret): + f.write('{\n') + f.write('\tWINPR_ASSERT(settings);\n\n') + f.write('\tswitch (id)\n') + f.write('\t{\n') + if values: + for val in values: + write_getter_case(f, val) + f.write('\t\tdefault:\n') + f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id, freerdp_settings_get_name_for_key(id), freerdp_settings_get_type_name_for_key(id));\n') + f.write('\t\t\tWINPR_ASSERT(FALSE);\n') + f.write('\t\t\treturn ' + ret + ';\n') + f.write('\t}\n') + f.write('}\n\n') + +def write_getter(f, entry_dict, entry_type, entry_name, postfix): + isString = 'string' in entry_name + isPointer = 'pointer' in entry_name + values = get_values(entry_dict, entry_type) + + typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize() + typestr = typestr.replace('_Uint', '_UInt') + + if isPointer: + f.write('void*') + elif isString: + f.write('const ' + entry_type) + else: + f.write(entry_type) + + if isPointer: + f.write(' freerdp_settings_get_pointer_writable(rdpSettings* settings, ' + typestr + ' id)\n') + else: + f.write(' freerdp_settings_get_' + entry_name.lower() + '(const rdpSettings* settings, ' + typestr + ' id)\n') + if isString or isPointer: + ret = 'NULL'; + elif 'bool' in entry_name: + ret = 'FALSE'; + else: + ret = '0'; + + write_getter_body(f, values, ret) + + if isString: + f.write('char* freerdp_settings_get_' + entry_name.lower() + '_writable(rdpSettings* settings, ' + typestr + ' id)\n') + write_getter_body(f, values, ret) + +def write_setter_case(f, val, postfix, isPointer): + f.write('\t\tcase FreeRDP_' + val + ':\n') + if isPointer: + f.write('\t\t\tsettings->' + val + ' = cnv.v;\n') + f.write('\t\t\tbreak;\n\n') + elif not postfix: + f.write('\t\t\tsettings->' + val + ' = cnv.c;\n') + f.write('\t\t\tbreak;\n\n') + elif len(postfix) <= 1: + f.write('\t\t\treturn update_string' + postfix + '(&settings->' + val + ', cnv.c, len);\n\n') + else: + f.write('\t\t\treturn update_string' + postfix + '(&settings->' + val + ', cnv.cc, len, cleanup);\n\n') + +def write_setter(f, entry_dict, entry_type, entry_name, postfix): + isString = 'string' in entry_name + isPointer = 'pointer' in entry_name + values = get_values(entry_dict, entry_type) + + typestr = 'FreeRDP_Settings_Keys_' + entry_name.capitalize() + typestr = typestr.replace('_Uint', '_UInt') + f.write('BOOL freerdp_settings_set_' + entry_name.lower()) + f.write(postfix) + f.write('(rdpSettings* settings, ' + typestr + ' id, ') + if isString and len(postfix) > 1 or isPointer: + f.write('const ') + if not isPointer: + f.write(entry_type + ' val') + else: + f.write('void* val') + if isString and len(postfix) <= 1: + f.write(', size_t len)\n') + elif isString: + f.write(', size_t len, BOOL cleanup)\n') + else: + f.write(')\n') + f.write('{\n') + f.write('\tunion\n') + f.write('\t{\n') + f.write('\t\tvoid* v;\n') + f.write('\t\tconst void* cv;\n') + if not isPointer: + f.write(' ' + entry_type + ' c;\n') + f.write(' const ' + entry_type + ' cc;\n') + f.write('} cnv;\n') + + f.write('\tWINPR_ASSERT(settings);\n\n') + if isPointer: + f.write('\tcnv.cv = val;\n\n') + elif isString: + f.write('\tcnv.cc = val;\n\n') + else: + f.write('\tcnv.c = val;\n\n') + f.write('\tswitch (id)\n') + f.write('\t{\n') + if values: + for val in values: + write_setter_case(f, val, postfix, isPointer) + f.write('\t\tdefault:\n') + f.write('\t\t\tWLog_ERR(TAG, "Invalid key index %" PRIuz " [%s|%s]", id, freerdp_settings_get_name_for_key(id), freerdp_settings_get_type_name_for_key(id));\n') + f.write('\t\t\treturn FALSE;\n') + f.write('\t}\n') + f.write('\treturn TRUE;\n') + f.write('}\n\n') + f.write('\n') + if isString and len(postfix) <= 1: + f.write('BOOL freerdp_settings_set_string_len(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* val, size_t len)\n') + f.write('{\n') + f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n') + f.write('}\n') + f.write('\n') + + f.write('BOOL freerdp_settings_set_string(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* val)\n') + f.write('{\n') + f.write('\tsize_t len = 0;\n') + f.write('\tif (val) len = strlen(val);\n') + f.write('\treturn freerdp_settings_set_string_copy_(settings, id, val, len, TRUE);\n') + f.write('}\n') + f.write('\n') + +name = os.path.dirname(os.path.realpath(__file__)) +begin = "WARNING: this data structure is carefully padded for ABI stability!" +end = "WARNING: End of ABI stable zone!" + +print('begin parsing settings header') +try: + type_list = dict() + + with open(name + "/../include/freerdp/settings_types_private.h", "r") as f: + lines = f.readlines() + started = False + for line in lines: + if not started: + if begin in line: + started = True + continue + + if end in line: + break + + sline = line.strip() + if not sline: + continue + if sline.startswith('/'): + continue + if sline.startswith('*'): + continue + if 'padding' in sline: + continue + + if sline.startswith('SETTINGS_DEPRECATED(ALIGN64'): + sline = sline[27:].strip() + + sline = sline[:sline.find(');')] + pair = sline.split() + if pair[0] in type_list: + type_list[pair[0]].append(pair[1]) + else: + type_list[pair[0]] = [pair[1]] + + with open(name + '/../libfreerdp/common/settings_getters.c', 'w+') as f: + f.write('/* Generated by ' + '' + ' */\n\n') + f.write('#include "../core/settings.h"\n\n') + f.write('#include <winpr/assert.h>\n') + f.write('#include <freerdp/settings.h>\n') + f.write('#include <freerdp/log.h>\n\n') + f.write('#define TAG FREERDP_TAG("common.settings")\n\n') + + f.write('static void free_string(char** current, BOOL cleanup)\n') + f.write('{\n') + f.write('\tif (cleanup)\n') + f.write('\t{\n') + f.write('\t\tif (*current)\n') + f.write('\t\t\tmemset(*current, 0, strlen(*current));\n') + f.write('\t\tfree(*current);\n') + f.write('\t\t(*current) = NULL;\n') + f.write('\t}\n') + f.write('}\n\n') + + f.write('static BOOL alloc_empty_string(char** current, const char* next, size_t next_len)\n') + f.write('{\n') + f.write('\tif (!next && (next_len > 0))\n') + f.write('\t{\n') + f.write('\t\t*current = calloc(next_len, 1);\n') + f.write('\t\treturn (*current != NULL);\n') + f.write('\t}\n') + f.write('\treturn FALSE;\n') + f.write('}\n\n') + + + f.write('static BOOL update_string_copy_(char** current, const char* next, size_t next_len, BOOL cleanup)\n') + f.write('{\n') + f.write('\tfree_string(current, cleanup);\n') + f.write('\n') + f.write('\tif (alloc_empty_string(current, next, next_len))\n') + f.write('\t\treturn TRUE;\n') + f.write('\n') + f.write('\t*current = (next ? strndup(next, next_len) : NULL);\n') + f.write('\treturn !next || (*current != NULL);\n') + f.write('}\n\n') + + f.write('static BOOL update_string_(char** current, char* next, size_t next_len)\n') + f.write('{\n') + f.write('\tfree_string(current, TRUE);\n') + f.write('\n') + f.write('\tif (alloc_empty_string(current, next, next_len))\n') + f.write('\t\treturn TRUE;\n') + f.write('\n') + f.write('\t*current = next;\n') + f.write('\treturn !next || (*current != NULL);\n') + f.write('}\n\n') + + getter_list = dict(type_list) + setter_list = dict(type_list) + setter_list2 = dict(type_list) + write_getter(f, getter_list, 'BOOL', 'bool', '') + write_setter(f, setter_list, 'BOOL', 'bool', '') + write_getter(f, getter_list, 'UINT16', 'uint16', '') + write_setter(f, setter_list, 'UINT16', 'uint16', '') + write_getter(f, getter_list, 'INT16', 'int16', '') + write_setter(f, setter_list, 'INT16', 'int16', '') + write_getter(f, getter_list, 'UINT32', 'uint32', '') + write_setter(f, setter_list, 'UINT32', 'uint32', '') + write_getter(f, getter_list, 'INT32', 'int32', '') + write_setter(f, setter_list, 'INT32', 'int32', '') + write_getter(f, getter_list, 'UINT64', 'uint64', '') + write_setter(f, setter_list, 'UINT64', 'uint64', '') + write_getter(f, getter_list, 'INT64', 'int64', '') + write_setter(f, setter_list, 'INT64', 'int64', '') + write_getter(f, getter_list, 'char*', 'string', '_') + write_setter(f, setter_list, 'char*', 'string', '_') + write_setter(f, setter_list2, 'char*', 'string', '_copy_') + write_getter(f, getter_list, '*', 'pointer', '') + write_setter(f, setter_list, '*', 'pointer', '') + + f.write('\n') + + with open(name + '/../libfreerdp/common/settings_str.h', 'w+') as f: + f.write('/* Generated by ' + '' + ' */\n\n') + f.write('#ifndef FREERDP_CORE_SETTINGS_STR_H\n') + f.write('#define FREERDP_CORE_SETTINGS_STR_H\n\n') + f.write('#include "../core/settings.h"\n\n') + f.write('#include <freerdp/settings.h>\n') + f.write('#include <freerdp/log.h>\n\n') + f.write('#define TAG FREERDP_TAG("common.settings")\n\n') + + getter_list = dict(type_list) + write_str(f, getter_list) + f.write('#endif\n') + f.write('\n') + + + with open(name + '/../libfreerdp/core/test/settings_property_lists.h', 'w+') as f: + f.write('#ifndef TEST_SETTINGS_PROPERTY_LISTS\n') + f.write('#define TEST_SETTINGS_PROPERTY_LISTS\n\n') + + write_entry(f, type_list, 'BOOL', 'bool') + write_entry(f, type_list, 'UINT16', 'uint16') + write_entry(f, type_list, 'INT16', 'int16') + write_entry(f, type_list, 'UINT32', 'uint32') + write_entry(f, type_list, 'INT32', 'int32') + write_entry(f, type_list, 'UINT64', 'uint64') + write_entry(f, type_list, 'INT64', 'int64') + write_entry(f, type_list, 'char*', 'string') + write_entry(f, type_list, '*', 'pointer') + + f.write('#endif /* TEST_SETTINGS_PROPERTY_LISTS */\n\n') + + print('remaining:\n' + str(type_list)) +except IOError as e: + print('failed to parse settings header ' + str(e)) + sys.exit(-1) +print('ended parsing settings header') diff --git a/tools/wireshark/rdp-udp.lua b/tools/wireshark/rdp-udp.lua new file mode 100644 index 0000000..ce80ece --- /dev/null +++ b/tools/wireshark/rdp-udp.lua @@ -0,0 +1,709 @@ +--[[ + RDP UDP transport dissector for wireshark + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Copyright 2021 David Fort <contact@hardening-consulting.com> +--]] + +local sslDissector = Dissector.get ("tls") +local dtlsDissector = Dissector.get ("dtls") + +local dprint = function(...) + print(table.concat({"Lua:", ...}," ")) +end + +local dprint2 = dprint + +dprint2("loading RDP-UDP with wireshark=", get_version()) + +local rdpudp = Proto("rdpudp", "UDP transport for RDP") + +-- UDP1 fields +local pf_udp_snSourceAck = ProtoField.uint32("rdpudp.snsourceack", "snSourceAck", base.HEX) +local pf_udp_ReceiveWindowSize = ProtoField.uint16("rdpudp.receivewindowsize", "ReceiveWindowSize", base.DEC) +local pf_udp_flags = ProtoField.uint16("rdpudp.flags", "Flags", base.HEX) + +RDPUDP_SYN = 0x0001 +RDPUDP_FIN = 0x0002 +RDPUDP_ACK = 0x0004 +RDPUDP_DATA = 0x0008 +RDPUDP_FEC = 0x0010 +RDPUDP_CN = 0x0020 +RDPUDP_CWR = 0x0040 +RDPUDP_AOA = 0x0100 +RDPUDP_SYNLOSSY = 0x0200 +RDPUDP_ACKDELAYED = 0x0400 +RDPUDP_CORRELATIONID = 0x0800 +RDPUDP_SYNEX = 0x1000 + +local pf_udp_flag_syn = ProtoField.bool("rdpudp.flags.syn", "Syn", base.HEX, nil, RDPUDP_SYN) +local pf_udp_flag_fin = ProtoField.bool("rdpudp.flags.fin", "Fin", base.HEX, nil, RDPUDP_FIN) +local pf_udp_flag_ack = ProtoField.bool("rdpudp.flags.ack", "Ack", base.HEX, nil, RDPUDP_ACK) +local pf_udp_flag_data = ProtoField.bool("rdpudp.flags.data", "Data", base.HEX, nil, RDPUDP_DATA) +local pf_udp_flag_fec = ProtoField.bool("rdpudp.flags.fec", "FECData", base.HEX, nil, RDPUDP_FEC) +local pf_udp_flag_cn = ProtoField.bool("rdpudp.flags.cn", "CN", base.HEX, nil, RDPUDP_CN) +local pf_udp_flag_cwr = ProtoField.bool("rdpudp.flags.cwr", "CWR", base.HEX, nil, RDPUDP_CWR) +local pf_udp_flag_aoa = ProtoField.bool("rdpudp.flags.aoa", "Ack of Acks", base.HEX, nil, RDPUDP_AOA) +local pf_udp_flag_synlossy = ProtoField.bool("rdpudp.flags.synlossy", "Syn lossy", base.HEX, nil, RDPUDP_SYNLOSSY) +local pf_udp_flag_ackdelayed = ProtoField.bool("rdpudp.flags.ackdelayed", "Ack delayed", base.HEX, nil, RDPUDP_ACKDELAYED) +local pf_udp_flag_correlationId = ProtoField.bool("rdpudp.flags.correlationid", "Correlation id", base.HEX, nil, RDPUDP_CORRELATIONID) +local pf_udp_flag_synex = ProtoField.bool("rdpudp.flags.synex", "SynEx", base.HEX, nil, RDPUDP_SYNEX) + +local pf_udp_snInitialSequenceNumber = ProtoField.uint32("rdpudp.initialsequencenumber", "Initial SequenceNumber", base.HEX) +local pf_udp_upstreamMtu = ProtoField.uint16("rdpudp.upstreammtu", "Upstream MTU", base.DEC) +local pf_udp_downstreamMtu = ProtoField.uint16("rdpudp.downstreammtu", "DownStream MTU", base.DEC) + +local pf_udp_correlationId = ProtoField.new("Correlation Id", "rdpudp.correlationid", ftypes.BYTES) + +local pf_udp_synex_flags = ProtoField.uint16("rdpudp.synex.flags", "Flags", base.HEX) +local pf_udp_synex_flag_version = ProtoField.bool("rdpudp.synex.flags.versioninfo", "Version info", base.HEX, nil, 0x0001) +local pf_udp_synex_version = ProtoField.uint16("rdpudp.synex.version", "Version", base.HEX, {[1]="Version 1", [2]="Version 2", [0x101]="Version 3"}) +local pf_udp_synex_cookiehash = ProtoField.new("Cookie Hash", "rdpudp.synex.cookiehash", ftypes.BYTES) + +local pf_udp_ack_vectorsize = ProtoField.uint16("rdpudp.ack.vectorsize", "uAckVectorSize", base.DEC) +local pf_udp_ack_item = ProtoField.uint8("rdpudp.ack.item", "Ack item", base.HEX) +local pf_udp_ack_item_state = ProtoField.uint8("rdpudp.ack.item.state", "VECTOR_ELEMENT_STATE", base.HEX, {[0]="Received", [1]="Reserved 1", [2]="Reserved 2", [3]="Pending"}, 0xc0) +local pf_udp_ack_item_rle = ProtoField.uint8("rdpudp.ack.item.rle", "Run length", base.DEC, nil, 0x3f) + +local pf_udp_fec_coded = ProtoField.uint32("rdpudp.fec.coded", "snCoded", base.HEX) +local pf_udp_fec_sourcestart = ProtoField.uint32("rdpudp.fec.sourcestart", "snSourceStart", base.HEX) +local pf_udp_fec_range = ProtoField.uint8("rdpudp.fec.range", "Range", base.DEC) +local pf_udp_fec_fecindex = ProtoField.uint8("rdpudp.fec.fecindex", "Fec index", base.HEX) + +local pf_udp_resetseqenum = ProtoField.uint32("rdpudp.resetSeqNum", "snResetSeqNum", base.HEX) + +local pf_udp_source_sncoded = ProtoField.uint32("rdpudp.data.sncoded", "snCoded", base.HEX) +local pf_udp_source_snSourceStart = ProtoField.uint32("rdpudp.data.sourceStart", "snSourceStart", base.HEX) + + +-- UDP2 fields +local pf_PacketPrefixByte = ProtoField.new("PacketPrefixByte", "rdpudp2.prefixbyte", ftypes.UINT8, nil, base.HEX) + +local pf_packetType = ProtoField.uint8("rdpudp2.packetType", "PacketType", base.HEX, {[0] = "Data", [8] = "Dummy"}, 0x1e, "type of packet") + +RDPUDP2_ACK = 0x0001 +RDPUDP2_DATA = 0x0004 +RDPUDP2_ACKVEC = 0x0008 +RDPUDP2_AOA = 0x0010 +RDPUDP2_OVERHEAD = 0x0040 +RDPUDP2_DELAYACK = 0x00100 + +local pf_flags = ProtoField.uint16("rdpudp2.flags", "Flags", base.HEX, nil, 0xfff, "flags") +local pf_flag_ack = ProtoField.bool("rdpudp2.flags.ack", "Ack", base.HEX, nil, RDPUDP2_ACK, "packet contains Ack payload") +local pf_flag_data = ProtoField.bool("rdpudp2.flags.data", "Data", base.HEX, nil, RDPUDP2_DATA, "packet contains Data payload") +local pf_flag_ackvec = ProtoField.bool("rdpudp2.flags.ackvec", "AckVec", base.HEX, nil, RDPUDP2_ACKVEC, "packet contains AckVec payload") +local pf_flag_aoa = ProtoField.bool("rdpudp2.flags.ackofacks", "AckOfAcks", base.HEX, nil, RDPUDP2_AOA, "packet contains AckOfAcks payload") +local pf_flag_overhead = ProtoField.bool("rdpudp2.flags.overheadsize", "OverheadSize", base.HEX, nil, RDPUDP2_OVERHEAD, "packet contains OverheadSize payload") +local pf_flag_delayackinfo = ProtoField.bool("rdpudp2.flags.delayackinfo", "DelayedAckInfo", base.HEX, nil, RDPUDP2_DELAYACK, "packet contains DelayedAckInfo payload") + +local pf_logWindow = ProtoField.uint16("rdpudp2.logWindow", "LogWindow", base.DEC, nil, 0xf000, "flags") + +local pf_AckSeq = ProtoField.uint16("rdpudp2.ack.seqnum", "Base Seq", base.HEX) +local pf_AckTs = ProtoField.uint24("rdpudp2.ack.ts", "receivedTS", base.DEC) +local pf_AckSendTimeGap = ProtoField.uint8("rdpudp2.ack.sendTimeGap", "sendTimeGap", base.DEC) +local pf_ndelayedAcks = ProtoField.uint8("rdpudp2.ack.numDelayedAcks", "NumDelayedAcks", base.DEC, nil, 0x0f) +local pf_delayedTimeScale = ProtoField.uint8("rdpudp2.ack.delayedTimeScale", "delayedTimeScale", base.DEC, nil, 0xf0) +local pf_delayedAcks = ProtoField.new("Delayed acks", "rdpudp2.ack.delayedAcks", ftypes.BYTES) +local pf_delayedAck = ProtoField.uint8("rdpudp2.ack.delayedAck", "Delayed ack", base.DEC) + +local pf_OverHeadSize = ProtoField.uint8("rdpudp2.overheadsize", "Overhead size", base.DEC) + +local pf_DelayAckMax = ProtoField.uint8("rdpudp2.delayackinfo.max", "MaxDelayedAcks", base.DEC) +local pf_DelayAckTimeout = ProtoField.uint8("rdpudp2.delayackinfo.timeout", "DelayedAckTimeoutInMs", base.DEC) + +local pf_AckOfAcks = ProtoField.uint16("rdpudp2.ackofacks", "Ack of Acks", base.HEX) + +local pf_DataSeqNumber = ProtoField.uint16("rdpudp2.data.seqnum", "sequence number", base.HEX) +local pf_DataChannelSeqNumber = ProtoField.uint16("rdpudp2.data.channelseqnumber", "Channel sequence number", base.HEX) +local pf_Data = ProtoField.new("Data", "rdpudp2.data", ftypes.BYTES) + +local pf_AckvecBaseSeq = ProtoField.uint16("rdpudp2.ackvec.baseseqnum", "Base sequence number", base.HEX) +local pf_AckvecCodecAckVecSize = ProtoField.uint16("rdpudp2.ackvec.codecackvecsize", "Codec ackvec size", base.DEC, nil, 0x7f) +local pf_AckvecHaveTs = ProtoField.bool("rdpudp2.ackvec.havets", "have timestamp", base.DEC, nil, 0x80) +local pf_AckvecTimeStamp = ProtoField.uint24("rdpudp2.ackvec.timestamp", "Timestamp", base.HEX) +local pf_AckvecCodedAck = ProtoField.uint8("rdpudp2.ackvec.codecAck", "Coded Ack", base.HEX) +local pf_AckvecCodedAckMode = ProtoField.uint8("rdpudp2.ackvec.codecAckMode", "Mode", base.HEX, {[0]="Bitmap", [1]="Run length"}, 0x80) +local pf_AckvecCodedAckRleState = ProtoField.uint8("rdpudp2.ackvec.codecAckRleState", "State", base.HEX, {[0]="lost",[1]="received"}, 0x40) +local pf_AckvecCodedAckRleLen = ProtoField.uint8("rdpudp2.ackvec.codecAckRleLen", "Length", base.DEC, nil, 0x3f) + +rdpudp.fields = { + -- UDP1 + pf_udp_snSourceAck, pf_udp_ReceiveWindowSize, pf_udp_flags, pf_udp_flag_syn, + pf_udp_flag_fin, pf_udp_flag_ack, pf_udp_flag_data, pf_udp_flag_fec, pf_udp_flag_cn, + pf_udp_flag_cwr, pf_udp_flag_aoa, pf_udp_flag_synlossy, pf_udp_flag_ackdelayed, + pf_udp_flag_correlationId, pf_udp_flag_synex, + pf_udp_snInitialSequenceNumber, pf_udp_upstreamMtu, pf_udp_downstreamMtu, + pf_udp_correlationId, + pf_udp_synex_flags, pf_udp_synex_flag_version, pf_udp_synex_version, pf_udp_synex_cookiehash, + pf_udp_ack_vectorsize, pf_udp_ack_item, pf_udp_ack_item_state, pf_udp_ack_item_rle, + pf_udp_fec_coded, pf_udp_fec_sourcestart, pf_udp_fec_range, pf_udp_fec_fecindex, + pf_udp_resetseqenum, + pf_udp_source_sncoded, pf_udp_source_snSourceStart, + + -- UDP2 + pf_PacketPrefixByte, pf_packetType, + pf_flags, pf_flag_ack, pf_flag_data, pf_flag_ackvec, pf_flag_aoa, pf_flag_overhead, pf_flag_delayackinfo, + pf_logWindow, + pf_Ack, pf_AckSeq, pf_AckTs, pf_AckSendTimeGap, pf_ndelayedAcks, pf_delayedTimeScale, pf_delayedAcks, + pf_OverHeadSize, + pf_DelayAckMax, pf_DelayAckTimeout, + pf_AckOfAcks, + pf_DataSeqNumber, pf_Data, pf_DataChannelSeqNumber, + pf_Ackvec, pf_AckvecBaseSeq, pf_AckvecCodecAckVecSize, pf_AckvecHaveTs, pf_AckvecTimeStamp, pf_AckvecCodedAck, pf_AckvecCodedAckMode, + pf_AckvecCodedAckRleState, pf_AckvecCodedAckRleLen +} + +rdpudp.prefs.track_udp2_peer_states = Pref.bool("Track state of UDP2 peers", true, "Keep track of state of UDP2 peers (receiver and sender windows") +rdpudp.prefs.debug_ssl = Pref.bool("SSL debug message", false, "print verbose message of the SSL fragments reassembly") + + + +local field_rdpudp_flags = Field.new("rdpudp.flags") +local field_rdpudp2_packetType = Field.new("rdpudp2.packetType") +local field_rdpudp2_channelSeqNumber = Field.new("rdpudp2.data.channelseqnumber") +local field_rdpudp2_ackvec_base = Field.new("rdpudp2.ackvec.baseseqnum") + +function unwrapPacket(tvbuf) + local len = tvbuf:reported_length_remaining() + local ret = tvbuf:bytes(7, 1) .. tvbuf:bytes(1, 6) .. tvbuf:bytes(0, 1) .. tvbuf:bytes(8, len-8) + --dprint2("iput first bytes=", tvbuf:bytes(0, 9):tohex(true, " ")) + --dprint2("oput first bytes=", ret:subset(0, 9):tohex(true, " ")) + return ret:tvb("RDP-UDP unwrapped") +end + +function rdpudp.init() + udpComms = {} +end + +function computePacketKey(pktinfo) + local addr_lo = pktinfo.net_src + local addr_hi = pktinfo.net_dst + local port_lo = pktinfo.src_port + local port_hi = pktinfo.dst_port + + if addr_lo > addr_hi then + addr_hi, addr_lo = addr_lo, addr_hi + port_hi, port_lo = port_lo, port_hi + end + + return tostring(addr_lo) .. ":" .. tostring(port_lo) .. " -> " .. tostring(addr_hi) .. ":" .. tostring(port_hi) +end + +function tableItem(pktinfo) + local key = computePacketKey(pktinfo) + local ret = udpComms[key] + if ret == nil then + dprint2(pktinfo.number .. " creating entry for " .. key) + udpComms[key] = { isLossy = false, switchToUdp2 = nil, sslFragments = {}, + serverAddr = nil, clientAddr = nil, + serverState = { receiveLow = nil, receiveHigh = nil, senderLow = nil, senderHigh = nil }, + clientState = { receiveLow = nil, receiveHigh = nil, senderLow = nil, senderHigh = nil } + } + ret = udpComms[key] + end + return ret +end + +function doAlign(v, alignment) + local rest = v % alignment + if rest ~= 0 then + return v + alignment - rest + end + return v +end + +function dissectV1(tvbuf, pktinfo, tree) + --dprint2("dissecting in UDP1 mode") + local pktlen = tvbuf:reported_length_remaining() + + tree:add(pf_udp_snSourceAck, tvbuf:range(0, 4)) + tree:add(pf_udp_ReceiveWindowSize, tvbuf:range(4, 2)) + local flagsRange = tvbuf:range(6, 2) + local flagsItem = tree:add(pf_udp_flags, flagsRange) + -- + flagsItem:add(pf_udp_flag_syn, flagsRange) + flagsItem:add(pf_udp_flag_fin, flagsRange) + flagsItem:add(pf_udp_flag_ack, flagsRange) + flagsItem:add(pf_udp_flag_data, flagsRange) + flagsItem:add(pf_udp_flag_fec, flagsRange) + flagsItem:add(pf_udp_flag_cn, flagsRange) + flagsItem:add(pf_udp_flag_cwr, flagsRange) + flagsItem:add(pf_udp_flag_aoa, flagsRange) + flagsItem:add(pf_udp_flag_synlossy, flagsRange) + flagsItem:add(pf_udp_flag_ackdelayed, flagsRange) + flagsItem:add(pf_udp_flag_correlationId, flagsRange) + flagsItem:add(pf_udp_flag_synex, flagsRange) + + startAt = 8 + local flags = flagsRange:uint() + local haveSyn = bit32.band(flags, RDPUDP_SYN) ~= 0 + local haveAck = bit32.band(flags, RDPUDP_ACK) ~= 0 + local isLossySyn = bit32.band(flags, RDPUDP_SYNLOSSY) ~= 0 + local tableRecord = tableItem(pktinfo) + + + if isLossySyn then + tableRecord.isLossy = true + end + + if haveSyn then + -- dprint2("rdpudp - SYN") + local synItem = tree:add("Syn", tvbuf:range(startAt, 8)) + + synItem:add(pf_udp_snInitialSequenceNumber, tvbuf:range(startAt, 4)) + synItem:add(pf_udp_upstreamMtu, tvbuf:range(startAt+4, 2)) + synItem:add(pf_udp_downstreamMtu, tvbuf:range(startAt+6, 2)) + + startAt = startAt + 8 + end + + if bit32.band(flags, RDPUDP_CORRELATIONID) ~= 0 then + -- dprint2("rdpudp - CorrelationId") + tree:add(pf_udp_correlationId, tvbuf:range(startAt, 16)) + startAt = startAt + 32 + end + + if bit32.band(flags, RDPUDP_SYNEX) ~= 0 then + -- dprint2("rdpudp - SynEx") + local synexItem = tree:add("SynEx") + + local synexFlagsRange = tvbuf:range(startAt, 2) + local synexFlags = synexItem:add(pf_udp_synex_flags, synexFlagsRange); + -- + synexFlags:add(pf_udp_synex_flag_version, synexFlagsRange) + local exflags = synexFlagsRange:uint() + startAt = startAt + 2 + if bit32.band(exflags, 1) ~= 0 then + synexItem:add(pf_udp_synex_version, tvbuf:range(startAt, 2)) + local versionVal = tvbuf:range(startAt, 2):uint() + startAt = startAt + 2 + + if versionVal == 0x101 then + if not haveAck then + synexItem:add(pf_udp_synex_cookiehash, tvbuf:range(startAt, 32)) + startAt = startAt + 32 + else + -- switch to UDP2 + tableRecord.switchToUdp2 = pktinfo.number + end + end + end + + local mask = RDPUDP_SYN + RDPUDP_ACK + if bit32.band(flags, mask) == mask then + tableRecord.serverAddr = tostring(pktinfo.net_src) + tableRecord.clientAddr = tostring(pktinfo.net_dst) + -- dprint2(pktinfo.number .. ": key='" .. computePacketKey(pktinfo) .. + -- "' setting server=" .. tableRecord.serverAddr .. " client=" .. tableRecord.clientAddr) + end + end + + if haveAck and not haveSyn then + -- dprint2("rdpudp - Ack") + local ackItem = tree:add("Ack") + ackItem:add(pf_udp_ack_vectorsize, tvbuf:range(startAt, 2)) + + local i = 0 + uAckVectorSize = tvbuf:range(startAt, 2):uint() + while i < uAckVectorSize do + local ackRange = tvbuf:range(startAt + 2 + i, 1) + local ack = ackItem:add(pf_udp_ack_item, ackRange) + ack:add(pf_udp_ack_item_state, ackRange) + ack:add(pf_udp_ack_item_rle, ackRange) + i = i + 1 + end -- while + + -- aligned on a dword (4 bytes) boundary + -- dprint2("pktinfo=",pktinfo.number," blockSz=",doAlign(2 + uAckVectorSize, 4)) + startAt = startAt + doAlign(2 + uAckVectorSize, 4) + end + + if bit32.band(flags, RDPUDP_FEC) ~= 0 then + -- dprint2("rdpudp - FEC header") + local fecItem = tree:add("FEC", tvbuf:range(startAt, 12)) + fecItem:add(pf_udp_fec_coded, tvbuf:range(startAt, 4)) + fecItem:add(pf_udp_fec_sourcestart, tvbuf:range(startAt+4, 4)) + fecItem:add(pf_udp_fec_range, tvbuf:range(startAt+8, 1)) + fecItem:add(pf_udp_fec_fecindex, tvbuf:range(startAt+9, 1)) + + startAt = startAt + (4 * 3) + end + + if bit32.band(flags, RDPUDP_AOA) ~= 0 then + -- dprint2("rdpudp - AOA") + tree:add(pf_udp_resetseqenum, tvbuf:range(startAt, 4)) + startAt = startAt + 4 + end + + if bit32.band(flags, RDPUDP_DATA) ~= 0 then + -- dprint2("rdpudp - Data") + local dataItem = tree:add("Data") + dataItem:add(pf_udp_source_sncoded, tvbuf:range(startAt, 4)) + dataItem:add(pf_udp_source_snSourceStart, tvbuf:range(startAt+4, 4)) + startAt = startAt + 8 + + local payload = tvbuf:range(startAt) + local subTvb = payload:tvb("payload") + if tableRecord.isLossy then + dtlsDissector:call(subTvb, pktinfo, dataItem) + else + sslDissector:call(subTvb, pktinfo, dataItem) + end + end + + return pktlen +end + +-- given a tvb containing SSL records returns the part of the buffer that has complete +-- SSL records +function getCompleteSslRecordsLen(tvb) + local startAt = 0 + local remLen = tvb:reported_length_remaining() + + while remLen > 5 do + local recordLen = 5 + tvb:range(startAt+3, 2):uint() + if remLen < recordLen then + break + end + startAt = startAt + recordLen + remLen = remLen - recordLen + end -- while + + return startAt; +end + +TLS_OK = 0 +TLS_SHORT = 1 +TLS_NOT_TLS = 2 +TLS_NOT_COMPLETE = 3 +sslResNames = {[0]="TLS_OK", [1]="TLS_SHORT", [2]="TLS_NOT_TLS", [3]="TLS_NOT_COMPLETE"} + +function checkSslRecord(tvb) + local remLen = tvb:reported_length_remaining() + + if remLen <= 5 then + return TLS_SHORT, 0 + end + + local b0 = tvb:range(0, 1):uint() + if b0 < 0x14 or b0 > 0x17 then + -- dprint2("doesn't look like a SSL record, b0=",b0) + return TLS_NOT_TLS, 0 + end + + local recordLen = 5 + tvb:range(3, 2):uint() + if remLen < recordLen then + return TLS_NOT_COMPLETE, recordLen + end + return TLS_OK, recordLen +end + + +function getSslFragments(pktinfo) + local addr0 = pktinfo.net_src + local addr1 = pktinfo.net_dst + local port0 = pktinfo.src_port + local port1 = pktinfo.dst_port + local key = tostring(addr0) .. ":" .. tostring(port0) .. "->" .. tostring(addr1) .. ":" .. tostring(port1) + + local tableRecord = tableItem(pktinfo) + if tableRecord.sslFragments[key] == nil then + tableRecord.sslFragments[key] = {} + end + + return tableRecord.sslFragments[key] +end + +function dissectV2(in_tvbuf, pktinfo, tree) + -- dprint2("dissecting in UDP2 mode") + local pktlen = in_tvbuf:reported_length_remaining() + if pktlen < 7 then + dprint2("packet ", pktinfo.number, " too short, len=", pktlen) + return + end + + local conversation = tableItem(pktinfo) + local sourceState = nil + local targetState = nil + if rdpudp.prefs.track_udp2_peer_states then + if tostring(pktinfo.net_dst) == conversation.serverAddr then + sourceState = conversation.clientState + targetState = conversation.serverState + else + sourceState = conversation.serverState + targetState = conversation.clientState + end + end + + pktinfo.cols.info = "" + local info = "(" + local tvbuf = unwrapPacket(in_tvbuf) + local prefixRange = tvbuf:range(0, 1) + local prefix_tree = tree:add(pf_PacketPrefixByte, prefixRange) + -- + local packetType = prefix_tree:add(pf_packetType, prefixRange) + + local flagsRange = tvbuf:range(1,2) + local flagsTree = tree:add_le(pf_flags, flagsRange) + -- + flagsTree:add_packet_field(pf_flag_ack, flagsRange, ENC_LITTLE_ENDIAN) + flagsTree:add_le(pf_flag_data, flagsRange) + flagsTree:add_le(pf_flag_ackvec, flagsRange) + flagsTree:add_le(pf_flag_aoa, flagsRange) + flagsTree:add_le(pf_flag_overhead, flagsRange) + flagsTree:add_le(pf_flag_delayackinfo, flagsRange) + + tree:add_le(pf_logWindow, flagsRange) + + local flags = tvbuf:range(1,2):le_uint() + + local startAt = 3 + if bit32.band(flags, RDPUDP2_ACK) ~= 0 then + -- dprint2("got ACK payload") + info = info .. "ACK," + local ackTree = tree:add("Ack") + + ackTree:add_le(pf_AckSeq, tvbuf:range(startAt, 2)) + ackTree:add_le(pf_AckTs, tvbuf:range(startAt+2, 3)) + ackTree:add(pf_AckSendTimeGap, tvbuf:range(startAt+5, 1)) + ackTree:add(pf_ndelayedAcks, tvbuf:range(startAt+6, 1)) + ackTree:add(pf_delayedTimeScale, tvbuf:range(startAt+6, 1)) + + local ackSeq = tvbuf:range(startAt, 2):le_uint() + local ackTs = tvbuf:range(startAt+2, 3):le_uint() + local nacks = bit32.band(tvbuf:range(startAt+6, 1):le_uint(), 0xf) + local delayAckTimeScale = bit32.rshift(bit32.band(tvbuf:range(startAt+6, 1):le_uint(), 0xf0), 4) + -- dprint2(pktinfo.number,": nACKs=", nacks, "delayAckTS=", bit32.rshift(delayAckTimeScale, 4)) + + if rdpudp.prefs.track_udp2_peer_states then + targetState.senderLow = ackSeq + end + + startAt = startAt + 7 + if nacks ~= 0 then + local acksItem = ackTree:add(pf_delayedAcks, tvbuf:range(startAt, nacks)) + local i + for i = nacks-1, 0, -1 do + local ackDelay = tvbuf:range(startAt+i, 1):le_uint() * bit32.lshift(1, delayAckTimeScale) + acksItem:add(pf_delayedAck, tvbuf:range(startAt+i, 1), "seq=0x" .. string.format("%0.4x", ackSeq-i-1) .. " ts=" .. ackTs-ackDelay) + end + acksItem:add(pf_delayedAck, tvbuf:range(startAt, nacks), "seq=0x" .. string.format("%0.4x", ackSeq) .. " ts=" .. ackTs):set_generated() + end + startAt = startAt + nacks + end + + if bit32.band(flags, RDPUDP2_OVERHEAD) ~= 0 then + info = info .. "OVERHEAD," + + tree:add_le(pf_OverHeadSize, tvbuf:range(startAt, 1)) + startAt = startAt + 1 + end + + if bit32.band(flags, RDPUDP2_DELAYACK) ~= 0 then + info = info .. "DELAYEDACK," + + local delayAckItem = tree:add("DelayAckInfo", tvbuf:range(startAt, 3)) + delayAckItem:add_le(pf_DelayAckMax, tvbuf:range(startAt, 1)) + delayAckItem:add_le(pf_DelayAckTimeout, tvbuf:range(startAt+1, 2)) + startAt = startAt + 3 + end + + if bit32.band(flags, RDPUDP2_AOA) ~= 0 then + info = info .. "AOA," + tree:add_le(pf_AckOfAcks, tvbuf:range(startAt, 2)) + startAt = startAt + 2 + end + + local dataTree + local isDummy = (field_rdpudp2_packetType()() == 0x8) + if bit32.band(flags, RDPUDP2_DATA) ~= 0 then + if isDummy then + info = info .. "DUMMY," + else + info = info .. "DATA," + end + dataTree = tree:add(isDummy and "Dummy Data" or "Data") + dataTree:add_le(pf_DataSeqNumber, tvbuf:range(startAt, 2)) + startAt = startAt + 2 + end + + if bit32.band(flags, RDPUDP2_ACKVEC) ~= 0 then + -- dprint2("got ACKVEC payload") + info = info .. "ACKVEC," + + local codedAckVecSizeA = tvbuf:range(startAt+2, 1):le_uint() + local codedAckVecSize = bit32.band(codedAckVecSizeA, 0x7f) + local haveTs = bit32.band(codedAckVecSizeA, 0x80) ~= 0 + + local ackVecTree = tree:add("AckVec") + ackVecTree:add_le(pf_AckvecBaseSeq, tvbuf:range(startAt, 2)) + ackVecTree:add(pf_AckvecCodecAckVecSize, tvbuf:range(startAt+2, 1)) + ackVecTree:add(pf_AckvecHaveTs, tvbuf:range(startAt+2, 1)) + startAt = startAt + 3 + if haveTs then + ackVecTree:add_le(pf_AckvecTimeStamp, tvbuf:range(startAt, 4)) + startAt = startAt + 4 + end + local codedAckVector = ackVecTree:add("Vector", tvbuf:range(startAt, codedAckVecSize)) + local seqNumber = field_rdpudp2_ackvec_base()() + for i = 0, codedAckVecSize-1, 1 do + local bRange = tvbuf:range(startAt + i, 1) + local b = bRange:uint() + + local codedAck = codedAckVector:add(pf_AckvecCodedAck, bRange, b) + codedAck:add(pf_AckvecCodedAckMode, bRange) + + local itemString = ""; + if bit32.band(b, 0x80) == 0 then + -- bitmap length mode + itemString = string.format("bitmap(0x%0.2x): ", b) + local mask = 0x1 + for j = 0, 7-1 do + flag = "!" + if bit32.band(b, mask) ~= 0 then + flag = "" + end + + itemString = itemString .. " " .. flag .. string.format("%0.4x", seqNumber) + mask = mask * 2 + seqNumber = seqNumber + 1 + end + else + -- run length mode + codedAck:add(pf_AckvecCodedAckRleState, bRange) + codedAck:add(pf_AckvecCodedAckRleLen, bRange) + + local rleLen = bit32.band(b, 0x3f) + itemString = "rle(len=" .. rleLen .. "): ".. (bit32.band(b, 0x40) and "received" or "lost") .. + string.format(" %0.4x -> %0.4x", seqNumber, seqNumber + rleLen) + seqNumber = seqNumber + rleLen + end + + codedAck:set_text(itemString) + + end + + startAt = startAt + codedAckVecSize + end + + if not isDummy and bit32.band(flags, RDPUDP2_DATA) ~= 0 then + dataTree:add_le(pf_DataChannelSeqNumber, tvbuf:range(startAt, 2)) + local payload = tvbuf:range(startAt + 2) + local subTvb = payload:tvb("payload") + + local channelSeqId = field_rdpudp2_channelSeqNumber()() + local sslFragments = getSslFragments(pktinfo) + local workTvb = nil + + local sslRes, recordLen = checkSslRecord(subTvb) + if rdpudp.prefs.debug_ssl then + dprint2("packet=", pktinfo.number, " channelSeq=", channelSeqId, + " dataLen=", subTvb:reported_length_remaining(), + " sslRes=", sslResNames[sslRes], + " recordLen=", recordLen) + end + if sslRes == TLS_OK then + workTvb = subTvb + elseif sslRes == TLS_SHORT or sslRes == TLS_NOT_COMPLETE then + if rdpudp.prefs.debug_ssl then + dprint2("packet=", pktinfo.number, " recording fragment len=", subTvb:len()) + end + + local frag = ByteArray.new() + frag:append(subTvb:bytes()) + sslFragments[channelSeqId] = frag + + elseif sslRes == TLS_NOT_TLS then + local prevFragment = sslFragments[channelSeqId-1] + if rdpudp.prefs.debug_ssl then + dprint2("packet=",pktinfo.number," picking channelSeq=", channelSeqId-1, " havePrevFragment=", prevFragment ~= nil and "ok" or "no") + end + if prevFragment ~= nil then + -- dprint2("prevLen=",prevFragment:len(), " subTvbLen=",subTvb:len()) + local testBytes = prevFragment .. subTvb:bytes() + local testTvb = ByteArray.tvb(testBytes, "reassembled fragment") + + sslRes, recordLen = checkSslRecord(testTvb) + if rdpudp.prefs.debug_ssl then + dprint2("packet=", pktinfo.number, + " reassembled len=", testTvb:reported_length_remaining(), + " sslRes=", sslResNames[sslRes], + " recordLen=", recordLen) + end + if sslRes == TLS_OK then + workTvb = testTvb + end + end + end + + if workTvb ~= nil then + repeat + if rdpudp.prefs.debug_ssl then + dprint2("treating workTvbLen=", workTvb:reported_length_remaining(), " recordLen=",recordLen) + end + local sslFragment = workTvb:range(0, recordLen):tvb("SSL fragment") + sslDissector:call(sslFragment, pktinfo, dataTree) + + workTvb = workTvb:range(recordLen):tvb() + sslRes, recordLen = checkSslRecord(workTvb) + + if sslRes == TLS_SHORT or sslRes == TLS_NOT_COMPLETE then + if rdpudp.prefs.debug_ssl then + dprint2("packet=", pktinfo.number, " recording fragment len=", subTvb:len()) + end + + local frag = ByteArray.new() + frag:append(workTvb:bytes()) + sslFragments[channelSeqId] = frag + end + + until sslRes ~= TLS_OK or workTvb:reported_length_remaining() == 0 + else + dataTree:add_le(pf_Data, payload) + end + end + + info = string.sub(info, 0, -2) .. ")" + pktinfo.cols.info = info -- .. tostring(pktinfo.cols.info) + if rdpudp.prefs.track_udp2_peer_states then + local stateTrackItem = tree:add("UDP2 state tracking") + stateTrackItem:set_generated() + stateTrackItem:add(tostring(pktinfo.net_dst) == conversation.serverAddr and "Client -> Server" or "Server -> Client") + end + + + return pktlen +end + +function rdpudp.dissector(in_tvbuf, pktinfo, root) + -- dprint2("rdpudp.dissector called") + pktinfo.cols.protocol:set("RDP-UDP") + + local pktlen = in_tvbuf:reported_length_remaining() + local tree = root:add(rdpudp, in_tvbuf:range(0,pktlen)) + + local tableRecord = tableItem(pktinfo) + local doDissectV1 = true + if tableRecord.switchToUdp2 ~= nil and tableRecord.switchToUdp2 < pktinfo.number then + doDissectV1 = false + end + + if doDissectV1 then + return dissectV1(in_tvbuf, pktinfo, tree) + end + + return dissectV2(in_tvbuf, pktinfo, tree) +end + +DissectorTable.get("udp.port"):add(3389, rdpudp) |