• (二)RFB协议具体通信说明


    、   消息说明 

    8.1     握手消息 

    8.1.1       RFB协议版本号


    1、vnc服务器发送所能够支持的最高RFB协议版本号给客户端,格式如下:

        “RFB xxx.yyy ”共12bytes,

      比如:“RFB 003.006 ”,即版本号为3.6,版本号固定格式为×××.×××,不足部分前面补零。
       


    2、客户端回复将要使用的版本号,格式如上。客户端的版本号必须小于或等于服务器版本号。这样服务器可以实现向后兼容。
    3、目前发布的协议版本主要有3.3、3.7、3.8(3.5版本被报告存在问题),最高版本号为4.0。

    8.1.2       协商安全类型 


    (一)v3.7以上版本安全类型
      服务器发送所支持的安全类型列表,如果客户端能支持服务器的某一安全类型,那么客户端就会发送一个字节来确认连接的安全类型:

      

      

      如果安全类型数是0,那么连接失败(例如服务器不支持客户请求版本号),这样就会有字符串来描述失败原因:

       

      服务器在发送原因字串后,就会关闭连接。


    (二)3.7以下版本(以vnc认证为例)
      1、服务器发送一个无符号的32位整数标识一个安全类型(与认证有关)。
        

    安全类型:

      

     其他认证类型:

      

    说明:
    ①0,连接失败(例如服务器不支持客户请求版本号),这样就会有字符串来描述失败原因:


     服务器发送完reason-string就关闭连接。
    ②NONE,不需要认证(不要输密码),协议数据将被使用明文发送。
    V3.8 以上版本, 还会带有安全结果的消息。
    V3.3 和 3.7 协议直接进入初始报文.
    ③VNC认证,协议数据将采用明文发送,服务器发送一个16 字节的随机数。

     

    客户端使用DES对验证进行加密,使用用户密码作为密钥,把16 字节的回复返回到服务器。

     

    随之而来的就是安全结果消息。

     


    2、服务器发送16位随机数。
    3、客户端使用DES对验证进行加密,使用用户密码作为密钥,把加密后的16字节返回给服务器。
    4、服务器对安全认证进行确认,返回值为无符号32位整数,如果为0则表示成功,1表示失败。如果不成功,服务器直接关闭连接。
       

      V3.8 以上版本 如果不成功,就会有字符串来描述失败原因,并关闭连接。

      

      对于V3.8以下,如果不成功,服务器直接关闭连接。

    8.2     初始化消息 


      1、客户端发送一个字节的初始化消息。
       

      如果允许服务器其他客户继续连接,那么共享标志应该是非零(真)。否则,服务器将断开其他客户的连接。
      2、服务器发送初始化消息,主要告知客户端服务器的帧缓存(桌面屏幕)的高、宽、象素格式和桌面相关的名称。
       

      这个跟实现有关,有些实现是先发送24个字节,然后再发送桌面名字字符串。名称字符串格式如:sh-yinghua -1 ( 192.168.70.69 )。
    帧缓存宽度一般为水平分辨率的大小,帧缓存高度一般是垂直分辨率的大小,比如1024×768等。
      象素格式主要包括以下段:
       

      服务器象素定义服务器本来的象素格式,这种象素格式会被一直使用,除非客户端使用设置象素格式消息来请求另一种象素格式。

      bits-per-pixel是表示每个像素值需要的位数。这个数字必须大于等于depth,而depth用来表示像素值中有用的位数。目前位每象素必须是8,16 或32——小于8 位象素不被支持。如果多字节象素被看做big-endian,那么Big-endian 标志非零。当然了,这对8 位每象素没有任何意义。

      如果真彩标志非零,那么最后6 项规定如何按照象素值来确定红、绿、蓝的亮度。红的最大值是红色的最大值(=2 ^n - 1, n 表示用在红色上的位数)。注意这个值一般在big endian的顺序中。红色-替换表示要得到最低明显bit 所需要的替换个数。绿色最大值、绿色-替换和蓝色最大值、蓝色-替换和红色类似。要在0—红色最大值之间找一个红色值,按照以下步骤进行:

    1.    遵循big-endian 标志进行象素值。(例如:如果big-endian 标志为0,主机的字节顺序是big endian,然后交换)。
    2.    使用红色—替换将右边替换。
    3.    和红色最大值进行逻辑与(按照主机字节顺序)。

     如果真彩标志是零,那么服务器使用的象素值不是直接由红、绿、蓝的亮度组成,但是服务为索引到颜色图中去。颜色图中的项目是由服务器使用“设置颜色面板条目” (FixColourMapEntries)消息进行设置的。
      说明:位/象素一般为显示设置的颜色质量位数。


      目前的任何服务器都还不能支持FixColourMapEntries消息,只有基于X的服务器才能支持颜色映射。实际上,为了能够完全支持颜色映射,客户端大概需要能够指定特殊的、服务器不会使用的像素值。这可能会加在未来的协议版本里。

    8.3     客户端到服务器的消息 


      所有客户端到服务器的消息第一个字节都为消息类型,数据类型U8。
      客户到服务器的消息在本文中有如下定义:
       

      其余的注册消息类型有:
       

      值得注意的是:如果要发送未在本文中定义的消息,那么必须得到服务器端的消息确认。

    8.3.1       SetPixelFormat设置象素格式消息


      “帧缓存更新”消息中设置什么格式的象素值如何设置。
      如果客户端没有发送“设置象素格式”消息,那么服务器发送的象素值将遵循在服务器初始化消息中所包括的象素格式。
      如果真彩标志是零,那么意味着使用“颜色面板”,只要客户端发送颜色面板空的消息,或者是面板项被服务器端重设,服务器可以使用设置颜色面板项目进行颜色面板的设置。
       

      注:其中的象素格式如在上文中的描述。

    8.3.2       SetEncoding设置编码格式


      设置编码方式可以来确定服务器发送象素数据的类型。消息中编码方式的顺序是客户端按照优先级来排列(第一个拥有最高的优先级)。服务器可能选择这种顺序,也可能不选择。
      象素数据也可以使用“原始编码”如果没有具体说明。

      除了基本的编码方式,客户端也可以请求“伪编码”通告服务器它支持某一种扩展协议。如果服务器不支持这种扩展,它就会忽略这种伪编码。注意:这意味着客户端在得到服务器的确认之前都要假设服务器并不支持它的扩展。
       

    接下来就是编码数目个编码类型的重复

       

    由编码类型决定编码格式。


    8.3.3       FramebufferUpdateRequest请求帧缓存更新


      通知服务器,客户对帧缓冲区中的某个区域感兴趣,这个区域由x坐标、y坐标、宽度和高度几个参数限定。
      服务器通常对FramebufferUpdateRequest消息的响应,是发送一条FramebufferUpdate消息。
      注意,可以发送一条FramebufferUpdate消息用来回复几条FramebufferUpdateRequest消息。
      服务器假定客户保留了帧服务器中它感兴趣的所有部分的副本。这意味着,服务器通常只需要向客户发送增量部分的更新。但是,如果由于某种原因,客户丢失了它所需要的一个特定区域的内容,就发送一条FramebufferUpdateRequest消息,把消息中的incremental(增量)置为0(false。这要求服务器把指定区域的全部内容尽可能快地发送过来。这个区域的更新不会使用copy rectangle编码方式。如果客户没有丢失它感兴趣区域的任何内容,就发送一条FramebufferUpdateRequest消息,把消息中的incremental设为非零(true。当帧缓冲区中的指定区域发生变化时,服务器会发送一条FramebufferUpdate消息。
    注意,FramebufferUpdateRequest和FramebufferUpdate之间可能会有一段不确定长的间隔。
      
    对于速度快的客户,它可能希望以固定频率发送增量的FramebufferUpdateRequests消息,以避免占用网络资源。
       

      增量标志incremental为0时,表示必须发送完整内容过来。

    8.3.4       KeyEvent按键事件


      某一个键的按下与释放。如果某一个键被按下,那么按下标志非零。释放的时候变为零。
      在X Window 系统中键本身被赋值为“keysym”。
       

      对于大多数键来说,“keysym”与ASCII码相对应,具体参考《The Xlib ReferenceManual》或者参考。在Linux上为/usr/include/X11/keysymdef.h。
      对于大多数普通键,“keysym”和ASCII码的值是一致的(前面3个字节为0,最后一个字节为ASCII码)。其他的命令键为:
       

    8.3.5       PointerEvent鼠标(指针)事件

      检测指针移动或者某一个键的按下或释放。指针目前在(x坐标、y 坐标),鼠标按钮的各键采用1到8位掩码标识,0 表示松开,1 表示按下。拿普通鼠标来说,全零表示鼠标移动,第1,2,3 分别对应左、中、右键。对于滑轮鼠标来说,滚轮向上对应第4位,滚轮向下对应第5位。拖动操作是不断的发送左键按下的消息,并变换鼠标的坐标
     

    8.3.6       ClientCutText客户端文本剪切


      客户端有新的ISO8859 - 1(Latin - 1) 文本在它的剪切缓存里,行的末尾通过新行字符(值为10)来表示。 需要无回车(值为13)。目前还没有找到传输非Latin - 1 字符集的方法。
       

    8.4     服务器到客户消息 


      服务器到客户消息在本文中定义如下:
      

      其余注册的消息类型:
       

      注意在服务器发送消息之前必须确认客户端支持相关扩展,通常在请求“伪编码”的时候使用。

    8.4.1       FramebufferUpdate帧缓存更新


      帧缓存更新是由一系列像素数据矩形而组成,这些矩形会被客户端送入它的帧缓存中。
      它是对客户端帧缓存更新请求的响应。而在请求和响应之间有可能存在不确定时期。
       

      随着像素数据矩形的个数,每个矩形包括以下内容:
        

      后面就是特定编码的数据。



     设置颜色面板条目


      当像素格式使用“颜色面板”时,消息告诉客户端对应像素值如何映射为RGB亮度。
        

      下面就是重复具体的色彩
       

      目前对颜色映射的支持还很少甚至没有。这方面已经做了一些初步的工作,但是还没有完成。
      目前,只有基于X 的服务器能够完全支持颜色映射。

    8.4.2       SetColorMapEntries

      当且仅当在客户端发送FramebufferUpdateRequest并同意使用color map的时候。服务端才可能发送SetColorMapEntries消息,这消息可能一次只更新整块color map中的一部分。

      color map每个值是16bits,范围[0,65535]

      此消息头部定义:

       

      后续就是number-of-colors个RGB值,每个RGB值对应如下格式:

      

    8.4.3       Bell响铃


      如果有响铃事件,就在客户端上响铃。

      

    8.4.4       ServerCutText服务器剪切文本


      如果服务器的剪切板有新内容,服务器主动发送该消息给客户端
       


    8.4.5       Encodings编码

       本文的编码类型

       

      其他编码类型

     

      
    原始编码( Raw 编码)


      即采用原始的像素数据,而不进行任何的加工处理。在这种情况下,对于一个宽度乘以高度(即面积)为N的矩形,数据就由N个像素值组成,这些值表示按照扫描线顺序从左到右排列的每个像素。很明显,这种编码方式是最简单的,也是效率最低的。
      RFB要求所有的客户都必须能够处理这种原始编码的数据,并且在客户没有特别指定需要某种编码方式的时候,RFB服务器就默认生成原始编码。
        

    复制矩形编码( CopyRect编码)


      CopyRect 编码方式对于客户端在某些已经有了相同的象素数据的时候是非常简单和有效的。这种编码方式在网络中表现为x,y 坐标。让客户端知道去拷贝那一个矩形的象素数据。它可以应用于很多种情况。最明显的就是当用户在屏幕上移动某一个窗口的时候,还有在窗口内容滚动的时候。在优化画的时候不是很明显,一个比较智能的服务器可能只会发送一次,因为它知道在客户端的帧缓存里已经存在了。
      复制矩形编码并不是完全独立地发送所有的数据矩形,而是对于像素值完全相同的一组矩形,只发送第一个矩形全部数据,随后的矩形则只需要发送左上角X、Y坐标。实际上,复制矩形编码主要指的就是随后的这一系列X、Y坐标,而对于第一个矩形具体采用何种编码类型并没有限制,仅仅需要知道第一个矩形在帧缓冲区中的位置,以便于完成复制操作。因此,往往是把复制矩形编码和其它针对某一个矩形的编码类型结合使用。
      接下来使用CopyRect 编码方式发送相同的式样。
       

    二维行程编码( rise-and-run-length,RRE)


      RRE表示提升和运行长度,正如它名字暗示的那样,它实质上表示二维向量的运行长度编码。RRE把矩形编码成可以被客户机的图形引擎翻译的格式。RRE不适合复杂的桌面,但在一些情况下比较有用。
      RRE的思想就是把像素矩形的数据分成一些子区域,和一些压缩原始区域的单元。最近最佳的分区方式一般是比较容易计算的。
      编码是由像素值组成的,Vb(基本上是在矩形中最常用的像素值)和一个计数N,紧接着是N的子矩形列表,这些里面由数组组成,(x,y)是对应子矩形的坐标,表示子矩形上-左的坐标值,(w,h) 则表示子矩形的宽高。客户端可以通过绘制使用背景像素数据值,然后再根据子矩形来绘制原始矩形。
      二维行程编码本质上是对行程编码的一个二维模拟,而其压缩度可以保证与行程编码相同甚至更好。而且更重要的是,采用RRE编码的矩形被传送到客户端以后,可以立即有效地被最简单的图形引擎所还原。
    在传输中,数据以下面的头开始描述:
       

    后面跟随重复的子矩形结构:
       

    CoRRE 编码


      CoRRE是RRE的变体,它把发送的最大矩形限制在255×255个像素以内,用一个字节就能表示子矩形的维度。如果服务器想要发送一个超出限制的矩形,则只要把它划分成几个更小的RFB矩形即可。“对于通常的桌面,这样的方式具有比RRE更好的压缩度”。
      实际上,如果进一步限制矩形的大小,就能够获得最好的压缩度。“矩形的最大值越小,决策的尺度就越好”。但是,如果把矩形的最大值限制得太小,就增加了矩形的数量,而由于每个RFB矩形都会有一定的开销,结果反而会使压缩度变差。所以应该选择一个比较恰当的数字。在目前的实现中,采用的最大值为48×48。

    Hextile 编码


      Hextile 是RRE编码的变种,矩形被分割成16×16 小片,允许每个小片的维数为4位,总共16 位。把原始矩形划分成小块是预定义的,这意味着每个块的位置与大小不需要明确地指定。
      矩形被分割的小片从上开始,遵守自左到右,自顶向下的顺序。小片的编码内容按照预定的顺序进行编码。如果整个矩形的宽度不是16 的整数倍,那么每行最后的小片也相应减少。高度也类似。
      每个小片可以使用raw 编码,也可以是RRE编码的变种,用一个类型字节来进行说明即可。每个小片有一个背景像素值。但是,如果小片的背景像素值和前一个小片相同,那么就不需要明确定义。如果小片的子矩形有相同的像素值,那么前景像素值就可以只定义一次。和背景像素值一样,前景像素值也可以通过前一个小片获得。因此由小片组成的数据是按照顺序进行编码的。每一个小片以子编码类型的字节开始。它是位数的掩码组成。
       

      如果Raw 位被设置,那么其余的位就无效;接着是宽X高像素值(宽和高是小片的宽高)。否则其他的位就有效。
      背景定义-如果设置,那么像素值就会跟着小片的背景色:
       

      在矩形中的第一片非Raw 小片必须设置这一位,如果不设置,那么它的背景就会和上一片相同。

      前景定义-如果设置,那么像素值就会定义小片中所有子矩形的前景色。
      

      如果这一位被设置,那么子矩形着色位必须为0。
      任意子矩形-如果设置,那么一个字节包含着子矩形的个数。
       

      如果这一位不设置,那么就不会有子矩形。(例如,整个小片就是背景颜色)

      子矩形着色-如果设置,那么任意子矩形的像素值的优先级都高于子矩形的颜色定义,因此子矩形是:
       

      如果不设置,所有子矩形都是前景色的颜色,如果前景定义没有设置,那么前景色和前一个片的相同。子矩形就是:
        

      每一个子矩形的位置和大小都是使用两位进行定义,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矩形必须严格的按照顺序进行编码和译码。
       

      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高像素值(宽和高为对应片的宽和高,对应像素值如下:

    2 - 16 -打包的样式类型。对应像素值是由palet teSize(=子编码)像素值,打包像素值组成,每个打包像素值表示为一位区域服从样式索引(0 表示第一个条目),对应palet teSize 2,1 位被使用,palet teSize 3,4 有两位被使用,从5 - 16 均有4 位区域被使用。位的区域被打包成字节,最重要的位表示最左边像素。因为片并不是8,4,2 像素宽的乘积,所以填充位被用来按照字节数排列每一个行。

     

      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]等等。
       

    129 -未使用
    130 - 255 调色RLE。调色紧跟其后,由palet teSize = (subencoding - 128) 像素值组成:
       

    接下来就合简单RLE相似,一些不断重复的执行组成,一直到片结束。执行长度通过
    调色板索引来表示。
       

    如果执行长度使用多于一位来表示调色板索引,并且最高位被设置。那么就会带有执行长度。
     
      

    8.4.6       伪编码 

    • 指针/鼠标伪编码


      如果客户端请求指针/鼠标伪编码,那么就是说它有能力进行本地绘制鼠标。这样就可以明显改善传输性能。服务器通过发送带有伪鼠标编码的伪矩形来设置鼠标的形状作为更新的一部分。伪矩形的x 和y 表示鼠标的热点,宽和高表示用像素来表示鼠标的宽和高。包含宽X高像素值的数据带有位掩码。位掩码是由从左到右,从上到下的扫描线组成,而每一扫描线被填充为floor((width +7) / 8)。对应每一字节最重要的位表示最左边像素,对应1 位表示相应指针的像素是正确的。
       

    • 桌面大小伪编码


      如果客户端请求桌面大小伪编码,那么就是说它能处理帧缓存宽/高的改变。服务器通过发送带有桌面大小伪编码的伪矩形作为上一个矩形来完成一次更新。伪矩形的x 和y 被忽略,而宽和高表示帧缓存新的宽和高。没有其他的数据与伪矩形有关。



    九、   协议漏洞及解决方法 


      RealVNC VNC Server采用的RFB(远程帧缓冲区)协议允许客户端与服务端协商合适的认证方法,协议的实现上存在设计错误,远程攻击者可以绕过认证无需口令实现对服务器的访问。
    具体操作细节如下

    1) 服务端发送其版本“RFB 003.008 ”
    2) 客户端回复其版本“RFB 003.008 ”
    3) 服务端发送1个字节,等于所提供安全类型的数量
    3a) 服务端发送字节数组说明所提供的安全类型
    4) 客户端回复1个字节,从3a的数组中选择安全类型
    5) 如果需要的话执行握手,然后是服务端的“0000”

      RealVNC 4.1.1或之前版本在实现RFB 003.008协议时没有检查判断在上面第4步中客户端所发送的字节是否为服务器在3a步中所提供的,因此认证就从服务端转移到了客户端。攻击者可以强制客户端请求“Type 1 - None”为安全类型,无需口令字段便可以访问服务器。
      危害:远程攻击者可以绕过认证无需口令实现对服务器的访问。
      解决方法:检查客户端请求的安全类型是否为服务器支持的类型之一,否则断开连接,或者禁止无认证的安全类型

  • 相关阅读:
    sqlite
    c++primer
    c++ std find_last_of
    c语言
    boost serialization
    ssh autologin
    c/c++文件相关
    AndroidTreeView等例子
    and
    解决Gradle 依赖下载慢以及android开发释放c盘空间及android虚拟机访问网络--以及访问本机
  • 原文地址:https://www.cnblogs.com/szBeginner/p/7887686.html
Copyright © 2020-2023  润新知