---
# System prepended metadata

title: Write-up Android Crackmes

---

# Write-up Android Crackmes

Created by: Hùng Hoàng Anh
Created time: July 21, 2023 11:57 PM

# **Android UnCrackable L1**

Using APK Lab, an extension of VS Code to reverse this app.

[](https://hackmd.io/_uploads/S1muZipR2.png)


There are 2 condition statements to prevent debug and login as root. I deleted smali lines to by this check.

![](https://hackmd.io/_uploads/r1iubi6Cn.png)


![](https://hackmd.io/_uploads/SyltZiTC2.png)


I saw verify function to check password:

![](https://hackmd.io/_uploads/Sk4tZiaC3.png)


`a.a(obj)` is a compare function to return true if `obj` equal secret key.

Step into function `a.a()`

![](https://hackmd.io/_uploads/r1FFbopAh.png)


As you can see, the secret key is `new String(bArr)` string object so I set a breakpoint at the smali line refer to this line.

I use JADX_GUI and Genymotion to debug this app. 

![](https://hackmd.io/_uploads/SJJ5-jT0n.png)


Boommm

I got the key

![](https://hackmd.io/_uploads/BkHc-ja0n.png)


v1 is the register save value of `new String(bArr)` 

Secret key: `I want to believe`

## Frida approach:

```jsx
Java.perform(function(){
    Java.use("sg.vantagepoint.uncrackable1.MainActivity")
    .a
    .overload("java.lang.String")
    .implementation = function (string) {
        console.log("Bypass root check: " + string)
    };
    Java.use("java.lang.String")
    .equals
    .overload("java.lang.Object")
    .implementation = function (arg) {
        if(this == "flag") console.log("Password: " + arg)
        return this.equals(arg);
    };
    Java.use("sg.vantagepoint.uncrackable1.MainActivity").onResume.implementation = function () {
        this.onResume();
        Java.use("sg.vantagepoint.uncrackable1.a").a(Java.use('java.lang.String').$new("flag"));
    };
})
```

Result:

![](https://hackmd.io/_uploads/B1ysZjpR2.png)


# **Android UnCrackable L2**


Patch `onCreate()` to bypass root and debugger check. 

```jsx
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4

    invoke-direct {p0}, Lsg/vantagepoint/uncrackable2/MainActivity;->init()V

    goto :goto_0

    invoke-static {}, Lsg/vantagepoint/a/b;->a()Z

    move-result v0

    if-nez v0, :cond_0

    invoke-static {}, Lsg/vantagepoint/a/b;->b()Z

    move-result v0

    if-nez v0, :cond_0

    invoke-static {}, Lsg/vantagepoint/a/b;->c()Z

    move-result v0

    if-eqz v0, :cond_1

    :cond_0
    const-string v0, "Root detected!"

    invoke-direct {p0, v0}, Lsg/vantagepoint/uncrackable2/MainActivity;->a(Ljava/lang/String;)V

    :cond_1
    invoke-virtual {p0}, Lsg/vantagepoint/uncrackable2/MainActivity;->getApplicationContext()Landroid/content/Context;

    move-result-object v0

    invoke-static {v0}, Lsg/vantagepoint/a/a;->a(Landroid/content/Context;)Z

    move-result v0

    if-eqz v0, :cond_2

    const-string v0, "App is debuggable!"

    invoke-direct {p0, v0}, Lsg/vantagepoint/uncrackable2/MainActivity;->a(Ljava/lang/String;)V

    :cond_2
    new-instance v0, Lsg/vantagepoint/uncrackable2/MainActivity$2;

    invoke-direct {v0, p0}, Lsg/vantagepoint/uncrackable2/MainActivity$2;-><init>(Lsg/vantagepoint/uncrackable2/MainActivity;)V

    const/4 v1, 0x3

    new-array v1, v1, [Ljava/lang/Void;

    const/4 v2, 0x0

    const/4 v3, 0x0

    aput-object v3, v1, v2

    const/4 v2, 0x1

    aput-object v3, v1, v2

    const/4 v2, 0x2

    aput-object v3, v1, v2

    invoke-virtual {v0, v1}, Lsg/vantagepoint/uncrackable2/MainActivity$2;->execute([Ljava/lang/Object;)Landroid/os/AsyncTask;
    
    :goto_0

    new-instance v0, Lsg/vantagepoint/uncrackable2/CodeCheck;

    invoke-direct {v0}, Lsg/vantagepoint/uncrackable2/CodeCheck;-><init>()V

    iput-object v0, p0, Lsg/vantagepoint/uncrackable2/MainActivity;->m:Lsg/vantagepoint/uncrackable2/CodeCheck;

    invoke-super {p0, p1}, Landroid/support/v7/app/c;->onCreate(Landroid/os/Bundle;)V

    const p1, 0x7f09001b

    invoke-virtual {p0, p1}, Lsg/vantagepoint/uncrackable2/MainActivity;->setContentView(I)V

    return-void
.end method

```

Let’s dive into main code:

![](https://hackmd.io/_uploads/SkPo-o6R2.png)


This app using `m4a()` from `CodeCheck` to check for the correct key.

![](https://hackmd.io/_uploads/Hy2j-iaA2.png)


Jump into class `CodeCheck` I saw native funtion `bar` so I found the lib file.

![](https://hackmd.io/_uploads/HJS3biTRn.png)


Lib `foo` had been loaded in `MainActivity` class.

I reversed `[libfoo.so](http://libfoo.so)` using `ghidra` and I found function `bar`:

![](https://hackmd.io/_uploads/HJ53-jTAn.png)


This function compare input string with an string save in the memory. But secret string has been splited into some memory addresses located side by side shown in above picture.

I concatenate these variables to:

`0x6873696620656874206c6c6120726f6620736b6e616854`

This is the hex present of the secret string after flipped

Using [cyberchef](https://cyberchef.io/) to decode it to string and reverse: 

![Uploading file..._c98lh8ngn]()

![](https://hackmd.io/_uploads/HkwT-iTC2.png)

The secret string is `Thanks for all the fish`

## Frida approach:

```jsx
Java.perform(function () {
    Java.use("sg.vantagepoint.uncrackable2.MainActivity")
        .a
        .overload("java.lang.String")
        .implementation = function (string) {
            console.log("Bypass root and degguber check: " + string);
        };

    Java.use('sg.vantagepoint.uncrackable2.MainActivity').onResume.implementation = function(){
        this.onResume()
        Java.use("sg.vantagepoint.uncrackable2.CodeCheck").$new().a(Java.use('java.lang.String').$new("Jvc9fqeJVkbNAqpCESxr5it"));

        Interceptor.detachAll();
    }
});

Interceptor.attach(Module.findExportByName('libc.so', 'strncmp'), {
    onEnter: function (args) {
        let inputStr = "Jvc9fqeJVkbNAqpCESxr5it";
        if(args[2].toInt32() != inputStr.length ) return;
        try{
            let str1 = args[0].readCString(inputStr.length);
            let str2 = args[1].readCString(inputStr.length);

            if(str1.includes(inputStr) || str2.includes(inputStr)){
                console.log(`Flag in: ${str1} || ${str2}`)
            }
        }catch(e){
            console.log(e);
        }
    }
});
```

PoC:

![](https://hackmd.io/_uploads/B111MopR2.png)

# **Android UnCrackable L3**

Using JEB to decompile APK and its native library:

`onCreate()`

![](https://hackmd.io/_uploads/r10kGjpC3.png)


In order to bypass `onCreate()` function we just need to change `showDialog()`

```jsx
Java.perform(function () {
    Java.use("sg.vantagepoint.uncrackable3.MainActivity")
        .showDialog
        .overload("java.lang.String")
        .implementation = function (string) {
            console.log("Bypassed showDialog, message: ", string);
            return;
        };
    Java.use("sg.vantagepoint.uncrackable3.MainActivity").$init.implementation = function () {
        this.$init();
        printSecretKey();
    };
});
```

But when run frida we meet an error:

![](https://hackmd.io/_uploads/BJNefi6R3.png)


According to this error,  `goodbye()` in native library is called in order to exit program.

![](https://hackmd.io/_uploads/Hk5gMopAh.png)


Go deep into natve lib, I saw a function called `goodbye()`, when `strstr()`is call with an argument named as “frida” or “xposed”, the program will exit.

`strstr()` is a function of `libc.so`, I change strstr function in order to run frida.

```jsx
Interceptor.attach(Module.findExportByName('libc.so', 'strstr'), {
    onEnter: function (args) {
        this.fridaDetected = 0;
        if (args[1].readUtf8String().indexOf("frida") != -1) {
            this.fridaDetected = 1;
        }
    },
    onLeave: function (retVal) {
        if (this.fridaDetected == 1) retVal.replace(0);
    }
});
```

![](https://hackmd.io/_uploads/r1lWMop0h.png)


`code_check()`using `bar()` from native lib to check password:

![](https://hackmd.io/_uploads/HJHZGj602.png)


Go to `bar()`:

![](https://hackmd.io/_uploads/Syq-MjaC3.png)


It use xor to check password. `password = xor__key ^ secret_key`

`secret_key` is generated by `generate_secret()`

Use this code to find `secret_key`:

```jsx
function printSecretKey() {
    Interceptor.attach(Module.getBaseAddress('libfoo.so').add(0x0fa0), {
        onEnter: function (args) {
            console.log("Secret key before generate at address: " + args[0]);
            this.answerAdress = args[0];
            console.log(hexdump(args[0], {
                offset: 0,
                length: 24,
                header: true,
                ansi: true
            }));
        },
        onLeave: function (retVal) {
            console.log("Secret key after generate: ");
            console.log(hexdump(this.answerAdress, {
                offset: 0,
                length: 24,
                header: true,
                ansi: true
            }));
        }
    });
    Java.use("sg.vantagepoint.uncrackable3.MainActivity").onResume.implementation = function () {
        this.onResume();
        Java.choose("sg.vantagepoint.uncrackable3.CodeCheck", {
            onMatch: function (instance) {
                instance.check_code(Java.use("java.lang.String").$new("123"));
                return "stop";
            },
            onComplete: function () { }
        });
    };
}
```

 Combine above scripts:

```jsx
Java.perform(function () {
    Java.use("sg.vantagepoint.uncrackable3.MainActivity")
        .showDialog
        .overload("java.lang.String")
        .implementation = function (string) {
            console.log("Bypassed showDialog, message: ", string);
            return;
        };
    Java.use("sg.vantagepoint.uncrackable3.MainActivity").$init.implementation = function () {
        this.$init();
        printSecretKey();
    };
});

Interceptor.attach(Module.findExportByName('libc.so', 'strstr'), {
    onEnter: function (args) {
        this.fridaDetected = 0;
        if (args[1].readUtf8String().indexOf("frida") != -1) {
            this.fridaDetected = 1;
        }
    },
    onLeave: function (retVal) {
        if (this.fridaDetected == 1) retVal.replace(0);
    }
});

function printSecretKey() {
    Interceptor.attach(Module.getBaseAddress('libfoo.so').add(0x0fa0), {
        onEnter: function (args) {
            console.log("Secret key before generate at address: " + args[0]);
            this.answerAdress = args[0];
            console.log(hexdump(args[0], {
                offset: 0,
                length: 24,
                header: true,
                ansi: true
            }));
        },
        onLeave: function (retVal) {
            console.log("Secret key after generate: ");
            console.log(hexdump(this.answerAdress, {
                offset: 0,
                length: 24,
                header: true,
                ansi: true
            }));
        }
    });
    Java.use("sg.vantagepoint.uncrackable3.MainActivity").onResume.implementation = function () {
        this.onResume();
        Java.choose("sg.vantagepoint.uncrackable3.CodeCheck", {
            onMatch: function (instance) {
                instance.check_code(Java.use("java.lang.String").$new("123"));
                return "stop";
            },
            onComplete: function () { }
        });
    };
}
```

Secret key as bytes array:

![](https://hackmd.io/_uploads/ryXfGi6C3.png)


Find password using cyberchef:

![](https://hackmd.io/_uploads/S1PMzoTRh.png)
