RPC/XDR/NFS系列之----远程过程调用

来源:互联网 发布:代理模式java有几种 编辑:程序博客网 时间:2024/05/18 07:38
概述:

    本文重点介绍远程过程调用的概念,同时对于RPC报文
    给点感性认识,就是抓包上来看看啦。

测试:

    RedHat6.0

目录:

    ★ Sun Microsystems 的远程过程调用
    ★ 远程程序和远程过程
    ★ 减少远程过程的形式参数数量
    ★ 标识远程程序和远程过程
    ★ 远程程序的版本号
    ★ 远程程序中的互斥
    ★ 零或多次执行
    ★ RPC重传机制
    ★ 动态端口映射(重要)
    ★ ONC RPC的报文格式(Redhat)
    ★ 对远程过程的参数进行序列化
    ★ 鉴别机制
    ★ RPC请求报文的一个例子(协议分析软件抓包)
    ★ 其他问题(需要讨论)
    ★ 相关RFC以及参考资料

★ Sun Microsystems 的远程过程调用

Sun定义了自己特定形式的远程过程调用,称为Sun RPC、开放网络计算RPC
(Open Network Computing, ONC RPC),简称RPC。ONC RPC已经成为事实上
的标准,并成为许多应用软件的实现机制,包括网络文件系统(NFS, Network
File System)。

ONC RPC允许底层支持协议为TCP或者UDP,使用了XDR。ONC RPC还提供了rpcgen
这样的工具简化分布式编程。

★ 远程程序和远程过程

ONC RPC定义了一个远程程序,对应于我们平时所理解的RPC Server,它包含
一组远程过程以及所有远程过程所共享的远程全局数据。比如我们在
<< RPC/XDR/NFS系列之----RPC编程初战(1) >>中提供的rdictd就是一个RPC 
Server,
也是一个远程程序,其中包含了几个远程过程,insertw、lookupw等等,字典本身

是远程全局数据。

★ 减少远程过程的形式参数数量

在RPC编程中,通常使用一个结构收集所有的远程过程所需要的参数,而把这个结

作为形式参数传递给远程过程。同理,远程过程的所有需要返回的数据可以收集到

一个结构中统一返回。

★ 标识远程程序和远程过程

ONC RPC标准中,在某个计算机上运行的远程程序(RPC Server)拥有唯一分配的
32bit
整数标识,rpc client需要使用该标识。此外,ONC RPC为一个给定的远程程序中

每个远程过程分配了一个整数标识,依序递增1,2,...,N,0永远保留作为回送远程

过程(echo procedure),用于测试该远程程序是否正常运做。

ONC RPC已经划分了远程程序号:

0x00000000 - 0x1fffffff Sun Microsystems公司
0x20000000 - 0x3fffffff 用户使用
0x40000000 - 0x5fffffff
0x60000000 - 0x7fffffff 保留
0x80000000 - 0x9fffffff 保留
0xa0000000 - 0xbfffffff 保留
0xc0000000 - 0xdfffffff 保留
0xe0000000 - 0xffffffff 保留

在第一组中已经分配的远程程序号有:

名字            远程程序号      描述

portmap         100000          端口映射器
rstatd          100001          rstatd
rusersd         100002
nfs             100003          网络文件系统
ypserv          100004          yp(现在叫做NIS)
mountd          100005
dbxd            100006
ypbind          100007          NIS绑定程序
walld           100008          rwalld
yppasswdd       100009
etherstatd      100010          以太网统计
rquotad         100011
sprayd          100012
selection_svc   100015          选择服务
dbsessionmgr    100016
rexd            100017          远程执行
office_auto     100018
lockd           100020
nlockmgr        100021
status          100024          状态监视器
bootparamd      100026
pcnfsd          150001

scz注:100017是个相当危险的服务,如果你不是烧昏了就关闭这个服务。
       它几乎没有什么特别的保护机制,却可以做很危险的操作。

★ 远程程序的版本号


远程程序的第一个版本往往分配给版本号1。
在目前的实现当中,每个RPC报文通过一个三元组标识给定主机上所期望
的接收者:

( prog, vers, proc )

ONC RPC允许一台主机同时运行远程程序的多个版本,这些不同版本之间
互相不影响。

★ 远程程序中的互斥

ONC RPC机制使得单个远程程序一次只能支持一个远程过程调用,当前远程
过程调用完成之前会自动阻塞其他远程过程调用,程序员设计分布式程序时
不需要考虑这种互斥。

★ 零或多次执行

ONC RPC标准中,用至少一次语义(at least once semantics)描述
rpc client至少接收到了一个应答,用零或多次语义(zero or more semantics)
描述rpc client没有接收到应答。

下面解释一下这些语义:

至少一次语义 ---- 远程过程调用返回了,调用者只能认为远程过程至少被调用
                  了一次,而不能认为远程过程恰好被调用用了一次,因为远
                  程过程调用应答可能丢失,远程过程调用请求可能重复。

零或多次语义 ---- 远程过程调用没有返回,调用者不能认为远程过程一定没有
                  被调用,远程过程调用应答可能丢失。

后者意味着程序员的一个重要责任,选择UDP支持RPC,则设计的程序必须能够
容忍零或多次执行语义。这要求相应的远程过程调用是幂等的(idempotent)。

★ RPC重传机制

ONC RPC支持一种简单的超时重传机制,但无法保证严格意义下的可靠性。默认的

超时机制采用了固定(非自适应)超时时间间隔和固定重试次数。当RPC库软件发送

一个报文时(对应于进行一次远程过程调用),它便启动一个定时器,如果定时器在

远程过程调用应答达到前期满,软件便重发请求,程序员可以为某个给定应用调整

超时时间间隔以及重试次数,但无法自适应。这种简单机制无法保证可靠性,无法

保证rpc client可以判断远程过程执行情况。如果网络丢失了所有应答,rpc 
client
可能会重传几次请求,每次请求都导致远程过程被调用一次,然而当rpc client
端的RPC库软件达到重试上限,它会宣布远程过程无法被调用。

必须注意,应用程序不能将失败解释成远程过程从来没有被调用过,很可能已经
被调用了多次。

★ 动态端口映射(重要)

UDP/TCP使用16bit的协议端口号,而ONC RPC使用32bit的远程程序号,无法在二者

之间进行直接映射。此外,不是所有的RPC Server都能被分配一个唯一的协议端口

号。

scz注:NFS使用了UDP 2049,这个端口是固定使用的。

RPC Server的潜在数量超过了分配周知端口的能力,但RPC Server本身和其他
server
没有什么特别不同。在任意给定时间,某台主机上仅仅运行了少量RPC Server,可

为这些RPC Server临时分配协议端口号用于socket通信。

问题在于,如果RPC Server没有采用固定的周知端口,而是动态从操作系统获得空

协议端口号的话,RPC Client无法直接与之建立socket通信,因为client根本不知

这次server启动后获得的端口号。所以必须辅助以其他技术,以便rpc client可以

知此次rpc server启动后获得的端口号。

ONC RPC提供了一个动态端口映射服务,每个RPC Server启动的时候向本机上运行

动态端口映射器注册自己的远程程序号、版本号、动态获得的端口号,而RPC 
Client
用远程程序号+版本号向远程主机上的portmapper查询相应的动态端口号,一旦
rpc client获得了rpc server的动态端口号,就不用理会portmapper了,直接使用

targetIp+targetPort建立socket通信。


scz注:这里我们需要讨论一个问题。rpc server启动之后杀掉portmapper。自己
编写
       rpc client,不向远程主机的portmapper查询rpc server的动态端口,而
是直
       接使用通过其他手段获知的端口号建立socket通信,有什么问题?个人觉
得可
       行。于是可以绕过portmapper的一些监视功能,是这样吗?比如可以用
       rpcinfo -p先查询到rpc server的端口,然后从命令行参数指定给rpc 
client
       使用。

       portmapper在周知端口111上被动打开,这里存在另外一些问题。根据
Douglas
       的说法,rpc server注册以及rpc client查询都是直接通过111端口进行的

       在Stevens的著作中,portmapper本身就是一个rpc server,提供了四个远

       过程,rpcinfo用到了这些远程过程。portmapper作为rpc server会向自己
注册,
       只不过注册的端口号不是操作系统动态分配得到的,而是固定使用111。
       rpc server注册动态端口时使用了RPC,portmapper提供注册和反注册两个
远程
       过程供rpc server启动时调用。rpc client查询时也使用了RPC,
portmapper提供
       了查询远程过程供rpc client查询时调用。portmapper提供的第四个远程
过程
       是返回所有已经注册的条目,rpcinfo -p调用了这个远程过程。

       必须理解的是,进行RPC调用与使用固定端口还是使用动态端口无关,RPC
调用
       本身是通过socket通信发送RPC报文完成的。所以portmapper使用111、
nfs使用
       2049与它们提供远程过程不冲突。这里也该记住,不是所有的RPC Server
都动态
       获取端口,不是所有的RPC Client都向portmapper查询,基于portmapper
的保护
       机制是可以突破的。

★ ONC RPC的报文格式(Redhat)

在/usr/include/rpc/rpc_msg.h中有如下定义:

/*
 * Body of a reply to an rpc request.
 */
struct reply_body {
        enum reply_stat rp_stat;
        union {
                struct accepted_reply RP_ar;
                struct rejected_reply RP_dr;
        } ru;
#define rp_acpt ru.RP_ar
#define rp_rjct ru.RP_dr
};

/*
 * Body of an rpc request call.
 */
struct call_body {
        u_long cb_rpcvers;      /* must be equal to two */
        u_long cb_prog;
        u_long cb_vers;
        u_long cb_proc;
        struct opaque_auth cb_cred;
        struct opaque_auth cb_verf; /* protocol specific - provided by 
client */
};

/*
 * The rpc message
 */
struct rpc_msg {
        u_long                  rm_xid;
        enum msg_type           rm_direction;
        union {
                struct call_body RM_cmb;
                struct reply_body RM_rmb;
        } ru;
#define rm_call         ru.RM_cmb
#define rm_reply        ru.RM_rmb
};
#define acpted_rply     ru.RM_rmb.ru.RP_ar
#define rjcted_rply     ru.RM_rmb.ru.RP_dr

enum msg_type {
        CALL=0,
        REPLY=1
};

struct rpc_msg结构定义了RPC报文的格式。
struct call_body结构定义了RPC请求报文体。
struct reply_body结构定义了RPC应答报文体。

现在我们来看RPC请求报文体struct call_body,RPC协议版本号用于
保证rpc client和rpc server使用相同的RPC协议版本,目前必须等于
2,注意到注释/* must be equal to two */。远程程序号、远程程序
版本号以及远程过程号用于唯一确定RPC请求的接收者,最后两个成员
用于远程过程鉴别rpc client的身份。

我们将在后面的系列中通过协议分析软件抓包来观察一个完整的RPC
报文。和系统安全有关的结构成员主要是RPC请求报文体的最后两个
成员,这个安全问题将在后续系列中介绍。

★ 对远程过程的参数进行序列化

在一个RPC请求报文中,鉴别成员之后含有远程过程的参数,参数的个数
以及参数类型取决于被调用的远程过程。RPC机制将使用XDR转换远程过程
使用的每个参数。

scz注:如果远程过程的某个参数由链表那样复杂的数据结构组成,此时
       必须使用相对指针代替绝对地址,比如使用索引。然后进行XDR编
       码并发送。

术语序列化(marshal)、线性化(linearize)或者串行化(serialize)都是指
这种对远程过程所使用参数进行XDR编码的过程。一般说,rpc client
将参数序列化,rpc server进行参数的反序列化。尽管允许远程过程使用
复杂的参数类型,但序列化和反序列化需要大量的时间的资源,不推荐
远程过程使用链表这类的参数。

scz注:搞过java编程的朋友,可能对序列化这个术语并不陌生,可是你当
       初是否真正理解过这个概念呢,反正我97年的时候感到这个概念很
       不好理解。现在再回头看看xdr_example函数,不就是对struct example
       进行序列化的么。既然结构可以序列化,那么class也可以序列化。
       所以大多数对象都可以序列化。


★ 鉴别机制

在/usr/include/rpc/auth.h中有如下内容:

/*
 * Authentication info.  Opaque to client.
 */
struct opaque_auth {
        enum_t  oa_flavor;              /* flavor of auth */
        caddr_t oa_base;                /* address of more auth stuff 
*/
        u_int   oa_length;              /* not to exceed 
MAX_AUTH_BYTES */
};

#define AUTH_NONE       0               /* no authentication */
#define AUTH_NULL       0               /* backward compatibility */
#define AUTH_SYS        1               /* unix style (uid, gids) */
#define AUTH_UNIX       AUTH_SYS
#define AUTH_SHORT      2               /* short hand unix style */
#define AUTH_DES        3               /* des style (encrypted 
timestamps) */
#define AUTH_DH         AUTH_DES        /* Diffie-Hellman (this is 
DES) */
#define AUTH_KERB       4               /* kerberos style */

RPC定义了多种针对rpc client的鉴别机制,其中比较常见的是AUTH_UNIX
和AUTH_DES两种鉴别方式。鉴别信息的具体格式和解释不是由RPC本身定义
的,而是由具体的鉴别机制定义。

在/usr/include/rpc/auth_unix.h中有如下内容:

/*
 * Unix style credentials.
 */
struct authunix_parms
  {
    u_long aup_time;
    char *aup_machname;
    __uid_t aup_uid;
    __gid_t aup_gid;
    u_int aup_len;
    __gid_t *aup_gids;
  };

AUTH_UNIX鉴别机制定义的鉴别信息如上,首先是时间戳(rpc client的本地时间)
,其次
是rpc client所在主机名,接下来是当前进行RPC调用的用户UID、GID以及辅GID,
辅GID
可能有多个。

scz注:显然AUTH_UNIX的鉴别信息很容易伪造成功,基于这种鉴别的保护都是极其
脆弱的,
       很多古老的NFS攻击就是利用了这个鉴别机制的脆弱。

★ RPC请求报文的一个例子(协议分析软件抓包)

下面的包是这样抓到的,在Linux Server上启动NFS SERVER,然后在
98上用Omni NFS Client只读mount一个逻辑盘上来,然后删除该盘上
的文件,自然会删除失败。同时在98上运行snifferpro2.6,就是
deepin上载到202.101.106.14上的那个呀,抓到的报文很多,其中一
个如下:

数据链路层
aa bb cc dd ee ff 目标MAC = aa:bb:cc:dd:ee:ff
00 00 00 11 11 11 源MAC   = 00:00:00:11:11:11
08 00 IP

IP层
45 00 
00 90 IP报文总长度,包括IP Header,144字节
4e 16 00 00 20 
11 UDP
44 20 IP报文头部校验和
c0 a8 43 6a 源IP   = 192.168.67.106
c0 a8 43 6c 目标IP = 192.168.67.108

UDP层
03 e9 源PORT   = 1001
08 01 目标PORT = 2049
00 7c UDP报文长度,包括UDP Header,124字节
c6 09 UDP校验和

RPC层
80 00 00 00 RPC Transaction ID = 2147483648
00 00 00 00 RPC Type           = 0 ( RPC CALL )
00 00 00 02 RPC Version        = 2 ( 目前必须是2 )
00 01 86 a3 远程程序号         = 100003 ( NFS )
00 00 00 02 远程程序版本号     = 2
00 00 00 0a 远程过程号         = 10 ( Remove file )
00 00 00 01 使用AUTH_UNIX鉴别机制
00 00 00 1c AUTH_UNIX鉴别信息总长度28个字节
12 34 56 78 rpc client本地时间戳 = 305419896
00 00 00 03 rpc client所在主机名3个字节,不包括结束的\0
73 63 7a 00 rpc client所在主机名为 scz ,注意是asciiz串
00 00 01 f4 uid = 500 ( scz )
00 00 00 64 gid = 100 ( users )
00 00 00 01 总共一个辅GID
00 00 00 64 gid = 100 ( users )
00 00 00 00 使用AUTH_NULL鉴别机制
00 00 00 00 AUTH_NULL鉴别信息总长度0个字节

NFS层
ca ba eb fe 2a 68 0e 00 04 30 1d 00 04 03 00 00 
    NFS File Handle = 0xCABAEBFE2A680E0004301D0004030000 这里不做任何
XDR转换
04 03 00 00 2a 68 0e 00 a1 a5 5a 4c 00 00 00 00 
00 00 00 09 NFS File Name 9个字节,不包括结束的\0
69 70 73 70 6f 6f 66 2e 63 00 NFS File Name = ipspoof.c,注意是asciiz串

63 00 应该是些填充字节

为了方便协议分析,我修改了98和Linux下的MAC地址。

[scz@ /home/scz]> id
uid=500(scz) gid=100(users) groups=100(users)
[scz@ /home/scz]> 

XDR表示一个变长数据结构时,总是以该数据项总长度开始,比如上面的asciiz串

以及struct authunix_parms结构本身的外部表示。一个这样的RPC请求报文很容
易伪造成功,在系列灌水的后面会介绍这方面的内容。

★ 其他问题(需要讨论)

1. 引入端口映射器虽然解决了RPC Server使用动态端口的问题,但也加大了服务

   方负载。

2. rpc client可以把刚刚向远程portmapper查询获得的rpc server之端口进行高

   缓存。只是当rpc server崩溃并重启后,存在不一致性。

3. portmapper的概念可以扩展到rpc server以外的服务,但不推荐,也不现实。


4. 需要阅读RFC 1057,以了解rpc client如何指定是使用udp上的rpc server还是

   使用tcp上的rpc server。这个问题与clnt_create函数有关否?

5. 使用你的rpcinfo,加深理解。

6. 查阅CORBA( Common Object Request Broker Architecture )的资料,看看和

   RPC什么关系。

scz注:corba的概念很多朋友是从java编程中接触到的,我对之完全不了解,
       如果你了解,请介绍一二。想不到三年后我会再次遇到这个概念,
       想想当初疯狂修理java的时光,感慨很多。

★ 相关RFC以及参考资料

RFC 1057 << RPC: Remote Procedure Call Protocol Specification Version 
2 >>

它是Sun Microsystems 公司定义的ONC RPC标准,描述了本文大多数概念

RFC 1831

新版本,这是一个建议标准。

<<solaris网络编程指南>>

Douglas E. Comer & David L. Stevens
<< Internetworking With TCP/IP Vol III >>

W.Richard Stevens
<< TCP/IP Illustrated Volume I >>

scz < mailto: cloudsky@263.net >
2000.02.22 20:00 (待续)

原创粉丝点击