概述
RFB(远程帧缓冲)是一个用于远程访问图形用户接口的简单协议。
适用于所有的桌面系统和应用,包括X11,Windows和Macintosh等。
- 把用户所在的一端(包括显示器、键盘和鼠标)被称为RFB客户端。
- 而帧缓冲发生变化的一端(桌面系统和应用)称为RFB服务器。
RFB协议是一个瘦客户协议。协议设计的重点是减小对客户端的要求。这样,客户端可以运行在多种范围的硬件上,实现的任务是使客户端尽可能地简单。
RFB协议也使得客户端是“无状态”的。如果一个客户端和服务器断开了连接,稍后再一次连接到这台服务器上,用户的会话不会被关闭,状态会一直保持着。
显示
RFB协议的显示部分基于一个简单的画图原理:“将一个矩形块的象素点放在给定位置(x,y)上”。这样做初看起来也许非常低效,因为要将用户所有的图形组件都画出来。
但是由于可以为象素数据进行多种不同的编码,可以根据不同的参数比如网络带宽、客户端计算速度和服务器处理的速度等选择灵活的编码方式。
- 一系列的矩形块组成了一个帧缓冲更新。
- 一个更新描述了帧缓冲从一个状态到另一个状态的变化情况。
输入
- 输入协议是基于键盘和多键鼠标设备的标准工作站模型
- 当用户敲了一下键盘或者鼠标,或者移动了一下鼠标,客户端把这些输入事件简单地传送给服务器
- 输入事件可以由其它非标准I/O设备产生,如笔形手写板引擎也可以生成键盘事件
- 事件按照RFB协议规定的格式进行发送即可
- 之后服务端对事件进行相应处理
像素数据的表示
RFB客户端和服务器最初的交互包括协商将要传输的象素数据的格式和编码类型。
协商被设计成使客户端的工作尽可能的简单。底线是服务器必须可以一直提供客户端想要的象素数据的格式。(但是如果客户端可以处理多种编码类型,它会选择对服务器来说最容易生成的编码。)
象素格式是指象素值颜色表示法
- 最常用的象素格式是24位或16位真彩色
- 它通过位来直接实现像素值到红、绿、蓝亮度的转换
- 8 位“颜色映射”可以任意映射像素值到RGB亮度的转换
- 编码是指怎样通过网络把矩形象素点的数据发送出去
- 每一个矩形象素点的数据被加了一个前缀包括它在屏幕上的位置,矩形象素点的宽度和高度,以及一个“编码类型”来描述该象素的编码方式
- 然后是经过编码的数据本身
- 目前的编码方式主要有
Raw
CopyRect
RRE
Hextile
ZRLE
- 在实际应用中我们一般使用
ZRLE
、Hextile
和CopyRect
,因为它们提供了典型桌面的最好压缩 - 其他可能的编码方式还包括,用于静态图片的JPEG和用于动态图像有效传输的MPEG。协议可以通过增加新的编码方式来进行扩展
协议的扩展
- 新的编码方式
- 一种新的协议可以通过与现存的客户端和服务端进行相关兼容的添加。因为现存的服务器将会忽略它们所不支持的新编码方式。所以客户端通过新的编码方式进行请求也就不会有结果返回
- 伪编码方式
- 除了真正的编码方式,客户端也可以请求“伪编码”通告服务器,它支持某一协议的扩展。服务器如果不支持这种扩展,那么它将忽略
- 客户端必须先假设服务器端不支持这种扩展,直到它获得服务器端支持的确认
- 新的安全方式
- 添加一个新型的安全方式会带来无限的灵活性,它通过修改协议的一些行为,但是并没有牺牲现存客户端和服务器端的兼容性
- 客户端和服务器端可以通过协议好的安全方式进行交流,当然并不一定与RFB协议类似
协议消息
RFB协议可以进行可靠的传输,如字节流或基于消息的。
和大多数协议一样,它也是通过TCP /IP协议簇连接。
协议由三部分组成:
- 握手报文
- 对协议版本和加密方式进行协商
- 初始化报文
- 用于客户端和服务器的初始化消息
- 正常协议的交互
- 客户端可以按需发送消息,然后可以获得服务器的回复
所有的消息以消息类型开始,接下来是特定的消息数据。
协议消息描述的基本类型有:U8、U16、U32、S8、S16、S32。(U表示无符号整数,S表示有符号整数。)
所有字节整数(除了像素值本身)遵从big endian顺序。
关于大端小端:
不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序
- 小端
- 将低序字节存储在起始地址
- 大端
- 将高序字节存储在起始地址
示例:
内存地址:4000-4001-4002-4003
在内存中0X01020304的存储方式:
- 小端
04030201
- 大端
01020304
X86系列的CPU都是小端模式的字节序
在内存中
0X01020304
的存储方式
协议流程
协议详情
握手消息
RFB协议版本号
- VNC服务器发送能够支持的最高RFB协议版本号给客户端
- 格式如下
RFB xxx.yyy\n
共12个字节- 例如:
RFB 003.006\n
:版本号为3.6
- 客户端回复将要使用的版本号
- 格式如上
- 注:客户端的版本号要小于等于服务器的版本号,便于服务器向后兼容
- 目前发布的协议版本主要有
3.3
3.5
- 该版本被报告存在问题
3.7
3.8
4.0
协议安全类型
v3.7以上版本
- 服务器发送所支持的安全类型列表
No. of bytes | Type | Description |
1 | U8 | number-of-security-types |
number-of-security-types | U8 array | security-types |
- 如果客户端能支持服务器的某一安全类型,那么客户端就会发送一个字节来确认连接的安全类型
No. of bytes | Type | Description |
1 | U8 | security-types |
- 如果安全类型数是0,那么连接失败,会有字符串描述失败原因
No. of bytes | Type | Description |
4 | U32 | reason-length |
reason-length | U8 array | reason-string |
- 服务器发送完原因描述字符串之后,就会关闭连接
v3.7以下版本
- 服务器先发送一个无符号的32位整数,标识一个安全类型(用于认证)
No. of bytes | Type | Description |
4 | U32 | security-type |
安全类型
Number | Name |
0 | Invalid |
1 | None |
2 | VNC Authentication |
- Invalid`
- 连接失败(例如服务器不支持客户端请求版本号)
None
- 不需要认证(不需要输入密码),协议数据将被使用明文发送
VNC Authentication
- 协议数据将采用明文发送,服务器发送一个16字节的随机数
其他认证的类型:
Number | Name |
5 | RA2 |
6 | RA2ne |
16 | Tight |
17 | Ultra |
18 | TLS |
19 | VeNCrypt |
服务器发送16位随机数。
客户端使用DES
对验证进行加密,使用用户密码作为密钥,把16字节的回复返回到服务器
No. of bytes | Type | Description |
16 | U8 | response |
服务器对安全认证进行确认,返回值为无符号32位整数,如果为0则表示成功,1表示失败。如果不成功,服务器直接关闭连接。
No. of bytes | Type | Description |
4 | U32 | status: |
4 | 0 | OK |
4 | 1 | Failed |
这一步,V3.8以下的版本,如果不成功,服务器直接关闭连接。V3.8以上的版本会返回失败原因,在关闭连接
No. of bytes | Type | Description |
4 | U32 | reason-length |
reason-length | U8 array | reason-string |
初始化消息
- 客户端发送一个字节的初始化消息
No. of bytes | Type | Description |
1 | U8 | shared-flag |
如果允许服务器其他客户继续连接,那么shared-flag应该是非零(真)。否则,服务器将断开其他客户的连接
- 服务器发送初始化消息,主要告知客户端服务器的帧缓存(桌面屏幕)的高、宽、象素格式和桌面相关的名称
No. of bytes | Type | Description |
2 | U16 | framebufffer-width |
2 | U16 | framebuffer-height |
16 | PIXEL_FORMAT | server-pixel-format |
4 | U32 | name-length |
name-length | U8 array | name-string |
帧缓存宽度一般为水平分辨率的大小,帧缓存高度一般是垂直分辨率的大小,比如1024×768等
No. of bytes | Type | Description |
1 | U8 | bits-per-pixel |
1 | U8 | depth |
1 | U8 | big-endian-flag |
1 | U8 | true-colour-flag |
2 | U16 | red-max |
2 | U16 | green-max |
2 | U16 | blue-max |
1 | U8 | red-shift |
1 | U8 | green-shift |
1 | U8 | blue-shift |
3 | padding |
服务器象素定义服务器本来的象素格式,这种象素格式会被一直使用,除非客户端使用设置象素格式消息来请求另一种象素格式
bits-per-pixel是表示每个像素值需要的位数。这个数字必须大于等于depth.
depth用来表示像素值中有用的位数。
目前位每象素必须是8,16 或32——小于8 位象素不被支持。如果多字节象素被看做**big-endian**,那么Big-endian 标志非零。当然了,这对8 位每象素没有任何意义。
如果真彩标志非零,那么最后6 项规定如何按照象素值来确定红、绿、蓝的亮度。
如果真彩标志是零,那么服务器使用的象素值不是直接由红、绿、蓝的亮度组成,但是服务为索引到颜色图中去。颜色图中的项目是由服务器使用“设置颜色面板条目” (FixColourMapEntries)消息进行设置的。
客户端收到服务器消息
所有客户端到服务器的消息第一个字节都为消息类型,数据类型U8。
客户端到服务器的消息定义如下:
Number | Name |
0 | SetPixelFormat |
2 | SetEncodings |
3 | FramebufferUpdateRequest |
4 | KeyEvent |
5 | PointerEvent |
6 | ClientCutText |
其余的注册消息类型:
Number | Name |
255 | Anthony Liguori |
值得注意的是:如果要发送未在本文中定义的消息,那么必须得到服务器端的消息确认。
SetPixelFormat设置像素格式消息
如果客户端没有发送“设置象素格式”消息,那么服务器发送的象素值将遵循在服务器初始化消息中所包括的象素格式。
如果真彩标志是零,那么意味着使用“颜色面板”,只要客户端发送颜色面板空的消息,或者是面板项被服务器端重设,服务器可以使用设置颜色面板项目进行颜色面板的设置。
No. of bytes | Type | Description |
1 | U8 | message-type |
3 | padding | |
16 | PIXEL_FORMAT | pixel-format |
SetEncoding设置编码格式
设置编码方式可以来确定服务器发送象素数据的类型。消息中编码方式的顺序是客户端按照优先级来排列(第一个拥有最高的优先级)。服务器可能选择这种顺序,也可能不选择。
象素数据也可以使用“原始编码”如果没有具体说明。
除了基本的编码方式,客户端也可以请求“伪编码”通告服务器它支持某一种扩展协议。如果服务器不支持这种扩展,它就会忽略这种伪编码。注意:这意味着客户端在得到服务器的确认之前都要假设服务器并不支持它的扩展。
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | padding | |
2 | U16 | number-of-encodings |
FramebufferUpdateRequest请求帧缓存更新
通知服务器,客户对帧缓冲区中的某个区域感兴趣,这个区域由x坐标、y坐标、宽度和高度几个参数限定。
服务器通常对FramebufferUpdateRequest消息的响应,是发送一条FramebufferUpdate消息。
注意,可以发送一条FramebufferUpdate消息用来回复几条FramebufferUpdateRequest消息。
服务器假定客户保留了帧服务器中它感兴趣的所有部分的副本。这意味着,服务器通常只需要向客户发送增量部分的更新。但是,如果由于某种原因,客户丢失了它所需要的一个特定区域的内容,就发送一条FramebufferUpdateRequest消息,把消息中的incremental(增量)置为0(false)。这要求服务器把指定区域的全部内容尽可能快地发送过来。这个区域的更新不会使用copy rectangle编码方式。如果客户没有丢失它感兴趣区域的任何内容,就发送一条FramebufferUpdateRequest消息,把消息中的incremental设为非零(true)。当帧缓冲区中的指定区域发生变化时,服务器会发送一条FramebufferUpdate消息。
注意,在FramebufferUpdateRequest和FramebufferUpdate之间可能会有一段不确定长的间隔。
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | U8 | incremental |
2 | U16 | x-position |
2 | U16 | y-position |
2 | U16 | width |
2 | U16 | height |
incremental
为0时,表示必须发送完整内容过来。
KeyEvent按键事件
某一个按键被按下,那么按下标志非0。释放的时候变为0。
X Windows系统中,键本身被赋值为keysym
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | U8 | down-flag |
2 | padding | |
4 | U32 | key |
对于大多数键来说,keysym
与ASCII
码相对应,如下:
Key name | Key value |
BackSpace | 0xff08 |
Tab | 0xff09 |
Return or Enter | 0xff0d |
Escape | 0xff1b |
Insert | 0xff63 |
Delete | 0xffff |
Home | 0xff50 |
End | 0xff57 |
Page Up | 0xff55 |
Page Down | 0xff56 |
Left | 0xff51 |
Up | 0xff52 |
Right | 0xff53 |
Down | 0xff54 |
F1 | 0xffbe |
F2 | 0xffbf |
F3 | 0xffc0 |
F4 | 0xffc1 |
... | ... |
F12 | 0xffc9 |
Shift(left) | 0xffe1 |
Shift(right) | 0xffe2 |
Control(left) | 0xffe3 |
Control(right) | 0xffe4 |
Meta(left) | 0xffe7 |
Meta(right) | 0xffe8 |
Alt(left) | 0xffe9 |
Alt(right) | 0xffea |
pointerEvent鼠标(指针)事件
检测鼠标移动或某一个键的按下或释放。
指针目前在(x坐标、y 坐标),鼠标按钮的各键采用1到8位掩码标识,0 表示松开,1 表示按下。
- 拿普通鼠标来说,全零表示鼠标移动,第1,2,3 分别对应左、中、右键
- 对于滑轮鼠标来说,滚轮向上对应第4位,滚轮向下对应第5位
拖动操作是不断的发送左键按下的消息,并变换鼠标的坐标。
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | U8 | button-mask |
2 | U16 | x-position |
2 | U16 | y-position |
ClientCutText客户端文本剪贴
客户端有新的ISO8859 - 1(Latin - 1) 文本在它的剪切缓存里,行的末尾通过新行字符(值为10)来表示。 需要无回车(值为13)。目前还没有找到传输非Latin - 1 字符集的方法。
No. of bytes | Type | Description |
1 | U8 | message-type |
3 | padding | |
4 | U32 | length |
length | U8 array | text |
服务器收到客户端消息
服务器到客户端的消息在文本中定义如下:
Number | Name |
0 | FramebufferUpdate |
1 | SetColourMapEntries |
2 | Bell |
3 | ServerCutText |
其余注册的消息类型:
Number | Name |
0 | FramebufferUpdate |
1 | SetColourMapEntries |
2 | Bell |
3 | ServerCutText |
注意:
在服务器发送消息之前,必须确认客户端支持相关扩展,通常在请求“伪编码”的时候使用。
FramebufferUpdate帧缓存更新
帧缓存更新是由一系列像素数据矩形而组成,这些矩形会被客户端送入它的帧缓存中。
它是对客户端帧缓存更新请求的响应。而在请求和响应之间有可能存在不确定时期。
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | padding | |
2 | U16 | number-of-rectangles |
随着像素数据,每个矩形包括以下的内容:
No. of bytes | Type | Description |
2 | U16 | x-position |
2 | U16 | y-position |
2 | U16 | width |
2 | U16 | height |
4 | S32 | encoding-type |
设置颜色面板:
- 当像素格式使用“颜色面板”时,消息告诉客户端对应像素值如何映射为RGB亮度。
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | padding | |
2 | U16 | first-colour |
2 | U16 | number-of-colours |
后面是重复具体的色彩
No. of bytes | Type | Description |
2 | U16 | red |
2 | U16 | green |
2 | U16 | blue |
SetColorMapEntries
当且仅当在客户端发送FramebufferUpdateRequest并同意使用color map的时候。服务端才可能发送SetColorMapEntries消息,这消息可能一次只更新整块color map中的一部分。
color map每个值是16bits,范围[0,65535]
此消息头:
No. of bytes | Type | Description |
1 | U8 | message-type |
1 | padding | |
2 | U16 | first-color |
2 | U16 | number-of-colors |
后续是number-of-colors
个RGB值,每个RGB值对应格式如下:
No. of bytes | Type | Description |
2 | U16 | red |
2 | U16 | green |
2 | U16 | blue |
Bell响铃
- 如果有响铃事件,就在客户端上响铃
No. of bytes | Type | Description |
1 | U8 | message-type |
ServerCutText服务器剪贴文本
- 如果服务器的剪贴板有新内容,服务器主动发送该消息给客户端
No. of bytes | Type | Description |
1 | U8 | message-type |
3 | padding | |
4 | U32 | length |
length | U8 array | text |
编码方式
Encodings
编码
Number | Name |
0 | Raw |
1 | CopyRect |
2 | RRE |
5 | Hextile |
16 | ZRLE |
-239 | Curosr pseudo-encoding |
-223 | Desktop size pseudo-encoding |
其他编码类型
Number | Name |
4 | CoRRE |
6,7,8 | zlib,tight,zlibhex |
-272 to -257 | Anthony Liguori |
-256 to -240 | |
-238 to -224 | |
-222 to -1 | tight options |
原始编码(Raw
)
即采用原始的像素数据,而不进行任何的加工处理。在这种情况下,对于一个宽度乘以高度(即面积)为N的矩形,数据就由N个像素值组成,这些值表示按照扫描线顺序从左到右排列的每个像素。很明显,这种编码方式是最简单的,也是效率最低的。
RFB要求所有的客户端都必须能够处理这种原始编码的数据,并且在客户没有特别指定需要某种编码方式的时候,RFB服务器就默认生成原始编码。
No. of bytes | Type | Description |
width*height*bytesPerPixel | PIXEL array | pixels |
复制矩形编码(CopyRect
)
CopyRect 编码方式对于客户端在某些已经有了相同的象素数据的时候是非常简单和有效的。
这种编码方式在网络中表现为x,y 坐标。让客户端知道去拷贝那一个矩形的象素数据。
复制矩形编码并不是完全独立地发送所有的数据矩形,而是对于像素值完全相同的一组矩形,只发送第一个矩形全部数据,随后的矩形则只需要发送左上角X、Y坐标。
实际上,复制矩形编码主要指的就是随后的这一系列X、Y坐标,而对于第一个矩形具体采用何种编码类型并没有限制,仅仅需要知道第一个矩形在帧缓冲区中的位置,以便于完成复制操作。
因此,往往是把复制矩形编码和其它针对某一个矩形的编码类型结合使用。
No. of bytes | Type | Description |
2 | U16 | src-x-position |
2 | U16 | src-y-position |
二维行程编码(RRE:rise-and-run-length
)
RRE表示提升和运行长度,正如它名字暗示的那样,它实质上表示二维向量的运行长度编码。
RRE把矩形编码成可以被客户机的图形引擎翻译的格式。
RRE不适合复杂的桌面,但在一些情况下比较有用。
RRE的思想就是把像素矩形的数据分成一些子区域,和一些压缩原始区域的单元。
编码是由像素值组成的,Vb(基本上是在矩形中最常用的像素值)和一个计数N,紧接着是N的子矩形列表,这些里面由数组组成,(x,y)是对应子矩形的坐标,表示子矩形上-左的坐标值,(w,h) 则表示子矩形的宽高。客户端可以通过绘制使用背景像素数据值,然后再根据子矩形来绘制原始矩形。
二维行程编码本质上是对行程编码的一个二维模拟,而其压缩度可以保证与行程编码相同甚至更好。而且更重要的是,采用RRE编码的矩形被传送到客户端以后,可以立即有效地被最简单的图形引擎所还原。
No. of bytes | Type | Description |
4 | U32 | number-of-subrectangles |
bytesPerPixel | PIXEL | background-pixel-value |
后面跟随重复的子矩形结构:
No. of bytes | Type | Description |
bytesPerPixel | PIXEL | subrect-pixel-value |
2 | U16 | x-position |
2 | U16 | y-position |
2 | U16 | width |
2 | U16 | height |
CoRRE
编码
CoRRE是RRE的变体,它把发送的最大矩形限制在255×255个像素以内,用一个字节就能表示子矩形的维度。如果服务器想要发送一个超出限制的矩形,则只要把它划分成几个更小的RFB矩形即可。
实际上,如果进一步限制矩形的大小,就能够获得最好的压缩度。“矩形的最大值越小,决策的尺度就越好”。但是,如果把矩形的最大值限制得太小,就增加了矩形的数量,而由于每个RFB矩形都会有一定的开销,结果反而会使压缩度变差。所以应该选择一个比较恰当的数字。在目前的实现中,采用的最大值为48×48。
Hextile
编码
Hextile 是RRE编码的变种,矩形被分割成16×16 小片,允许每个小片的维数为4位,总共16 位。
把原始矩形划分成小块是预定义的,这意味着每个块的位置与大小不需要明确地指定。
矩形被分割的小片从上开始,遵守自左到右,自顶向下的顺序。小片的编码内容按照预定的顺序进行编码。如果整个矩形的宽度不是16 的整数倍,那么每行最后的小片也相应减少。高度也类似。
每个小片可以使用raw 编码,也可以是RRE编码的变种,用一个类型字节来进行说明即可。每个小片有一个背景像素值。但是,如果小片的背景像素值和前一个小片相同,那么就不需要明确定义。如果小片的子矩形有相同的像素值,那么前景像素值就可以只定义一次。和背景像素值一样,前景像素值也可以通过前一个小片获得。因此由小片组成的数据是按照顺序进行编码的。每一个小片以子编码类型的字节开始。它是位数的掩码组成。
No. of bytes | Type-value | Description |
1 | U8- | subencoding-mask: |
1 | U8-1 | Raw |
1 | U8-2 | BackgroundSpecified |
1 | U8-4 | ForegroundSpecified |
1 | U8-8 | AnySubrects |
1 | U8-16 | SubrectsColoured |
- 如果Raw 位被设置,那么其余的位就无效;接着是宽X高像素值(宽和高是小片的宽高)。否则其他的位就有效
- 背景定义-如果设置,那么像素值就会跟着小片的背景色:
No. of bytes | Type-value | Description |
bytesPerPixel | PIXEL | background-pixel-value |
在矩形中的第一片非Raw 小片必须设置这一位,如果不设置,那么它的背景就会和上一片相同。
- 前景定义-如果设置,那么像素值就会定义小片中所有子矩形的前景色:
No. of bytes | Type-value | Description |
bytesPerPixel | PIXEL | roreground-pixel-value |
如果这一位被设置,那么子矩形着色位必须为0。
- 任意子矩形-如果设置,那么一个字节包含着子矩形的个数。
No. of bytes | Type-value | Description |
1 | U8 | number-of-subrectangles |
如果这一位不设置,那么就不会有子矩形。(例如,整个小片就是背景颜色)
- 子矩形着色-如果设置,那么任意子矩形的像素值的优先级都高于子矩形的颜色定义,因此子矩形是:
No. of bytes | Type-value | Description |
bytesPerPixel | PIXEL | subrect-pixel-value |
1 | U8 | x-and-y-position |
1 | U8 | width-and-height |
如果不设置,所有子矩形都是前景色的颜色,如果前景定义没有设置,那么前景色和前一个片的相同。子矩形就是:
No. of bytes | Type-value | Description |
1 | U8 | x-and-y-position |
1 | U8 | width-and-height |
每一个子矩形的位置和大小都是使用两位进行定义,x - and - y - position 和width -And - height。最重要的四位x - an d - y - posi tion 定义X的位置,不重要的定义Y位置。最重要的四位width - and - height 定义宽度- 1,不重要的定义高度- 1。
ZRLE
编码
ZRLE(Zlib Run - Length Encoding),它结合了zlib 压缩,片技术、调色板和运行长度编码。在传输中,矩形以4 字节长度区域开始,紧接着是zlib 压缩的数据,一个单一的zlib“流”对象被用在RFB协议的连接上,因此ZRLE矩形必须严格的按照顺序进行编码和译码。
No. of bytes | Type-value | Description |
4 | U32 | length |
length | U8 array | zlibData |
zlibData 在没有压缩之前,代表了由64x64 像素组成的从左到右,从高到低的顺序的片,和hextile 编码有点类似。如果整个矩形的宽度不是64 的整数倍,那么每行最后的小片也相应减少。高度也类似。
ZRLE编码利用了一种新的压缩像素CPIXEL(Compres se d PIXEL)。这个和PIXEL有着相同的像素格式,除了真彩标志是非零,位每像素是32,色深不大于24。所有的位组成红,绿和蓝的亮度填充最不重要的或最重要的三字节。如果CPIXEL只有3 字节长,并且包含有合适的最不重要或最重要3 字节。那么bytesPerCPixel 就是CPIXEL的字节数。每片都是以子编码类型字节开始,如果片被使用运行长度编码,那么本字节的最高位
就会被设置。其余7 位表示绘图样式-零表示没有样式,1 表示片为单色,2 - 127 表示对应的样式。可能的子编码值如下:
0 - Raw 像素数据 宽X高像素值(宽和高为对应片的宽和高,对应像素值如下:
No. of bytes | Type-value | Description |
width*height*bytesPerPixel | CPIXEL array | pixels |
2 - 16 -打包的样式类型。对应像素值是由palet teSize(=子编码)像素值,打包像素值组成,每个打包像素值表示为一位区域服从样式索引(0 表示第一个条目),对应palet teSize 2,1 位被使用,palet teSize 3,4 有两位被使用,从5 - 16 均有4 位区域被使用。位的区域被打包成字节,最重要的位表示最左边像素。因为片并不是8,4,2 像素宽的乘积,所以填充位被用来按照字节数排列每一个行。
No. of bytes | Type-value | Description |
paletteSize*bytesPerCPixel | CPIXEL array | palette |
m | U8 array | packedPixels |
m 表示打包像素的字节数。对于palet teSize 2 就是floor((width + 7) / 8) x height,相应3,4 就是floor((width + 3) / 4) x height,而5 - 16 就是floor((width + 1) / 2)xheight。
17 - 127 未使用(对于palet te RLE并没有什么优势)。128 -简单RLE 它由一些不断重复的执行组成,一直到片结束。执行可能从一行的结束到另一行的开始。每一次运行是通过一个像素值和像素值长度来表示的。长度一般为1 个或多个字节。经过计算多于所有字节总和+ 1 作为长度。除了255 任何字节值都隐含最后的字节。例如长度1 表示为[0],255 表示为[254],256 表示为[255,0],257 表示为[255,1],510 表示为[255,254],511 表示为[255,255,0]等等。
No. of bytes | Type-value | Description |
bytesPerCPixel | CPIXEL | pixelValue |
floor((runlength-1)/255) | U8 array | |
1 | U8 | (runLength-1)%255 |
129 -未使用
130 - 255 调色RLE。调色紧跟其后,由palet teSize = (subencoding - 128) 像素值组成:
No. of bytes | Type-value | Description |
paletteSize*bytesPerCPixel | CPIXEL array | palette |
接下来就合简单RLE相似,一些不断重复的执行组成,一直到片结束。执行长度通过
调色板索引来表示。
No. of bytes | Type-value | Description |
1 | U8 | paletteIndex |
如果执行长度使用多于一位来表示调色板索引,并且最高位被设置。那么就会带有执行长度。
No. of bytes | Type-value | Description |
1 | U8 | paletteIndex+128 |
floor((runLength-1)/255) | U8 array 255 | |
1 | U8 | (runLength-1)%255 |
伪编码
- 指针/鼠标伪编码
如果客户端请求指针/鼠标伪编码,那么就是说它有能力进行本地绘制鼠标。这样就可以明显改善传输性能。服务器通过发送带有伪鼠标编码的伪矩形来设置鼠标的形状作为更新的一部分。伪矩形的x 和y 表示鼠标的热点,宽和高表示用像素来表示鼠标的宽和高。包含宽X高像素值的数据带有位掩码。位掩码是由从左到右,从上到下的扫描线组成,而每一扫描线被填充为floor((width +7) / 8)。对应每一字节最重要的位表示最左边像素,对应1 位表示相应指针的像素是正确的。
No. of bytes | Type-value | Description |
width*height*bytesPerPixel | PIXEL array | cursor-pixels |
floor((width+7)/8)*height | U8 array | bimask |
- 桌面大小伪编码
如果客户端请求桌面大小伪编码,那么就是说它能处理帧缓存宽/高的改变。服务器通过发送带有桌面大小伪编码的伪矩形作为上一个矩形来完成一次更新。伪矩形的x 和y 被忽略,而宽和高表示帧缓存新的宽和高。没有其他的数据与伪矩形有关。
协议漏洞&&解决方案
RealVNC VNC Server采用的RFB(远程帧缓冲区)协议允许客户端与服务端协商合适的认证方法,协议的实现上存在设计错误,远程攻击者可以绕过认证无需口令实现对服务器的访问。
- 服务端发送其版本
RFB 003.008\n
- 客户端回复其版本
RFB 003.008\n
- 服务端发送1个字节,等于所提供安全类型的数量
- 服务端发送字节数组说明所提供的安全类型
- 客户端回复1个字节,从3a的数组中选择安全类型
- 如果需要的话执行握手,然后是服务端的
0000
RealVNC 4.1.1或之前版本在实现RFB 003.008协议时没有检查判断在上面第4步中客户端所发送的字节是否为服务器在3a步中所提供的,因此认证就从服务端转移到了客户端。攻击者可以强制客户端请求“Type 1 - None”为安全类型,无需口令字段便可以访问服务器。
危害:远程攻击者可以绕过认证无需口令实现对服务器的访问。
解决方法:检查客户端请求的安全类型是否为服务器支持的类型之一,否则断开连接,或者禁止无认证的安全类型
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ KWP2000协议04/14
- ♥ HTTP协议相关学习一03/22
- ♥ 51CTO:Linux C++网络编程三08/16
- ♥ CAN-BUS协议11/28
- ♥ TCP 协议11/13
- ♥ 网络相关11/21