Skip to content

Commit 78b25aa

Browse files
authored
feat: Add Spoof app signature patch (#5158)
1 parent 745ff89 commit 78b25aa

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ appcompat = "1.7.0"
1111
okhttp = "5.0.0-alpha.14"
1212
retrofit = "2.11.0"
1313
guava = "33.4.0-jre"
14+
apksig = "8.10.1"
1415

1516
[libraries]
1617
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
1718
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
1819
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
1920
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
2021
guava = { module = "com.google.guava:guava", version.ref = "guava" }
21-
22+
apksig = { group = "com.android.tools.build", name = "apksig", version.ref = "apksig" }
2223

2324
[plugins]
2425
android-library = { id = "com.android.library", version.ref = "agp" }

patches/api/patches.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ public final class app/revanced/patches/all/misc/shortcut/sharetargets/RemoveSha
116116
public static final fun getRemoveShareTargetsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
117117
}
118118

119+
public final class app/revanced/patches/all/misc/spoof/SignatureSpoofPatchKt {
120+
public static final fun getSignatureSpoofPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
121+
}
122+
119123
public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt {
120124
public static final fun getSetTargetSdkVersion34 ()Lapp/revanced/patcher/patch/ResourcePatch;
121125
}

patches/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ patches {
1515
dependencies {
1616
// Required due to smali, or build fails. Can be removed once smali is bumped.
1717
implementation(libs.guava)
18+
19+
implementation(libs.apksig)
20+
1821
// Android API stubs defined here.
1922
compileOnly(project(":patches:stub"))
2023
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package app.revanced.patches.all.misc.spoof
2+
3+
import app.revanced.patcher.patch.resourcePatch
4+
import app.revanced.patcher.patch.stringOption
5+
import app.revanced.util.getNode
6+
import com.android.apksig.ApkVerifier
7+
import com.android.apksig.apk.ApkFormatException
8+
import org.w3c.dom.Element
9+
import java.io.ByteArrayInputStream
10+
import java.io.IOException
11+
import java.nio.file.Files
12+
import java.nio.file.InvalidPathException
13+
import java.nio.file.attribute.BasicFileAttributes
14+
import java.security.NoSuchAlgorithmException
15+
import java.security.cert.CertificateException
16+
import java.security.cert.CertificateFactory
17+
import java.util.*
18+
import kotlin.io.path.Path
19+
20+
val signatureSpoofPatch = resourcePatch(
21+
name = "Spoof app signature",
22+
description = "Spoofs the app signature via the \"fake-signature\" meta key. " +
23+
"This patch only works with patched device roms.",
24+
use = false,
25+
) {
26+
val signature by stringOption(
27+
key = "spoofedAppSignature",
28+
title = "Signature",
29+
validator = { signature ->
30+
optionToSignature(signature) != null
31+
},
32+
description = "The hex-encoded signature or path to an apk file with the desired signature",
33+
required = true,
34+
)
35+
execute {
36+
document("AndroidManifest.xml").use { document ->
37+
val manifest = document.getNode("manifest") as Element
38+
39+
val fakeSignaturePermission = document.createElement("uses-permission")
40+
fakeSignaturePermission.setAttribute("android:name", "android.permission.FAKE_PACKAGE_SIGNATURE")
41+
manifest.appendChild(fakeSignaturePermission)
42+
43+
val application = document.getNode("application") ?: {
44+
val child = document.createElement("application")
45+
manifest.appendChild(child)
46+
child
47+
} as Element;
48+
49+
val fakeSignatureMetadata = document.createElement("meta-data")
50+
fakeSignatureMetadata.setAttribute("android:name", "fake-signature")
51+
fakeSignatureMetadata.setAttribute("android:value", optionToSignature(signature))
52+
application.appendChild(fakeSignatureMetadata)
53+
}
54+
}
55+
}
56+
57+
internal fun optionToSignature(signature: String?): String? {
58+
if (signature == null) {
59+
return null;
60+
}
61+
try {
62+
// TODO: Replace with signature.hexToByteArray when stable in kotlin
63+
val signatureBytes = HexFormat.of()
64+
.parseHex(signature)
65+
val factory = CertificateFactory.getInstance("X.509")
66+
factory.generateCertificate(ByteArrayInputStream(signatureBytes))
67+
return signature;
68+
} catch (_: IllegalArgumentException) {
69+
} catch (_: CertificateException) {
70+
}
71+
try {
72+
val signaturePath = Path(signature)
73+
if (!Files.readAttributes(signaturePath, BasicFileAttributes::class.java).isRegularFile) {
74+
return null;
75+
}
76+
val verifier = ApkVerifier.Builder(signaturePath.toFile())
77+
.build()
78+
79+
val result = verifier.verify()
80+
if (result.isVerifiedUsingV3Scheme) {
81+
return HexFormat.of().formatHex(result.v3SchemeSigners[0].certificate.encoded)
82+
} else if (result.isVerifiedUsingV2Scheme) {
83+
return HexFormat.of().formatHex(result.v2SchemeSigners[0].certificate.encoded)
84+
} else if (result.isVerifiedUsingV1Scheme) {
85+
return HexFormat.of().formatHex(result.v1SchemeSigners[0].certificate.encoded)
86+
}
87+
88+
return null;
89+
} catch (_: IOException) {
90+
} catch (_: InvalidPathException) {
91+
} catch (_: ApkFormatException) {
92+
} catch (_: NoSuchAlgorithmException) {
93+
} catch (_: IllegalArgumentException) {}
94+
return null;
95+
}

0 commit comments

Comments
 (0)