首页 > 其他分享 >RMI基础

RMI基础

时间:2024-08-05 16:51:47浏览次数:9  
标签:return 基础 port try catch new RMI throw

从 IDEA 断点分析 RMI 通信原理

1. 流程分析总览

首先 RMI 有三部分:

·RMI Registry

·RMI Server

·RMI Client

关于流程图,放在文件里面了

2. 创建远程服务

RMIServer

public class RMIServer {
public static void main(String[] args) throws Exception{
    IRemoteObj remoteObj = new RemoteObjImpl();
//        Registry r = LocateRegistry.createRegistry(1099);
//        r.bind("remoteObj",remoteObj);
}
}

RemoteObjImpl

public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException{

}

public  String sayHello(String keywords){
    String upKeywords = keywords.toUpperCase();
    System.out.println(upKeywords);
    return upKeywords;
}
}

我们来研究一下,他是怎么把服务器发到网络上的,在RMIServerIRemoteObj remoteObj = new RemoteObjImpl();打个断点,开始调试,f7进入,然后shift+f7,再次进入。

protected UnicastRemoteObject() throws RemoteException
{
    this(0);
}

RemoteObjImpl 这个类是继承于 UnicastRemoteObject 的,所以先会到父类的构造函数,父类的构造函数这里的 port 传入了 0,它代表一个随机端口,我们f7接着进去。

protected UnicastRemoteObject(int port) throws RemoteException
{
    this.port = port;
    exportObject((Remote) this, port);
}

然后父类把port赋值为0,远程服务这里如果传入的是 0,它会被发布到网络上的一个随机端口,我们可以继续往下看一看。先 f8 到 exportObject(),再 f7 跳进去看

public static Remote exportObject(Remote obj, int port)
    throws RemoteException
{
    return exportObject(obj, new UnicastServerRef(port));
}

exportObject() 是一个静态函数,它就是主要负责将远程服务发布到网络上

我们来看这个静态函数,第一个参数是 obj 对象,第二个参数是 new UnicastServerRef(port),第二个参数是用来处理网络请求的。继续往下面跟,去到了 UnicastServerRef 的构造函数。这里跟的操作先 f7,然后点击 UnicastServerRef 跟进

public UnicastServerRef(int port) {
    super(new LiveRef(port));
}

跟进去之后UnicastServerRef的构造函数,我们看到它new了一个 LiveRef(port),这个非常重要,它算是一个网络引用的类,跟进this看一看。

public LiveRef(ObjID objID, int port) {
    this(objID, TCPEndpoint.getLocalEndpoint(port), true);
}

第一个参数 ID,第三个参数为 true,所以我们重点关注一下第二个参数。

TCPEndpoint 是一个网络请求的类,我们可以去看一下它的构造函数,传参进去一个 IP 与一个端口,也就是说传进去一个 IP 和一个端口,就可以进行网络请求。

public TCPEndpoint(String host, int port) {
    this(host, port, null, null);
}

我们进入LiveRef的构造函数

public LiveRef(ObjID objID, Endpoint endpoint, boolean isLocal) {
    ep = endpoint;
    id = objID;
    this.isLocal = isLocal;
}

这时候我们可以看一下一些赋值,发现 hostport 是赋值到了 endpoint 里面,而 endpoint 又是被封装在 LiveRef 里面的,所以记住数据是在 LiveRef 里面即可,并且这一LiveRef至始至终只会存在一个。

回到上文那个地方,继续 f7 进入 super 看一看它的父类 UnicastRef,这里就证明整个创建远程服务的过程只会存在一个 LiveRef。

 public UnicastRef(LiveRef liveRef) {
    ref = liveRef;
}

一路 f7 到一个静态函数 exportObject(),我们后续的操作过程都与 exportObject() 有关,基本都是在调用它,这一段不是很重要,一路 f7 就好了。直到此处出现 Stub

public Remote exportObject(Remote impl, Object data,
                           boolean permanent)
    throws RemoteException
{
    Class<?> implClass = impl.getClass();
    Remote stub;

    try {
        stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
    } catch (IllegalArgumentException e) {
        throw new ExportException(
            "remote object implements illegal remote interface", e);
    }
    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

    Target target =
        new Target(impl, this, stub, ref.getObjID(), permanent);
    ref.exportObject(target);
    hashToMethod_Map = hashToMethod_Maps.get(implClass);
    return stub;
}

RMI 先在 Service 的地方,也就是服务端创建一个 Stub,再把 Stub 传到 RMI Registry 中,最后让 RMI Client 去获取 Stub。

我们进去研究一下怎么创建的

public static Remote createProxy(Class<?> implClass,
                                 RemoteRef clientRef,
                                 boolean forceStubUse)
    throws StubNotFoundException
{
    Class<?> remoteClass;

    try {
        remoteClass = getRemoteClass(implClass);
    } catch (ClassNotFoundException ex ) {
        throw new StubNotFoundException(
            "object does not implement a remote interface: " +
            implClass.getName());
    }

    if (forceStubUse ||
        !(ignoreStubClasses || !stubClassExists(remoteClass)))
    {
        return createStub(remoteClass, clientRef);
    }

    final ClassLoader loader = implClass.getClassLoader();
    final Class<?>[] interfaces = getRemoteInterfaces(implClass);
    final InvocationHandler handler =
        new RemoteObjectInvocationHandler(clientRef);

    /* REMIND: private remote interfaces? */

    try {
        return AccessController.doPrivileged(new PrivilegedAction<Remote>() {
            public Remote run() {
                return (Remote) Proxy.newProxyInstance(loader,
                                                       interfaces,
                                                       handler);
            }});
    } catch (IllegalArgumentException e) {
        throw new StubNotFoundException("unable to create proxy", e);
    }
}

这个判断暂时不用管,后续我们会碰到,那个时候再讲。再往下走,我们可以看到这是很明显的类加载的地方

  AccessController.doPrivileged(new PrivilegedAction<Remote>() {
            public Remote run() {
                return (Remote) Proxy.newProxyInstance(loader,
                                                       interfaces,
                                                       handler);

第一个参数是 AppClassLoader,第二个参数是一个远程接口,第三个参数是调用处理器,调用处理器里面只有一个 ref,它也是和之前我们看到的 ref 是同一个,创建远程服务当中永远只有一个 ref,此处就把动态代理创建好了。

Target target =
        new Target(impl, this, stub, ref.getObjID(), permanent);

继续 f8,到 Target 这里,Target 这里相当于一个总的封装,将所有用的东西放到 Target 里面

public Target(Remote impl, Dispatcher disp, Remote stub, ObjID id,
              boolean permanent)
{
    this.weakImpl = new WeakRef(impl, ObjectTable.reapQueue);
    this.disp = disp;
    this.stub = stub;
    this.id = id;
    this.acc = AccessController.getContext();

    
    ClassLoader threadContextLoader =
        Thread.currentThread().getContextClassLoader();
    ClassLoader serverLoader = impl.getClass().getClassLoader();
    if (checkLoaderAncestry(threadContextLoader, serverLoader)) {
        this.ccl = threadContextLoader;
    } else {
        this.ccl = serverLoader;
    }

    this.permanent = permanent;
    if (permanent) {
        pinImpl();
    }
}

Dispatcher disp服务端 Remote stub客户端的数据

然后f8,到下一句ref.exportObject(target);,我们跟进去看一下它的发布逻辑是怎么一回事,一路 f7 到listen

   public void exportObject(Target target) throws RemoteException {
    
    synchronized (this) {
        listen();
        exportCount++;
    }
    boolean ok = false;
    try {
        super.exportObject(target);
        ok = true;
    } finally {
        if (!ok) {
            synchronized (this) {
                decrementExportCount();
            }
        }
    }
}

从这里开始,第一句语句 listen,真正处理网络请求了跟进去。
先获取 TCPEndpoint然后我们继续 f8 往后看,直到 server = ep.newServerSocket();

   ServerSocket newServerSocket() throws IOException {
    if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
        TCPTransport.tcpLog.log(Log.VERBOSE,
            "creating server socket on " + this);
    }

    RMIServerSocketFactory serverFactory = ssf;
    if (serverFactory == null) {
        serverFactory = chooseFactory();
    }
    ServerSocket server = serverFactory.createServerSocket(listenPort);

    // if we listened on an anonymous port, set the default port
    // (for this socket factory)
    if (listenPort == 0)
        setDefaultPort(server.getLocalPort(), csf, ssf);

    return server;
}

他开了一个socket,已经准备好了,等别人来连接

   if (listenPort == 0)
        setDefaultPort(server.getLocalPort(), csf, ssf);

若前面端口是0,那么就会给你随机一个端口

然后接着回到listen,然后到hread t = AccessController.doPrivileged( new NewThreadAction(new AcceptLoop(server), "TCP Accept-" + port, true));这一步

进入AcceptLoop(server)

  private class AcceptLoop implements Runnable {

    private final ServerSocket serverSocket;

   
    private long lastExceptionTime = 0L;
    private int recentExceptionCount;

    AcceptLoop(ServerSocket serverSocket) {
        this.serverSocket = serverSocket;
    }

    public void run() {
        try {
            executeAcceptLoop();
        } finally {
            try {
                
                serverSocket.close();
            } catch (IOException e) {
            }
        }
    }

然后进入executeAcceptLoop();

 private void executeAcceptLoop() {
        if (tcpLog.isLoggable(Log.BRIEF)) {
            tcpLog.log(Log.BRIEF, "listening on port " +
                       getEndpoint().getPort());
        }}

进行连接处理,然后就完成了。

3. 创建注册中心

创建注册中心与服务端是独立的,所以谁先谁后无所谓,本质上是一整个东西。

public class RMIServer {
public static void main(String[] args) throws Exception{
    IRemoteObj remoteObj = new RemoteObjImpl();
    Registry r = LocateRegistry.createRegistry(1099);
    r.bind("remoteObj",remoteObj);
}
}

在第二句打上断点,然后进入createRegistry.

  public static Registry createRegistry(int port) throws RemoteException {
    return new RegistryImpl(port);
}

然后接着f7,到了RegistryImpl

public RegistryImpl(int port)
    throws RemoteException
{
    if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {
        // grant permission for default port only.
        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                public Void run() throws RemoteException {
                    LiveRef lref = new LiveRef(id, port);
                    setup(new UnicastServerRef(lref));
                    return null;
                }
            }, null, new SocketPermission("localhost:"+port, "listen,accept"));
        } catch (PrivilegedActionException pae) {
            throw (RemoteException)pae.getException();
        }
    } else {
        LiveRef lref = new LiveRef(id, port);
        setup(new UnicastServerRef(lref));
    }
}

先判断 port 是否为注册中心的 port,以及是否开启了 SecurityManager,也就是一系列的安全检查。然后就不会进入,他会进入

        LiveRef lref = new LiveRef(id, port);
        setup(new UnicastServerRef(lref));

LiveRef和上面的是一样的,就不看了,我们目光转向setup

 private void setup(UnicastServerRef uref)
    throws RemoteException
{
    ref = uref;
    uref.exportObject(this, null, true);
}

跟进之后发现和之前是一样的,也是先赋值,然后进行 exportObject() 方法的调用。区别在于第三个参数的不同,名为 permanent,第一张是 false,第二张是 true,这代表我们创建注册中心这个对象,是一个永久对象,而之前远程对象是一个临时对象。f7 进到 exportObject,就和发布远程对象一样,到了创建 Stub 的阶段。

 public Remote exportObject(Remote impl, Object data,
                           boolean permanent)
    throws RemoteException
{
    Class<?> implClass = impl.getClass();
    Remote stub;

    try {
        stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
    } catch (IllegalArgumentException e) {
        throw new ExportException(
            "remote object implements illegal remote interface", e);
    }
    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

    Target target =
        new Target(impl, this, stub, ref.getObjID(), permanent);
    ref.exportObject(target);
    hashToMethod_Map = hashToMethod_Maps.get(implClass);
    return stub;
}

我们到stub = Util.createProxy(implClass, getClientRef(), forceStubUse);看看

public static Remote createProxy(Class<?> implClass,
                                 RemoteRef clientRef,
                                 boolean forceStubUse)
    throws StubNotFoundException
{
    Class<?> remoteClass;

    try {
        remoteClass = getRemoteClass(implClass);
    } catch (ClassNotFoundException ex ) {
        throw new StubNotFoundException(
            "object does not implement a remote interface: " +
            implClass.getName());
    }

    if (forceStubUse ||
        !(ignoreStubClasses || !stubClassExists(remoteClass)))
    {
        return createStub(remoteClass, clientRef);
    }

    final ClassLoader loader = implClass.getClassLoader();
    final Class<?>[] interfaces = getRemoteInterfaces(implClass);
    final InvocationHandler handler =
        new RemoteObjectInvocationHandler(clientRef);

    /* REMIND: private remote interfaces? */

    try {
        return AccessController.doPrivileged(new PrivilegedAction<Remote>() {
            public Remote run() {
                return (Remote) Proxy.newProxyInstance(loader,
                                                       interfaces,
                                                       handler);
            }});
    } catch (IllegalArgumentException e) {
        throw new StubNotFoundException("unable to create proxy", e);
    }
}

首先这里要做一个判断。可以跟进 stubClassExists 进行判断

   private static boolean stubClassExists(Class<?> remoteClass) {
    if (!withoutStubs.containsKey(remoteClass)) {
        try {
            Class.forName(remoteClass.getName() + "_Stub",
                          false,
                          remoteClass.getClassLoader());
            return true;

        } catch (ClassNotFoundException cnfe) {
            withoutStubs.put(remoteClass, null);
        }
    }
    return false;
}

我们看到这个地方,是判断是否能获取到 RegistryImpl_Stub 这个类,换句话说,也就是若 RegistryImpl_Stub 这个类存在,则返回 True,反之 False。我们可以找到 RegistryImpl_Stub 这个类是存在的。
接着我们进入return createStub(remoteClass, clientRef);

  private static RemoteStub createStub(Class<?> remoteClass, RemoteRef ref)
    throws StubNotFoundException
{
    String stubname = remoteClass.getName() + "_Stub";
    try {
        Class<?> stubcl =
            Class.forName(stubname, false, remoteClass.getClassLoader());
        Constructor<?> cons = stubcl.getConstructor(stubConsParamTypes);
        return (RemoteStub) cons.newInstance(new Object[] { ref });

    } catch (ClassNotFoundException e) {
        throw new StubNotFoundException(
            "Stub class not found: " + stubname, e);
    } catch (NoSuchMethodException e) {
        throw new StubNotFoundException(
            "Stub class missing constructor: " + stubname, e);
    } catch (InstantiationException e) {
        throw new StubNotFoundException(
            "Can't create instance of stub class: " + stubname, e);
    } catch (IllegalAccessException e) {
        throw new StubNotFoundException(
            "Stub class constructor not public: " + stubname, e);
    } catch (InvocationTargetException e) {
        throw new StubNotFoundException(
            "Exception creating instance of stub class: " + stubname, e);
    } catch (ClassCastException e) {
        throw new StubNotFoundException(
            "Stub class not instance of RemoteStub: " + stubname, e);
    }
}

这个就是创建一个代理,然后把ref传进去

    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

继续往下,如果是服务端定义好的,就调用 setSkeleton() 方法,跟进去。

     public void setSkeleton(Remote impl) throws RemoteException {
    if (!withoutSkeletons.containsKey(impl.getClass())) {
        try {
            skel = Util.createSkeleton(impl);
        } catch (SkeletonNotFoundException e) {
            
            withoutSkeletons.put(impl.getClass(), null);
        }
    }
}

然后这里有一个 createSkeleton() 方法,一看名字就知道是用来创建 Skeleton 的,而 Skeleton 在我们的那幅图中,作为服务端的代理。

    static Skeleton createSkeleton(Remote object)
    throws SkeletonNotFoundException
{
    Class<?> cl;
    try {
        cl = getRemoteClass(object.getClass());
    } catch (ClassNotFoundException ex ) {
        throw new SkeletonNotFoundException(
            "object does not implement a remote interface: " +
            object.getClass().getName());
    }

    // now try to load the skeleton based ont he name of the class
    String skelname = cl.getName() + "_Skel";
    try {
        Class<?> skelcl = Class.forName(skelname, false, cl.getClassLoader());

        return (Skeleton)skelcl.newInstance();
    } catch (ClassNotFoundException ex) {
        throw new SkeletonNotFoundException("Skeleton class not found: " +
                                            skelname, ex);
    } catch (InstantiationException ex) {
        throw new SkeletonNotFoundException("Can't create skeleton: " +
                                            skelname, ex);
    } catch (IllegalAccessException ex) {
        throw new SkeletonNotFoundException("No public constructor: " +
                                            skelname, ex);
    } catch (ClassCastException ex) {
        throw new SkeletonNotFoundException(
            "Skeleton not of correct class: " + skelname, ex);
    }
}

Skeleton 是用forName()的方式创建的,再往后走,又到了 Target 的地方,Target 部分的作用也与之前一样,用于储存封装的数据,所以这一段和前面一样,就迅速跳过了

继续走,到了ref.exportObject(target);
public void exportObject(Target target) throws RemoteException {
ep.exportObject(target);
}
接着f7

 public void exportObject(Target target) throws RemoteException {
    transport.exportObject(target);
}

接着f7

    public void exportObject(Target target) throws RemoteException {
    
    synchronized (this) {
        listen();
        exportCount++;
    }
    boolean ok = false;
    try {
        super.exportObject(target);
        ok = true;
    } finally {
        if (!ok) {
            synchronized (this) {
                decrementExportCount();
            }
        }
    }
}

listen这部分我们很熟悉,让我们到下面super.exportObject(target);看看

public void exportObject(Target target) throws RemoteException {
    target.setExportedTransport(this);
    ObjectTable.putTarget(target);
}

putTarget() 方法,它会把封装的数据放进去。

4.绑定

绑定也就是最后一步,bind 操作

public class RMIServer {
public static void main(String[] args) throws Exception{
    IRemoteObj remoteObj = new RemoteObjImpl();
    Registry r = LocateRegistry.createRegistry(1099);
    r.bind("remoteObj",remoteObj);
}
}

断点下在bind上面

然后我们进去

public void bind(String name, Remote obj)
    throws RemoteException, AlreadyBoundException, AccessException
{
    checkAccess("Registry.bind");
    synchronized (bindings) {
        Remote curr = bindings.get(name);
        if (curr != null)
            throw new AlreadyBoundException(name);
        bindings.put(name, obj);
    }
}

checkAccess("Registry.bind");检查是不是本地绑定,都会通过的,然后我们接着往下

         Remote curr = bindings.get(name);
        if (curr != null)
            throw new AlreadyBoundException(name);

检查一下 bindings 这里面是否有东西,其实bindings就是一个 HashTable。如果里面有数据的话就抛出异常。

继续往前走,就是 bindings.put(name, obj);,就是把 IP 和端口放进去,到此处就结束l了

5.客户端请求,客户端调用注册中心

这一部分是存在漏洞的点,原因很简单,这里有一些个有问题的反序列化

public class RMIClient {
public static  void main(String[] args) throws Exception{
    Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
    IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
    remoteObj.sayHello("hello");
}
}

在第一句下个断点,进到 getRegistry() 方法里面

public static Registry getRegistry(String host, int port)
    throws RemoteException
{
    return getRegistry(host, port, null);
}  

接着进去

 public static Registry getRegistry(String host, int port,
                                   RMIClientSocketFactory csf)
    throws RemoteException
{
    Registry registry = null;

    if (port <= 0)
        port = Registry.REGISTRY_PORT;

    if (host == null || host.length() == 0) {
        // If host is blank (as returned by "file:" URL in 1.0.2 used in
        // java.rmi.Naming), try to convert to real local host name so
        // that the RegistryImpl's checkAccess will not fail.
        try {
            host = java.net.InetAddress.getLocalHost().getHostAddress();
        } catch (Exception e) {
            // If that failed, at least try "" (localhost) anyway...
            host = "";
        }
    }
    LiveRef liveRef =
        new LiveRef(new ObjID(ObjID.REGISTRY_ID),
                    new TCPEndpoint(host, port, csf, null),
                    false);
    RemoteRef ref =
        (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);

    return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
}

port传的是1099host传的是127.0.0.1,就和之前一样,新建了一个 Ref,然后把该封装的都封装到 Ref 里面进去,获取到了注册中心的 Stub

然后我们进入下一句,查找远程对象,IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");这里调试的话,因为对应的 Java 编译过的 class 文件是 1.1 的版本,无法进行打断点,我们直接看。

   public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
    try {
        RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);

        try {
            ObjectOutput var3 = var2.getOutputStream();
            var3.writeObject(var1);
        } catch (IOException var18) {
            throw new MarshalException("error marshalling arguments", var18);
        }

        super.ref.invoke(var2);

        Remote var23;
        try {
            ObjectInput var6 = var2.getInputStream();
            var23 = (Remote)var6.readObject();
        } catch (IOException var15) {
            throw new UnmarshalException("error unmarshalling return", var15);
        } catch (ClassNotFoundException var16) {
            throw new UnmarshalException("error unmarshalling return", var16);
        } finally {
            super.ref.done(var2);
        }

        return var23;
    } catch (RuntimeException var19) {
        throw var19;
    } catch (RemoteException var20) {
        throw var20;
    } catch (NotBoundException var21) {
        throw var21;
    } catch (Exception var22) {
        throw new UnexpectedException("undeclared checked exception", var22);
    }
}

lookeup是通过序列化传进去的,他会在var3.writeObject(var1);反序列化。
然后就进入super.ref.invoke(var2);,然后他会到 void invoke(RemoteCall call) throws Exception;,我们走到他的父类UnicastRefinvoke

  public void invoke(RemoteCall call) throws Exception {
    try {
        clientRefLog.log(Log.VERBOSE, "execute call");

        call.executeCall();

    } catch (RemoteException e) {
        clientRefLog.log(Log.BRIEF, "exception: ", e);
        free(call, false);
        throw e;

    } catch (Error e) {
        clientRefLog.log(Log.BRIEF, "error: ", e);
        free(call, false);
        throw e;

    } catch (RuntimeException e) {
        clientRefLog.log(Log.BRIEF, "exception: ", e);
        free(call, false);
        throw e;

    } catch (Exception e) {
        clientRefLog.log(Log.BRIEF, "exception: ", e);
        free(call, true);
        throw e;
    }
}

invoke() 方法里面会调用 call.executeCall(),它是真正处理网络请求的方法,也就是客户端的网络请求都是通过这个方法实现的。这个方法后续再细讲,我们先进去。

  public void executeCall() throws Exception {
    byte returnType;

    // read result header
    DGCAckHandler ackHandler = null;
    try {
        if (out != null) {
            ackHandler = out.getDGCAckHandler();
        }
        releaseOutputStream();
        DataInputStream rd = new DataInputStream(conn.getInputStream());
        byte op = rd.readByte();
        if (op != TransportConstants.Return) {
            if (Transport.transportLog.isLoggable(Log.BRIEF)) {
                Transport.transportLog.log(Log.BRIEF,
                    "transport return code invalid: " + op);
            }
            throw new UnmarshalException("Transport return code invalid");
        }
        getInputStream();
        returnType = in.readByte();
        in.readID();        // id for DGC acknowledgement
    } catch (UnmarshalException e) {
        throw e;
    } catch (IOException e) {
        throw new UnmarshalException("Error unmarshaling return header",
                                     e);
    } finally {
        if (ackHandler != null) {
            ackHandler.release();
        }
    }

    // read return value
    switch (returnType) {
    case TransportConstants.NormalReturn:
        break;

    case TransportConstants.ExceptionalReturn:
        Object ex;
        try {
            ex = in.readObject();
        } catch (Exception e) {
            throw new UnmarshalException("Error unmarshaling return", e);
        }

        // An exception should have been received,
        // if so throw it, else flag error
        if (ex instanceof Exception) {
            exceptionReceivedFromServer((Exception) ex);
        } else {
            throw new UnmarshalException("Return type not Exception");
        }
        // Exception is thrown before fallthrough can occur
    default:
        if (Transport.transportLog.isLoggable(Log.BRIEF)) {
            Transport.transportLog.log(Log.BRIEF,
                "return code invalid: " + returnType);
        }
        throw new UnmarshalException("Return code invalid");
    }
}

这个invokeexecuteCall()里面有个攻击的点就是ex = in.readObject();这一行,他是报错,然后到Switch找错误的地方,然后到这个错误,他为了让报错感觉完整,他用了流传递,然后反序列化出来,我们在注册中心返回一个恶意流,然后就可以在客户端返回一个恶意对象,这个漏洞很广,因为所有网络请求都要到invoke

我们接着在lookup往下走

            RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
            ObjectInput var6 = var2.getInputStream();
            var23 = (Remote)var6.readObject();

他先是创建一个创建一个新的远程调用,然后把他变成输出流,然后反序列化,那么我们在注册中心搞一个恶意对象就可以打穿他了

6. 客户端请求,客户端请求服务端

主文

public class RMIClient {
public static  void main(String[] args) throws Exception{
    Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
    IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
    remoteObj.sayHello("hello");
}
}

我们跟进第三句,要force into。

 public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
{
    if (! Proxy.isProxyClass(proxy.getClass())) {
        throw new IllegalArgumentException("not a proxy");
    }

    if (Proxy.getInvocationHandler(proxy) != this) {
        throw new IllegalArgumentException("handler mismatch");
    }

    if (method.getDeclaringClass() == Object.class) {
        return invokeObjectMethod(proxy, method, args);
    } else if ("finalize".equals(method.getName()) && method.getParameterCount() == 0 &&
        !allowFinalizeInvocation) {
        return null; // ignore
    } else {
        return invokeRemoteMethod(proxy, method, args);
    }
}

前面都是catch异常,我们一路f8,到最后一句return invokeRemoteMethod(proxy, method, args);,然后我们进去。

private Object invokeRemoteMethod(Object proxy,
                                  Method method,
                                  Object[] args)
    throws Exception
{
    try {
        if (!(proxy instanceof Remote)) {
            throw new IllegalArgumentException(
                "proxy not Remote instance");
        }
        return ref.invoke((Remote) proxy, method, args,
                          getMethodHash(method));
    } catch (Exception e) {
        if (!(e instanceof RuntimeException)) {
            Class<?> cl = proxy.getClass();
            try {
                method = cl.getMethod(method.getName(),
                                      method.getParameterTypes());
            } catch (NoSuchMethodException nsme) {
                throw (IllegalArgumentException)
                    new IllegalArgumentException().initCause(nsme);
            }
            Class<?> thrownType = e.getClass();
            for (Class<?> declaredType : method.getExceptionTypes()) {
                if (declaredType.isAssignableFrom(thrownType)) {
                    throw e;
                }
            }
            e = new UnexpectedException("unexpected exception", e);
        }
        throw e;
    }
}

我们跟进return ref.invoke((Remote) proxy, method, args,getMethodHash(method));invoke

 public Object invoke(Remote obj,
                 Method method,
                 Object[] params,
                 long opnum)
throws Exception
{
if (clientRefLog.isLoggable(Log.VERBOSE)) {
    clientRefLog.log(Log.VERBOSE, "method: " + method);
}

if (clientCallLog.isLoggable(Log.VERBOSE)) {
    logClientCall(obj, method);
}

Connection conn = ref.getChannel().newConnection();
RemoteCall call = null;
boolean reuse = true;
boolean alreadyFreed = false;

try {
    if (clientRefLog.isLoggable(Log.VERBOSE)) {
        clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);
    }

    call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);

    try {
        ObjectOutput out = call.getOutputStream();
        marshalCustomCallData(out);
        Class<?>[] types = method.getParameterTypes();
        for (int i = 0; i < types.length; i++) {
            marshalValue(types[i], params[i], out);
        }
    } catch (IOException e) {
        clientRefLog.log(Log.BRIEF,
            "IOException marshalling arguments: ", e);
        throw new MarshalException("error marshalling arguments", e);
    }

    call.executeCall();

    try {
        Class<?> rtype = method.getReturnType();
        if (rtype == void.class)
            return null;
        ObjectInput in = call.getInputStream();
        Object returnValue = unmarshalValue(rtype, in);
        alreadyFreed = true;
        clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
        ref.getChannel().free(conn, true);

        return returnValue;

    } catch (IOException e) {
        clientRefLog.log(Log.BRIEF,
                         "IOException unmarshalling return: ", e);
        throw new UnmarshalException("error unmarshalling return", e);
    } catch (ClassNotFoundException e) {
        clientRefLog.log(Log.BRIEF,
            "ClassNotFoundException unmarshalling return: ", e);

        throw new UnmarshalException("error unmarshalling return", e);
    } finally {
        try {
            call.done();
        } catch (IOException e) {
            reuse = false;
        }
    }

} catch (RuntimeException e) {
    if ((call == null) ||
        (((StreamRemoteCall) call).getServerException() != e))
    {
        reuse = false;
    }
    throw e;

} catch (RemoteException e) {
    reuse = false;
    throw e;

} catch (Error e) {
    reuse = false;
    throw e;

} finally {
    if (!alreadyFreed) {
        if (clientRefLog.isLoggable(Log.BRIEF)) {
            clientRefLog.log(Log.BRIEF, "free connection (reuse = " +
                                   reuse + ")");
        }
        ref.getChannel().free(conn, reuse);
    }
}
}

代码解释

这部分代码检查日志记录的详细级别,并记录要调用的方法信息。

if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "method: " + method);
}

获取一个新的网络连接,这个连接将用于传输方法调用的请求和响应。

Connection conn = ref.getChannel().newConnection();

创建一个 StreamRemoteCall 对象,它封装了远程调用的上下文信息,包括连接、对象ID和操作编号。

call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);

将方法调用的参数序列化并写入输出流,以便通过网络发送到远程服务器。

ObjectOutput out = call.getOutputStream();
marshalCustomCallData(out);
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < types.length; i++) {
         marshalValue(types[i], params[i], out);
 }

这个方法执行实际的远程调用,等待服务器返回结果。

call.executeCall();

从输入流中读取并反序列化远程调用的返回值。

  ObjectInput in = call.getInputStream();
  Object returnValue = unmarshalValue(rtype, in);

在完成远程调用后,释放连接。reuse 变量决定了连接是否可以被重用。

  ref.getChannel().free(conn, reuse);

主文

我们接着走,进入marshlValue

 protected static void marshalValue(Class<?> type, Object value,
                                   ObjectOutput out)
    throws IOException
{
    if (type.isPrimitive()) {
        if (type == int.class) {
            out.writeInt(((Integer) value).intValue());
        } else if (type == boolean.class) {
            out.writeBoolean(((Boolean) value).booleanValue());
        } else if (type == byte.class) {
            out.writeByte(((Byte) value).byteValue());
        } else if (type == char.class) {
            out.writeChar(((Character) value).charValue());
        } else if (type == short.class) {
            out.writeShort(((Short) value).shortValue());
        } else if (type == long.class) {
            out.writeLong(((Long) value).longValue());
        } else if (type == float.class) {
            out.writeFloat(((Float) value).floatValue());
        } else if (type == double.class) {
            out.writeDouble(((Double) value).doubleValue());
        } else {
            throw new Error("Unrecognized primitive type: " + type);
        }
    } else {
        out.writeObject(value);
    }
}

他就是序列化,然后走到call.executeCall();前面说过他是个攻击的地方,他也叫JRMP协议攻击然后接着往前走到Object returnValue = unmarshalValue(rtype, in);进去

 protected static Object unmarshalValue(Class<?> type, ObjectInput in)
    throws IOException, ClassNotFoundException
{
    if (type.isPrimitive()) {
        if (type == int.class) {
            return Integer.valueOf(in.readInt());
        } else if (type == boolean.class) {
            return Boolean.valueOf(in.readBoolean());
        } else if (type == byte.class) {
            return Byte.valueOf(in.readByte());
        } else if (type == char.class) {
            return Character.valueOf(in.readChar());
        } else if (type == short.class) {
            return Short.valueOf(in.readShort());
        } else if (type == long.class) {
            return Long.valueOf(in.readLong());
        } else if (type == float.class) {
            return Float.valueOf(in.readFloat());
        } else if (type == double.class) {
            return Double.valueOf(in.readDouble());
        } else {
            throw new Error("Unrecognized primitive type: " + type);
        }
    } else {
        return in.readObject();
    }
}

这里是反序列化,可以恶意攻击

7.客户端打注册中心

public class RMIServer {
public static void main(String[] args) throws Exception{
    IRemoteObj remoteObj = new RemoteObjImpl();
    Registry r = LocateRegistry.createRegistry(1099);
    r.bind("remoteObj",remoteObj);
}
}

我们打断点到第二句。
进去,和一开始分析的一样

public static Registry createRegistry(int port) throws RemoteException {
    return new RegistryImpl(port);
}

然后接着f7进去

public RegistryImpl(int port)
    throws RemoteException
{
    if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {
        // grant permission for default port only.
        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                public Void run() throws RemoteException {
                    LiveRef lref = new LiveRef(id, port);
                    setup(new UnicastServerRef(lref));
                    return null;
                }
            }, null, new SocketPermission("localhost:"+port, "listen,accept"));
        } catch (PrivilegedActionException pae) {
            throw (RemoteException)pae.getException();
        }
    } else {
        LiveRef lref = new LiveRef(id, port);
        setup(new UnicastServerRef(lref));
    }
}

然后进setup

private void setup(UnicastServerRef uref)
    throws RemoteException
{
    ref = uref;
    uref.exportObject(this, null, true);
}

然后进exportObject

 public Remote exportObject(Remote impl, Object data,
                           boolean permanent)
    throws RemoteException
{
    Class<?> implClass = impl.getClass();
    Remote stub;

    try {
        stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
    } catch (IllegalArgumentException e) {
        throw new ExportException(
            "remote object implements illegal remote interface", e);
    }
    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

    Target target =
        new Target(impl, this, stub, ref.getObjID(), permanent);
    ref.exportObject(target);
    hashToMethod_Map = hashToMethod_Maps.get(implClass);
    return stub;
}

接着进ref.exportObject(target);

public void exportObject(Target target) throws RemoteException {
    ep.exportObject(target);
}

接着进 ep.exportObject(target);

public void exportObject(Target target) throws RemoteException {
    transport.exportObject(target);
}

接着进transport.exportObject(target);

 public void exportObject(Target target) throws RemoteException {
   
    synchronized (this) {
        listen();
        exportCount++;
    } 
    boolean ok = false;
    try {
        super.exportObject(target);
        ok = true;
    } finally {
        if (!ok) {
            synchronized (this) {
                decrementExportCount();
            }
        }
    }
}

然后我们进listen()

private void listen() throws RemoteException {
    assert Thread.holdsLock(this);
    TCPEndpoint ep = getEndpoint();
    int port = ep.getPort();

    if (server == null) {
        if (tcpLog.isLoggable(Log.BRIEF)) {
            tcpLog.log(Log.BRIEF,
                "(port " + port + ") create server socket");
        }

        try {
            server = ep.newServerSocket();
            Thread t = AccessController.doPrivileged(
                new NewThreadAction(new AcceptLoop(server),
                                    "TCP Accept-" + port, true));
            t.start();
        } catch (java.net.BindException e) {
            throw new ExportException("Port already in use: " + port, e);
        } catch (IOException e) {
            throw new ExportException("Listen failed on port: " + port, e);
        }

    } else {
        // otherwise verify security access to existing server socket
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkListen(port);
        }
    }
}

然后我们进Thread t = AccessController.doPrivileged(new NewThreadAction(new AcceptLoop(server),"TCP Accept-" + port, true));AcceptLoop

  private class AcceptLoop implements Runnable {

    private final ServerSocket serverSocket;

    // state for throttling loop on exceptions (local to accept thread)
    private long lastExceptionTime = 0L;
    private int recentExceptionCount;

    AcceptLoop(ServerSocket serverSocket) {
        this.serverSocket = serverSocket;
    }

    public void run() {
        try {
            executeAcceptLoop();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
            }
        }
    }

然后我们进executeAcceptLoop();

private void executeAcceptLoop() {
        if (tcpLog.isLoggable(Log.BRIEF)) {
            tcpLog.log(Log.BRIEF, "listening on port " +
                       getEndpoint().getPort());
        }

        while (true) {
            Socket socket = null;
            try {
                socket = serverSocket.accept();

                
                InetAddress clientAddr = socket.getInetAddress();
                String clientHost = (clientAddr != null
                                     ? clientAddr.getHostAddress()
                                     : "0.0.0.0");

                
                try {
                    connectionThreadPool.execute(
                        new ConnectionHandler(socket, clientHost));
                } catch (RejectedExecutionException e) {
                    closeSocket(socket);
                    tcpLog.log(Log.BRIEF,
                               "rejected connection from " + clientHost);
                }

            } catch (Throwable t) {
                try {
                    
                    if (serverSocket.isClosed()) {
                        break;
                    }

                    try {
                        if (tcpLog.isLoggable(Level.WARNING)) {
                            tcpLog.log(Level.WARNING,
                                       "accept loop for " + serverSocket +
                                       " throws", t);
                        }
                    } catch (Throwable tt) {
                    }
                } finally {
                   
                    if (socket != null) {
                        closeSocket(socket);
                    }
                }

                
                if (!(t instanceof SecurityException)) {
                    try {
                        TCPEndpoint.shedConnectionCaches();
                    } catch (Throwable tt) {
                    }
                }

                
                if (t instanceof Exception ||
                    t instanceof OutOfMemoryError ||
                    t instanceof NoClassDefFoundError)
                {
                    if (!continueAfterAcceptFailure(t)) {
                        return;
                    }
                    // continue loop
                } else if (t instanceof Error) {
                    throw (Error) t;
                } else {
                    throw new UndeclaredThrowableException(t);
                }
            }
        }
    }

我们进到connectionThreadPool.execute(new ConnectionHandler(socket, clientHost));,然后来到他的run方法

  public void run() {
        Thread t = Thread.currentThread();
        String name = t.getName();
        try {
            t.setName("RMI TCP Connection(" +
                      connectionCount.incrementAndGet() +
                      ")-" + remoteHost);
            AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
                run0();
                return null;
            }, NOPERMS_ACC);
        } finally {
            t.setName(name);
        }
    }

他调用了run0

   private void run0() {
        TCPEndpoint endpoint = getEndpoint();
        int port = endpoint.getPort();

        threadConnectionHandler.set(this);

        // set socket to disable Nagle's algorithm (always send
        // immediately)
        // TBD: should this be left up to socket factory instead?
        try {
            socket.setTcpNoDelay(true);
        } catch (Exception e) {
            // if we fail to set this, ignore and proceed anyway
        }
        // set socket to timeout after excessive idle time
        try {
            if (connectionReadTimeout > 0)
                socket.setSoTimeout(connectionReadTimeout);
        } catch (Exception e) {
            // too bad, continue anyway
        }

        try {
            InputStream sockIn = socket.getInputStream();
            InputStream bufIn = sockIn.markSupported()
                    ? sockIn
                    : new BufferedInputStream(sockIn);

            // Read magic (or HTTP wrapper)
            bufIn.mark(4);
            DataInputStream in = new DataInputStream(bufIn);
            int magic = in.readInt();

            if (magic == POST) {
                tcpLog.log(Log.BRIEF, "decoding HTTP-wrapped call");

                // It's really a HTTP-wrapped request.  Repackage
                // the socket in a HttpReceiveSocket, reinitialize
                // sockIn and in, and reread magic.
                bufIn.reset();      // unread "POST"

                try {
                    socket = new HttpReceiveSocket(socket, bufIn, null);
                    remoteHost = "0.0.0.0";
                    sockIn = socket.getInputStream();
                    bufIn = new BufferedInputStream(sockIn);
                    in = new DataInputStream(bufIn);
                    magic = in.readInt();

                } catch (IOException e) {
                    throw new RemoteException("Error HTTP-unwrapping call",
                                              e);
                }
            }
            // bufIn's mark will invalidate itself when it overflows
            // so it doesn't have to be turned off

            // read and verify transport header
            short version = in.readShort();
            if (magic != TransportConstants.Magic ||
                version != TransportConstants.Version) {
                // protocol mismatch detected...
                // just close socket: this would recurse if we marshal an
                // exception to the client and the protocol at other end
                // doesn't match.
                closeSocket(socket);
                return;
            }

            OutputStream sockOut = socket.getOutputStream();
            BufferedOutputStream bufOut =
                new BufferedOutputStream(sockOut);
            DataOutputStream out = new DataOutputStream(bufOut);

            int remotePort = socket.getPort();

            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "accepted socket from [" +
                                 remoteHost + ":" + remotePort + "]");
            }

            TCPEndpoint ep;
            TCPChannel ch;
            TCPConnection conn;

            // send ack (or nack) for protocol
            byte protocol = in.readByte();
            switch (protocol) {
            case TransportConstants.SingleOpProtocol:
                // no ack for protocol

                // create dummy channel for receiving messages
                ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
                                     endpoint.getClientSocketFactory(),
                                     endpoint.getServerSocketFactory());
                ch = new TCPChannel(TCPTransport.this, ep);
                conn = new TCPConnection(ch, socket, bufIn, bufOut);

                // read input messages
                handleMessages(conn, false);
                break;

            case TransportConstants.StreamProtocol:
                // send ack
                out.writeByte(TransportConstants.ProtocolAck);

                // suggest endpoint (in case client doesn't know host name)
                if (tcpLog.isLoggable(Log.VERBOSE)) {
                    tcpLog.log(Log.VERBOSE, "(port " + port +
                        ") " + "suggesting " + remoteHost + ":" +
                        remotePort);
                }

                out.writeUTF(remoteHost);
                out.writeInt(remotePort);
                out.flush();

                // read and discard (possibly bogus) endpoint
                // REMIND: would be faster to read 2 bytes then skip N+4
                String clientHost = in.readUTF();
                int    clientPort = in.readInt();
                if (tcpLog.isLoggable(Log.VERBOSE)) {
                    tcpLog.log(Log.VERBOSE, "(port " + port +
                        ") client using " + clientHost + ":" + clientPort);
                }

                // create dummy channel for receiving messages
                // (why not use clientHost and clientPort?)
                ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
                                     endpoint.getClientSocketFactory(),
                                     endpoint.getServerSocketFactory());
                ch = new TCPChannel(TCPTransport.this, ep);
                conn = new TCPConnection(ch, socket, bufIn, bufOut);

                // read input messages
                handleMessages(conn, true);
                break;

            case TransportConstants.MultiplexProtocol:
                if (tcpLog.isLoggable(Log.VERBOSE)) {
                    tcpLog.log(Log.VERBOSE, "(port " + port +
                        ") accepting multiplex protocol");
                }

                // send ack
                out.writeByte(TransportConstants.ProtocolAck);

                // suggest endpoint (in case client doesn't already have one)
                if (tcpLog.isLoggable(Log.VERBOSE)) {
                    tcpLog.log(Log.VERBOSE, "(port " + port +
                        ") suggesting " + remoteHost + ":" + remotePort);
                }

                out.writeUTF(remoteHost);
                out.writeInt(remotePort);
                out.flush();

                // read endpoint client has decided to use
                ep = new TCPEndpoint(in.readUTF(), in.readInt(),
                                     endpoint.getClientSocketFactory(),
                                     endpoint.getServerSocketFactory());
                if (tcpLog.isLoggable(Log.VERBOSE)) {
                    tcpLog.log(Log.VERBOSE, "(port " +
                        port + ") client using " +
                        ep.getHost() + ":" + ep.getPort());
                }

                ConnectionMultiplexer multiplexer;
                synchronized (channelTable) {
                    // create or find channel for this endpoint
                    ch = getChannel(ep);
                    multiplexer =
                        new ConnectionMultiplexer(ch, bufIn, sockOut,
                                                  false);
                    ch.useMultiplexer(multiplexer);
                }
                multiplexer.run();
                break;

            default:
                // protocol not understood, send nack and close socket
                out.writeByte(TransportConstants.ProtocolNack);
                out.flush();
                break;
            }

        } catch (IOException e) {
            // socket in unknown state: destroy socket
            tcpLog.log(Log.BRIEF, "terminated with exception:", e);
        } finally {
            closeSocket(socket);
        }
    }

这个run0就是个处理网络请求的,他重点在handleMessages(conn, false);,我们进去。

  void handleMessages(Connection conn, boolean persistent) {
    int port = getEndpoint().getPort();

    try {
        DataInputStream in = new DataInputStream(conn.getInputStream());
        do {
            int op = in.read();     // transport op
            if (op == -1) {
                if (tcpLog.isLoggable(Log.BRIEF)) {
                    tcpLog.log(Log.BRIEF, "(port " +
                        port + ") connection closed");
                }
                break;
            }

            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + port +
                    ") op = " + op);
            }

            switch (op) {
            case TransportConstants.Call:
                // service incoming RMI call
                RemoteCall call = new StreamRemoteCall(conn);
                if (serviceCall(call) == false)
                    return;
                break;

            case TransportConstants.Ping:
                // send ack for ping
                DataOutputStream out =
                    new DataOutputStream(conn.getOutputStream());
                out.writeByte(TransportConstants.PingAck);
                conn.releaseOutputStream();
                break;

            case TransportConstants.DGCAck:
                DGCAckHandler.received(UID.read(in));
                break;

            default:
                throw new IOException("unknown transport op " + op);
            }
        } while (persistent);

    } catch (IOException e) {
        // exception during processing causes connection to close (below)
        if (tcpLog.isLoggable(Log.BRIEF)) {
            tcpLog.log(Log.BRIEF, "(port " + port +
                ") exception: ", e);
        }
    } finally {
        try {
            conn.close();
        } catch (IOException ex) {
            // eat exception
        }
    }
}

这个是看他传进去字段值,做case处理,默认的是serviceCall(call)

 public boolean serviceCall(final RemoteCall call) {
    try {
        /* read object id */
        final Remote impl;
        ObjID id;

        try {
            id = ObjID.read(call.getInputStream());
        } catch (java.io.IOException e) {
            throw new MarshalException("unable to read objID", e);
        }

        /* get the remote object */
        Transport transport = id.equals(dgcID) ? null : this;
        Target target =
            ObjectTable.getTarget(new ObjectEndpoint(id, transport));

        if (target == null || (impl = target.getImpl()) == null) {
            throw new NoSuchObjectException("no such object in table");
        }

        final Dispatcher disp = target.getDispatcher();
        target.incrementCallCount();
        try {
            /* call the dispatcher */
            transportLog.log(Log.VERBOSE, "call dispatcher");

            final AccessControlContext acc =
                target.getAccessControlContext();
            ClassLoader ccl = target.getContextClassLoader();

            ClassLoader savedCcl = Thread.currentThread().getContextClassLoader();

            try {
                setContextClassLoader(ccl);
                currentTransport.set(this);
                try {
                    java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedExceptionAction<Void>() {
                        public Void run() throws IOException {
                            checkAcceptPermission(acc);
                            disp.dispatch(impl, call);
                            return null;
                        }
                    }, acc);
                } catch (java.security.PrivilegedActionException pae) {
                    throw (IOException) pae.getException();
                }
            } finally {
                setContextClassLoader(savedCcl);
                currentTransport.set(null);
            }

        } catch (IOException ex) {
            transportLog.log(Log.BRIEF,
                             "exception thrown by dispatcher: ", ex);
            return false;
        } finally {
            target.decrementCallCount();
        }

    } catch (RemoteException e) {

        // if calls are being logged, write out exception
        if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
            // include client host name if possible
            String clientHost = "";
            try {
                clientHost = "[" +
                    RemoteServer.getClientHost() + "] ";
            } catch (ServerNotActiveException ex) {
            }
            String message = clientHost + "exception: ";
            UnicastServerRef.callLog.log(Log.BRIEF, message, e);
        }

        
        try {
            ObjectOutput out = call.getResultStream(false);
            UnicastServerRef.clearStackTraces(e);
            out.writeObject(e);
            call.releaseOutputStream();

        } catch (IOException ie) {
            transportLog.log(Log.BRIEF,
                "exception thrown marshalling exception: ", ie);
            return false;
        }
    }

    return true;
}

这里面的Target target =ObjectTable.getTarget(new ObjectEndpoint(id, transport));这个就是在我们传进去的表里面查找

我们在if (target == null || (impl = target.getImpl()) == null)这里下个断点,然后客户端请求。

然后我们往下走,走到 disp.dispatch(impl, call);,进去。

public void dispatch(Remote obj, RemoteCall call) throws IOException {
    
    int num;
    long op;

    try {
        // read remote call header
        ObjectInput in;
        try {
            in = call.getInputStream();
            num = in.readInt();
            if (num >= 0) {
                if (skel != null) {
                    oldDispatch(obj, call, num);
                    return;
                } else {
                    throw new UnmarshalException(
                        "skeleton class not found but required " +
                        "for client version");
                }
            }
            op = in.readLong();
        } catch (Exception readEx) {
            throw new UnmarshalException("error unmarshalling call header",
                                         readEx);
        }

       
        MarshalInputStream marshalStream = (MarshalInputStream) in;
        marshalStream.skipDefaultResolveClass();

        Method method = hashToMethod_Map.get(op);
        if (method == null) {
            throw new UnmarshalException("unrecognized method hash: " +
                "method not supported by remote object");
        }

        // if calls are being logged, write out object id and operation
        logCall(obj, method);

        // unmarshal parameters
        Class<?>[] types = method.getParameterTypes();
        Object[] params = new Object[types.length];

        try {
            unmarshalCustomCallData(in);
            for (int i = 0; i < types.length; i++) {
                params[i] = unmarshalValue(types[i], in);
            }
        } catch (java.io.IOException e) {
            throw new UnmarshalException(
                "error unmarshalling arguments", e);
        } catch (ClassNotFoundException e) {
            throw new UnmarshalException(
                "error unmarshalling arguments", e);
        } finally {
            call.releaseInputStream();
        }

        // make upcall on remote object
        Object result;
        try {
            result = method.invoke(obj, params);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }

        // marshal return value
        try {
            ObjectOutput out = call.getResultStream(true);
            Class<?> rtype = method.getReturnType();
            if (rtype != void.class) {
                marshalValue(rtype, result, out);
            }
        } catch (IOException ex) {
            throw new MarshalException("error marshalling return", ex);
           
        }
    } catch (Throwable e) {
        logCallException(e);

        ObjectOutput out = call.getResultStream(false);
        if (e instanceof Error) {
            e = new ServerError(
                "Error occurred in server thread", (Error) e);
        } else if (e instanceof RemoteException) {
            e = new ServerException(
                "RemoteException occurred in server thread",
                (Exception) e);
        }
        if (suppressStackTraces) {
            clearStackTraces(e);
        }
        out.writeObject(e);
    } finally {
        call.releaseInputStream(); // in case skeleton doesn't
        call.releaseOutputStream();
    }
}

然后我们进 oldDispatch(obj, call, num);

public void oldDispatch(Remote obj, RemoteCall call, int op)
    throws IOException
{
    long hash;              // hash for matching stub with skeleton

    try {
        // read remote call header
        ObjectInput in;
        try {
            in = call.getInputStream();
            try {
                Class<?> clazz = Class.forName("sun.rmi.transport.DGCImpl_Skel");
                if (clazz.isAssignableFrom(skel.getClass())) {
                    ((MarshalInputStream)in).useCodebaseOnly();
                }
            } catch (ClassNotFoundException ignore) { }
            hash = in.readLong();
        } catch (Exception readEx) {
            throw new UnmarshalException("error unmarshalling call header",
                                         readEx);
        }

        // if calls are being logged, write out object id and operation
        logCall(obj, skel.getOperations()[op]);
        unmarshalCustomCallData(in);
        // dispatch to skeleton for remote object
        skel.dispatch(obj, call, op, hash);

    } catch (Throwable e) {
        logCallException(e);

        ObjectOutput out = call.getResultStream(false);
        if (e instanceof Error) {
            e = new ServerError(
                "Error occurred in server thread", (Error) e);
        } else if (e instanceof RemoteException) {
            e = new ServerException(
                "RemoteException occurred in server thread",
                (Exception) e);
        }
        if (suppressStackTraces) {
            clearStackTraces(e);
        }
        out.writeObject(e);
    } finally {
        call.releaseInputStream(); // in case skeleton doesn't
        call.releaseOutputStream();
    }
}

然后我们进skel.dispatch(obj, call, op, hash);

 public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
    if (var4 != 4905912898345647071L) {
        throw new SkeletonMismatchException("interface hash mismatch");
    } else {
        RegistryImpl var6 = (RegistryImpl)var1;
        String var7;
        Remote var8;
        ObjectInput var10;
        ObjectInput var11;
        switch (var3) {
            case 0:
                try {
                    var11 = var2.getInputStream();
                    var7 = (String)var11.readObject();
                    var8 = (Remote)var11.readObject();
                } catch (IOException var94) {
                    throw new UnmarshalException("error unmarshalling arguments", var94);
                } catch (ClassNotFoundException var95) {
                    throw new UnmarshalException("error unmarshalling arguments", var95);
                } finally {
                    var2.releaseInputStream();
                }

                var6.bind(var7, var8);

                try {
                    var2.getResultStream(true);
                    break;
                } catch (IOException var93) {
                    throw new MarshalException("error marshalling return", var93);
                }
            case 1:
                var2.releaseInputStream();
                String[] var97 = var6.list();

                try {
                    ObjectOutput var98 = var2.getResultStream(true);
                    var98.writeObject(var97);
                    break;
                } catch (IOException var92) {
                    throw new MarshalException("error marshalling return", var92);
                }
            case 2:
                try {
                    var10 = var2.getInputStream();
                    var7 = (String)var10.readObject();
                } catch (IOException var89) {
                    throw new UnmarshalException("error unmarshalling arguments", var89);
                } catch (ClassNotFoundException var90) {
                    throw new UnmarshalException("error unmarshalling arguments", var90);
                } finally {
                    var2.releaseInputStream();
                }

                var8 = var6.lookup(var7);

                try {
                    ObjectOutput var9 = var2.getResultStream(true);
                    var9.writeObject(var8);
                    break;
                } catch (IOException var88) {
                    throw new MarshalException("error marshalling return", var88);
                }
            case 3:
                try {
                    var11 = var2.getInputStream();
                    var7 = (String)var11.readObject();
                    var8 = (Remote)var11.readObject();
                } catch (IOException var85) {
                    throw new UnmarshalException("error unmarshalling arguments", var85);
                } catch (ClassNotFoundException var86) {
                    throw new UnmarshalException("error unmarshalling arguments", var86);
                } finally {
                    var2.releaseInputStream();
                }

                var6.rebind(var7, var8);

                try {
                    var2.getResultStream(true);
                    break;
                } catch (IOException var84) {
                    throw new MarshalException("error marshalling return", var84);
                }
            case 4:
                try {
                    var10 = var2.getInputStream();
                    var7 = (String)var10.readObject();
                } catch (IOException var81) {
                    throw new UnmarshalException("error unmarshalling arguments", var81);
                } catch (ClassNotFoundException var82) {
                    throw new UnmarshalException("error unmarshalling arguments", var82);
                } finally {
                    var2.releaseInputStream();
                }

                var6.unbind(var7);

                try {
                    var2.getResultStream(true);
                    break;
                } catch (IOException var80) {
                    throw new MarshalException("error marshalling return", var80);
                }
            default:
                throw new UnmarshalException("invalid method number");
        }

    }
}

这里就是客户端打注册中心的攻击地方。

攻击方式

先介绍一下这段源码吧,很长,基本都是在做 case 的工作。
我们与注册中心进行交互可以使用如下几种方式:

 list

 bind

 rebind

 unbind

 lookup

这几种方法位于 RegistryImpl_Skel#dispatch 中,也就是我们现在 dispatch 这个方法的地方。

如果存在对传入的对象调用 readObject 方法,则可以利用,dispatch 里面对应关系如下:

0->bind
1->list
2->lookup
3->rebind
4->unbind

只要中间是有反序列化就是可以攻击的,这里只有list没有readobject,所以list不行

8.客户端请求服务端是怎么处理的

他一样是会进入这个dispatch

 public void dispatch(Remote obj, RemoteCall call) throws IOException {
    
    int num;
    long op;

    try {
        // read remote call header
        ObjectInput in;
        try {
            in = call.getInputStream();
            num = in.readInt();
            if (num >= 0) {
                if (skel != null) {
                    oldDispatch(obj, call, num);
                    return;
                } else {
                    throw new UnmarshalException(
                        "skeleton class not found but required " +
                        "for client version");
                }
            }
            op = in.readLong();
        } catch (Exception readEx) {
            throw new UnmarshalException("error unmarshalling call header",
                                         readEx);
        }

       
        MarshalInputStream marshalStream = (MarshalInputStream) in;
        marshalStream.skipDefaultResolveClass();

        Method method = hashToMethod_Map.get(op);
        if (method == null) {
            throw new UnmarshalException("unrecognized method hash: " +
                "method not supported by remote object");
        }

        // if calls are being logged, write out object id and operation
        logCall(obj, method);

        // unmarshal parameters
        Class<?>[] types = method.getParameterTypes();
        Object[] params = new Object[types.length];

        try {
            unmarshalCustomCallData(in);
            for (int i = 0; i < types.length; i++) {
                params[i] = unmarshalValue(types[i], in);
            }
        } catch (java.io.IOException e) {
            throw new UnmarshalException(
                "error unmarshalling arguments", e);
        } catch (ClassNotFoundException e) {
            throw new UnmarshalException(
                "error unmarshalling arguments", e);
        } finally {
            call.releaseInputStream();
        }

        // make upcall on remote object
        Object result;
        try {
            result = method.invoke(obj, params);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }

        // marshal return value
        try {
            ObjectOutput out = call.getResultStream(true);
            Class<?> rtype = method.getReturnType();
            if (rtype != void.class) {
                marshalValue(rtype, result, out);
            }
        } catch (IOException ex) {
            throw new MarshalException("error marshalling return", ex);
           
        }
    } catch (Throwable e) {
        logCallException(e);

        ObjectOutput out = call.getResultStream(false);
        if (e instanceof Error) {
            e = new ServerError(
                "Error occurred in server thread", (Error) e);
        } else if (e instanceof RemoteException) {
            e = new ServerException(
                "RemoteException occurred in server thread",
                (Exception) e);
        }
        if (suppressStackTraces) {
            clearStackTraces(e);
        }
        out.writeObject(e);
    } finally {
        call.releaseInputStream(); // in case skeleton doesn't
        call.releaseOutputStream();
    }
}

但是不一样的是他不是进入if (skel != null),因为skelnull
,然后我们接着往下走

     for (int i = 0; i < types.length; i++) {
                params[i] = unmarshalValue(types[i], in);
            }

到这里,他和之前一样是反序列化,是存在漏洞的。

9.DGC

DGC是用于内存回收的,且端口随机。

他在exportObjectputTarget里面

public void exportObject(Target target) throws RemoteException {
    target.setExportedTransport(this);
    ObjectTable.putTarget(target);
}

进去putTarget
static void putTarget(Target target) throws ExportException {
ObjectEndpoint oe = target.getObjectEndpoint();
WeakRef weakImpl = target.getWeakImpl();

    if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
        DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe);
    }

    synchronized (tableLock) {
        /**
         * Do nothing if impl has already been collected (see 6597112). Check while
         * holding tableLock to ensure that Reaper cannot process weakImpl in between
         * null check and put/increment effects.
         */
        if (target.getImpl() != null) {
            if (objTable.containsKey(oe)) {
                throw new ExportException(
                    "internal error: ObjID already in use");
            } else if (implTable.containsKey(weakImpl)) {
                throw new ExportException("object already exported");
            }

            objTable.put(oe, target);
            implTable.put(weakImpl, target);

            if (!target.isPermanent()) {
                incrementKeepAliveCount();
            }
        }
    }
}

然后我们看到里面的if

 if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
        DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe);
    }

dgclog是个静态变量

 static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc",
    LogStream.parseLevel(AccessController.doPrivileged(
        new GetPropertyAction("sun.rmi.dgc.logLevel"))));

所以他会调用DGCImpl里面的静态方法

static {
    
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            ClassLoader savedCcl =
                Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(
                    ClassLoader.getSystemClassLoader());

                try {
                    dgc = new DGCImpl();
                    ObjID dgcID = new ObjID(ObjID.DGC_ID);
                    LiveRef ref = new LiveRef(dgcID, 0);
                    UnicastServerRef disp = new UnicastServerRef(ref);
                    Remote stub =
                        Util.createProxy(DGCImpl.class,
                                         new UnicastRef(ref), true);
                    disp.setSkeleton(dgc);

                    Permissions perms = new Permissions();
                    perms.add(new SocketPermission("*", "accept,resolve"));
                    ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };
                    AccessControlContext acceptAcc = new AccessControlContext(pd);

                    Target target = AccessController.doPrivileged(
                        new PrivilegedAction<Target>() {
                            public Target run() {
                                return new Target(dgc, disp, stub, dgcID, true);
                            }
                        }, acceptAcc);

                    ObjectTable.putTarget(target);
                } catch (RemoteException e) {
                    throw new Error(
                        "exception initializing server-side DGC", e);
                }
            } finally {
                Thread.currentThread().setContextClassLoader(savedCcl);
            }
            return null;
        }
    });

来到createProxy和之前的创建代理一样

public static Remote createProxy(Class<?> implClass,
                                 RemoteRef clientRef,
                                 boolean forceStubUse)
    throws StubNotFoundException
{
    Class<?> remoteClass;

    try {
        remoteClass = getRemoteClass(implClass);
    } catch (ClassNotFoundException ex ) {
        throw new StubNotFoundException(
            "object does not implement a remote interface: " +
            implClass.getName());
    }

    if (forceStubUse ||
        !(ignoreStubClasses || !stubClassExists(remoteClass)))
    {
        return createStub(remoteClass, clientRef);
    }

    final ClassLoader loader = implClass.getClassLoader();
    final Class<?>[] interfaces = getRemoteInterfaces(implClass);
    final InvocationHandler handler =
        new RemoteObjectInvocationHandler(clientRef);

    /* REMIND: private remote interfaces? */

    try {
        return AccessController.doPrivileged(new PrivilegedAction<Remote>() {
            public Remote run() {
                return (Remote) Proxy.newProxyInstance(loader,
                                                       interfaces,
                                                       handler);
            }});
    } catch (IllegalArgumentException e) {
        throw new StubNotFoundException("unable to create proxy", e);
    }
}

进到createStub

  private static RemoteStub createStub(Class<?> remoteClass, RemoteRef ref)
    throws StubNotFoundException
{
    String stubname = remoteClass.getName() + "_Stub";

   
    try {
        Class<?> stubcl =
            Class.forName(stubname, false, remoteClass.getClassLoader());
        Constructor<?> cons = stubcl.getConstructor(stubConsParamTypes);
        return (RemoteStub) cons.newInstance(new Object[] { ref });

    } catch (ClassNotFoundException e) {
        throw new StubNotFoundException(
            "Stub class not found: " + stubname, e);
    } catch (NoSuchMethodException e) {
        throw new StubNotFoundException(
            "Stub class missing constructor: " + stubname, e);
    } catch (InstantiationException e) {
        throw new StubNotFoundException(
            "Can't create instance of stub class: " + stubname, e);
    } catch (IllegalAccessException e) {
        throw new StubNotFoundException(
            "Stub class constructor not public: " + stubname, e);
    } catch (InvocationTargetException e) {
        throw new StubNotFoundException(
            "Exception creating instance of stub class: " + stubname, e);
    } catch (ClassCastException e) {
        throw new StubNotFoundException(
            "Stub class not instance of RemoteStub: " + stubname, e);
    }
}

这里和注册中心创建远程服务一样,尝试是否可以获取到这一个类 DGCImpl_Stub

我们重点关注一下 DGCStub 里面有漏洞的地方。

DGCImpl_Stub 这个类下,它有两个方法,一个是 clean,另外一个是 dirtyclean 就是”强”清除内存,dirty 就是”弱”清除内存。

 public void clean(ObjID[] var1, long var2, VMID var4, boolean var5) throws RemoteException {
    try {
        RemoteCall var6 = super.ref.newCall(this, operations, 0, -669196253586618813L);

        try {
            ObjectOutput var7 = var6.getOutputStream();
            var7.writeObject(var1);
            var7.writeLong(var2);
            var7.writeObject(var4);
            var7.writeBoolean(var5);
        } catch (IOException var8) {
            throw new MarshalException("error marshalling arguments", var8);
        }

        super.ref.invoke(var6);
        super.ref.done(var6);
    } catch (RuntimeException var9) {
        throw var9;
    } catch (RemoteException var10) {
        throw var10;
    } catch (Exception var11) {
        throw new UnexpectedException("undeclared checked exception", var11);
    }
}

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {
    try {
        RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);

        try {
            ObjectOutput var6 = var5.getOutputStream();
            var6.writeObject(var1);
            var6.writeLong(var2);
            var6.writeObject(var4);
        } catch (IOException var20) {
            throw new MarshalException("error marshalling arguments", var20);
        }

        super.ref.invoke(var5);

        Lease var24;
        try {
            ObjectInput var9 = var5.getInputStream();
            var24 = (Lease)var9.readObject();
        } catch (IOException var17) {
            throw new UnmarshalException("error unmarshalling return", var17);
        } catch (ClassNotFoundException var18) {
            throw new UnmarshalException("error unmarshalling return", var18);
        } finally {
            super.ref.done(var5);
        }

        return var24;
    } catch (RuntimeException var21) {
        throw var21;
    } catch (RemoteException var22) {
        throw var22;
    } catch (Exception var23) {
        throw new UnexpectedException("undeclared checked exception", var23);
    }
}

}

都是存在漏洞的

我们到DGCImpl_Skel看看

    public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
    if (var4 != -669196253586618813L) {
        throw new SkeletonMismatchException("interface hash mismatch");
    } else {
        DGCImpl var6 = (DGCImpl)var1;
        ObjID[] var7;
        long var8;
        switch (var3) {
            case 0:
                VMID var39;
                boolean var40;
                try {
                    ObjectInput var14 = var2.getInputStream();
                    var7 = (ObjID[])var14.readObject();
                    var8 = var14.readLong();
                    var39 = (VMID)var14.readObject();
                    var40 = var14.readBoolean();
                } catch (IOException var36) {
                    throw new UnmarshalException("error unmarshalling arguments", var36);
                } catch (ClassNotFoundException var37) {
                    throw new UnmarshalException("error unmarshalling arguments", var37);
                } finally {
                    var2.releaseInputStream();
                }

                var6.clean(var7, var8, var39, var40);

                try {
                    var2.getResultStream(true);
                    break;
                } catch (IOException var35) {
                    throw new MarshalException("error marshalling return", var35);
                }
            case 1:
                Lease var10;
                try {
                    ObjectInput var13 = var2.getInputStream();
                    var7 = (ObjID[])var13.readObject();
                    var8 = var13.readLong();
                    var10 = (Lease)var13.readObject();
                } catch (IOException var32) {
                    throw new UnmarshalException("error unmarshalling arguments", var32);
                } catch (ClassNotFoundException var33) {
                    throw new UnmarshalException("error unmarshalling arguments", var33);
                } finally {
                    var2.releaseInputStream();
                }

                Lease var11 = var6.dirty(var7, var8, var10);

                try {
                    ObjectOutput var12 = var2.getResultStream(true);
                    var12.writeObject(var11);
                    break;
                } catch (IOException var31) {
                    throw new MarshalException("error marshalling return", var31);
                }
            default:
                throw new UnmarshalException("invalid method number");
        }

    }
}

也是存在漏洞的

标签:return,基础,port,try,catch,new,RMI,throw
From: https://www.cnblogs.com/20031225gbz/p/18343570

相关文章

  • USB协议基础篇
    初次接触USB的同学,可能会被里面各种名词给搞晕,下面就来梳理一下这些知识,希望能帮助大家理解USB。一,从最常见的名词说起1.1什么是USB这个我就不多解释了,直译就是通用串行总线。再不明白的就百度。1.2USB协议版本USB1.0/1.1(low/fullspeed),传输速率最大为12MbpsUSB2......
  • java基础 之 重写equals时为什么要重写hashCode
    文章目录前言回答了解哈希hashCode()总结前言了解equals戳这里→java基础之equals和==的区别请记住这句话:两个对象相同,哈希码一定相同。哈希码相同,两个对象不一定相同。回答只重写equals()方法,不重写hashCode()方法:存在A.equals(B)为true,但是hashCode不......
  • 云原生数据基础设施之kubeblocks
    一、kubeblocks简介参考文档:https://kubeblocks.io/docs/release-0.9/user_docs/overview/introduction1.KubeBlocks是什么?​KubeBlocks是基于Kubernetes的云原生数据基础设施,将顶级云服务提供商的大规模生产经验与增强的可用性和稳定性改进相结合,帮助用户轻松构建容器化......
  • linux 基础知识汇总
    1、Linux文件系统概述Linux文件系统是指操作系统用来控制文件如何存储和检索的结构和逻辑。文件系统结构根目录:/Linux文件系统从根目录(/)开始,这是所有文件和目录的起点。目录结构:Linux使用层次化目录结构,每个目录包含文件和子目录。挂载点:各种文件系统通过挂载点(mo......
  • Arcgis基础知识-地理信息系统基本概念
    Arcgis基础知识-地理信息系统基本概念​一、基本概念地理信息系统(GeographicInformationSystem,简称GIS)是一种采集、存储、管理、分析、显示与应用地理信息的计算机系统,是分析和处理海量地理数据的通用技术。简单来说对空间数据的显示,编辑处理,分析应用,打印输出的系统。......
  • 【Java基础知识4】反射
    一、反射机制Java反射机制是指在程序的运行过程中,对于任意一个类,都能够知道它的所有属性和方法;对于任意一个对象,都能够知道调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为JAVA语言的反射机制二、反射的核心内容反射的核心内容是JVM在运行时动态......
  • 【Java基础知识3】泛型
    一、泛型的意义泛型的本质是将类型参数化,从而达到代码复用。即:在不创建新的类型下,通过泛型指定不同类型来控制形参具体类型,简单来讲就是,当我们不知道用什么数据类型接收数据的情况下,可以使用泛型来接收。代码示例:未使用泛型情况下:privatestaticintadd(inta,intb){......
  • 【Java基础知识5】异常
    一、什么是异常?正常程序所不能处理或没有处理的不正常行为称为异常。Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。二、异常的层次结构三、异常的分类1.Throwable异常体系的顶层类,其派生出两个重要的子类, Error 和......
  • java面对对象基础
    1.对象的内存布局类实例化为对象:类实例化为对象的语句一定要加括号,否则编译不通过//类名生成对象名=new类名()//类名生成对象名=new类名()ToolMytool=newTool();classTool{ publicbooleanoddOreven(intnum){ if(num%2!=0){ returntrue; }else{ retur......
  • Java基础语法
    关键字和保留字在Java编程语言中,关键字和保留字是语言的基本组成部分。它们有特定的含义并在Java程序中扮演着重要的角色。理解这些关键字和保留字对于编写有效的Java代码至关重要。1.关键字(Keywords)关键字是Java语言中具有特殊含义的词,它们在编译器中被预定义并用于执行特定......