mirror of
https://github.com/topjohnwu/Fingerface.git
synced 2024-11-23 03:39:50 +00:00
Rewrite Xposed hooks
This commit is contained in:
parent
b632715b61
commit
880db8c1e5
@ -4,6 +4,7 @@
|
||||
package="com.edison.fingerface">
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -0,0 +1,25 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package android.hardware.fingerprint
|
||||
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
|
||||
class MyAuthenticationCallback(private val callback: FingerprintManager.AuthenticationCallback) :
|
||||
BiometricPrompt.AuthenticationCallback() {
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
callback.onAuthenticationError(errorCode, errString)
|
||||
}
|
||||
|
||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
||||
callback.onAuthenticationHelp(helpCode, helpString)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
callback.onAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
callback.onAuthenticationSucceeded(MyAuthenticationResult(result))
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package android.hardware.fingerprint;
|
||||
|
||||
public class MyAuthenticationResult extends FingerprintManager.AuthenticationResult {
|
||||
|
||||
FingerprintManager.CryptoObject cryptoObject;
|
||||
|
||||
public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
|
||||
this.cryptoObject = cryptoObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FingerprintManager.CryptoObject getCryptoObject() {
|
||||
if (cryptoObject != null) {
|
||||
return cryptoObject;
|
||||
}
|
||||
return super.getCryptoObject();
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package android.hardware.fingerprint
|
||||
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
|
||||
class MyAuthenticationResult(private val result: BiometricPrompt.AuthenticationResult) :
|
||||
FingerprintManager.AuthenticationResult() {
|
||||
|
||||
private fun BiometricPrompt.CryptoObject.toLegacy() = when {
|
||||
cipher != null -> FingerprintManager.CryptoObject(cipher)
|
||||
mac != null -> FingerprintManager.CryptoObject(mac)
|
||||
signature != null -> FingerprintManager.CryptoObject(signature)
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getCryptoObject(): FingerprintManager.CryptoObject? {
|
||||
return result.cryptoObject?.toLegacy()
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
package android.hardware.fingerprint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class MyFingerprintManager extends FingerprintManager {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private KeyGenerator keyGenerator;
|
||||
private KeyStore keyStore;
|
||||
private Cipher cipher;
|
||||
|
||||
private String KEY_NAME = "MyFingerprintManager";
|
||||
|
||||
public void SetContext(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHardwareDetected() {
|
||||
|
||||
Log.d("XposedHandler", "isHardwareDetected");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnrolledFingerprints() {
|
||||
Log.d("XposedHandler", "hasEnrolledFingerprints");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(final CryptoObject crypto, CancellationSignal cancel,
|
||||
int flags, final AuthenticationCallback callback, Handler handler) {
|
||||
|
||||
BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(mContext).setTitle("FaceID").setNegativeButton(mContext.getString(android.R.string.cancel), mContext.getMainExecutor(), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).build();
|
||||
|
||||
BiometricPrompt.CryptoObject c = null;
|
||||
|
||||
if (crypto == null) {
|
||||
|
||||
generateKey();
|
||||
initCipher();
|
||||
c = new BiometricPrompt.CryptoObject(cipher);
|
||||
|
||||
} else if (crypto.getCipher() != null) {
|
||||
c = new BiometricPrompt.CryptoObject(crypto.getCipher());
|
||||
} else if (crypto.getMac() != null) {
|
||||
c = new BiometricPrompt.CryptoObject(crypto.getMac());
|
||||
} else if (crypto.getSignature() != null) {
|
||||
c = new BiometricPrompt.CryptoObject(crypto.getSignature());
|
||||
}
|
||||
|
||||
biometricPrompt.authenticate(c, cancel, mContext.getMainExecutor(), new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
callback.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
callback.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
||||
|
||||
BiometricPrompt.CryptoObject BioCrypto = result.getCryptoObject();
|
||||
|
||||
CryptoObject c = null;
|
||||
|
||||
if (BioCrypto.getCipher() != null) {
|
||||
c = new CryptoObject(BioCrypto.getCipher());
|
||||
} else if (BioCrypto.getMac() != null) {
|
||||
c = new CryptoObject(BioCrypto.getMac());
|
||||
} else if (BioCrypto.getSignature() != null) {
|
||||
c = new CryptoObject(BioCrypto.getSignature());
|
||||
}
|
||||
|
||||
AuthenticationResult fingerprintResult = new AuthenticationResult();
|
||||
|
||||
if (crypto != null) {
|
||||
fingerprintResult = new MyAuthenticationResult();
|
||||
((MyAuthenticationResult) fingerprintResult).setCryptoObject(c);
|
||||
}
|
||||
|
||||
callback.onAuthenticationSucceeded(fingerprintResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
callback.onAuthenticationFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void generateKey() {
|
||||
try {
|
||||
|
||||
keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
keyStore.load(null);
|
||||
|
||||
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||
keyGenerator.init(new
|
||||
KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
.build());
|
||||
|
||||
keyGenerator.generateKey();
|
||||
|
||||
} catch (KeyStoreException
|
||||
| NoSuchAlgorithmException
|
||||
| NoSuchProviderException
|
||||
| InvalidAlgorithmParameterException
|
||||
| CertificateException
|
||||
| IOException exc) {
|
||||
exc.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initCipher() {
|
||||
try {
|
||||
cipher = Cipher.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
|
||||
} catch (NoSuchAlgorithmException |
|
||||
NoSuchPaddingException e) {
|
||||
throw new RuntimeException("Failed to get Cipher", e);
|
||||
}
|
||||
|
||||
try {
|
||||
keyStore.load(null);
|
||||
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
|
||||
null);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return true;
|
||||
|
||||
|
||||
} catch (KeyPermanentlyInvalidatedException e) {
|
||||
return false;
|
||||
|
||||
} catch (KeyStoreException | CertificateException
|
||||
| UnrecoverableKeyException | IOException
|
||||
| NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
|
||||
throw new RuntimeException("Failed to init Cipher", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package android.hardware.fingerprint
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.hardware.biometrics.BiometricManager
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import com.edison.fingerface.HandlerExecutor
|
||||
|
||||
class MyFingerprintManager(private val context: Context) : FingerprintManager() {
|
||||
|
||||
private val manager = context.getSystemService(BiometricManager::class.java)!!
|
||||
|
||||
override fun isHardwareDetected(): Boolean {
|
||||
Log.d(TAG, "isHardwareDetected")
|
||||
return when (manager.canAuthenticate()) {
|
||||
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasEnrolledFingerprints(): Boolean {
|
||||
Log.d(TAG, "hasEnrolledFingerprints")
|
||||
return when (manager.canAuthenticate()) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
|
||||
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoObject.toBio() = when {
|
||||
cipher != null -> BiometricPrompt.CryptoObject(cipher)
|
||||
mac != null -> BiometricPrompt.CryptoObject(mac)
|
||||
signature != null -> BiometricPrompt.CryptoObject(signature)
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun authenticate(
|
||||
crypto: CryptoObject?, cancel: CancellationSignal?,
|
||||
flags: Int, legacyCallback: AuthenticationCallback, handler: Handler?
|
||||
) {
|
||||
Log.d(TAG, "authenticate")
|
||||
|
||||
val bioCrypto = crypto?.toBio()
|
||||
val cancelSignal = cancel ?: CancellationSignal()
|
||||
val executor = handler?.let { HandlerExecutor(it) } ?: context.mainExecutor
|
||||
val callback = MyAuthenticationCallback(legacyCallback)
|
||||
|
||||
val biometricPrompt = BiometricPrompt.Builder(context).setTitle("Fingerface")
|
||||
.setNegativeButton(
|
||||
context.getString(android.R.string.cancel),
|
||||
executor,
|
||||
DialogInterface.OnClickListener { dialog, _ -> dialog.dismiss() })
|
||||
.build()
|
||||
|
||||
if (bioCrypto == null) {
|
||||
biometricPrompt.authenticate(cancelSignal, executor, callback)
|
||||
} else {
|
||||
biometricPrompt.authenticate(bioCrypto, cancelSignal, executor, callback)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "Fingerface"
|
||||
}
|
||||
}
|
13
app/src/main/java/com/edison/fingerface/HandlerExecutor.kt
Normal file
13
app/src/main/java/com/edison/fingerface/HandlerExecutor.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package com.edison.fingerface
|
||||
|
||||
import android.os.Handler
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.RejectedExecutionException
|
||||
|
||||
class HandlerExecutor(private val handler: Handler) : Executor {
|
||||
override fun execute(command: Runnable) {
|
||||
if (!handler.post(command)) {
|
||||
throw RejectedExecutionException("$handler is shutting down");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.edison.fingerface
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XModuleResources
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.util.Log
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XSharedPreferences
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import android.hardware.fingerprint.MyFingerprintManager
|
||||
|
||||
|
||||
class ModFingerface {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun initAndroid(prefs: XSharedPreferences, classLoader: ClassLoader, modRes: XModuleResources) {
|
||||
val getSystemServiceHook = object : XC_MethodHook() {
|
||||
override fun afterHookedMethod(param: MethodHookParam?) {
|
||||
if (param!!.args[0].toString() == Context.FINGERPRINT_SERVICE) {
|
||||
Log.d("XposedHandler", "Get Fingerpring service, call from:${param!!.method.name}, thisObj:${param!!.result}")
|
||||
val mgr = MyFingerprintManager()
|
||||
mgr.SetContext(param!!.thisObject as Context)
|
||||
param!!.result = mgr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XposedBridge.hookAllMethods(
|
||||
Class.forName("android.app.ContextImpl"),
|
||||
"getSystemService",
|
||||
getSystemServiceHook
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package com.edison.fingerface
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XModuleResources
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.util.Log
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XSharedPreferences
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import android.hardware.fingerprint.MyFingerprintManager
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import android.R.attr.classLoader
|
||||
|
||||
|
||||
class ModPackageManagerService {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun initAndroid(prefs: XSharedPreferences, classLoader: ClassLoader, modRes: XModuleResources) {
|
||||
|
||||
val hasSystemFeatureHook = object : XC_MethodHook() {
|
||||
|
||||
override fun afterHookedMethod(param: MethodHookParam?) {
|
||||
if (param!!.args[0].toString() == PackageManager.FEATURE_FINGERPRINT) {
|
||||
param!!.result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XposedHelpers.findAndHookMethod("android.app.ContextImpl", classLoader,
|
||||
"getPackageManager", object : XC_MethodHook() {
|
||||
@Throws(Throwable::class)
|
||||
override fun afterHookedMethod(param: MethodHookParam?) {
|
||||
if (param!!.result is PackageManager) {
|
||||
val clazz = param.result.javaClass
|
||||
val packageMethod = clazz.getDeclaredMethod("hasSystemFeature", String::class.java)
|
||||
|
||||
XposedBridge.hookMethod(packageMethod, hasSystemFeatureHook)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package com.edison.fingerface;
|
||||
|
||||
import android.app.AndroidAppHelper;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.content.res.XModuleResources;
|
||||
import android.content.res.XResources;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import de.robv.android.xposed.*;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class XposedHandler implements IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookInitPackageResources {
|
||||
public static final String PACKAGE_NAME = XposedHandler.class.getPackage().getName();
|
||||
private static final String TAG = "Fingerface";
|
||||
|
||||
private static XSharedPreferences prefs;
|
||||
public static XModuleResources modRes;
|
||||
private static String MODULE_PATH = null;
|
||||
|
||||
public static List<Pair<XResources, String>> packageResources = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void initZygote(StartupParam startupParam) throws Throwable {
|
||||
MODULE_PATH = startupParam.modulePath;
|
||||
modRes = XModuleResources.createInstance(MODULE_PATH, null);
|
||||
prefs = new XSharedPreferences(PACKAGE_NAME);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable {
|
||||
|
||||
if (resparam.packageName.equals("android")) {
|
||||
return;
|
||||
}
|
||||
|
||||
XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, resparam.res);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
|
||||
if (!lpparam.processName.equals("android") && !lpparam.processName.equals("com.android.systemui")) {
|
||||
ModFingerface.initAndroid(prefs, lpparam.classLoader, modRes);
|
||||
ModPackageManagerService.initAndroid(prefs, lpparam.classLoader, modRes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
62
app/src/main/java/com/edison/fingerface/XposedHandler.kt
Normal file
62
app/src/main/java/com/edison/fingerface/XposedHandler.kt
Normal file
@ -0,0 +1,62 @@
|
||||
package com.edison.fingerface
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.XModuleResources
|
||||
import android.hardware.fingerprint.MyFingerprintManager
|
||||
import de.robv.android.xposed.*
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||
|
||||
class XposedHandler : IXposedHookZygoteInit, IXposedHookLoadPackage {
|
||||
|
||||
private lateinit var modulePath: String
|
||||
private lateinit var modRes: XModuleResources
|
||||
private lateinit var prefs: XSharedPreferences
|
||||
|
||||
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
||||
modulePath = startupParam.modulePath
|
||||
modRes = XModuleResources.createInstance(modulePath, null)
|
||||
prefs = XSharedPreferences(BuildConfig.APPLICATION_ID)
|
||||
|
||||
}
|
||||
|
||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
||||
if (lpparam.processName != "android" && lpparam.processName != "com.android.systemui") {
|
||||
hookFingerprintService(lpparam.classLoader)
|
||||
hookPackageManager(lpparam.classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hookFingerprintService(cl: ClassLoader) {
|
||||
val getSystemServiceHook = object : XC_MethodHook() {
|
||||
override fun afterHookedMethod(param: MethodHookParam) {
|
||||
if (param.args[0].toString() == Context.FINGERPRINT_SERVICE) {
|
||||
param.result = MyFingerprintManager(param.thisObject as Context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XposedHelpers.findAndHookMethod(
|
||||
"android.app.ContextImpl", cl,
|
||||
"getSystemService", String::class.java,
|
||||
getSystemServiceHook
|
||||
)
|
||||
}
|
||||
|
||||
private fun hookPackageManager(cl: ClassLoader) {
|
||||
val hasSystemFeatureHook = object : XC_MethodHook() {
|
||||
override fun afterHookedMethod(param: MethodHookParam) {
|
||||
if (param.args[0].toString() == PackageManager.FEATURE_FINGERPRINT) {
|
||||
param.result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XposedHelpers.findAndHookMethod(
|
||||
"android.app.ApplicationPackageManager", cl,
|
||||
"hasSystemFeature", String::class.java,
|
||||
hasSystemFeatureHook
|
||||
)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user