CODE-REVIEW, REVERSE-ENGINEERING

Finding command execution sinks in decompiled JVM languages

When reverse engineering an application that is shipped as compiled bytecode (jar file, war file, class files, etc.), we normally use a decompiler and then audit the resulting Java code. The catch is that the language the application was written in might not have been Java! Indeed, there are multiple languages that target the Java Virtual Machine (JVM) and produce bytecode just like Java does. On top of generating generally strange decompiled code, this has for effect that the common potentially dangerous functions we normally look for might be different than the ones used in Java. For this blog post, I’m going to be looking at how each language executes shell commands and what it looks like once decompiled.

Java is our baseline here. In this language we’d normally look for Runtime.getRuntime().exec(command) or usage of the Process or ProcessBuilder classes. Let’s see how the other languages do it. Keep in mind that all JVM languages generally have a way to call standard Java classes so what’s shown below should be seen as a supplement and not a replacement.

This is not an exhaustive list as there are many JVM languages but I reviewed the most popular ones.

Note: I’m competent in exactly none of those languages and gathered the information on how to execute shell commands by reading some documentation. I don’t know how idiomatic these things are but they exist.

Kotlin

Kotlin is the language of choice for Android development but it can also be used in other contexts. Reverse engineers rejoice: it’s the language in this list that’s the most similar to standard Java and invoking shell commands uses the exact same classes.

Nothing to see here, moving along.

Groovy

Groovy seems to have an implicit conversion of a string to a process with the execute method.

println "whoami".execute().text

I tried decompiling with jd-cli and procyon and both seemed to struggle, with the latter having the most complete output (shown below). It seemed to me like the actual code that would hint towards a shell command being invoked was missing however so I looked at the bytecode instead.

import org.codehaus.groovy.runtime.callsite.CallSiteArray;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
import groovy.lang.GroovyObject;
import org.codehaus.groovy.runtime.InvokerHelper;
import groovy.lang.Binding;
import java.lang.ref.SoftReference;
import org.codehaus.groovy.reflection.ClassInfo;
import groovy.lang.Script;

//
// Decompiled by Procyon v0.6.0
//

public class test extends Script
{
    private static /* synthetic */ SoftReference $callSiteArray;

    public test() {
        $getCallSiteArray();
    }

    public test(final Binding context) {
        $getCallSiteArray();
        super(context);
    }

    public static void main(final String... args) {
        $getCallSiteArray()[0].callStatic((Class)InvokerHelper.class, (Object)test.class, (Object)args);
    }

    public Object run() {
        final CallSite[] $getCallSiteArray = $getCallSiteArray();
        return $getCallSiteArray[1].callCurrent((GroovyObject)this, $getCallSiteArray[2].callGetProperty($getCallSiteArray[3].call((Object)"whoami")));
    }

    private static /* synthetic */ CallSiteArray $createCallSiteArray() {
        final String[] array = new String[4];
        $createCallSiteArray_1(array);
        return new CallSiteArray((Class)test.class, array);
    }

    private static /* synthetic */ CallSite[] $getCallSiteArray() {
        CallSiteArray $createCallSiteArray;
        if (test.$callSiteArray == null || ($createCallSiteArray = test.$callSiteArray.get()) == null) {
            $createCallSiteArray = $createCallSiteArray();
            test.$callSiteArray = new SoftReference($createCallSiteArray);
        }
        return $createCallSiteArray.array;
    }
}

In the bytecode we can find the definition for $createcallSiteArray_1 that decompilers appear to miss.

     private static synthetic void $createCallSiteArray_1(java.lang.String[] arg0) { //([Ljava/lang/String;)V
             aload 0
             ldc 0 (java.lang.Integer)
             ldc "runScript" (java.lang.String)
             aastore
             aload 0
             ldc 1 (java.lang.Integer)
             ldc "println" (java.lang.String)
             aastore
             aload 0
             ldc 2 (java.lang.Integer)
             ldc "text" (java.lang.String)
             aastore
             aload 0
             ldc 3 (java.lang.Integer)
             ldc "execute" (java.lang.String)
             aastore
             return
     }

I’m showing only that method definition because the bytecode in general is very verbose, but that seems to load the name of the methods that are going to be invoked at runtime so if you’re reversing a Groovy app, grepping for ldc "execute" (java.lang.String) in the bytecode might be a good idea!

Scala

In Scala it’s possible to call ! or !! on strings to execute them as shell commands. ! prints the output on stdout and returns the exit code while !! returns the command output as a string.

import scala.sys.process._

object Main {
  def main(args: Array[String]) {
    "whoami".!
    "uname -a".!!
  }
}

Those can easily be identified as $bang and $bang$bang and I have to say that I absolutely love that.

import scala.sys.process.package$;

//
// Decompiled by Procyon v0.6.0
//

public final class Main$
{
    public static final Main$ MODULE$;

    static {
        new Main$();
    }

    public void main(final String[] args) {
        package$.MODULE$.stringToProcess("whoami").$bang();
        package$.MODULE$.stringToProcess("uname -a").$bang$bang();
    }

    private Main$() {
        MODULE$ = this;
    }
}

Clojure

Clojure is a Lisp dialect and the biggest departure from Java’s syntax.

(ns test.core)
(require '[clojure.java.shell :as shell])

(defn -main [& args]
  (shell/sh "uname" "-a"))

This is another one that’s fairly easy to identify in the decompiled output, grepping for clojure.java.shell should get you what you’re looking for.

//
// Decompiled by Procyon v0.6.0
//

package test;

import clojure.lang.RT;
import clojure.lang.IFn;
import clojure.lang.ISeq;
import clojure.lang.Var;
import clojure.lang.RestFn;

public final class core$_main extends RestFn
{
    public static final Var const__0;

    public static Object invokeStatic(final ISeq args) {
        return ((IFn)core$_main.const__0.getRawRoot()).invoke((Object)"uname", (Object)"-a");
    }

    public Object doInvoke(final Object o) {
        return invokeStatic((ISeq)o);
    }

    public int getRequiredArity() {
        return 0;
    }

    static {
        const__0 = RT.var("clojure.java.shell", "sh");
    }
}

Conclusion

This “article” was fairly low on words and was meant more as a reference I can come back to whenever I reverse an application written in any of those languages (reversing a Scala application actually prompted me to write this). I figured I’d make this public instead of keeping it in my notes so everyone can benefit. I hope it can be useful to you as well! Happy hunting.

References & tools