1
1
mirror of https://github.com/wader/fq.git synced 2024-08-17 16:00:41 +03:00

Sending PyRDP parser upstream

For project history see: https://github.com/GoSecure/fq-pyrdp/

Co-authored-by: Lisandro Ubiedo <lubiedo@gosecure.net>
This commit is contained in:
Olivier Bilodeau 2024-06-04 14:55:41 -04:00 committed by Mattias Wadman
parent 2bc6c768c7
commit f4d9428855
11 changed files with 4170 additions and 0 deletions

View File

@ -153,6 +153,7 @@ prores_frame Apple ProRes frame
protobuf Protobuf
protobuf_widevine Widevine protobuf
pssh_playready PlayReady PSSH
pyrdp PyRDP Replay Files
rtmp Real-Time Messaging Protocol
sll2_packet Linux cooked capture encapsulation v2
sll_packet Linux cooked capture encapsulation

View File

@ -50,6 +50,7 @@ import (
_ "github.com/wader/fq/format/postgres"
_ "github.com/wader/fq/format/prores"
_ "github.com/wader/fq/format/protobuf"
_ "github.com/wader/fq/format/pyrdp"
_ "github.com/wader/fq/format/riff"
_ "github.com/wader/fq/format/rtmp"
_ "github.com/wader/fq/format/tar"

View File

@ -162,6 +162,7 @@ var (
Protobuf = &decode.Group{Name: "protobuf"}
ProtobufWidevine = &decode.Group{Name: "protobuf_widevine"}
PSSH_Playready = &decode.Group{Name: "pssh_playready"}
PYRDP = &decode.Group{Name: "pyrdp"}
RTMP = &decode.Group{Name: "rtmp"}
SLL_Packet = &decode.Group{Name: "sll_packet"}
SLL2_Packet = &decode.Group{Name: "sll2_packet"}

View File

@ -0,0 +1,108 @@
// Copyright (c) 2022-2023 GoSecure Inc.
package pyrdp
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
const (
RDP4 = 0x80001
RDP5 = 0x80004
RDP10 = 0x80005
RDP10_1 = 0x80006
RDP10_2 = 0x80007
RDP10_3 = 0x80008
RDP10_4 = 0x80009
RDP10_5 = 0x8000A
RDP10_6 = 0x8000B
RDP10_7 = 0x8000C
RDP10_8 = 0x8000d
RDP10_9 = 0x8000e
RDP10_10 = 0x8000f
)
var RDPVersionMap = scalar.UintMap{
RDP4: {Sym: "rdp4", Description: "RDP 4"},
RDP5: {Sym: "rdp5", Description: "RDP 5"},
RDP10: {Sym: "rdp10", Description: "RDP 10"},
RDP10_1: {Sym: "rdp10_1", Description: "RDP 10.1"},
RDP10_2: {Sym: "rdp10_2", Description: "RDP 10.2"},
RDP10_3: {Sym: "rdp10_3", Description: "RDP 10.3"},
RDP10_4: {Sym: "rdp10_4", Description: "RDP 10.4"},
RDP10_5: {Sym: "rdp10_5", Description: "RDP 10.5"},
RDP10_6: {Sym: "rdp10_6", Description: "RDP 10.6"},
RDP10_7: {Sym: "rdp10_7", Description: "RDP 10.7"},
RDP10_8: {Sym: "rdp10_8", Description: "RDP 10.8"},
RDP10_9: {Sym: "rdp10_9", Description: "RDP 10.9"},
RDP10_10: {Sym: "rdp10_10", Description: "RDP 10.10"},
}
const (
CLIENT_CORE = 0xC001
CLIENT_SECURITY = 0xC002
CLIENT_NETWORK = 0xC003
CLIENT_CLUSTER = 0xC004
)
// TODO: Fill descriptions.
var clientDataMap = scalar.UintMap{
CLIENT_CORE: {Sym: "client_core", Description: ""},
CLIENT_SECURITY: {Sym: "client_security", Description: ""},
CLIENT_NETWORK: {Sym: "client_network", Description: ""},
CLIENT_CLUSTER: {Sym: "client_cluster", Description: ""},
}
func ParseClientData(d *decode.D, length int64) {
d.FieldStruct("client_data", func(d *decode.D) {
header := d.FieldU16("header", clientDataMap)
data_len := int64(d.FieldU16("length") - 4)
switch header {
case CLIENT_CORE:
ParseClientDataCore(d, data_len)
case CLIENT_SECURITY:
ParseClientDataSecurity(d, data_len)
case CLIENT_NETWORK:
ParseClientDataNetwork(d, data_len)
case CLIENT_CLUSTER:
ParseClientDataCluster(d, data_len)
default:
// Assert() once all functions are implemented and tested.
d.FieldRawLen("data", data_len*8)
return
}
})
}
func ParseClientDataCore(d *decode.D, length int64) {
d.FieldU32("version", RDPVersionMap)
d.FieldU16("desktop_width")
d.FieldU16("desktop_height")
d.FieldU16("color_depth")
d.FieldU16("sas_sequence")
d.FieldU32("keyboard_layout")
d.FieldU32("client_build")
d.FieldStrFn("client_name", toTextUTF16Fn(32))
d.FieldU32("keyboard_type")
d.FieldU32("keyboard_sub_type")
d.FieldU32("keyboard_function_key")
d.FieldRawLen("ime_file_name", 64*8)
d.FieldRawLen("code_data", 98*8)
}
func ParseClientDataSecurity(d *decode.D, length int64) {
d.FieldU32("encryption_methods")
d.FieldU32("ext_encryption_methods")
}
func ParseClientDataNetwork(d *decode.D, length int64) {
d.FieldU32("channel_count")
length -= 4
d.FieldRawLen("channel_def_array", length*8)
}
func ParseClientDataCluster(d *decode.D, length int64) {
d.FieldU32("flags")
d.FieldU32("redirected_session_id")
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
package pyrdp
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
func ParseClientInfo(d *decode.D, length int64) {
d.FieldStruct("client_info", func(d *decode.D) {
pos := d.Pos()
var (
is_unicode bool
has_null bool
null_n uint64 = 0
unicode_n uint64 = 0
)
code_page := d.FieldU32("code_page")
flags := d.U32()
d.SeekRel(-4 * 8)
d.FieldStruct("flags", decodeFlagsFn)
is_unicode = ((flags & INFO_UNICODE) != 0)
has_null = (code_page == 1252 || is_unicode)
if has_null {
null_n = 1
}
if is_unicode {
unicode_n = 2
}
domain_length := int(d.FieldU16("domain_length") + null_n*unicode_n)
username_length := int(d.FieldU16("username_length") + null_n*unicode_n)
password_length := int(d.FieldU16("password_length") + null_n*unicode_n)
alternate_shell_length := int(d.FieldU16("alternate_shell_length") + null_n*unicode_n)
working_dir_length := int(d.FieldU16("working_dir_length") + null_n*unicode_n)
d.FieldStrFn("domain", toTextUTF16Fn(domain_length))
d.FieldStrFn("username", toTextUTF16Fn(username_length))
d.FieldStrFn("password", toTextUTF16Fn(password_length))
d.FieldStrFn("alternate_shell", toTextUTF16Fn(alternate_shell_length))
d.FieldStrFn("working_dir", toTextUTF16Fn(working_dir_length))
extra_length := length - ((d.Pos() - pos) / 8)
if extra_length > 0 {
d.FieldStruct("extra_info", func(d *decode.D) {
d.FieldU16("address_family", scalar.UintHex)
address_length := int(d.FieldU16("address_length"))
d.FieldStrFn("address", toTextUTF16Fn(address_length))
client_dir_length := int(d.FieldU16("client_dir_length"))
d.FieldStrFn("clientDir", toTextUTF16Fn(client_dir_length))
// TS_TIME_ZONE_INFORMATION structure
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/526ed635-d7a9-4d3c-bbe1-4e3fb17585f4
d.FieldU32("timezone_bias")
d.FieldStrFn("timezone_standardname", toTextUTF16Fn(64))
})
// XXX: there's more extra info but here's everything we need from the
// client (other than UTC info)
}
})
}
const (
// flags
INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002
INFO_AUTOLOGON = 0x00000008
INFO_UNICODE = 0x00000010
INFO_MAXIMIZESHELL = 0x00000020
INFO_LOGONNOTIFY = 0x00000040
INFO_COMPRESSION = 0x00000080
INFO_ENABLEWINDOWSKEY = 0x00000100
INFO_REMOTECONSOLEAUDIO = 0x00002000
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
INFO_RAIL = 0x00008000
INFO_LOGONERRORS = 0x00010000
INFO_MOUSE_HAS_WHEEL = 0x00020000
INFO_PASSWORD_IS_SC_PIN = 0x00040000
INFO_NOAUDIOPLAYBACK = 0x00080000
INFO_USING_SAVED_CREDS = 0x00100000
INFO_AUDIOCAPTURE = 0x00200000
INFO_VIDEO_DISABLE = 0x00400000
INFO_RESERVED1 = 0x00800000
INFO_RESERVED2 = 0x01000000
INFO_HIDEF_RAIL_SUPPORTED = 0x02000000
)
func decodeFlagsFn(d *decode.D) {
d.FieldBool("mouse")
d.FieldBool("disabledctrlaltdel")
d.SeekRel(1)
d.FieldBool("autologon")
d.FieldBool("unicode")
d.FieldBool("maximizeshell")
d.FieldBool("logonnotify")
d.FieldBool("compression")
d.FieldBool("enablewindowskey")
d.SeekRel(4)
d.FieldBool("remoteconsoleaudio")
d.FieldBool("force_encrypted_cs_pdu")
d.FieldBool("rail")
d.FieldBool("logonerrors")
d.FieldBool("mouse_has_wheel")
d.FieldBool("password_is_sc_pin")
d.FieldBool("noaudioplayback")
d.FieldBool("using_saved_creds")
d.FieldBool("audiocapture")
d.FieldBool("video_disable")
d.FieldBool("reserved1")
d.FieldBool("reserved2")
d.FieldBool("hidef_rail_supported")
d.SeekRel(int64(d.Pos()) % 31)
}

View File

@ -0,0 +1,73 @@
// Copyright (c) 2022-2023 GoSecure Inc.
package pyrdp
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
const (
// Message types.
CB_MONITOR_READY = 0x0001
CB_FORMAT_LIST = 0x0002
CB_FORMAT_LIST_RESPONSE = 0x0003
CB_FORMAT_DATA_REQUEST = 0x0004
CB_FORMAT_DATA_RESPONSE = 0x0005
CB_TEMP_DIRECTORY = 0x0006
CB_CLIP_CAPS = 0x0007
CB_FILECONTENTS_REQUEST = 0x0008
CB_FILECONTENTS_RESPONSE = 0x0009
CB_LOCK_CLIPDATA = 0x000A
CB_UNLOCK_CLIPDATA = 0x000B
// Message flags.
NONE = 0
CB_RESPONSE_OK = 0x0001
CB_RESPONSE_FAIL = 0x0002
CB_ASCII_NAMES = 0x0004
)
// TODO: Fill the descriptions.
var cbTypesMap = scalar.UintMap{
CB_MONITOR_READY: {Sym: "cb_monitor_ready", Description: ""},
CB_FORMAT_LIST: {Sym: "cb_format_list", Description: ""},
CB_FORMAT_LIST_RESPONSE: {Sym: "cb_format_list_response", Description: ""},
CB_FORMAT_DATA_REQUEST: {Sym: "cb_format_data_request", Description: ""},
CB_FORMAT_DATA_RESPONSE: {Sym: "cb_format_data_response", Description: ""},
CB_TEMP_DIRECTORY: {Sym: "cb_temp_directory", Description: ""},
CB_CLIP_CAPS: {Sym: "cb_clip_caps", Description: ""},
CB_FILECONTENTS_REQUEST: {Sym: "cb_filecontents_request", Description: ""},
CB_FILECONTENTS_RESPONSE: {Sym: "cb_filecontents_response", Description: ""},
CB_LOCK_CLIPDATA: {Sym: "cb_lock_clipdata", Description: ""},
CB_UNLOCK_CLIPDATA: {Sym: "cb_unlock_clipdata", Description: ""},
}
var cbFlagsMap = scalar.UintMap{
NONE: {Sym: "none", Description: ""},
CB_RESPONSE_OK: {Sym: "cb_response_ok", Description: ""},
CB_RESPONSE_FAIL: {Sym: "cb_response_fail", Description: ""},
CB_ASCII_NAMES: {Sym: "cb_ascii_names", Description: ""},
}
var cbParseFnMap = map[uint16]interface{}{
CB_FORMAT_DATA_RESPONSE: parseCbFormatDataResponse,
}
func ParseClipboardData(d *decode.D, length int64) {
d.FieldStruct("clipboard_data", func(d *decode.D) {
msg_type := uint16(d.FieldU16("msg_type", cbTypesMap))
d.FieldU16("msg_flags", cbFlagsMap)
data_length := d.FieldU32("data_len")
if _, ok := cbParseFnMap[msg_type]; ok {
cbParseFnMap[msg_type].(func(d *decode.D, length uint64))(d, data_length)
return
}
// Assert() once all functions are implemented.
d.FieldRawLen("data", int64(data_length*8))
})
}
func parseCbFormatDataResponse(d *decode.D, length uint64) {
d.FieldRawLen("data", int64(length*8))
}

View File

@ -0,0 +1,125 @@
// Copyright (c) 2022-2023 GoSecure Inc.
package pyrdp
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
const (
// Security Flags.
FASTPATH_INPUT_SECURE_CHECKSUM = 1
FASTPATH_INPUT_ENCRYPTED = 2
// Event codes.
FASTPATH_INPUT_EVENT_SCANCODE = 0
FASTPATH_INPUT_EVENT_MOUSE = 1
FASTPATH_INPUT_EVENT_MOUSEX = 2
FASTPATH_INPUT_EVENT_SYNC = 3
FASTPATH_INPUT_EVENT_UNICODE = 4
FASTPATH_INPUT_EVENT_QOE_TIMESTAMP = 6
)
var eventCodesMap = scalar.UintMap{
FASTPATH_INPUT_EVENT_SCANCODE: {Sym: "fastpath_input_event_scancode", Description: ""},
FASTPATH_INPUT_EVENT_MOUSE: {Sym: "fastpath_input_event_mouse", Description: ""},
FASTPATH_INPUT_EVENT_MOUSEX: {Sym: "fastpath_input_event_mousex", Description: ""},
FASTPATH_INPUT_EVENT_SYNC: {Sym: "fastpath_input_event_sync", Description: ""},
FASTPATH_INPUT_EVENT_UNICODE: {Sym: "fastpath_input_event_unicode", Description: ""},
FASTPATH_INPUT_EVENT_QOE_TIMESTAMP: {Sym: "fastpath_input_event_qoe_timestamp", Description: ""},
}
var eventFnMap = map[int]interface{}{
FASTPATH_INPUT_EVENT_SCANCODE: parseFastpathInputEventScancode,
FASTPATH_INPUT_EVENT_MOUSE: parseFastpathInputEventMouse,
FASTPATH_INPUT_EVENT_MOUSEX: parseFastpathInputEventMousex,
FASTPATH_INPUT_EVENT_SYNC: parseFastpathInputEventSync,
FASTPATH_INPUT_EVENT_UNICODE: parseFastpathInputEventUnicode,
FASTPATH_INPUT_EVENT_QOE_TIMESTAMP: parseFastpathInputEventQoeTimestamp,
}
var fastPathInputEventLengthsMap = map[int]int{
FASTPATH_INPUT_EVENT_SCANCODE: 2,
FASTPATH_INPUT_EVENT_MOUSE: 7,
FASTPATH_INPUT_EVENT_MOUSEX: 7,
FASTPATH_INPUT_EVENT_SYNC: 1,
FASTPATH_INPUT_EVENT_UNICODE: 3,
FASTPATH_INPUT_EVENT_QOE_TIMESTAMP: 5,
}
func ParseFastPathInput(d *decode.D, length int64) {
d.FieldStruct("fastpath_input", func(d *decode.D) {
// var (
// events uint8 = 1
// )
pos := d.Pos()
d.FieldStruct("input_header", func(d *decode.D) {
d.FieldU2("action", scalar.UintHex)
// events = uint8(d.FieldU4("events") & 0xf)
d.FieldU4("events", scalar.UintHex)
flags := d.FieldU2("flags", scalar.UintHex)
if flags&FASTPATH_INPUT_ENCRYPTED != 0 {
panic("Encrypted fast-path not implemented.")
}
})
input_length := d.FieldU8("input_length1", scalar.UintHex)
if input_length&0x80 != 0 {
input_length = ((input_length & 0x7f) << 8) | d.FieldU8("input_length2", scalar.UintHex)
}
// d.FieldU64("data_signature", scalar.Hex)
// fmt.Fprintf(os.Stderr, "events:%d\n", events)
// d.FieldArray("events", func(d *decode.D) {
// for ; events > 0; events-- {
// var event_type int
//
// d.FieldStruct("event_header", func(d *decode.D) {
// event_type = int(d.FieldU3("type", eventCodesMap))
// d.FieldU5("flags")
// })
//
// if _, ok := eventFnMap[event_type]; !ok {
// // panic("fastpath_input: Unknow event code.\n")
// fmt.Fprint(os.Stderr, "fastpath_input: Unknow event code.\n")
// } else {
// eventFnMap[event_type].(func(d *decode.D))(d)
// }
// }
// })
input_length -= uint64(d.Pos()-pos) / 8
if input_length > 0 {
d.FieldRawLen("data", int64(input_length*8))
}
})
}
func parseFastpathInputEventScancode(d *decode.D) {
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/089d362b-31eb-4a1a-b6fa-92fe61bb5dbf
d.FieldU8("key_code", charMapper)
}
func parseFastpathInputEventMouse(d *decode.D) {
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/16a96ded-b3d3-4468-b993-9c7a51297510
d.FieldU16("pointer_flags", scalar.UintHex)
d.FieldU16("x")
d.FieldU16("y")
}
func parseFastpathInputEventMousex(d *decode.D) {
// https: //docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/2ef7632f-2f2a-4de7-ab58-2585cedcdf48
d.FieldU16("pointer_flags", scalar.UintHex)
d.FieldU16("x")
d.FieldU16("y")
}
func parseFastpathInputEventSync(d *decode.D) {
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/6c5d0ef9-4653-4d69-9ba9-09ba3acd660f
d.FieldU16("padding")
d.FieldU32("toggle_flags")
}
func parseFastpathInputEventUnicode(d *decode.D) {
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/e7b13e98-d800-42bb-9a1d-6948537d2317
d.FieldU16("unicode_code", scalar.UintHex)
}
func parseFastpathInputEventQoeTimestamp(d *decode.D) {}

32
format/pyrdp/pdu/util.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright (c) 2022-2023 GoSecure Inc.
package pyrdp
import (
"fmt"
"os"
"strings"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
"golang.org/x/text/encoding/unicode"
)
func toTextUTF16Fn(length int) func(d *decode.D) string {
return func(d *decode.D) string {
enc := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM)
decoder := enc.NewDecoder()
decoded, _ := decoder.String(string(d.BytesLen(length)))
return strings.TrimRight(decoded, "\x00")
}
}
func printPos(d *decode.D) {
fmt.Fprintf(os.Stderr, "Pos: %d\n", d.Pos())
}
var charMapper = scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
char := s.Actual
s.Sym = fmt.Sprintf("%c", int(char))
return s, nil
})

144
format/pyrdp/pyrdp.go Normal file
View File

@ -0,0 +1,144 @@
// Processes PyRDP replay files
// https://github.com/GoSecure/pyrdp
//
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
//
// Maintainer: Olivier Bilodeau <olivier.bilodeau@flare.io>
// Author: Lisandro Ubiedo
package pyrdp
import (
"time"
"github.com/wader/fq/format"
pyrdp "github.com/wader/fq/format/pyrdp/pdu"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
const (
READ_EXTRA = true
// PDU Types.
PDU_FAST_PATH_INPUT = 1 // Ex: scan codes, mouse, etc.
PDU_FAST_PATH_OUTPUT = 2 // Ex: image
PDU_CLIENT_INFO = 3 // Creds on connection
PDU_SLOW_PATH_PDU = 4 // For slow-path PDUs
PDU_CONNECTION_CLOSE = 5 // To advertise the end of the connection
PDU_CLIPBOARD_DATA = 6 // To collect clipboard data
PDU_CLIENT_DATA = 7 // Contains the clientName
PDU_MOUSE_MOVE = 8 // Mouse move event from the player
PDU_MOUSE_BUTTON = 9 // Mouse button event from the player
PDU_MOUSE_WHEEL = 10 // Mouse wheel event from the player
PDU_KEYBOARD = 11 // Keyboard event from the player
PDU_TEXT = 12 // Text event from the player
PDU_FORWARDING_STATE = 13 // Event from the player to change the state of I/O forwarding
PDU_BITMAP = 14 // Bitmap event from the player
PDU_DEVICE_MAPPING = 15 // Device mapping event notification
PDU_DIRECTORY_LISTING_REQUEST = 16 // Directory listing request from the player
PDU_DIRECTORY_LISTING_RESPONSE = 17 // Directory listing response to the player
PDU_FILE_DOWNLOAD_REQUEST = 18 // File download request from the player
PDU_FILE_DOWNLOAD_RESPONSE = 19 // File download response to the player
PDU_FILE_DOWNLOAD_COMPLETE = 20 // File download completion notification to the player
)
// TODO: Fill all descriptions.
var pduTypesMap = scalar.UintMap{
PDU_FAST_PATH_INPUT: {Sym: "pdu_fastpath_input", Description: ""},
PDU_FAST_PATH_OUTPUT: {Sym: "pdu_fastpath_output", Description: ""},
PDU_CLIENT_INFO: {Sym: "pdu_client_info", Description: ""},
PDU_SLOW_PATH_PDU: {Sym: "pdu_slow_path_pdu", Description: ""},
PDU_CONNECTION_CLOSE: {Sym: "pdu_connection_close", Description: ""},
PDU_CLIPBOARD_DATA: {Sym: "pdu_clipboard_data", Description: ""},
PDU_CLIENT_DATA: {Sym: "pdu_client_data", Description: ""},
PDU_MOUSE_MOVE: {Sym: "pdu_mouse_move", Description: ""},
PDU_MOUSE_BUTTON: {Sym: "pdu_mouse_button", Description: ""},
PDU_MOUSE_WHEEL: {Sym: "pdu_mouse_wheel", Description: ""},
PDU_KEYBOARD: {Sym: "pdu_keyboard", Description: ""},
PDU_TEXT: {Sym: "pdu_text", Description: ""},
PDU_FORWARDING_STATE: {Sym: "pdu_forwarding_state", Description: ""},
PDU_BITMAP: {Sym: "pdu_bitmap", Description: ""},
PDU_DEVICE_MAPPING: {Sym: "pdu_device_mapping", Description: ""},
PDU_DIRECTORY_LISTING_REQUEST: {Sym: "pdu_directory_listing_request", Description: ""},
PDU_DIRECTORY_LISTING_RESPONSE: {Sym: "pdu_directory_listing_response", Description: ""},
PDU_FILE_DOWNLOAD_REQUEST: {Sym: "pdu_file_download_request", Description: ""},
PDU_FILE_DOWNLOAD_RESPONSE: {Sym: "pdu_file_download_response", Description: ""},
PDU_FILE_DOWNLOAD_COMPLETE: {Sym: "pdu_file_download_complete", Description: ""},
}
var pduParsersMap = map[uint16]interface{}{
PDU_FAST_PATH_INPUT: pyrdp.ParseFastPathInput,
// PDU_FAST_PATH_OUTPUT: pyrdp.ParseFastPathOut,
PDU_CLIENT_INFO: pyrdp.ParseClientInfo,
// PDU_SLOW_PATH_PDU: pyrdp.ParseSlowPathPDU,
PDU_CONNECTION_CLOSE: noParse,
PDU_CLIPBOARD_DATA: pyrdp.ParseClipboardData,
PDU_CLIENT_DATA: pyrdp.ParseClientData,
// PDU_MOUSE_MOVE: pyrdp.ParseMouseMove,
// PDU_MOUSE_BUTTON: pyrdp.ParseMouseButton,
// PDU_MOUSE_WHEEL: pyrdp.ParseMouseWheel,
// PDU_KEYBOARD: pyrdp.ParseKeyboard,
// PDU_TEXT: pyrdp.ParseText,
// PDU_FORWARDING_STATE: pyrdp.ParseForwardingState,
// PDU_BITMAP: pyrdp.ParseBitmap,
// PDU_DEVICE_MAPPING: pyrdp.ParseDeviceMapping,
// PDU_DIRECTORY_LISTING_REQUEST: pyrdp.ParseDirectoryListingRequest,
// PDU_DIRECTORY_LISTING_RESPONSE: pyrdp.ParseDirectoryListingResponse,
// PDU_FILE_DOWNLOAD_REQUEST: pyrdp.ParseFileDownloadRequest,
// PDU_FILE_DOWNLOAD_RESPONSE: pyrdp.ParseFileDownloadResponse,
// PDU_FILE_DOWNLOAD_COMPLETE: pyrdp.ParseFileDownloadComplete,
}
func init() {
interp.RegisterFormat(
format.PYRDP,
&decode.Format{
Description: "PyRDP Replay Files",
DecodeFn: decodePYRDP,
})
}
func decodePYRDP(d *decode.D) any {
d.Endian = decode.LittleEndian
d.FieldArray("events", func(d *decode.D) {
for !d.End() {
d.FieldStruct("event", func(d *decode.D) {
pos := d.Pos()
size := d.FieldU64("size") // minus the length
pdu_type := uint16(d.FieldU16("pdu_type", pduTypesMap))
d.FieldU64("timestamp", timestampMapper)
pdu_size := int64(size - 18)
if _, ok := pduParsersMap[pdu_type]; !ok { // catch undeclared parsers
if pdu_size > 0 {
d.FieldRawLen("data", int64(pdu_size*8))
}
return
}
pduParsersMap[uint16(pdu_type)].(func(d *decode.D, length int64))(
d, pdu_size)
curr := d.Pos() - pos
if READ_EXTRA {
d.FieldRawLen("extra", (int64(size)*8)-curr) // seek whatever is left
} else {
d.SeekRel((int64(size) * 8) - curr) // read whatever is left
}
})
}
})
return nil
}
func noParse(d *decode.D, length int64) {
return
}
var timestampMapper = scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
s.Sym = time.UnixMilli(int64(s.Actual)).UTC().String()
return s, nil
})

3568
format/pyrdp/testdata/test.fqtest vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
format/pyrdp/testdata/test.pyrdp vendored Normal file

Binary file not shown.