@TOC
日常吐槽:这份工作确实无聊,不出活,没进步就是我上班的真实写照,学习进步还是要看下班后。
上一篇文章我们看了RMI客户端侧代码获取一个注册中心的操作,这一次我们来看看lookup
函数是怎么工作的,有不对的地方大佬轻点喷。
在上一篇中我们知道最终获取到的Registry
对象是由RegistryImpl_Stub
不断转型过来的,那么使用该对象调用lookup
函数理论上来说也就是会调用RegistryImpl_Stub
中的lookup
函数。
首先打断点进入到lookup
函数:
果然是调用的RegistryImpl_Stub
中的函数,到这里前期我们有两个点需要关注一是116行,一是123行。
首先我们跟进116行的方法,这里调用的是ref
对象的newCall,从名字我们大概猜到可能是要发送请求了,首先我们用wireshark过滤一下1099端口,然后让代码执行完116行:
标记1处由客户端向注册中心发送请求,发送要使用的JRMI版本号,2处由注册中心向客户端确认版本号,至于第三处也是由客户端发起,具体干了什么我不太清楚(通过后面的分析我发现这一步应该也是类似于TCP三次握手那样的一个确认机制,注册中心返回自己的IP地址,然后客户端提取ip放到这个包里面,最后发送到注册中心,注册中心再对这次请求进行确认):
看这意思发送了一个ip地址过去,难道是协商要用哪一张网卡????172这个ip还是我安装wsl的时候生成的一张虚拟网卡。
看名字还是个虚拟以太网交换机??这我就更迷惑了。。。不过暂时这不重要。
那么现在我们就进入到这个newCall
方法中看一下都做了什么吧。
看到进入了UnicastRef
这个类的中,这里注意第343行的代码,首先调用了ref
对象的getChannel
方法肯定是会返回一个对象的,然后再调用这个对象的newConnection
方法,看着名字已经很清楚了这应该就是发送请求的关键函数了。getChannel
不出意外是获取一个socket通道,然后newConnection
方法发送请求,我们看看对不对,首先进入getChannel
方法:
看到进入了LiveRef#getChannel
,这里大眼一瞧就注意到了第152行。调用了ep
对象的getChannel
方法,如果还记得上一篇的话,在获取注册中心的时候我们获得过一次TCPEndpoint
对象然后赋值给了ep
,这里就是了,ep
中封装了注册中心的host与port:
进入到getChannel
方法:
果然来到了TCPEndpoint
类。419行又是函数套函数,不过getOutBoundTransport
应该是个类函数,翻译成中文就是获取对外绑定传输,强行翻译了一波。。。。从名字看不出来什么,进到函数里面看看:
欧,又有,先看getLocalEndpoint
函数吧,进去:
1 | public static TCPEndpoint getLocalEndpoint(int port, |
长的有点过粪了。。。。
看到函数中代码是异步执行的,这玩意儿我python中的异步都没咋搞清楚,更别说java了,但不影响我看代码。。。
首先new了一个TCPEndpoint
类型的endpointkey,暂时不知道干什么的,然后调用了localEndpoint
的get方法,看看localEndpoint
是什么Map
类型,键为TCPEndpoint
类型,值为LinkedList集合类型。所以
201就是根据endpointkey
从Map
中 取值,然后202行调用resampleLocalHost
方法对主机名进行重新采样,我猜就是再获取一下对host进行解析,不妨跟进去看一看:
257行获取了一个字符串,看命名应该是从properties中获取hostname,跟进getHostnameProperty
函数看一下:
这里有个GetPropertyAction
类,上网查了一下粗糙的理解就是获取properties的值,进入函数看了看就是个赋值操作,不知道为甚么网上那样说。
最终的结论是resampleLocalHost
方法还是返回了注册中心的IP地址,可能因为我这里在本地看不出来区别,分开的话应该就能判断出一些什么了。
然后回到getLocalEndpoint
方法
又创建了一个TCPEndpoint
对象,不过这时候有了主机名了localhost,一个LinkedList
把新的ep
放进去,然后又设置了一下ep的listenport
属性,然后新建一个TCPTransport
赋值给了ep
的transport
,真有趣,一个套一个。。。。最后返回了ep
,一个TCPEndpoint
对象。
然后抛出到getOutboundTransport
方法又是这样的:
还是要的transport
你说有趣不有趣。
依次往上抛,最终还是调用的TCPTransport
对象的getChannel
方法。
这个channelTable
又是一个Map
一个端点对应一个TCP通道,很明显,我们还没有建立通道,所以这个表现在肯定是空的,所以在get的时候一是获取了个寂寞。
既然表示空的,下面的操作自然就是创建一个通道然后建立映射关系。然后返回这个通道,终于getChannel
方法结束了,然后就是建立连接了,也就是调用TCPChannel
对象的newConnection
方法。
代码挺长的,说白了就是看看是不是已经有了一个连接了,如果有了就直接拿来用,如果没有就新建一个,我们直接进到新建的逻辑里面:
1 | private Connection createConnection() throws RemoteException { |
这一段代码就长的有点离大谱了,主要关注两个out.flush
就是这行代码发起了请求,前面都是些前戏。。。
216与217行的代码已经很清楚了,新建一个socket,然后进行TCP连接,也就是进行三次握手,217执行完毕后,我们就可以使用wireshark看到:
妥妥的握手报文。
然后就是创建一个使用conn
创建一个输出流,然后将其封装为数据输出流,然后再为输出流设置JRMI幻数与版本:
看一下协议幻数与版本号分别是多少:
然后判断连接是否可重用,嗯,我看过了,是可重用的,所以又给输出流写了TransportConstants.StreamProtocol
,这个值为0x4b
这个编号我还好奇去转了一下码:
然后我去查了一下协议号:
这75是个什么鬼。。。对不上啊,只能解释为这两不是一个概念了。。。嗯,肯定不是一个概念,在out.flush
发包后,我们看看看wireshark抓到的包:
看红框里面的16进制值为4a524d4900024b
,就是协议幻数+版本号+流协议标识,知道了这个,我们就有了伪造RMI协议的第一个关键信息了。。。。。
看看对响应的处理:
首先封装了一个DataInputStream
流对象,然后从输入流里面读了1个字节出来作为协议确认码ack,此处为78
,然后判断ack是否等于TransportConstants.ProtocolAck
,即0x4e
,翻译为十进制就是78,这里是相等的。
然后继续读输入流得到suggestedHost
与suggestedPort
然后设置TCPEndpoint
的host为获取到的suggestedHost
,localhost
也为获取到的suggestedHost
,然后创建一个本地端点,通过这个端点获取主机与端口设置给输出流,然后out.flush将缓冲区的数据发送出去,通过wireshark抓包
看到这里发送的请求报文中确实有ip地址,但是没有看到端口啊,哪里除了问题?
注意到这儿获取本地端点的时候传入的端口就是0,抓包看到的请求为0也算正常。
这里就获取到了伪造RMI协议的第二个关键数据。。。。。这是越来越刑啊。。。。。
总之到目前为止,发送RMI请求的连接已经获取完毕的,接下来的工作就是客户端携带要查询的key去注册中心查询是否存在对应的对象,然后注册中心将返回一个存根给客户端,然后客户端利用这个存根再去访问服务器的skeleton,服务器骨架访问服务器查看是否存在这样一个方法,根据客户端发送过来的方法名与参数执行对应的方法,然后将执行的结果返回给客户端由客户端存根接收然后转发给客户端。
好了今天时间比较晚了,预知后事如何,请听下回分解。。。。