Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I deserialize the object, if it was moved to another package or renamed?

Consider the following situation:

There is a serialization file, created by the older version of the application. Unfortunately, the package has changed for the class, that has been serialized. And now I need to load the information from this file into the same class, but located in different package. This class has serialVersionUID defined and has not changed (i.e. is compatible).

Question: Is it possible to load the new class instances from this file using any tricks (except trivial copying the class into old package and then using the deserialization wrapper logic)? It is possible to use readResolve() to recover from moving/renaming the class? If not, please, explain why.

like image 856
dma_k Avatar asked Mar 01 '10 20:03

dma_k


People also ask

Which method is used to deserialize an object?

The ObjectInputStream class contains readObject() method for deserializing an object.

Which method is used to deserialize from an open like object?

The load() method deserializes from an open file-like object.

How do you serialize and deserialize an object?

The Serialization is a process of changing the state of an object into a byte stream, an object is said to be serializable if its class or parent classes implement either the Serializable or Externalizable interface and the Deserialization is a process of converting the serialized object back into a copy of an object.


4 Answers

It is possible:

class HackedObjectInputStream extends ObjectInputStream {      public HackedObjectInputStream(InputStream in) throws IOException {         super(in);     }      @Override     protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {         ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();          if (resultClassDescriptor.getName().equals("oldpackage.Clazz"))             resultClassDescriptor = ObjectStreamClass.lookup(newpackage.Clazz.class);          return resultClassDescriptor;     } } 

This also allows one to ignore serialVersionUIDs mismatch or even deserialize a class if its field structure was changed.

like image 162
Igor Nardin Avatar answered Oct 09 '22 16:10

Igor Nardin


Question: Is it possible to load the new class instances from this file using any tricks (except trivial copying the class into old package and then using the deserialization wrapper logic)?

I don't think there are any other "tricks" you could use that don't involve at least a partial reimplementation of the serialization protocol.

Edit: there is in fact a hook that allows this if you control the deserialization process, see the other answer.

It is possible to use readResolve() to recover from moving/renaming the class? If not, please, explain why.

No, because the deserialization mechanism will fail much earlier, at the stage where it tries to locate the class that's being deserialized - it has no way of knowing that a class in a different package has a readResolve() method it's supposed to use.

like image 45
Michael Borgwardt Avatar answered Oct 09 '22 17:10

Michael Borgwardt


If you use Cygnus Hex Editor you can manually change the name of the package/class.

If the new name (always including the package) has the same size you can just replace the old name by the new name, but if the size has changed you need to update the first 2 chars before the name with new new length.

Right click the Standard Data Types and change to Big Endian.

The length is a Signed Word.

For example:

00 0E 70 61 63 6B 61 67 65 2E 53 61 6D 70 6C 65
.  .  p   a  c  k  a  g  e  .  S  a  m  p  l  e

is how package.Sample is writen. 00 0E means 14, the number of chars "package.Sample" has.

If we want to change to newpackage.Sample we replace that string to:

00 12 6E 65 77 70 61 63 6B 61 67 65 2E 53 61 6D 70 6C 65
.  .  n  e  w  p   a  c  k  a  g  e  .  S  a  m  p  l  e

00 12 means 18, the number of chars "newpackage.Sample" has.

And of course you can make a patcher to update this automatically.

like image 23
José Roberto Araújo Júnior Avatar answered Oct 09 '22 15:10

José Roberto Araújo Júnior


Use this class instead of ObjectInputStream if your classes moved to another namespace.

class SafeObjectInputStream extends ObjectInputStream {
    private final String oldNameSpace;
    private final String newNameSpace;

    public SafeObjectInputStream(InputStream in, String oldNameSpace, String newNameSpace) throws IOException {
        super(in);
        this.oldNameSpace = oldNameSpace;
        this.newNameSpace = newNameSpace;
    }

    @Override
    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        ObjectStreamClass result = super.readClassDescriptor();
        try {
            if (result.getName().contains(oldNameSpace)) {
                String newClassName = result.getName().replace(oldNameSpace, newNameSpace);
                // Test the class exists
                Class localClass = Class.forName(newClassName);

                Field nameField = ObjectStreamClass.class.getDeclaredField("name");
                nameField.setAccessible(true);
                nameField.set(result, newClassName);

                ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass)
                Field suidField = ObjectStreamClass.class.getDeclaredField("suid");
                suidField.setAccessible(true);
                suidField.set(result, localClassDescriptor.getSerialVersionUID());
        }
        } catch(Exception e) {
            throw new IOException("Exception when trying to replace namespace", e);
        }
        return result;
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (desc.getName().contains(oldNameSpace)) {
            String newClassName = desc.getName().replace(oldNameSpace, newNameSpace);
            return Class.forName(newClassName);
        }
        return super.resolveClass(desc);
    }
}

You may use it as follows:

ObjectInputStream objectStream = new SafeObjectInputStream(inputStream, "org.oldnamespace", "org.newnamespace");
objectStream.readObject();

It won't fail with StreamCorruptedException if some of your classes change. Instead, it will try to load as many fields as possible. You may perform data validation/upgrade by implementing readObject method in your classes.

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    // Validate read data here
}
like image 20
Ivan Nikitin Avatar answered Oct 09 '22 16:10

Ivan Nikitin