• No results found

Disobeying Constructor Conventions (DCC)

In document JAVA BYTECODE OBFUSCATION (Page 98-102)

Chapter 7 Exploiting The Design Gap

7.4 Disobeying Constructor Conventions (DCC)

The Java language specification by Gosling, et al. stipulates that class constructors — those methods used to instantiate a new object of that class type — must always call either an al-ternate constructor of the same class or their parent class’ constructor as the first statement.

“If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object,

7.4. Disobeying Constructor Conventions (DCC)

Figure 7.2: Effects of moving load instruction above if branch instructions.

then the constructor body is implicitly assumed by the compiler to begin with a superclass constructor invocation “super();”, an invocation of the constructor of its direct superclass that takes no arguments.” Section 2.12 [GJSB00]

In the event that no super constructor is called the Java compiler explicitly adds the call at the top of the method in the compiled output.

While this super call, as a rule, must be the first statement in the Java source it is, in fact, not required to be the first within the bytecode. By exploiting this fact, it is possible to create constructor methods whose bytecode representation cannot be converted into legal source.

This transformation does exactly this.2 It randomly chooses among four different ap-proaches to transforming constructors in order to confuse decompilers. Of the four tech-niques described below, all of them consider only simple super constructors with zero

pa-2Notice in Listings7.12and7.13that because the “super” call is not where the decompiler expects it to be, it is unable to label it as such and instead the actual underlying method name,<init>, appears. This is true for all four approaches. Listing7.14attempts to call a constructor based on the local name for the object reference — this is slightly different because it is SourceAgain output; Dava was unable to decompile it.

Exploiting The Design Gap

rameters. They could easily be extended to include all super constructors.

Wrap in Try: The first approach simply wraps the super constructor method call within a try block. This ensures that any decompiled source will be required to wrap the super constructor call in a try block to conform to the exception handling rules of Java. To properly allow the exception to propagate, the handler unit — a throw instruction

— is appended to the end of the method. However, this conversion immediately renders the source uncompilable. Listing7.11shows the original source of a simple object constructor. Listing 7.12 presents the same constructor after transformation (note that this is illegal source code and will not compile).

ObjectA(){ super();

}

Listing 7.11: Example object constructor method in Java source code form.

ObjectA(){

Listing 7.12: Example object constructor after wrap in try transformation. Decompiled by Dava

— it is not legal source code.

Throw Throwable: The second approach takes advantage of classes which are children of java.lang.Throwable. These are classes which can be thrown by the ex-ception handling mechanism of Java. It inserts a throw instruction before the super constructor call and creates a new try block in the method that traps just the new throwinstruction. The handler unit is designated to be the super constructor. This

7.4. Disobeying Constructor Conventions (DCC)

takes advantage of the fact that the class itself is throwable and can be pushed onto the stack through the throw mechanism instead of the standard push mechanism. The new try block can trap exceptions of the parent type, since the super constructor is a method from the parent type. This will further confuse the decompilers, as seen in Listing7.13.

Listing 7.13: Example object constructor after throwing itself as a throwable approach. ObjectA extends the java.lang.Throwable object. Decompiled by Dava — it is not legal source code.

Indirect through jsr: The third approach simply inserts a jsr jump instruction and a pop instruction directly before the super constructor call. The jsr’s target is the popinstruction, which removes the subsequent return address that is pushed on the stack as a result of the jsr instruction. This confuses the majority of decompilers which have problems dealing with jsr instructions.

Trap Ifnull Indirection: The final approach is the most complicated. Directly before the super constructor call new instructions are added: a dup followed by an ifnull.

The ifnull target is the super constructor call itself. This if branch instruction will clearly always be false since the object it is comparing is the object being instantiated in the current constructor. Two new instructions are inserted directly after the if (the false branch): a push null followed by a throw. A new trap is created spanning from the ifnull instruction up to the super constructor call. The catch block is appended to the end of the method as a sequence of pop, load o, goto sc, where o is the object being instantiated and sc is the super constructor call.

Exploiting The Design Gap

Listing 7.14: Example object constructor after adding trapped ifnull indirection. Decompiled by SourceAgain — it is not legal source code.

This final approach confuses decompilers because it is more difficult to deduce which local will be on the stack when the super constructor call site is reached. While Dava crashes trying to decompile this approach, SourceAgain produces the improper output in Listing7.14.

7.4.1 Performance Results (DCC)

Having discovered the cost of object creation and the significant performance penalties that can be incurred when tampering with that process (see Section6.5.1) it is not surprising to see a similar, albeit more universal, slowdown due to this constructor transformation.

In fact, the performance penalties seen here in Figure7.3are fairly large and it is clear that this transformation should be used sparingly, if at all. One should be especially careful in transforming programs tasked with large amounts of object creation.

In document JAVA BYTECODE OBFUSCATION (Page 98-102)