深入解析RDP协议之 [MS-RDPBCGR]
这篇博客主要解析 [MS-RDPBCGR]相关协议和内容,建议先去看wpo篇。尚未完结,持续更新(本博客代码片段以客户端视角进行解析)
# 概述
在概述中首先明确了和其他协议的关系,以下提取一些开发中常用的协议:
x224,还有一些协议在 [MS-RDPBCGR] 静态虚拟通道内通过隧道传输,这部分是一些虚拟通道的拓展,熟知的有粘贴板通道、文件传输通道、智能卡通道、音频输出通道等。
目前我本身比较关注的是GDI和RFX编解码器拓展。这两个对与优化视频传输很重要。还包括对UDP的协商,这个好像很有意思还没深入看。
rdp的在协商Capability之前还会有一些操作,其中重要的是对错误信息PDU的支持,这个是必要的,因为服务器必须知道如何将错误传回给客户端。功能集中交换的信息包括支持的PDU (opens new window)和绘图顺序、桌面尺寸和允许的颜色深度、输入设备支持、缓存结构和功能支持等数据。
除非另有说明,消息中的所有多字节字段必须以 little-endian 字节顺序编组。
# TPKT
**TPKT 被正式定义为“TCP 之上的 ISO 传输服务”。“TCP”和“ISO”与两个相互竞争的网络协议套件有关。TPKT 支持在这两个组之间进行翻译。PDU使用tpkt协议封装,[MS-RDPBCGR] 数据包封装在传输控制协议 (TCP) (opens new window)中。TCP 数据包必须封装在 IP 协议的版本 4 或 6 中。**与大多数网络协议一样,TPKT 也可以反向工作。当一个 TPKT 数据包到达时,TPKT 剥离其数据包结构并将携带的数据包向上传递到协议栈。接收数据的 OSI 协议不知道 TCP/IP 参与了数据传输。
在解析之前需要先说一下tpkt header,这是tpkt协议的关键,因为这里就是header+payload
# Tpkt header:(t123 section 8)

# x224
The length of the CR-TPDU shall not exceed 128 octets.

我们就拿x224的CR举例说明吧,这个是在RDP协商中 X.224 0 类连接请求传输协议数据单元 (TPDU)。0类和1类x224CDT都会设置成0
整个octet2转10进制int就是224。在客户端发送x224 pdu时,通常讲将协商请求一同发送,具体可以参考RDP_NEG_REQ (opens new window)。在解析x224crq包时,通常不会单独的解析CR和CDT,直接读入uint8,如果是224,那么说明解析正确,LI是当作length字段的标识使用的。这里没有使用价值。这个包占用7个字节,读到code(CRCDT)后,还剩下五个字节。client端通常会写入全0,server端处理可以直接丢弃。DST-REF (2 bytes)SRC-REF (2 bytes) [CR, CC] CLASS OPTION (1 byte) or [DR] REASON (1 byte)。具体可以参考上图。接下来的解析就有三种情况了:
- Cookie
- RDP Negotiation Request
- rdpCorrelationInfo
具体这里不再累述,可以参考Client X.224 Connection Request PDU (opens new window)
# Client Side Example
let mut buffer = Cursor::new(self.transport.read(2)?);
let mut action: u8 = 0;
action.read(&mut buffer)?;
if action == Action::FastPathActionX224 as u8 {
// read padding
let mut padding: u8 = 0;
padding.read(&mut buffer)?;
// now wait extended header
buffer = Cursor::new(self.transport.read(2)?);
let mut size = U16::BE(0);
size.read(&mut buffer)?;
// Minimal size must be 7
if size.inner() < 4 {
Err(Error::RdpError(RdpError::new(
RdpErrorKind::InvalidSize,
"Invalid minimal size for TPKT",
)))
} else {
// now wait for body
Ok(Payload::Raw(Cursor::new(
self.transport.read(size.inner() as usize - 4)?,
)))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Test Case
let mut tpkt = tpkt::Client::new(link::Link::new(link::Stream::Raw(Cursor::new(vec![3, 0, 0, 10, 0, 4, 3, 0, 0, 0]))));
if let tpkt::Payload::Raw(c) = tpkt.read().unwrap() {
assert_eq!(c.into_inner(), vec![0, 4, 3, 0, 0, 0])
}
else {
panic!("unexpected result")
}
2
3
4
5
6
7
说到这里就不得不说rdp对tpkt的两层封装,第一种也就上面说明的,还有一种就是快速路径
# Client Side Fast Path
Fast-Path Input Event PDU 用于将输入事件从客户端传输到服务器。快速路径从第一个字节开始修改客户端输入数据包,目的是提高带宽。TPKT Header([T123] (opens new window)第 8 节(上面介绍过来了)、X.224 Class 0 Data TPDU([X224] (opens new window)第 13.7 节)和 MCS 发送数据请求([T125] (opens new window)第 11.32 节)被替换;安全头(第2.2.8.1.1.2 节) (opens new window)折叠到快速路径输入头中,共享数据头(第 2.2.8.1.1.1.2 节) (opens new window)被新的快速路径格式替换。输入通知事件的内容(第2.2.8.1.1.3.1.1 (opens new window)节)也被更改以减小它们的大小,特别是通过删除或减少标题。对快速路径输入的支持在输入能力集(第 2.2.7.1.6 节) (opens new window)。
| fpOutputHeader uint8 | length1 uint8 | length2 uint8(optional) |fipsInformation (4 bytes)(optional)|dataSignature (8 bytes)fpOutputHeader|fpOutputUpdates (variable)
# fpOutputHeader
在解析fastpath时,他的头只有一个单字节。这一个字节包含了两个信息
The format of the fpOutputHeader byte is described by the following bitmask diagram.

- Security flags
- Action code
pub enum Action {
FastPathActionFastPath = 0x0,
FastPathActionX224 = 0x3,
}
2
3
4
如何解析Security flags呢?可以看到flags在最后两比特位置,简单,直接与高6位全0低2位全1,其实就是3,action、reserved同理。
| Flag (2 bits) | Meaning |
|---|---|
| FASTPATH_OUTPUT_SECURE_CHECKSUM0x1 | Indicates that the MAC (opens new window) signature for the PDU was generated using the "salted MAC generation" technique (section 5.3.6.1.1 (opens new window)). If this bit is not set, then the standard technique was used (sections 2.2.8.1.1.2.2 (opens new window) and 2.2.8.1.1.2.3 (opens new window)). |
| FASTPATH_OUTPUT_ENCRYPTED0x2 | Indicates that the PDU contains an 8-byte MAC signature after the optional length2 field (that is, the dataSignature field is present), and the contents of the PDU are encrypted using the negotiated encryption package (sections 5.3.2 (opens new window) and 5.3.6 (opens new window)). |
action (2 bits): A 2-bit, unsigned integer that indicates whether the PDU is in fast-path or slow-path format.
| Value (2 bits) | Meaning |
|---|---|
| FASTPATH_OUTPUT_ACTION_FASTPATH0x0 | Indicates that the PDU (opens new window) is a fast-path output PDU. |
| FASTPATH_OUTPUT_ACTION_X2240x3 | Indicates the presence of a TPKT Header ([T123] section 8) initial version byte which indicates that the PDU is a slow-path output PDU (in this case the full value of the initial byte MUST be 0x03). |
# length1 (1 byte):
An 8-bit, unsigned integer. If the most significant bit of the length1 field is not set, then the size of the PDU is in the range 1 to 127 bytes and the length1 field contains the overall PDU length (the length2 field is not present in this case). However, if the most significant bit of the length1 field is set, then the overall PDU length is given by the low 7 bits of the length1 field concatenated with the 8 bits of the length2 field, in big-endian order (the length2 field contains the low-order bits). The overall PDU length SHOULD be less than or equal to 16,383 bytes.
意思是如果未设置length1字段的最高有效位,则 PDU 的大小在 1 到 127 个字节的范围内,并且length1字段包含整个 PDU 长度(在这种情况下不存在length2字段)。但是,如果设置了长度 1字段的最高有效位,则整个 PDU 长度由length 1 字段的低 7 位与length 2 字段的 8 位以大端顺序连接(长度 2字段包含低位)。整个 PDU 长度应该小于或等于 16,383 字节。也就是十五位最高位1低位全为0。
最高有效位也就是二进制中从高位到低位第一个为1的位。因为是单字节,最高有效位为1得数最小就是0x80。这个数我们后面计算需要用到。
# 如何计算length?
如果设置了最高有效位:
get length2 as uint16 //对照上面的解释更好理解 all = ((length1 & !0x80) << 8) as uint16|length2 as uint16 length = all -31
2
3
4如果没有设置最高有效位:
length = length1 as usize -21
具体解析直接上代码(部分伪代码)
// fast path
let mut short_length: u8 = 0;
short_length.read(&mut buffer)?;
if short_length & 0x80 != 0 {
let mut hi_length: u8 = 0;
hi_length.read(&mut Cursor::new(self.transport.read(1)?))?;
let length: u16 = ((short_length & !0x80) as u16) << 8;
let length = length | hi_length as u16;
read(length as usize - 3)
......
} else {
//注意这里不同于上面的是没有读length2,所以需要减2
read(short_length as usize - 2)
......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
今天就写到这里吧......2022/10/6
细心的同学在阅读快速路径的时候应该注意到了,他对tpkt、x224都有处理,这也正是我们上面着重介绍的,那么快速路径还对哪些协议或者相关封装也作了处理呢?上面已经说了,那就是msc、安全头、共享数据头、输入事件通知。那我们接下来就从这几个部分开始。
到这里rdp协议的头部分封装协议大致就介绍完了,下面我们会对每个过程进行分析,其实在介绍x224和tpkt时已经内在介绍了协商的第一和第二步,下面一方面介绍mcs,一方面继续介绍协商。
# MCS IN RDP
先解释一下什么是mcs,文档中是这样描述的。
Multipoint Communication Service (MCS): A data transmission protocol and set of services defined by the ITU T.120 standard, specifically [T122] (opens new window) and [T125] (opens new window).
简单的说就是个传输协议,其次是个多点传输协议。当然,包括一些服务集,具体就不深究了。**MCS Connect Initial PDU 封装了 GCC(Generic Conference Control) 会议创建请求,该请求封装了连接的设置数据块。**在协商阶段,通过使用 MCS Connect Initial PDU(第2.2.1.3 (opens new window)节)和 MCS Connect Response PDU(第2.2.1.4 (opens new window)节)在客户端和服务器之间交换基本设置。连接初始 PDU 包含一个通用会议控制 (GCC) 会议创建请求,而连接响应 PDU 包含一个 GCC 会议创建响应。这两个 GCC 数据包包含连接的设置数据块(例如核心数据、安全数据和网络数据),由客户端和服务器读取。
# MCS Connect Initial PDU
具体参考MCS Connect Initial PDU (opens new window)
| tpktHeader | x224Data| mcsCi (variable) |gccCCrq (variable) |clientCoreData (variable)|......|
客户端不同的设置数据有不同的标头,如下表:
The TS_UD_HEADER precedes all data blocks in the client and server GCC user data.
| Value | Meaning |
|---|---|
| CS_CORE0xC001 | The data block that follows contains Client Core Data (opens new window) (section 2.2.1.3.2). |
| CS_SECURITY0xC002 | The data block that follows contains Client Security Data (opens new window) (section 2.2.1.3.3). |
| CS_NET0xC003 | The data block that follows contains Client Network Data (opens new window) (section 2.2.1.3.4). |
| CS_CLUSTER0xC004 | The data block that follows contains Client Cluster Data (opens new window) (section 2.2.1.3.5). |
| CS_MONITOR0xC005 | The data block that follows contains Client Monitor Data (section 2.2.1.3.6 (opens new window)). |
| CS_MCS_MSGCHANNEL0xC006 | The data block that follows contains Client Message Channel Data (section 2.2.1.3.7 (opens new window)). |
| CS_MONITOR_EX0xC008 | The data block that follows contains Client Monitor Extended Data (section 2.2.1.3.9 (opens new window)). |
| CS_MULTITRANSPORT0xC00A | The data block that follows contains Client Multitransport Channel Data (section 2.2.1.3.8 (opens new window)). |
| SC_CORE0x0C01 | The data block that follows contains Server Core Data (opens new window) (section 2.2.1.4.2). |
| SC_SECURITY0x0C02 | The data block that follows contains Server Security Data (opens new window) (section 2.2.1.4.3). |
| SC_NET0x0C03 | The data block that follows contains Server Network Data (opens new window) (section 2.2.1.4.4). |
| SC_MCS_MSGCHANNEL0x0C04 | The data block that follows contains Server Message Channel Data (section 2.2.1.4.5 (opens new window)). |
| SC_MULTITRANSPORT0x0C08 | The data block that follows contains Server Multitransport Channel Data (section 2.2.1.4.6 (opens new window)). |
length (2 bytes): A 16-bit, unsigned integer. The size in bytes of the data block, including this header.
相应的链接响应:
# TS_UD_CS_CORE
具体参考TS_UD_CS_CORE (opens new window)
| header (4 bytes) | version (4 bytes) | desktopWidth (2 bytes) |desktopheight (2 bytes) |......|
这里携带概述中所提到了基本属性了,报错需要用到的通道名称(cliprdr、rdpsnd、rdpdr)、屏幕宽高、键盘、协商的加密套件协议、色深等等。包括上文提到的对错误信息PDU的支持。不废话,上代码
"version" => U32::LE(client_parameter.rdp_version as u32),
"desktopWidth" => U16::LE(client_parameter.width),
"desktopHeight" => U16::LE(client_parameter.height),
"colorDepth" => U16::LE(ColorDepth::RnsUdColor8BPP as u16),
"sasSequence" => U16::LE(Sequence::RnsUdSasDel as u16),
"kbdLayout" => U32::LE(client_parameter.layout as u32),
"clientBuild" => U32::LE(18363), // Windows 10, Version 1909, same as FreeRDP
"clientName" => client_name.to_string().to_unicode(),
"keyboardType" => U32::LE(KeyboardType::Ibm101102Keys as u32),
"supportedColorDepths" => U16::LE(
//Support::RnsUd15BPPSupport as u16 |
Support::RnsUd16BPPSupport as u16 |
//Support::RnsUd24BPPSupport as u16 |
Support::RnsUd32BPPSupport as u16
),
"earlyCapabilityFlags" => U16::LE(capability_flags),
"clientDigProductId" => vec![0; 64],
"connectionType" => client_parameter.connection_type.map(|c|c as u8).unwrap_or(0),
"pad1octet" => 0 as u8,
"serverSelectedProtocol" => U32::LE(client_parameter.server_selected_protocol)
......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# TS_UD_CS_SEC
具体参考TS_UD_CS_SEC (opens new window)
| header (4 bytes) | encryptionMethods (4 bytes) | extEncryptionMethods (4 bytes) |extEncryptionMethods |
客户端安全数据主要是客户端支持并与标准 RDP 安全性结合使用的加密方法。客户端必须指定至少一种加密方法,服务器必须选择客户端指定的方法之一。
| Flag | Meaning |
|---|---|
| 40BIT_ENCRYPTION_FLAG0x00000001 | 40-bit session keys MUST be used to encrypt data (with RC4 (opens new window)) and generate Message Authentication Codes (MAC) (opens new window). |
| 128BIT_ENCRYPTION_FLAG0x00000002 | 128-bit session keys MUST be used to encrypt data (with RC4) and generate MACs. |
| 56BIT_ENCRYPTION_FLAG0x00000008 | 56-bit session keys MUST be used to encrypt data (with RC4) and generate MACs. |
| FIPS_ENCRYPTION_FLAG0x00000010 | All encryption and Message Authentication Code generation routines MUST be Federal Information Processing Standard (FIPS) 140-1 compliant. |
其他的属性设置这里就不多做介绍了,感兴趣的读者自行研究。不过回过头来看,我们好像没有分析mcsCi这个字段!其实不是博主漏掉了,因为这个变量关联到后面的userdata,也就是属性的设置,所以我们先看属性,再来分析这个变量。
# mcsCi
这里要想把这个鬼玩意说清楚,那就必须搬出来 T125 (opens new window) (section 11.1)了。这个说不清楚,不介绍了。xrdp对这个都没有处理。

# TS_UD_CS_MULTITRANSPORT
rdp当然是封装在tcp/ip协议中的,I/O 通道([MS-RDPBCGR] 第3.2.1.3 (opens new window) 和3.3.1.3 (opens new window)节)和可选消息通道([MS-RDPBCGR] 第 3.2.1.3 和3.3.1.5 (opens new window)节)封装在主 RDP 连接中并使用传输核心 RDP PDU、输入和图形数据。除了这两个通道之外,还有一组协商的静态虚拟通道([MS-RDPBCGR] 第1.3.3 (opens new window)节)。其中一个名为“DRDYNVC”的静态虚拟通道复用了一组动态虚拟通道([MS-RDPEDYC] (opens new window) 第1节 (opens new window), 2 (opens new window) 和3 (opens new window) )。
多传输连接通过单独的传输协议运行到主 RDP 连接,并多路复用一组选定的动态虚拟通道。下图说明了 RDP 通道和传输的层次结构和封装。

