Last active
December 7, 2024 15:26
-
-
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/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@WarrDaddy most likely the app is not using any of the methods this script attaches
@elliott-wen, @liangzaiyusai just replace the
stream.close();
line withstream.reset();
. You can add an additionalstream.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 withmarkSupported
: https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#markSupported%28%29Also 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