Google CTF 2020 – Sandbox

Google CTF 2020 Sandbox

Install the APK with ADB

				
					adb install -r google2020reverse.apk
				
			

Let’s inspect the source code with jadx.
We can see that jadx doesn’t work.
So move to dex2jar tool.

				
					d2j-dex2jar google2020reverse.apk
				
			

The MainActivity is called ő.
The code is malformed, that why we can’t decompile the code and then, read it.

The constructor and onCreate() are obfuscated with invalid try/catch (such as catch I, which is invalid because int is not a Throwable).

So we need patch the apk in the smali code.
First, we need decompile with apktool

				
					apktool d google2020reverse.apk
				
			

So, what is broken?
The catch I and catch J are not child classes of Throwable.
So we need go to
/smali/com/google/ctf/sandbox/ő.smali
And edit with nano, looking for invalid catches and then, delete.
Delete any .catch lines that use primitive types, such as:
.catch I → int
.catch J → long
.catch Z.catch F, etc.

Stay only with .catches that have L<class>;, such as Ljava/lang/Exception;.
Also, you must fix the .catch in ő$1.smali

I deleted in ő$1.smali
.catch I {:try_start_0 .. :try_end_0} :catch_1

And in ő$1.smali
.catch J {:try_start_0 .. :try_end_0} :catch_0

So now we just need rebuild the APK

				
					apktool b google2020reverse -o google2020fixed.apk
				
			

And now we can use dj2 and jd-gui

				
					d2j-dex2jar google2020fixed.apk
				
			
				
					jd-gui google2020fixed-dex2jar.jar
				
			

Now we can see the ő class fully.
In the end of the code, we see that here’s the validation:

				
					this();
i = arrayOfObject.length;
b = 0;
} catch (Exception|Error exception) {}
while (b < i) {
    exception.append(((Character)arrayOfObject[b]).charValue());
    b++;
} 
if (editText.getText().toString().equals(exception.toString())) {
    textView.setText("");
} else {
    textView.setText("❌");
} 
}
});
				
			

And we see that the flag have a length of 48
arrayOfObject[48] = Integer.valueOf(63);

We see in the R class this kind of encoding:

				
					public final class R {
    public static long[] (long paramLong1, long paramLong2) {
        if (paramLong1 == 0L)
            return new long[] { 0L, 1L }; 
        long[] arrayOfLong = (paramLong2 % paramLong1, paramLong1);
        return new long[] { arrayOfLong[1] - paramLong2 / paramLong1 * arrayOfLong[0], arrayOfLong[0] };
    }
}
				
			

But anyway, I will use jadx for a better reading.
jadx-gui google2020fixed-dex2jar.jar

No we can see the full code more clearly:

				
					public class ActivityC0007 extends Activity {

    /* renamed from: class, reason: not valid java name */
    long[] f13class;

    /* renamed from: ő */
    int f11;

    /* renamed from: ő */
    long[] f12;

    public ActivityC0007() {
        while (true) {
            try {
                this.f13class = new long[]{40999019, 2789358025L, 656272715, 18374979, 3237618335L, 1762529471, 685548119, 382114257, 1436905469, 2126016673, 3318315423L, 797150821};
                this.f12 = new long[12];
                this.f11 = 0;
                return;
            } catch (Error | Exception e) {
            }
        }
    }

    @Override // android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(C0006R.layout.activity_main);
        final EditText editText = (EditText) findViewById(C0006R.id.editText);
        final TextView textView = (TextView) findViewById(C0006R.id.textView);
        ((Button) findViewById(C0006R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.google.ctf.sandbox.ő.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                ActivityC0007.this.f11 = 0;
                try {
                    Object[] objArr = {65, 112, 112, 97, 114, 101, 110, 116, 108, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 116, 104, 101, 32, 102, 108, 97, 103, 46, 32, 87, 104, 97, 116, 39, 115, 32, 103, 111, 105, 110, 103, 32, 111, 110, 63};
                    StringBuilder sb = new StringBuilder();
                    for (Object obj : objArr) {
                        sb.append(((Character) obj).charValue());
                    }
                    if (editText.getText().toString().equals(sb.toString())) {
                        textView.setText("��");
                    } else {
                        textView.setText("❌");
                    }
                } catch (Error | Exception e) {
                    String obj2 = editText.getText().toString();
                    if (obj2.length() != 48) {
                        textView.setText("❌");
                        return;
                    }
                    for (int i = 0; i < obj2.length() / 4; i++) {
                        ActivityC0007.this.f12[i] = obj2.charAt((i * 4) + 3) << 24;
                        long[] jArr = ActivityC0007.this.f12;
                        jArr[i] = jArr[i] | (obj2.charAt((i * 4) + 2) << 16);
                        long[] jArr2 = ActivityC0007.this.f12;
                        jArr2[i] = jArr2[i] | (obj2.charAt((i * 4) + 1) << '\b');
                        long[] jArr3 = ActivityC0007.this.f12;
                        jArr3[i] = jArr3[i] | obj2.charAt(i * 4);
                    }
                    ActivityC0007 activityC0007 = ActivityC0007.this;
                    if (((C0006R.m0(ActivityC0007.this.f12[ActivityC0007.this.f11], 4294967296L)[0] % 4294967296L) + 4294967296L) % 4294967296L != ActivityC0007.this.f13class[ActivityC0007.this.f11]) {
                        textView.setText("❌");
                        return;
                    }
                    ActivityC0007.this.f11++;
                    if (ActivityC0007.this.f11 < ActivityC0007.this.f12.length) {
                        throw new RuntimeException();
                    }
                    textView.setText("��");
                }
            }
        });
    }
}
				
			

We can see the encoding, that iterates over the input of the user (4 char) at a time and create the array of 12 values.

We can make an bruteforce script in python using numba and numpy for a high speed.
Why?
The problem is that a normal script is taking too long or is getting “stuck” because:

  • The search space is huge(128-32)^4 = 96^4 = 84,934,656 combinations per 4-character segment.
    In total -> 1,019,215,872 combinations!!!

So, here’s a python script:

				
					import numpy as np
from numba import njit
from concurrent.futures import ProcessPoolExecutor
import sys

# No warns
@njit(fastmath=True)
def extended_gcd(a, b):
    if a == 0:
        return (0, 1)
    x, y = extended_gcd(b % a, a)
    return (y - (b // a) * x, x)

@njit
def find_segment_numba(target):
    for a0 in range(32, 127):
        for a1 in range(32, 127):
            for a2 in range(32, 127):
                for a3 in range(32, 127):
                    s = (a3 << 24) | (a2 << 16) | (a1 << 8) | a0
                    x, _ = extended_gcd(s, 4294967296)
                    if ((x % 4294967296 + 4294967296) % 4294967296) == target:
                        return (a0, a1, a2, a3)
    return (-1, -1, -1, -1)

def process_segment(target):
    result = find_segment_numba(target)
    if result[0] != -1:
        a0, a1, a2, a3 = result
        return bytes([a0, a1, a2, a3]).decode('latin-1')
    return "????"

if __name__ == "__main__":
    flag_values = [
        40999019, 2789358025, 656272715, 18374979,
        3237618335, 1762529471, 685548119, 382114257,
        1436905469, 2126016673, 3318315423, 797150821
    ]

    print("Starting optimized brute force search...")
    
    # Process segments
    with ProcessPoolExecutor() as executor:
        results = list(executor.map(process_segment, flag_values))
    
    flag = "".join(results)
    print("\nFinal flag:", flag)
				
			

Output:

				
					Starting optimized brute force search...

Final flag: CTF{y0u_c4n_k3ep_y0u?_m4gic_1_h4Ue_laser_b3ams!}
				
			

CTF{y0u_c4n_k3ep_y0u?_m4gic_1_h4Ue_laser_b3ams!}

I hope you found it useful (:

Leave a Reply

Your email address will not be published. Required fields are marked *