Skip to content

Instantly share code, notes, and snippets.

@ceres-c
Last active December 7, 2024 15:26
Show Gist options
  • Save ceres-c/cb3b69e53713d5ad9cf6aac9b8e895d2 to your computer and use it in GitHub Desktop.
Save ceres-c/cb3b69e53713d5ad9cf6aac9b8e895d2 to your computer and use it in GitHub Desktop.
Automatically extract KeyStore objects and relative password from Android applications with Frida - Read more: https://ceres-c.it/2018/12/16/frida-android-keystore/
#!/usr/bin/python3
'''
author: ceres-c
usage: ./frida-extract-keystore.py
Once the keystore(s) have been exported you have to convert them to PKCS12 using keytool
'''
import frida, sys, time
app_name = 'com.app.mobile'
i = 0
ext = ''
def on_message(message, data):
global i, ext
if (message['type'] == 'send' and 'event' in message['payload']):
if (message['payload']['event'] == '+found'):
i += 1
print("\n[+] Hooked keystore" + str(i) + "...")
elif (message['payload']['event'] == '+type'):
print(" [+] Cert Type: " + ''.join(message['payload']['certType']))
if (message['payload']['certType'] == 'PKCS12'):
ext = '.jks'
elif (message['payload']['event'] == '+pass'):
print(" [+] Password: " + ''.join(message['payload']['password']))
elif (message['payload']['event'] == '+write'):
print(" [+] Writing to file: keystore" + str(i) + ext)
f = open('keystore' + str(i) + ext, 'wb')
f.write(bytes.fromhex(message['payload']['cert']))
f.close()
else:
print(message)
jscode = """
setTimeout(function() {
Java.perform(function () {
var keyStoreLoadStream = Java.use('java.security.KeyStore')['load'].overload('java.io.InputStream', '[C');
/* following function hooks to a Keystore.load(InputStream stream, char[] password) */
keyStoreLoadStream.implementation = function(stream, charArray) {
/* sometimes this happen, I have no idea why, tho... */
if (stream == null) {
/* just to avoid interfering with app's flow */
this.load(stream, charArray);
return;
}
/* just to notice the client we've hooked a KeyStore.load */
send({event: '+found'});
/* read the buffer stream to a variable */
var hexString = readStreamToHex (stream);
/* send KeyStore type to client shell */
send({event: '+type', certType: this.getType()});
/* send KeyStore password to client shell */
send({event: '+pass', password: charArray});
/* send the string representation to client shell */
send({event: '+write', cert: hexString});
/* call the original implementation of 'load' */
this.load(stream, charArray);
/* no need to return anything */
}
});
},0);
/* following function reads an InputStream and returns an ASCII char representation of it */
function readStreamToHex (stream) {
var data = [];
var byteRead = stream.read();
while (byteRead != -1)
{
data.push( ('0' + (byteRead & 0xFF).toString(16)).slice(-2) );
/* <---------------- binary to hex ---------------> */
byteRead = stream.read();
}
stream.close();
return data.join('');
}
"""
print("[.] Attaching to device...")
try:
device = frida.get_usb_device()
except:
print("[-] Can't attach. Is the device connected?")
sys.exit()
print("[.] Spawning the app...")
try:
pid = device.spawn(app_name)
device.resume(pid)
time.sleep(1)
except:
print("[-] Can't spawn the App. Is filename correct?")
sys.exit()
print("[.] Attaching to process...")
try:
process = device.attach(pid)
except:
print("[-] Can't connect to App.")
sys.exit()
print("[.] Launching js code...")
print(" (run the app until needed, close it and then kill this script)")
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
try:
sys.stdin.read()
except KeyboardInterrupt:
print ("\nExiting now")
exit(0)
@Lartsch
Copy link

Lartsch commented Nov 9, 2021

Hey @ceres-c,
great script, thanks a lot. Used it for some applications successfully, but for some reason, with app I currently testm I receive no outputs (even though it is definitely writing to keystore).
Any idea why?
Best regards,
lartsch

@ceres-c
Copy link
Author

ceres-c commented Nov 10, 2021

Hey @Lartsch,
Sorry for the delay, but I have been working on other things this whole day. Right now it’s pretty late, but it might very well be that your application is not loading a KeyStore with the method I am attaching to. You have to decompile the app and search where it’s loading the KeyStore: most likely it is using a different overloaded load method. Once you know which method is being used, you have to figure out how to extract the data.
Maybe you’ll have to attach to another function altogether, since if I’m not mistaken there was some way to pass a certificate in a sort of non-readable object, but don’t quote me on that.
Keep me posted

@WarrDaddy
Copy link

WarrDaddy commented Nov 25, 2021

Hello @Ceres-s,

I run the script and it just hangs there after " Launching js code..."

[.] Attaching to device...
[.] Spawning the app...
[.] Attaching to process...
[.] Launching js code...
(run the app until needed, close it and then kill this script)

@elliott-wen
Copy link

elliott-wen commented Jun 11, 2022

Hi, I noticed one issue in the readStreamToHex function, where the stream is closed on behalf of the application. This may disrupt the application flow.

@liangzaiyusai
Copy link

I also have the save problem.

@ceres-c
Copy link
Author

ceres-c commented May 11, 2023

@WarrDaddy most likely the app is not using any of the methods this script attaches

@elliott-wen, @liangzaiyusai just replace the stream.close(); line with stream.reset();. You can add an additional stream.mark(); at the beginning of the function to be sure to reset to the same position when you're done, in case the stream was already partially read. This can be done only if the stream supports mark/reset, and that can be checked with markSupported: https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#markSupported%28%29
Also note that my keyStoreLoadStream.implementation is not doing anything with the keyStore so, if you want to preserve the original function behavior, most likely you also want to call the actual LoadStream function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment