1
1
mirror of https://github.com/wader/fq.git synced 2024-10-05 16:07:23 +03:00

Compare commits

...

18 Commits

Author SHA1 Message Date
Mattias Wadman
69ec44a934 pyrdp: Decode client info flags correctly
Flags bits are in LE byte order
Removed unsued info consts
2024-06-16 23:18:32 +02:00
Mattias Wadman
92ad156496 pyrdp: Reorganize pdu consts a bit 2024-06-16 22:42:28 +02:00
Mattias Wadman
86f28b6c21 pyrdp: Move pdu types into pdu package and some cleeanup 2024-06-16 22:42:28 +02:00
Mattias Wadman
36d5cd47ec pyrdp: Simplify always true condition 2024-06-16 22:42:28 +02:00
Mattias Wadman
3986f2029e pyrdp: Add unused fields to replace gap fields 2024-06-16 22:42:28 +02:00
Mattias Wadman
c59e7cc38e pyrdp: Less symbol stuttering 2024-06-16 22:42:28 +02:00
Mattias Wadman
a6b24688d7 pyrdp: camelCase vars and lowercase number literals 2024-06-16 22:42:28 +02:00
Mattias Wadman
67bf55ef3d pyrdp: Use field description for formatted timestamp
Same as other formats that use timestamps and makes it easier to use them in query,
number instead of a formatted string. Can still use todescription/0 to get
formatted timestamp.
2024-06-16 22:42:28 +02:00
Mattias Wadman
a79f64f593 help: Render mailto:addr as <addr> 2024-06-16 22:42:28 +02:00
Mattias Wadman
6ae10da44b pyrdp: Add doc and move init func to top 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
dab2c6ee7d Resolved all linter errors 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
fd85952648 doc: added format to README 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
ec03f87cd9 removed dead code 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
051493dee2 refactor: package name aligned with directory name 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
3904c45495 refactoring: UintMap to UintMapSymStr 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
87633e6eff refactor: Changed all FieldStrFn callers to FieldUTF16LE
One fieldname change clientDir to client_dir to align with everything else
2024-06-16 22:42:28 +02:00
Olivier Bilodeau
392fabefdf Clarified MIT license usage alongside copyright info 2024-06-16 22:42:28 +02:00
Olivier Bilodeau
5d35eb6a43 Sending PyRDP parser upstream
For project history see: https://github.com/GoSecure/fq-pyrdp/

Co-authored-by: Lisandro Ubiedo <lubiedo@gosecure.net>
2024-06-16 22:42:28 +02:00
15 changed files with 4149 additions and 1 deletions

View File

@ -138,6 +138,7 @@ prores_frame,
[protobuf](doc/formats.md#protobuf),
protobuf_widevine,
pssh_playready,
[pyrdp](doc/formats.md#pyrdp),
[rtmp](doc/formats.md#rtmp),
sll2_packet,
sll_packet,

View File

@ -110,6 +110,7 @@
|[`protobuf`](#protobuf) |Protobuf |<sub></sub>|
|`protobuf_widevine` |Widevine&nbsp;protobuf |<sub>`protobuf`</sub>|
|`pssh_playready` |PlayReady&nbsp;PSSH |<sub></sub>|
|[`pyrdp`](#pyrdp) |PyRDP&nbsp;Replay&nbsp;Files |<sub></sub>|
|[`rtmp`](#rtmp) |Real-Time&nbsp;Messaging&nbsp;Protocol |<sub>`amf0` `mpeg_asc`</sub>|
|`sll2_packet` |Linux&nbsp;cooked&nbsp;capture&nbsp;encapsulation&nbsp;v2 |<sub>`inet_packet`</sub>|
|`sll_packet` |Linux&nbsp;cooked&nbsp;capture&nbsp;encapsulation |<sub>`inet_packet`</sub>|
@ -1195,6 +1196,16 @@ $ fq -d protobuf '.fields[6].wire_value | protobuf | d' file
### References
- https://developers.google.com/protocol-buffers/docs/encoding
## pyrdp
PyRDP Replay Files.
### Authors
- Olivier Bilodeau <olivier.bilodeau@flare.io>, Maintainer
- Lisandro Ubiedo, Author
### References
- https://github.com/GoSecure/pyrdp
## rtmp
Real-Time Messaging Protocol.

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

@ -57,7 +57,11 @@ def _markdown_children_to_text($width):
| join("")
) as $text
| if $text == .destination then $text
else "\($text) (\(.destination))"
else
if .destination | startswith("mailto:") then
"<\(.destination[7:])>"
else "\($text) (\(.destination))"
end
end
)
elif .type == "code_block" then .literal | rtrimstr("\n") | split("\n") | " " + join("\n ")

View File

@ -0,0 +1,109 @@
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
// Licensed under the MIT License
package pdu
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.UintMapSymStr{
RDP4: "4",
RDP5: "5",
RDP10: "10",
RDP10_1: "10_1",
RDP10_2: "10_2",
RDP10_3: "10_3",
RDP10_4: "10_4",
RDP10_5: "10_5",
RDP10_6: "10_6",
RDP10_7: "10_7",
RDP10_8: "10_8",
RDP10_9: "10_9",
RDP10_10: "10_10",
}
const (
CLIENT_CORE = 0xc001
CLIENT_SECURITY = 0xc002
CLIENT_NETWORK = 0xc003
CLIENT_CLUSTER = 0xc004
)
var clientDataMap = scalar.UintMapSymStr{
CLIENT_CORE: "core",
CLIENT_SECURITY: "security",
CLIENT_NETWORK: "network",
CLIENT_CLUSTER: "cluster",
}
func parseClientData(d *decode.D, length int64) {
d.FieldStruct("client_data", func(d *decode.D) {
header := d.FieldU16("header", clientDataMap)
dataLen := int64(d.FieldU16("length") - 4)
switch header {
case CLIENT_CORE:
ParseClientDataCore(d, dataLen)
case CLIENT_SECURITY:
ParseClientDataSecurity(d, dataLen)
case CLIENT_NETWORK:
ParseClientDataNetwork(d, dataLen)
case CLIENT_CLUSTER:
ParseClientDataCluster(d, dataLen)
default:
// Assert() once all functions are implemented and tested.
d.FieldRawLen("data", dataLen*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.FieldUTF16LE("client_name", 32, scalar.StrActualTrim("\x00"))
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,90 @@
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
// Licensed under the MIT License
package pdu
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 (
isUnicode bool
hasNull bool
nullN uint64 = 0
unicodeN uint64 = 0
)
codePage := d.FieldU32("code_page")
d.FieldStruct("flags", func(d *decode.D) {
d.FieldBool("compression")
d.FieldBool("logonnotify")
d.FieldBool("maximizeshell")
isUnicode = d.FieldBool("unicode")
d.FieldBool("autologon")
d.FieldRawLen("unused0", 1)
d.FieldBool("disabledctrlaltdel")
d.FieldBool("mouse")
d.FieldBool("rail")
d.FieldBool("force_encrypted_cs_pdu")
d.FieldBool("remoteconsoleaudio")
d.FieldRawLen("unused1", 4)
d.FieldBool("enablewindowskey")
d.FieldBool("reserved1")
d.FieldBool("video_disable")
d.FieldBool("audiocapture")
d.FieldBool("using_saved_creds")
d.FieldBool("noaudioplayback")
d.FieldBool("password_is_sc_pin")
d.FieldBool("mouse_has_wheel")
d.FieldBool("logonerrors")
d.FieldRawLen("unused2", 6)
d.FieldBool("hidef_rail_supported")
d.FieldBool("reserved2")
})
hasNull = (codePage == 1252 || isUnicode)
if hasNull {
nullN = 1
}
if isUnicode {
unicodeN = 2
}
domainLength := int(d.FieldU16("domain_length") + nullN*unicodeN)
usernameLength := int(d.FieldU16("username_length") + nullN*unicodeN)
passwordLength := int(d.FieldU16("password_length") + nullN*unicodeN)
alternateShellLength := int(d.FieldU16("alternate_shell_length") + nullN*unicodeN)
workingDirLength := int(d.FieldU16("working_dir_length") + nullN*unicodeN)
d.FieldUTF16LE("domain", domainLength, scalar.StrActualTrim("\x00"))
d.FieldUTF16LE("username", usernameLength, scalar.StrActualTrim("\x00"))
d.FieldUTF16LE("password", passwordLength, scalar.StrActualTrim("\x00"))
d.FieldUTF16LE("alternate_shell", alternateShellLength, scalar.StrActualTrim("\x00"))
d.FieldUTF16LE("working_dir", workingDirLength, scalar.StrActualTrim("\x00"))
extraLength := length - ((d.Pos() - pos) / 8)
if extraLength > 0 {
d.FieldStruct("extra_info", func(d *decode.D) {
d.FieldU16("address_family", scalar.UintHex)
addressLength := int(d.FieldU16("address_length"))
d.FieldUTF16LE("address", addressLength, scalar.StrActualTrim("\x00"))
clientDirLength := int(d.FieldU16("client_dir_length"))
d.FieldUTF16LE("client_dir", clientDirLength, scalar.StrActualTrim("\x00"))
// 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.FieldUTF16LE("timezone_standardname", 64, scalar.StrActualTrim("\x00"))
})
// XXX: there's more extra info but here's everything we need from the
// client (other than UTC info)
}
})
}

View File

@ -0,0 +1,80 @@
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
// Licensed under the MIT License
package pdu
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
const (
// Message types.
CB_TYPE_MONITOR_READY = 0x0001
CB_TYPE_FORMAT_LIST = 0x0002
CB_TYPE_FORMAT_LIST_RESPONSE = 0x0003
CB_TYPE_FORMAT_DATA_REQUEST = 0x0004
CB_TYPE_FORMAT_DATA_RESPONSE = 0x0005
CB_TYPE_TEMP_DIRECTORY = 0x0006
CB_TYPE_CLIP_CAPS = 0x0007
CB_TYPE_FILECONTENTS_REQUEST = 0x0008
CB_TYPE_FILECONTENTS_RESPONSE = 0x0009
CB_TYPE_LOCK_CLIPDATA = 0x000a
CB_TYPE_UNLOCK_CLIPDATA = 0x000b
)
var cbTypesMap = scalar.UintMapSymStr{
CB_TYPE_MONITOR_READY: "monitor_ready",
CB_TYPE_FORMAT_LIST: "format_list",
CB_TYPE_FORMAT_LIST_RESPONSE: "format_list_response",
CB_TYPE_FORMAT_DATA_REQUEST: "format_data_request",
CB_TYPE_FORMAT_DATA_RESPONSE: "format_data_response",
CB_TYPE_TEMP_DIRECTORY: "temp_directory",
CB_TYPE_CLIP_CAPS: "clip_caps",
CB_TYPE_FILECONTENTS_REQUEST: "filecontents_request",
CB_TYPE_FILECONTENTS_RESPONSE: "filecontents_response",
CB_TYPE_LOCK_CLIPDATA: "lock_clipdata",
CB_TYPE_UNLOCK_CLIPDATA: "unlock_clipdata",
}
const (
// Message flags.
CB_FLAG_NONE = 0
CB_FLAG_RESPONSE_OK = 0x0001
CB_FLAG_RESPONSE_FAIL = 0x0002
CB_FLAG_ASCII_NAMES = 0x0004
)
var cbFlagsMap = scalar.UintMapSymStr{
CB_FLAG_NONE: "none",
CB_FLAG_RESPONSE_OK: "response_ok",
CB_FLAG_RESPONSE_FAIL: "response_fail",
CB_FLAG_ASCII_NAMES: "ascii_names",
}
var cbParseFnMap = map[uint16]interface{}{
CB_TYPE_FORMAT_DATA_RESPONSE: parseCbFormatDataResponse,
}
func parseClipboardData(d *decode.D, length int64) {
d.FieldStruct("clipboard_data", func(d *decode.D) {
msgType := uint16(d.FieldU16("msg_type", cbTypesMap))
d.FieldU16("msg_flags", cbFlagsMap)
dataLength := d.FieldU32("data_len")
cbParser, ok := cbParseFnMap[msgType]
if ok {
parseFn, ok := cbParser.(func(d *decode.D, length uint64))
if ok {
parseFn(d, dataLength)
return
}
}
// Assert() once all functions are implemented.
d.FieldRawLen("data", int64(dataLength*8))
})
}
func parseCbFormatDataResponse(d *decode.D, length uint64) {
d.FieldRawLen("data", int64(length*8))
}

View File

@ -0,0 +1,129 @@
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
// Licensed under the MIT License
package pdu
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
)
// commented because unused but we should use one-day
//var eventCodesMap = scalar.UintMapSymStr{
// FASTPATH_INPUT_EVENT_SCANCODE: "fastpath_input_event_scancode",
// FASTPATH_INPUT_EVENT_MOUSE: "fastpath_input_event_mouse",
// FASTPATH_INPUT_EVENT_MOUSEX: "fastpath_input_event_mousex",
// FASTPATH_INPUT_EVENT_SYNC: "fastpath_input_event_sync",
// FASTPATH_INPUT_EVENT_UNICODE: "fastpath_input_event_unicode",
// FASTPATH_INPUT_EVENT_QOE_TIMESTAMP: "fastpath_input_event_qoe_timestamp",
//}
//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.")
}
})
inputLength := d.FieldU8("input_length1", scalar.UintHex)
if inputLength&0x80 != 0 {
inputLength = ((inputLength & 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)
// }
// }
// })
inputLength -= uint64(d.Pos()-pos) / 8
if inputLength > 0 {
d.FieldRawLen("data", int64(inputLength*8))
}
})
}
//commented because unused but we should use one-day
//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) {}

78
format/pyrdp/pdu/types.go Normal file
View File

@ -0,0 +1,78 @@
package pdu
import (
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
const (
// PDU Types.
TYPE_FAST_PATH_INPUT = 1 // Ex: scan codes, mouse, etc.
TYPE_FAST_PATH_OUTPUT = 2 // Ex: image
TYPE_CLIENT_INFO = 3 // Creds on connection
TYPE_SLOW_PATH_PDU = 4 // For slow-path PDUs
TYPE_CONNECTION_CLOSE = 5 // To advertise the end of the connection
TYPE_CLIPBOARD_DATA = 6 // To collect clipboard data
TYPE_CLIENT_DATA = 7 // Contains the clientName
TYPE_MOUSE_MOVE = 8 // Mouse move event from the player
TYPE_MOUSE_BUTTON = 9 // Mouse button event from the player
TYPE_MOUSE_WHEEL = 10 // Mouse wheel event from the player
TYPE_KEYBOARD = 11 // Keyboard event from the player
TYPE_TEXT = 12 // Text event from the player
TYPE_FORWARDING_STATE = 13 // Event from the player to change the state of I/O forwarding
TYPE_BITMAP = 14 // Bitmap event from the player
TYPE_DEVICE_MAPPING = 15 // Device mapping event notification
TYPE_DIRECTORY_LISTING_REQUEST = 16 // Directory listing request from the player
TYPE_DIRECTORY_LISTING_RESPONSE = 17 // Directory listing response to the player
TYPE_FILE_DOWNLOAD_REQUEST = 18 // File download request from the player
TYPE_FILE_DOWNLOAD_RESPONSE = 19 // File download response to the player
TYPE_FILE_DOWNLOAD_COMPLETE = 20 // File download completion notification to the player
)
var TypesMap = scalar.UintMapSymStr{
TYPE_FAST_PATH_INPUT: "fastpath_input",
TYPE_FAST_PATH_OUTPUT: "fastpath_output",
TYPE_CLIENT_INFO: "client_info",
TYPE_SLOW_PATH_PDU: "slow_path_pdu",
TYPE_CONNECTION_CLOSE: "connection_close",
TYPE_CLIPBOARD_DATA: "clipboard_data",
TYPE_CLIENT_DATA: "client_data",
TYPE_MOUSE_MOVE: "mouse_move",
TYPE_MOUSE_BUTTON: "mouse_button",
TYPE_MOUSE_WHEEL: "mouse_wheel",
TYPE_KEYBOARD: "keyboard",
TYPE_TEXT: "text",
TYPE_FORWARDING_STATE: "forwarding_state",
TYPE_BITMAP: "bitmap",
TYPE_DEVICE_MAPPING: "device_mapping",
TYPE_DIRECTORY_LISTING_REQUEST: "directory_listing_request",
TYPE_DIRECTORY_LISTING_RESPONSE: "directory_listing_response",
TYPE_FILE_DOWNLOAD_REQUEST: "file_download_request",
TYPE_FILE_DOWNLOAD_RESPONSE: "file_download_response",
TYPE_FILE_DOWNLOAD_COMPLETE: "file_download_complete",
}
func noParse(d *decode.D, length int64) {}
var ParsersMap = map[uint16]interface{}{
TYPE_FAST_PATH_INPUT: parseFastPathInput,
// TYPE_FAST_PATH_OUTPUT: parseFastPathOut,
TYPE_CLIENT_INFO: parseClientInfo,
// TYPE_SLOW_PATH_PDU: parseSlowPathPDU,
TYPE_CONNECTION_CLOSE: noParse,
TYPE_CLIPBOARD_DATA: parseClipboardData,
TYPE_CLIENT_DATA: parseClientData,
// TYPE_MOUSE_MOVE: parseMouseMove,
// TYPE_MOUSE_BUTTON: parseMouseButton,
// TYPE_MOUSE_WHEEL: parseMouseWheel,
// TYPE_KEYBOARD: parseKeyboard,
// TYPE_TEXT: parseText,
// TYPE_FORWARDING_STATE: parseForwardingState,
// TYPE_BITMAP: parseBitmap,
// TYPE_DEVICE_MAPPING: parseDeviceMapping,
// TYPE_DIRECTORY_LISTING_REQUEST: parseDirectoryListingRequest,
// TYPE_DIRECTORY_LISTING_RESPONSE: parseDirectoryListingResponse,
// TYPE_FILE_DOWNLOAD_REQUEST: parseFileDownloadRequest,
// TYPE_FILE_DOWNLOAD_RESPONSE: parseFileDownloadResponse,
// TYPE_FILE_DOWNLOAD_COMPLETE: parseFileDownloadComplete,
}

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

@ -0,0 +1,68 @@
// Processes PyRDP replay files
// https://github.com/GoSecure/pyrdp
//
// Copyright (c) 2022-2023 GoSecure Inc.
// Copyright (c) 2024 Flare Systems
// Licensed under the MIT License
//
// Maintainer: Olivier Bilodeau <olivier.bilodeau@flare.io>
// Author: Lisandro Ubiedo
package pyrdp
import (
"embed"
"time"
"github.com/wader/fq/format"
"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"
)
//go:embed pyrdp.md
var pyrdpFS embed.FS
func init() {
interp.RegisterFormat(
format.PYRDP,
&decode.Format{
Description: "PyRDP Replay Files",
DecodeFn: decodePYRDP,
})
interp.RegisterFS(pyrdpFS)
}
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
pduType := uint16(d.FieldU16("pdu_type", pdu.TypesMap))
d.FieldU64("timestamp", scalar.UintActualUnixTimeDescription(time.Millisecond, time.RFC3339Nano))
pduSize := int64(size - 18)
pduParser, ok := pdu.ParsersMap[pduType]
if !ok { // catch undeclared parsers
if pduSize > 0 {
d.FieldRawLen("data", pduSize*8)
}
return
}
parseFn, ok := pduParser.(func(d *decode.D, length int64))
if !ok {
return
}
parseFn(d, pduSize)
curr := d.Pos() - pos
d.FieldRawLen("extra", (int64(size)*8)-curr) // seek whatever is left
})
}
})
return nil
}

6
format/pyrdp/pyrdp.md Normal file
View File

@ -0,0 +1,6 @@
### Authors
- Olivier Bilodeau <olivier.bilodeau@flare.io>, Maintainer
- Lisandro Ubiedo, Author
### References
- https://github.com/GoSecure/pyrdp

3569
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.