Pages

Monday, January 9, 2017

Decompiling jars obfuscated with AspectJ (e.g. D2FS4DCTM-WEB-4.5.0.jar or dfc.jar)

It is much easier to develop dfc.jar-based applications if dfc source code is available. Unless a jar is deliberately obfuscated, it can be easily decompiled. Unfortunaly, most of the methods of dfc.jar source code include AspectJ expressions that generate logging. Upon compilation AspectJ introduces lots of artificial try-catch blocks, if conditions, synthetic methods and classes. This leads to both trippling the size of the source code and obfuscation. In the decompiled code all AspectJ constructs can be easily eliminated using a simple java application. Unfortunately with most of decompilers, some methods, particularly synchronized or containing synchronized block, are transformed by AspectJ compiler into so complex byte code that they fail to be decompiled by ordinary decompilers.

Development of D2 listener plugins is also easier if the source code of the D2 services is available. The D2 services are encoded in D2FS4DCTM-WEB-4.5.0.jar. If you try to decompile it, you will notice that in all the service classes the original methods encoding all the service logic are missing from the decompiled code. If fact only artificial AspectJ methods remain visible.

For example, let's look into the source code of a decompiled short service class D2DetailService:

public class D2DetailsService extends D2fsAbstractService implements IDetailsService {

    public static Set<String> s_redirectedRefDetail;

    static {
        s_redirectedRefDetail = new HashSet<String>();
        s_redirectedRefDetail.add("Renditions");
        D2DetailsService.s_redirectedRefDetail.add("Audits");
    }

    @InjectSession(redirectReference = RedirectReferenceType.NONE)
    public DocItems getDetailContent(final Context context, final String id, final String detailName, final List<Attribute> parameters) throws Exception {
        return (DocItems) InjectSessionAspect.aspectOf().process(new D2DetailsService$AjcClosure1(new Object[]{this, context, id, detailName, parameters, Factory.makeJP(D2DetailsService.ajc$tjp_0, (Object) this, (Object) this, new Object[]{context, id, detailName, parameters})}).linkClosureAndJoinPoint(69648));
    }

    public class D2DetailsService$AjcClosure1 extends AroundClosure {

        public D2DetailsService$AjcClosure1(final Object[] array) {
            super(array);
        }

        public Object run(final Object[] array) {
            final Object[] state = super.state;
            return D2DetailsService.getDetailContent_aroundBody0((D2DetailsService) state[0], (Context) state[1], (String) state[2], (String) state[3], (List) state[4], (JoinPoint) state[5]);
        }
    }

    public static ID2Detail getD2DetailInstance(final Context context, String detailName) throws Exception {
        ID2Detail result = null;
        Class detailClass = null;
        try {
            detailName = StringUtil.getJavaName(detailName);
            detailClass = Class.forName(String.valueOf(ID2Detail.class.getPackage().getName()) + '.' + detailName);
            result = detailClass.newInstance();
        } catch (ClassNotFoundException ex) {
        }
        return result;
    }
}

@InjectSession annotation marks methods as the targets for transformation by AspectJ. The annotated method getDetailContent is indeed totally twisted by AspectJ. Namely, the original method is replaced by a substitute method that invokes a bizarre innner class that in turn calls the method getDetailContent_aroundBody0 containing the slightly mutilated code of the original getDetailContent method. The problem is that synthetic getDetailContent_aroundBody0 is missing in the decompiled code. Try any decompilers if you doubt this phenomenon.

The best decompiler for obfuscated and crippled java classes is cfr. It is an extraordinary tool that decompiles everything. However, some manual editing is often necessary for the methods, particularly including many blocks, that other decompilers fail to decompile. Let's see what we can recover with cfr from D2DetailsService service that was partially decompiled above.

public class D2DetailsService extends D2fsAbstractService implements IDetailsService {

    public static Set<String> s_redirectedRefDetail;

    static {
        s_redirectedRefDetail = new HashSet<String>();
        s_redirectedRefDetail.add("Renditions");
        s_redirectedRefDetail.add("Audits");
    }

    @InjectSession(redirectReference = RedirectReferenceType.NONE)
    public DocItems getDetailContent(Context context, String id, String detailName, List<Attribute> parameters) throws Exception {
        Context context2 = context;
        String string = id;
        String string2 = detailName;
        List<Attribute> list = parameters;
        Object[] arrobject = new Object[]{context2, string, string2, list};
        JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart) ajc$tjp_0, (Object) this, (Object) this, (Object[]) arrobject);
        Object[] arrobject2 = new Object[]{this, context2, string, string2, list, joinPoint};
        return (DocItems) InjectSessionAspect.aspectOf().process(new D2DetailsService$AjcClosure1(arrobject2).linkClosureAndJoinPoint(69648));
    }

    public class D2DetailsService$AjcClosure1 extends AroundClosure {

        public D2DetailsService$AjcClosure1(final Object[] array) {
            super(array);
        }

        public Object run(final Object[] array) {
            final Object[] state = super.state;
            return D2DetailsService.getDetailContent_aroundBody0((D2DetailsService) state[0], (Context) state[1], (String) state[2], (String) state[3], (List) state[4], (JoinPoint) state[5]);
        }
    }

    public static ID2Detail getD2DetailInstance(Context context, String detailName) throws Exception {
        ID2Detail result;
        result = null;
        Class detailClass = null;
        try {
            detailName = StringUtil.getJavaName((String) detailName);
            detailClass = Class.forName(String.valueOf(ID2Detail.class.getPackage().getName()) + '.' + detailName);
            result = (ID2Detail) detailClass.newInstance();
        } catch (ClassNotFoundException classNotFoundException) {
        }
        return result;
    }

    static final /* synthetic */ DocItems getDetailContent_aroundBody0(D2DetailsService ajc$this, Context context, String id, String detailName, List parameters, JoinPoint joinPoint) {
        ID2Detail detailInstance;
        DocItems result;
        D2fsContext d2fsContext;
        result = new DocItems();
        d2fsContext = (D2fsContext) context;
        d2fsContext.setParameterParser(parameters);
        if (id != null) {
            d2fsContext.getParameterParser().setParameter("id", (Object) id);
        }
        if (detailName != null && s_redirectedRefDetail.contains(detailName) && !d2fsContext.getParameterParser().getBooleanParameter("redirectedReference", false)) {
            D2fsContext sourceContext = null;
            try {
                sourceContext = ReferenceUtils.getSourceContext(d2fsContext, true);
                if (sourceContext != null) {
                    IDfId sourceId = sourceContext.getFirstId();
                    DocItems docItems = new D2DetailsService().getDetailContent((Context) sourceContext, sourceId.toString(), detailName, parameters);
                    return docItems;
                }
            } catch (Exception exception) {
                if (result.getUpperItem() == null) {
                    ContentBuilder.addUpperItem(result, d2fsContext, id, detailName, null);
                }
                DocItems docItems = result;
                return docItems;
            } finally {
                if (sourceContext != null) {
                    sourceContext.release(false);
                }
            }
        }
        if ((detailInstance = D2DetailsService.getD2DetailInstance(context, detailName)) != null) {
            result = detailInstance.getDetailContent(d2fsContext, id);
        }
        if (result.getUpperItem() == null) {
            ContentBuilder.addUpperItem(result, d2fsContext, id, detailName, null);
        }
        return result;
    }
}

In addition to the code analogous to the code that we saw above, we see the nicely decompiled synthetic getDetailContent_aroundBody0 method that essentially contains the untouched original code of the original getDetailContent method. However, unlike the original method, its derivative contains an irrelevant argument ajc$this added by AspectJ.

To sup up, if you develop Documentum applications base on dfc.jar, or if you develop plugins for D2, cfr decompiler is a must-have tool!

No comments:

Post a Comment