Read previous section, before continue!
Capture Secret from Memory
Checking MainActivity again, we see in the function
public void verify(View object) {
that it calls method a from class
sg.vantagepoint.uncrackable1.a:
if (a.a((String)object)) {
This is the decompilation of the sg.vantagepoint.uncrackable1.a class
:
package sg.vantagepoint.uncrackable1;
import android.util.Base64;
import android.util.Log;
public class a {
public static boolean a(String str) {
byte[] a;
String str2 = "8d127684cbc37c17616d806cf50473cc";
byte[] bArr = new byte[0];
try {
a = sg.vantagepoint.a.a.a(b(str2), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
} catch (Exception e) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("AES error:");
stringBuilder.append(e.getMessage());
Log.d("CodeCheck", stringBuilder.toString());
a = bArr;
}
return str.equals(new String(a));
}
public static byte[] b(String str) {
int length = str.length();
byte[] bArr = new byte[(length / 2)];
for (int i = 0; i < length; i += 2) {
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
}
return bArr;
}
}
Notice the str.equals
comparison at the end of the a method and the creation of the string bArr
in the try block above. a
is the return value of the function sg.vantagepoint.a.a.a
. The str.equals
comparison compares our input to a
. So what we are after is the return value of sg.vantagepoint.a.a.a.
We could now start to reverse engineer the string manipulation and decryption functions and work on the original encrypted strings, that are also contained in the code above. Or we let the app do all its maniplation and encryption that we really don't care for and just hook the sg.vantagepoint.a.a.a
function to catch its return value. The return value is the decrypted string (in form of a byte array) that our input gets compared to.
We overwrite the sg.vantagepoint.a.a.a
function, catch its return value and convert it into a readable string. This is the decrypted string we are looking for, so we print it out to the console and hopefully get our solution.
Putting the pieces together, here is the complete script:
vi scripts/captureSecretFromMemory.py
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
PACKAGE_NAME = "owasp.mstg.uncrackable1"
jscode= """
console.log("[*] Starting script");
Java.perform(function() {
console.log("[*] Hooking System.exit");
exitClass = Java.use("java.lang.System");
exitClass.exit.implementation = function() {
console.log("[*] System.exit called");
}
console.log("[*] Hooking a.class");
aclass = Java.use("sg.vantagepoint.a.a");
aclass.a.implementation = function(arg1,arg2){
console.log("[*]Hooking a.Class");
retval = this.a(arg1, arg2);
secret='';
for(i=0;i<retval.length;i++){
secret+=String.fromCharCode(retval[i]);
}
console.log("secret: "+secret);
return retval;
}
});
"""
try:
process = frida.get_usb_device().attach(PACKAGE_NAME)
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running Hook')
script.load()
sys.stdin.read()
except Exception as e:
print(e)
Run it.
$ sudo python scripts/captureSecretFromMemory.py
[*] Running Hook
[*] Starting script
[*] Hooking System.exit
[*] Hooking a.class
[*] System.exit called
[*] Hooking a.Class
secret: I want to believe
Nice. We actually got the decrypted string: I want to believe. That’s it. Let’s check if it works:
By now, I hope you are at least a little impressed by what you can do with Frida and it’s dynamic binary instrumentation capabilities.