Skip to content

Commit ef44eaa

Browse files
authored
feat(TikTok): Add Sanitize sharing links patch (#6176)
1 parent c73a03c commit ef44eaa

File tree

6 files changed

+155
-7
lines changed

6 files changed

+155
-7
lines changed

extensions/shared/library/src/main/java/app/revanced/extension/shared/privacy/LinkSanitizer.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ public class LinkSanitizer {
1717

1818
public LinkSanitizer(String ... parametersToRemove) {
1919
final int parameterCount = parametersToRemove.length;
20-
if (parameterCount == 0) {
21-
throw new IllegalArgumentException("No parameters specified");
22-
}
2320

2421
// List is faster if only checking a few parameters.
2522
this.parametersToRemove = parameterCount > 4
@@ -40,10 +37,12 @@ public Uri sanitizeUri(Uri uri) {
4037
try {
4138
Uri.Builder builder = uri.buildUpon().clearQuery();
4239

43-
for (String paramName : uri.getQueryParameterNames()) {
44-
if (!parametersToRemove.contains(paramName)) {
45-
for (String value : uri.getQueryParameters(paramName)) {
46-
builder.appendQueryParameter(paramName, value);
40+
if (!parametersToRemove.isEmpty()) {
41+
for (String paramName : uri.getQueryParameterNames()) {
42+
if (!parametersToRemove.contains(paramName)) {
43+
for (String value : uri.getQueryParameters(paramName)) {
44+
builder.appendQueryParameter(paramName, value);
45+
}
4746
}
4847
}
4948
}

extensions/tiktok/src/main/java/app/revanced/extension/tiktok/settings/preference/categories/ExtensionPreferenceCategory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ public boolean getSettingsStatus() {
2323
public void addPreferences(Context context) {
2424
addPreference(new ReVancedTikTokAboutPreference(context));
2525

26+
addPreference(new TogglePreference(context,
27+
"Sanitize sharing links",
28+
"Remove tracking parameters from shared links.",
29+
BaseSettings.SANITIZE_SHARED_LINKS
30+
));
31+
2632
addPreference(new TogglePreference(context,
2733
"Enable debug log",
2834
"Show extension debug log.",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package app.revanced.extension.tiktok.share;
2+
3+
import app.revanced.extension.shared.Logger;
4+
import app.revanced.extension.shared.privacy.LinkSanitizer;
5+
import app.revanced.extension.shared.settings.BaseSettings;
6+
7+
@SuppressWarnings("unused")
8+
public final class ShareUrlSanitizer {
9+
10+
private static final LinkSanitizer sanitizer = new LinkSanitizer();
11+
12+
/**
13+
* Injection point for setting check.
14+
*/
15+
public static boolean shouldSanitize() {
16+
return BaseSettings.SANITIZE_SHARED_LINKS.get();
17+
}
18+
19+
/**
20+
* Injection point for URL sanitization.
21+
*/
22+
public static String sanitizeShareUrl(final String url) {
23+
if (url == null || url.isEmpty()) {
24+
return url;
25+
}
26+
27+
return sanitizer.sanitizeUrlString(url);
28+
}
29+
}

patches/api/patches.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,10 @@ public final class app/revanced/patches/tiktok/misc/settings/SettingsPatchKt {
11881188
public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
11891189
}
11901190

1191+
public final class app/revanced/patches/tiktok/misc/share/SanitizeShareUrlsPatchKt {
1192+
public static final fun getSanitizeShareUrlsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
1193+
}
1194+
11911195
public final class app/revanced/patches/tiktok/misc/spoof/sim/SpoofSimPatchKt {
11921196
public static final fun getSpoofSimPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
11931197
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package app.revanced.patches.tiktok.misc.share
2+
3+
import app.revanced.patcher.fingerprint
4+
import com.android.tools.smali.dexlib2.AccessFlags
5+
import com.android.tools.smali.dexlib2.Opcode
6+
7+
internal val urlShorteningFingerprint = fingerprint {
8+
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
9+
returns("LX/")
10+
parameters(
11+
"I",
12+
"Ljava/lang/String;",
13+
"Ljava/lang/String;",
14+
"Ljava/lang/String;"
15+
)
16+
opcodes(Opcode.RETURN_OBJECT)
17+
18+
// Same Kotlin intrinsics literal on both variants.
19+
strings("getShortShareUrlObservab\u2026ongUrl, subBizSceneValue)")
20+
21+
custom { method, _ ->
22+
// LIZLLL is obfuscated by ProGuard/R8, but stable across both TikTok and Musically.
23+
method.name == "LIZLLL"
24+
}
25+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package app.revanced.patches.tiktok.misc.share
2+
3+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
4+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
5+
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
6+
import app.revanced.patcher.patch.bytecodePatch
7+
import app.revanced.patches.shared.PATCH_DESCRIPTION_SANITIZE_SHARING_LINKS
8+
import app.revanced.patches.shared.PATCH_NAME_SANITIZE_SHARING_LINKS
9+
import app.revanced.patches.tiktok.misc.extension.sharedExtensionPatch
10+
import app.revanced.util.findFreeRegister
11+
import app.revanced.util.getReference
12+
import app.revanced.util.indexOfFirstInstructionOrThrow
13+
import com.android.tools.smali.dexlib2.Opcode
14+
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
15+
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
16+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
17+
18+
private const val EXTENSION_CLASS_DESCRIPTOR =
19+
"Lapp/revanced/extension/tiktok/share/ShareUrlSanitizer;"
20+
21+
@Suppress("unused")
22+
val sanitizeShareUrlsPatch = bytecodePatch(
23+
name = PATCH_NAME_SANITIZE_SHARING_LINKS,
24+
description = PATCH_DESCRIPTION_SANITIZE_SHARING_LINKS,
25+
) {
26+
dependsOn(sharedExtensionPatch)
27+
28+
compatibleWith(
29+
"com.ss.android.ugc.trill"("36.5.4"),
30+
"com.zhiliaoapp.musically"("36.5.4"),
31+
)
32+
33+
execute {
34+
urlShorteningFingerprint.method.apply {
35+
val invokeIndex = indexOfFirstInstructionOrThrow {
36+
val ref = getReference<MethodReference>()
37+
ref?.name == "LIZ" && ref.definingClass.startsWith("LX/")
38+
}
39+
40+
val moveResultIndex = indexOfFirstInstructionOrThrow(invokeIndex, Opcode.MOVE_RESULT_OBJECT)
41+
val urlRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
42+
43+
// Resolve Observable wrapper classes at runtime
44+
val observableWrapperIndex = indexOfFirstInstructionOrThrow(Opcode.NEW_INSTANCE)
45+
val observableWrapperClass = getInstruction<ReferenceInstruction>(observableWrapperIndex)
46+
.reference.toString()
47+
48+
val observableFactoryIndex = indexOfFirstInstructionOrThrow {
49+
val ref = getReference<MethodReference>()
50+
ref?.name == "LJ" && ref.definingClass.startsWith("LX/")
51+
}
52+
val observableFactoryRef = getInstruction<ReferenceInstruction>(observableFactoryIndex)
53+
.reference as MethodReference
54+
55+
val observableFactoryClass = observableFactoryRef.definingClass
56+
val observableInterfaceType = observableFactoryRef.parameterTypes.first()
57+
val observableReturnType = observableFactoryRef.returnType
58+
59+
val wrapperRegister = findFreeRegister(moveResultIndex + 1, urlRegister)
60+
61+
// Check setting and conditionally sanitize share URL.
62+
addInstructionsWithLabels(
63+
moveResultIndex + 1,
64+
"""
65+
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldSanitize()Z
66+
move-result v$wrapperRegister
67+
if-eqz v$wrapperRegister, :skip_sanitization
68+
69+
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->sanitizeShareUrl(Ljava/lang/String;)Ljava/lang/String;
70+
move-result-object v$urlRegister
71+
72+
# Wrap sanitized URL and return early to bypass ShareExtService
73+
new-instance v$wrapperRegister, $observableWrapperClass
74+
invoke-direct { v$wrapperRegister, v$urlRegister }, $observableWrapperClass-><init>(Ljava/lang/String;)V
75+
invoke-static { v$wrapperRegister }, $observableFactoryClass->LJ($observableInterfaceType)$observableReturnType
76+
move-result-object v$urlRegister
77+
return-object v$urlRegister
78+
79+
:skip_sanitization
80+
nop
81+
"""
82+
)
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)