Home Writeup for Android Crackmes OWASP
Post
Cancel

Writeup for Android Crackmes OWASP

Android UnCrackable L1

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

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

I saw verify function to check password:

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

Step into function a.a()

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.

Boommm

I got the key

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

Secret key: I want to believe

Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:

Android UnCrackable L2

Patch onCreate() to bypass root and debugger check.

.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:

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

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

Lib foo had been loaded in MainActivity class.

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

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

Decode it to string and reverse:

The secret string is Thanks for all the fish

Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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:

Android UnCrackable L3

Using JEB to decompile APK and its native library:

onCreate()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

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

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.

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

Go to bar():

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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:

Find password using cyberchef:

Trending Tags