Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shitty Signature V2 hack. #70

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/src/main/java/toolkit/coderstory/CorePatchForR.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@

import com.coderstory.toolkit.BuildConfig;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -240,6 +243,24 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
});

V2Heck.classloader = loadPackageParam.classLoader;
hookAllMethods("android.util.apk.ApkSignatureSchemeV2Verifier", loadPackageParam.classLoader, "verifySigner", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
if(prefs.getBoolean("digestCreak", true)&&prefs.getBoolean("authcreak", true)){
Object res = null;
try {
res = V2Heck.verifySigner((ByteBuffer) param.args[0], (Map<Integer, byte[]>) param.args[1], (CertificateFactory) param.args[2]);
}catch (Exception|Error ignored)
{
}
if(res != null)
param.setResult(res);
}
}
});

var utilClass = findClass("com.android.server.pm.PackageManagerServiceUtils", loadPackageParam.classLoader);
if (utilClass != null) {
for (var m : utilClass.getDeclaredMethods()) {
Expand Down Expand Up @@ -272,4 +293,5 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
});
}

}
2 changes: 1 addition & 1 deletion app/src/main/java/toolkit/coderstory/MainHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class MainHook implements IXposedHookLoadPackage, IXposedHookZygoteInit {
public class MainHook implements IXposedHookLoadPackage, IXposedHookZygoteInit {
public static final String TAG = "CorePatch";

@Override
Expand Down
229 changes: 229 additions & 0 deletions app/src/main/java/toolkit/coderstory/V2Heck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package toolkit.coderstory;

import static de.robv.android.xposed.XposedHelpers.ClassNotFoundError;
import static de.robv.android.xposed.XposedHelpers.callStaticMethod;

import android.util.Pair;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import de.robv.android.xposed.XposedHelpers;

public final class V2Heck {
private static final String ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS = "android.util.apk.ApkSigningBlockUtils";
static ClassLoader classloader;

private static Class<?> findClass(String clsnm, ClassLoader clsldr) {
//Fix for UnIntelliJ suggestion
try {
return de.robv.android.xposed.XposedHelpers.findClass(clsnm, clsldr);
} catch (ClassNotFoundError ignored) {
}
return null;
}

private static <T> T runMethod(String cls, String mt, Object... args) throws XposedHelpers.InvocationTargetError {
final Class<?> cs = findClass(cls, classloader);
if (cs != null) {
try {
return (T) callStaticMethod(cs, mt, args);
} catch (NoSuchMethodError ignored) {
return null;
}
}
return null;
}

private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws XposedHelpers.InvocationTargetError, IOException {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "getLengthPrefixedSlice", source);
}

private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws XposedHelpers.InvocationTargetError, IOException {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "readLengthPrefixedByteArray", buf);
}

private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "isSupportedSignatureAlgorithm", sigAlgorithm);
}

private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "compareSignatureAlgorithm", sigAlgorithm1, sigAlgorithm2);
}

private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "getSignatureAlgorithmJcaKeyAlgorithm", sigAlgorithm);
}

private static Pair<String, ? extends AlgorithmParameterSpec>
getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "getSignatureAlgorithmJcaSignatureAlgorithm", sigAlgorithm);
}

private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "getSignatureAlgorithmContentDigestAlgorithm", sigAlgorithm);
}

private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
return runMethod(ANDROID_UTIL_APK_APK_SIGNING_BLOCK_UTILS, "getContentDigestAlgorithmJcaDigestAlgorithm", digestAlgorithm);
}

private static X509Certificate makeVerbatimX509Certificate(X509Certificate cer, byte[] dat) throws XposedHelpers.InvocationTargetError {
Class<?> cs = findClass("android.util.apk.VerbatimX509Certificate", classloader);
if (cs != null) {
try {
return (X509Certificate) XposedHelpers.newInstance(cs, cer, dat);
} catch (NoSuchMethodError | InstantiationError ignored) {
return null;
}
} else {
return null;
}
}

private static void verifyAdditionalAttributes(ByteBuffer attrs) {
runMethod("android.util.apk.ApkSignatureSchemeV2Verifier", "verifyAdditionalAttributes", attrs);
}

//modified to allow install by ignoring verify exception
public static X509Certificate[] verifySigner(
ByteBuffer signerBlock,
Map<Integer, byte[]> contentDigests,
CertificateFactory certFactory) throws SecurityException, IOException {
ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
int signatureCount = 0;
int bestSigAlgorithm = -1;
byte[] bestSigAlgorithmSignatureBytes = null;
List<Integer> signaturesSigAlgorithms = new ArrayList<>();
while (signatures.hasRemaining()) {
signatureCount++;
try {
ByteBuffer signature = getLengthPrefixedSlice(signatures);
if (signature.remaining() < 8) {
throw new SecurityException("Signature record too short");
}
int sigAlgorithm = signature.getInt();
signaturesSigAlgorithms.add(sigAlgorithm);
if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
continue;
}
if ((bestSigAlgorithm == -1)
|| (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
bestSigAlgorithm = sigAlgorithm;
bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
}
} catch (IOException | BufferUnderflowException e) {
throw new SecurityException(
"Failed to parse signature record #" + signatureCount,
e);
}
}
if (bestSigAlgorithm == -1) {
if (signatureCount == 0) {
throw new SecurityException("No signatures found");
} else {
throw new SecurityException("No supported signatures found");
}
}
String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
boolean sigVerified;
try {
PublicKey publicKey =
KeyFactory.getInstance(keyAlgorithm)
.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
sig.initVerify(publicKey);
if (jcaSignatureAlgorithmParams != null) {
sig.setParameter(jcaSignatureAlgorithmParams);
}
sig.update(signedData);
sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
| InvalidAlgorithmParameterException | SignatureException e) {
throw new SecurityException(
"Failed to verify " + jcaSignatureAlgorithm + " signature", e);
}
// Signature over signedData why need verify?
byte[] contentDigest = null;
signedData.clear();
ByteBuffer digests = getLengthPrefixedSlice(signedData);
List<Integer> digestsSigAlgorithms = new ArrayList<>();
int digestCount = 0;
while (digests.hasRemaining()) {
digestCount++;
try {
ByteBuffer digest = getLengthPrefixedSlice(digests);
if (digest.remaining() < 8) {
throw new IOException("Record too short");
}
int sigAlgorithm = digest.getInt();
digestsSigAlgorithms.add(sigAlgorithm);
if (sigAlgorithm == bestSigAlgorithm) {
contentDigest = readLengthPrefixedByteArray(digest);
}
} catch (IOException | BufferUnderflowException e) {
throw new IOException("Failed to parse digest record #" + digestCount, e);
}
}
if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
throw new SecurityException(
"Signature algorithms don't match between digests and signatures records");
}
int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
ByteBuffer certificates = getLengthPrefixedSlice(signedData);
List<X509Certificate> certs = new ArrayList<>();
int certificateCount = 0;
while (certificates.hasRemaining()) {
certificateCount++;
byte[] encodedCert = readLengthPrefixedByteArray(certificates);
X509Certificate certificate;
try {
certificate = (X509Certificate)
certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
} catch (CertificateException e) {
throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
}
certificate = makeVerbatimX509Certificate(
certificate, encodedCert);
certs.add(certificate);
}
if (certs.isEmpty()) {
throw new SecurityException("No certificates listed");
}
X509Certificate mainCertificate = certs.get(0);
byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
throw new SecurityException(
"Public key mismatch between certificate and signature record");
}
ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
verifyAdditionalAttributes(additionalAttrs);
return certs.toArray(new X509Certificate[certs.size()]);
}
}
2 changes: 1 addition & 1 deletion app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<string name="digestCreak">禁用APK签名验证</string>
<string name="digestCreak_summary">允许直接覆盖安装同包名不同签名的应用</string>
<string name="UsePreSig">安装时始终使用已装app的签名</string>
<string name="UsePreSig_summary">不是一般的<b>危险</b> 仅在绝对需要时启用</string>
<string name="UsePreSig_summary">不是一般的<b>危险</b> 仅在绝对需要时启用<b>(仅限V1)</b></string>
<string name="link">联系方式</string>
<string name="blog">博客主页</string>
<string name="weibo">微博主页</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<string name="digestCreak">Disable compare signatures</string>
<string name="digestCreak_summary">Allow re-install app with different signatures.</string>
<string name="UsePreSig">Use installed signatures</string>
<string name="UsePreSig_summary">Always use signatures from already installed apps when installing.\n This is extremely <b>dangerous</b>.\n Only enable when really needed!</string>
<string name="UsePreSig_summary">Always use signatures from already installed apps when installing.\n This is extremely <b>dangerous</b>.\n Only enable when really needed!<b>(V1)</b></string>
<string name="link"><b>Contact information</b></string>
<string name="blog">Blog</string>
<string name="blog_link" translatable="false">http://blog.coderstory.cn:4433</string>
Expand Down