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


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`
## 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:

# **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:

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
Using [cyberchef](https://cyberchef.io/) to decode it to string and reverse:
![Uploading file..._c98lh8ngn]()

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:

# **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()`
```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:

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.
```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);
}
});
```

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

Find password using cyberchef:
