[core] Cleanup Xposed Bridge implementation

This commit is contained in:
tehcneko 2021-02-22 14:35:09 +08:00
parent 535dd32e52
commit aaa8f4bc40
27 changed files with 113 additions and 1209 deletions

View File

@ -56,69 +56,16 @@ public final class AndroidAppHelper {
private static final boolean HAS_THEME_CONFIG_PARAMETER;
static {
CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
findClass("android.app.ActivityThread$ResourcesKey", null)
: findClass("android.content.res.ResourcesKey", null);
CLASS_RESOURCES_KEY = findClass("android.content.res.ResourcesKey", null);
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
&& findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
if (Build.VERSION.SDK_INT >= 24) {
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
return (Map) getObjectField(resourcesManager, "mResourceImpls");
} else if (Build.VERSION.SDK_INT >= 19) {
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
return (Map) getObjectField(resourcesManager, "mActiveResources");
} else {
return (Map) getObjectField(activityThread, "mActiveResources");
}
}
/* For SDK 15 & 16 */
private static Object createResourcesKey(String resDir, float scale) {
try {
if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, scale);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/* For SDK 17 & 18 & 23 */
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
try {
if (HAS_THEME_CONFIG_PARAMETER)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null);
else if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/* For SDK 19 - 22 */
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) {
try {
if (HAS_THEME_CONFIG_PARAMETER)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token);
else if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
return (Map) getObjectField(resourcesManager, "mResourceImpls");
}
/* For SDK 24+ */
@ -144,27 +91,13 @@ public final class AndroidAppHelper {
}
Object resourcesKey;
if (Build.VERSION.SDK_INT >= 24) {
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
setFloatField(compatInfo, "applicationScale", resources.hashCode());
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
} else if (Build.VERSION.SDK_INT == 23) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
} else if (Build.VERSION.SDK_INT >= 19) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null);
} else if (Build.VERSION.SDK_INT >= 17) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
} else {
resourcesKey = createResourcesKey(resDir, resources.hashCode());
}
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
setFloatField(compatInfo, "applicationScale", resources.hashCode());
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
if (resourcesKey != null) {
if (Build.VERSION.SDK_INT >= 24) {
Object resImpl = getObjectField(resources, "mResourcesImpl");
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
} else {
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
}
Object resImpl = getObjectField(resources, "mResourcesImpl");
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
}
}

View File

@ -119,11 +119,7 @@ public class XResources extends XResourcesSuperClass {
if (resDir != null) {
synchronized (sReplacementsCacheMap) {
mReplacementsCache = sReplacementsCacheMap.get(resDir);
if (mReplacementsCache == null) {
mReplacementsCache = new byte[128];
sReplacementsCacheMap.put(resDir, mReplacementsCache);
}
mReplacementsCache = sReplacementsCacheMap.computeIfAbsent(resDir, k -> new byte[128]);
}
}
@ -184,14 +180,10 @@ public class XResources extends XResourcesSuperClass {
return packageName;
PackageParser.PackageLite pkgInfo;
if (Build.VERSION.SDK_INT >= 21) {
try {
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
} catch (PackageParserException e) {
throw new IllegalStateException("Could not determine package name for " + resDir, e);
}
} else {
pkgInfo = PackageParser.parsePackageLite(resDir, 0);
try {
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
} catch (PackageParserException e) {
throw new IllegalStateException("Could not determine package name for " + resDir, e);
}
if (pkgInfo != null && pkgInfo.packageName != null) {
// Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser");
@ -265,7 +257,7 @@ public class XResources extends XResourcesSuperClass {
XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS);
if (details != null) {
LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks);
ViewGroup group = (ViewGroup) param.args[(Build.VERSION.SDK_INT < 23) ? 1 : 2];
ViewGroup group = (ViewGroup) param.args[2];
liparam.view = group.getChildAt(group.getChildCount() - 1);
liparam.resNames = details.resNames;
liparam.variant = details.variant;
@ -274,16 +266,8 @@ public class XResources extends XResourcesSuperClass {
}
}
};
if (Build.VERSION.SDK_INT < 21) {
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
AttributeSet.class, parseIncludeHook);
} else if (Build.VERSION.SDK_INT < 23) {
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
AttributeSet.class, boolean.class, parseIncludeHook);
} else {
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, Context.class,
View.class, AttributeSet.class, parseIncludeHook);
}
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, Context.class,
View.class, AttributeSet.class, parseIncludeHook);
}
/**
@ -662,9 +646,7 @@ public class XResources extends XResourcesSuperClass {
XmlResourceParser result = repRes.getAnimation(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
@ -924,9 +906,7 @@ public class XResources extends XResourcesSuperClass {
result = repRes.getLayout(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
} else {
@ -1080,9 +1060,7 @@ public class XResources extends XResourcesSuperClass {
XmlResourceParser result = repRes.getXml(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}

View File

@ -1,255 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed;
import android.os.Environment;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
import static de.robv.android.xposed.XposedHelpers.inputStreamToByteArray;
/**
* Helper class which can create a very simple .dex file, containing only a class definition
* with a super class (no methods, fields, ...).
*/
/*package*/ class DexCreator {
public static File DALVIK_CACHE = new File(Environment.getDataDirectory(), "dalvik-cache");
/** Returns the default dex file name for the class. */
public static File getDefaultFile(String childClz) {
return new File(DALVIK_CACHE, "xposed_" + childClz.substring(childClz.lastIndexOf('.') + 1) + ".dex");
}
/**
* Creates (or returns) the path to a dex file which defines the superclass of {@clz} as extending
* {@code realSuperClz}, which by itself must extend {@code topClz}.
*/
public static File ensure(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
if (!topClz.isAssignableFrom(realSuperClz)) {
throw new ClassCastException("Cannot initialize " + clz + " because " + realSuperClz + " does not extend " + topClz);
}
try {
return ensure("xposed.dummy." + clz + "SuperClass", realSuperClz);
} catch (IOException e) {
throw new IOException("Failed to create a superclass for " + clz, e);
}
}
/** Like {@link #ensure(File, String, String)}, just for the default dex file name. */
public static File ensure(String childClz, Class<?> superClz) throws IOException {
return ensure(getDefaultFile(childClz), childClz, superClz.getName());
}
/**
* Makes sure that the given file is a simple dex file containing the given classes.
* Creates the file if that's not the case.
*/
public static File ensure(File file, String childClz, String superClz) throws IOException {
// First check if a valid file exists.
try {
byte[] dex = inputStreamToByteArray(new FileInputStream(file));
if (matches(dex, childClz, superClz)) {
return file;
} else {
file.delete();
}
} catch (IOException e) {
file.delete();
}
// If not, create a new dex file.
byte[] dex = create(childClz, superClz);
FileOutputStream fos = new FileOutputStream(file);
fos.write(dex);
fos.close();
return file;
}
/**
* Checks whether the Dex file fits to the class names.
* Assumes that the file has been created with this class.
*/
public static boolean matches(byte[] dex, String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int pos = 0xa0;
if (pos + childBytes.length + superBytes.length >= dex.length) {
return false;
}
for (byte b : childFirst ? childBytes : superBytes) {
if (dex[pos++] != b) {
return false;
}
}
for (byte b : childFirst ? superBytes: childBytes) {
if (dex[pos++] != b) {
return false;
}
}
return true;
}
/** Creates the byte array for the dex file. */
public static byte[] create(String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int stringsSize = childBytes.length + superBytes.length;
int padding = -stringsSize & 3;
stringsSize += padding;
ByteArrayOutputStream out = new ByteArrayOutputStream();
// header
out.write("dex\n035\0".getBytes()); // magic
out.write(new byte[24]); // placeholder for checksum and signature
writeInt(out, 0xfc + stringsSize); // file size
writeInt(out, 0x70); // header size
writeInt(out, 0x12345678); // endian constant
writeInt(out, 0); // link size
writeInt(out, 0); // link offset
writeInt(out, 0xa4 + stringsSize); // map offset
writeInt(out, 2); // strings count
writeInt(out, 0x70); // strings offset
writeInt(out, 2); // types count
writeInt(out, 0x78); // types offset
writeInt(out, 0); // prototypes count
writeInt(out, 0); // prototypes offset
writeInt(out, 0); // fields count
writeInt(out, 0); // fields offset
writeInt(out, 0); // methods count
writeInt(out, 0); // methods offset
writeInt(out, 1); // classes count
writeInt(out, 0x80); // classes offset
writeInt(out, 0x5c + stringsSize); // data size
writeInt(out, 0xa0); // data offset
// string map
writeInt(out, 0xa0);
writeInt(out, 0xa0 + (childFirst ? childBytes.length : superBytes.length));
// types
writeInt(out, 0); // first type = first string
writeInt(out, 1); // second type = second string
// class definitions
writeInt(out, childFirst ? 0 : 1); // class to define = child type
writeInt(out, 1); // access flags = public
writeInt(out, childFirst ? 1 : 0); // super class = super type
writeInt(out, 0); // no interface
writeInt(out, -1); // no source file
writeInt(out, 0); // no annotations
writeInt(out, 0); // no class data
writeInt(out, 0); // no static values
// string data
out.write(childFirst ? childBytes : superBytes);
out.write(childFirst ? superBytes : childBytes);
out.write(new byte[padding]);
// annotations
writeInt(out, 0); // no items
// map
writeInt(out, 7); // items count
writeMapItem(out, 0, 1, 0); // header
writeMapItem(out, 1, 2, 0x70); // strings
writeMapItem(out, 2, 2, 0x78); // types
writeMapItem(out, 6, 1, 0x80); // classes
writeMapItem(out, 0x2002, 2, 0xa0); // string data
writeMapItem(out, 0x1003, 1, 0xa0 + stringsSize); // annotations
writeMapItem(out, 0x1000, 1, 0xa4 + stringsSize); // map list
byte[] buf = out.toByteArray();
updateSignature(buf);
updateChecksum(buf);
return buf;
}
private static void updateSignature(byte[] dex) {
// Update SHA-1 signature
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dex, 32, dex.length - 32);
md.digest(dex, 12, 20);
} catch (NoSuchAlgorithmException | DigestException e) {
throw new RuntimeException(e);
}
}
private static void updateChecksum(byte[] dex) {
// Update Adler32 checksum
Adler32 a32 = new Adler32();
a32.update(dex, 12, dex.length - 12);
int chksum = (int) a32.getValue();
dex[8] = (byte) (chksum & 0xff);
dex[9] = (byte) (chksum >> 8 & 0xff);
dex[10] = (byte) (chksum >> 16 & 0xff);
dex[11] = (byte) (chksum >> 24 & 0xff);
}
private static void writeUleb128(OutputStream out, int value) throws IOException {
while (value > 0x7f) {
out.write((value & 0x7f) | 0x80);
value >>>= 7;
}
out.write(value);
}
private static void writeInt(OutputStream out, int value) throws IOException {
out.write(value);
out.write(value >> 8);
out.write(value >> 16);
out.write(value >> 24);
}
private static void writeMapItem(OutputStream out, int type, int count, int offset) throws IOException {
writeInt(out, type);
writeInt(out, count);
writeInt(out, offset);
}
private static byte[] stringToBytes(String s) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
writeUleb128(bytes, s.length());
// This isn't MUTF-8, but should be OK.
bytes.write(s.getBytes("UTF-8"));
bytes.write(0);
return bytes.toByteArray();
}
private DexCreator() {}
}

View File

@ -24,9 +24,9 @@ import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
import static io.github.lsposed.lspd.nativebridge.PendingHooks.recordPendingMethodNative;
public final class PendingHooks {
@ -38,7 +38,7 @@ public final class PendingHooks {
public synchronized static void hookPendingMethod(Class<?> clazz) {
if (sPendingHooks.containsKey(clazz)) {
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
hookMethodNative(hook.getKey(), clazz, 0, hook.getValue());
LSPdConfigGlobal.getHookProvider().hookMethod(hook.getKey(), hook.getValue());
}
sPendingHooks.remove(clazz);
}
@ -47,13 +47,7 @@ public final class PendingHooks {
public synchronized static void recordPendingMethod(Method hookMethod,
XposedBridge.AdditionalHookInfo additionalInfo) {
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(),
new Function<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>() {
@Override
public ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> apply(Class<?> aClass) {
return new ConcurrentHashMap<>();
}
});
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
pending.put(hookMethod, additionalInfo);
recordPendingMethodNative(hookMethod, hookMethod.getDeclaringClass());

View File

@ -20,12 +20,6 @@
package de.robv.android.xposed;
import android.os.SELinux;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import de.robv.android.xposed.services.BaseService;
import de.robv.android.xposed.services.DirectAccessService;
@ -41,7 +35,8 @@ public final class SELinuxHelper {
* @return A boolean indicating whether SELinux is enabled.
*/
public static boolean isSELinuxEnabled() {
return sIsSELinuxEnabled;
// lsp: always enabled
return true;
}
/**
@ -50,36 +45,8 @@ public final class SELinuxHelper {
* @return A boolean indicating whether SELinux is enforcing.
*/
public static boolean isSELinuxEnforced() {
if (!sIsSELinuxEnabled) {
return false;
}
boolean result = false;
final File SELINUX_STATUS_FILE = new File("/sys/fs/selinux/enforce");
if (SELINUX_STATUS_FILE.exists()) {
try {
FileInputStream fis = new FileInputStream(SELINUX_STATUS_FILE);
int status = fis.read();
switch (status) {
case 49:
result = true;
break;
case 48:
result = false;
break;
default:
XposedBridge.log("Unexpected byte " + status + " in /sys/fs/selinux/enforce");
}
fis.close();
} catch (IOException e) {
if (e.getMessage().contains("Permission denied")) {
result = true;
} else {
XposedBridge.log("Failed to read SELinux status: " + e.getMessage());
result = false;
}
}
}
return result;
// lsp: always enforcing
return true;
}
/**
@ -88,7 +55,7 @@ public final class SELinuxHelper {
* @return A String representing the security context of the current process.
*/
public static String getContext() {
return sIsSELinuxEnabled ? SELinux.getContext() : null;
return null;
}
/**
@ -100,36 +67,9 @@ public final class SELinuxHelper {
* @return An instance of the service.
*/
public static BaseService getAppDataFileService() {
if (sServiceAppDataFile != null)
return sServiceAppDataFile;
throw new UnsupportedOperationException();
}
return sServiceAppDataFile;
}
private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
// ----------------------------------------------------------------------------
// TODO: SELinux status
private static boolean sIsSELinuxEnabled = false;
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
/*package*/ public static void initOnce() {
// ed: we assume all selinux policies have been added lively using magiskpolicy
try {
sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
} catch (NoClassDefFoundError ignored) {}
}
/*package*/ static void initForProcess(String packageName) {
// ed: sServiceAppDataFile has been initialized with default value
// if (sIsSELinuxEnabled) {
// if (packageName == null) { // Zygote
// sServiceAppDataFile = new ZygoteService();
// } else if (packageName.equals("android")) { //system_server
// sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
// } else { // app
// sServiceAppDataFile = new DirectAccessService();
// }
// } else {
// sServiceAppDataFile = new DirectAccessService();
// }
}
}

View File

@ -41,18 +41,15 @@ import java.util.Map;
import java.util.Set;
import dalvik.system.InMemoryDexClassLoader;
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback;
import external.com.android.dx.DexMaker;
import external.com.android.dx.TypeId;
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
import static de.robv.android.xposed.XposedHelpers.getIntField;
import static de.robv.android.xposed.XposedHelpers.setObjectField;
/**
@ -78,10 +75,6 @@ public final class XposedBridge {
/*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
private static int runtime = 2; // ed: only support art
private static final int RUNTIME_DALVIK = 1;
private static final int RUNTIME_ART = 2;
// This field is set "magically" on MIUI.
/*package*/ static long BOOT_START_TIME;
@ -95,14 +88,6 @@ public final class XposedBridge {
private XposedBridge() {}
/** @hide */
// protected static final class ToolEntryPoint {
// protected static void main(String[] args) {
// isZygote = false;
// XposedBridge.main(args);
// }
// }
public static volatile ClassLoader dummyClassLoader = null;
@ApiSensitive(Level.MIDDLE)
@ -143,15 +128,6 @@ public final class XposedBridge {
}
}
// private static boolean hadInitErrors() {
// // ed: assuming never had errors
// return false;
// }
// private static native int getRuntime();
// /*package*/ static native boolean startsSystemServer();
// /*package*/ static native String getStartClassName();
// /*package*/ native static boolean initXResourcesNative();
/**
* Returns the currently installed version of the Xposed framework.
*/
@ -227,28 +203,10 @@ public final class XposedBridge {
callbacks.add(callback);
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART) {
slot = 0;
parameterTypes = null;
returnType = null;
} else if (hookMethod instanceof Method) {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Method) hookMethod).getParameterTypes();
returnType = ((Method) hookMethod).getReturnType();
} else {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
returnType = null;
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
if (reflectMethod != null) {
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
LSPdConfigGlobal.getHookProvider().hookMethod(reflectMethod, (AdditionalHookInfo) additionalInfo);
} else {
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
}
@ -312,86 +270,6 @@ public final class XposedBridge {
return unhooks;
}
/**
* This method is called as a replacement for hooked methods.
*/
public static Object handleHookedMethod(Member method, long originalMethodId, Object additionalInfoObj,
Object thisObject, Object[] args) throws Throwable {
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
MethodHookParam param = new MethodHookParam();
param.method = method;
param.thisObject = thisObject;
param.args = args;
// call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
}
// call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);
// return
if (param.hasThrowable())
throw param.getThrowable();
else
return param.getResult();
}
/**
* Adds a callback to be executed when an app ("Android package") is loaded.
*
@ -428,51 +306,12 @@ public final class XposedBridge {
}
}
public static void clearInitPackageResources() {
synchronized (sInitPackageResourcesCallbacks) {
sInitPackageResourcesCallbacks.clear();
}
}
public static void hookInitZygote(XC_InitZygote callback) {
synchronized (sInitZygoteCallbacks) {
sInitZygoteCallbacks.add(callback);
}
}
public static void clearInitZygotes() {
synchronized (sInitZygoteCallbacks) {
sInitZygoteCallbacks.clear();
}
}
public static void callInitZygotes() {
XCallback.callAll(new IXposedHookZygoteInit.StartupParam(sInitZygoteCallbacks));
}
public static void clearAllCallbacks() {
clearLoadedPackages();
clearInitPackageResources();
clearInitZygotes();
}
/**
* Intercept every call to the specified method and call a handler function instead.
* @param method The method to intercept
*/
/*package*/ synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
int slot, final Object additionalInfoObj) {
LSPdConfigGlobal.getHookProvider().hookMethod(method, (AdditionalHookInfo) additionalInfoObj);
}
private static Object invokeOriginalMethodNative(Member method, long methodId,
Class<?>[] parameterTypes,
Class<?> returnType,
Object thisObject, Object[] args)
throws Throwable {
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
}
/**
* Basically the same as {@link Method#invoke}, but calls the original method
* as it was before the interception by Xposed. Also, access permissions are not checked.
@ -504,34 +343,18 @@ public final class XposedBridge {
args = EMPTY_ARRAY;
}
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART && (method instanceof Method || method instanceof Constructor)) {
parameterTypes = null;
returnType = null;
} else if (method instanceof Method) {
parameterTypes = ((Method) method).getParameterTypes();
returnType = ((Method) method).getReturnType();
} else if (method instanceof Constructor) {
parameterTypes = ((Constructor<?>) method).getParameterTypes();
returnType = null;
} else {
if (!(method instanceof Method) && !(method instanceof Constructor)) {
throw new IllegalArgumentException("method must be of type Method or Constructor");
}
long methodId = LSPdConfigGlobal.getHookProvider().getMethodId(method);
return invokeOriginalMethodNative(method, methodId, parameterTypes, returnType, thisObject, args);
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
}
private static void removeFinalFlagNative(Class clazz) {
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
}
// /*package*/ static native void closeFilesBeforeForkNative();
// /*package*/ static native void reopenFilesAfterForkNative();
//
// /*package*/ static native void invalidateCallersNative(Member[] methods);
/** @hide */
public static final class CopyOnWriteSortedSet<E> {
private transient volatile Object[] elements = EMPTY_ARRAY;
@ -582,13 +405,9 @@ public final class XposedBridge {
public static class AdditionalHookInfo {
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
public final Class<?>[] parameterTypes;
public final Class<?> returnType;
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) {
this.callbacks = callbacks;
this.parameterTypes = parameterTypes;
this.returnType = returnType;
}
}
}

View File

@ -1645,70 +1645,6 @@ public final class XposedHelpers {
}
}
/*package*/ static boolean fileContains(File file, String str) throws IOException {
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
// but the naive approach should be sufficient here.
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
if (line.contains(str)) {
return true;
}
}
return false;
} finally {
closeSilently(in);
}
}
//#################################################################################################
/**
* Returns the method that is overridden by the given method.
* It returns {@code null} if the method doesn't override another method or if that method is
* abstract, i.e. if this is the first implementation in the hierarchy.
*/
/*package*/ static Method getOverriddenMethod(Method method) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) {
return null;
}
String name = method.getName();
Class<?>[] parameters = method.getParameterTypes();
Class<?> clazz = method.getDeclaringClass().getSuperclass();
while (clazz != null) {
try {
Method superMethod = clazz.getDeclaredMethod(name, parameters);
modifiers = superMethod.getModifiers();
if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) {
return superMethod;
} else {
return null;
}
} catch (NoSuchMethodException ignored) {
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* Returns all methods which this class overrides.
*/
/*package*/ static Set<Method> getOverriddenMethods(Class<?> clazz) {
Set<Method> methods = new HashSet<>();
for (Method method : clazz.getDeclaredMethods()) {
Method overridden = getOverriddenMethod(method);
if (overridden != null) {
methods.add(overridden);
}
}
return methods;
}
//#################################################################################################
// TODO helpers for view traversing
/*To make it easier, I will try and implement some more helpers:

View File

@ -20,10 +20,10 @@
package de.robv.android.xposed;
import android.app.ActivityThread;
import android.app.AndroidAppHelper;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.TypedArray;
import android.content.res.XResources;
import android.os.Build;
@ -34,10 +34,10 @@ import android.util.Log;
import com.android.internal.os.ZygoteInit;
import hidden.HiddenApiBridge;
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -48,21 +48,18 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback;
import io.github.lsposed.lspd.nativebridge.NativeAPI;
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
@ -95,7 +92,7 @@ public final class XposedInit {
* Hook some methods which we want to create an easier interface for developers.
*/
/*package*/
public static void initForZygote(boolean isSystem) throws Throwable {
public static void initForZygote() throws Throwable {
// TODO Are these still needed for us?
// MIUI
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
@ -147,11 +144,7 @@ public final class XposedInit {
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
final String createResourceMethod;
if (Build.VERSION.SDK_INT <= 18) {
classGTLR = ActivityThread.class;
classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
createResourceMethod = "getOrCreateResources";
} else if (Build.VERSION.SDK_INT < 30) {
if (Build.VERSION.SDK_INT < 30) {
classGTLR = Class.forName("android.app.ResourcesManager");
classResKey = Class.forName("android.content.res.ResourcesKey");
createResourceMethod = "getOrCreateResources";
@ -161,106 +154,32 @@ public final class XposedInit {
createResourceMethod = "createResources";
}
if (Build.VERSION.SDK_INT >= 24) {
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
XResources newRes = cloneToXResources(param, resDir);
if (newRes == null) {
return;
}
Object activityToken = param.args[activityTokenIdx];
synchronized (param.thisObject) {
ArrayList<WeakReference<Resources>> resourceReferences;
if (activityToken != null) {
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
} else {
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
}
resourceReferences.add(new WeakReference(newRes));
}
}
});
} else {
hookAllConstructors(classResKey, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
latestResKey.set(param.thisObject);
}
});
hookAllMethods(classGTLR, "getTopLevelResources", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
latestResKey.set(null);
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
XResources newRes = cloneToXResources(param, resDir);
if (newRes == null) {
return;
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object key = latestResKey.get();
if (key == null) {
return;
}
latestResKey.set(null);
String resDir = (String) getObjectField(key, "mResDir");
XResources newRes = cloneToXResources(param, resDir);
if (newRes == null) {
return;
}
@SuppressWarnings("unchecked")
Map<Object, WeakReference<Resources>> mActiveResources =
(Map<Object, WeakReference<Resources>>) getObjectField(param.thisObject, "mActiveResources");
Object lockObject = (Build.VERSION.SDK_INT <= 18)
? getObjectField(param.thisObject, "mPackages") : param.thisObject;
synchronized (lockObject) {
WeakReference<Resources> existing = mActiveResources.put(key, new WeakReference<Resources>(newRes));
if (existing != null && existing.get() != null && existing.get().getAssets() != newRes.getAssets()) {
existing.get().getAssets().close();
}
Object activityToken = param.args[activityTokenIdx];
synchronized (param.thisObject) {
ArrayList<WeakReference<Resources>> resourceReferences;
if (activityToken != null) {
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
} else {
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
}
resourceReferences.add(new WeakReference(newRes));
}
});
if (Build.VERSION.SDK_INT >= 19) {
// This method exists only on CM-based ROMs
hookAllMethods(classGTLR, "getTopLevelThemedResources", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String resDir = (String) param.args[0];
cloneToXResources(param, resDir);
}
});
}
}
// Invalidate callers of methods overridden by XTypedArray
// if (Build.VERSION.SDK_INT >= 24) {
// Set<Method> methods = getOverriddenMethods(XResources.XTypedArray.class);
// XposedBridge.invalidateCallersNative(methods.toArray(new Member[methods.size()]));
// }
// Replace TypedArrays with XTypedArrays
// hookAllConstructors(TypedArray.class, new XC_MethodHook() {
// @Override
// protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// TypedArray typedArray = (TypedArray) param.thisObject;
// Resources res = typedArray.getResources();
// if (res instanceof XResources) {
// XResources.XTypedArray newTypedArray = new XResources.XTypedArray(res);
// XposedBridge.setObjectClass(typedArray, XResources.XTypedArray.class);
// }
// }
// });
});
findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class,
new XC_MethodHook() {
@ -286,8 +205,7 @@ public final class XposedInit {
// Replace system resources
XResources systemRes = new XResources(
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
XposedHelpers.callMethod(systemRes, "setImpl", XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
//systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
HiddenApiBridge.Resources_setImpl(systemRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
systemRes.initObject(null);
setStaticObjectField(Resources.class, "mSystem", systemRes);
@ -305,8 +223,7 @@ public final class XposedInit {
// Replace the returned resources with our subclass.
XResources newRes = new XResources(
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
XposedHelpers.callMethod(newRes, "setImpl", XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
//newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
HiddenApiBridge.Resources_setImpl(newRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
newRes.initObject(resDir);
// Invoke handleInitPackageResources().
@ -322,11 +239,6 @@ public final class XposedInit {
return newRes;
}
private static boolean needsToCloseFilesForFork() {
// ed: we always start to do our work after forking finishes
return false;
}
/**
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
*/
@ -457,34 +369,25 @@ public final class XposedInit {
}
final Object moduleInstance = moduleClass.newInstance();
if (XposedBridge.isZygote) {
if (moduleInstance instanceof IXposedHookZygoteInit) {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
if (moduleInstance instanceof IXposedHookZygoteInit) {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
if (callInitZygote) {
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
(IXposedHookLoadPackage) moduleInstance, apk));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
(IXposedHookInitPackageResources) moduleInstance, apk));
} else {
if (moduleInstance instanceof IXposedHookCmdInit) {
IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
param.modulePath = apk;
param.startClassName = startClassName;
((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
if (callInitZygote) {
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
(IXposedHookLoadPackage) moduleInstance, apk));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
(IXposedHookInitPackageResources) moduleInstance, apk));
} catch (Throwable t) {
Log.e(TAG, " Failed to load class " + moduleClassName, t);
return false;

View File

@ -1,29 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
/**
* Contains the base classes for callbacks.
*
* <p>For historical reasons, {@link de.robv.android.xposed.XC_MethodHook} and
* {@link de.robv.android.xposed.XC_MethodReplacement} are directly in the
* {@code de.robv.android.xposed} package.
*/
package de.robv.android.xposed.callbacks;

View File

@ -1,24 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
/**
* Contains the main classes of the Xposed framework.
*/
package de.robv.android.xposed;

View File

@ -1,186 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed.services;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import java.io.IOException;
/** @hide */
public final class BinderService extends BaseService {
public static final int TARGET_APP = 0;
public static final int TARGET_SYSTEM = 1;
/**
* Retrieve the binder service running in the specified context.
* @param target Either {@link #TARGET_APP} or {@link #TARGET_SYSTEM}.
* @return A reference to the service.
* @throws IllegalStateException In case the service doesn't exist (should never happen).
*/
public static BinderService getService(int target) {
if (target < 0 || target > sServices.length) {
throw new IllegalArgumentException("Invalid service target " + target);
}
synchronized (sServices) {
if (sServices[target] == null) {
sServices[target] = new BinderService(target);
}
return sServices[target];
}
}
@Override
public boolean checkFileAccess(String filename, int mode) {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
data.writeInt(mode);
try {
mRemote.transact(ACCESS_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
return false;
}
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result == 0;
}
@Override
public FileResult statFile(String filename) throws IOException {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
try {
mRemote.transact(STAT_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
throw new IOException(e);
}
reply.readException();
int errno = reply.readInt();
if (errno != 0)
throwCommonIOException(errno, null, filename, " while retrieving attributes for ");
long size = reply.readLong();
long time = reply.readLong();
reply.recycle();
data.recycle();
return new FileResult(size, time);
}
@Override
public byte[] readFile(String filename) throws IOException {
return readFile(filename, 0, 0, 0, 0).content;
}
@Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
return readFile(filename, 0, 0, previousSize, previousTime);
}
@Override
public FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
data.writeInt(offset);
data.writeInt(length);
data.writeLong(previousSize);
data.writeLong(previousTime);
try {
mRemote.transact(READ_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
throw new IOException(e);
}
reply.readException();
int errno = reply.readInt();
String errorMsg = reply.readString();
long size = reply.readLong();
long time = reply.readLong();
byte[] content = reply.createByteArray();
reply.recycle();
data.recycle();
switch (errno) {
case 0:
return new FileResult(content, size, time);
case 22: // EINVAL
if (errorMsg != null) {
IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (offset == 0 && length == 0)
throw new IOException(iae);
else
throw iae;
} else {
throw new IllegalArgumentException("Offset " + offset + " / Length " + length
+ " is out of range for " + filename + " with size " + size);
}
default:
throwCommonIOException(errno, errorMsg, filename, " while reading ");
throw new IllegalStateException(); // not reached
}
}
// ----------------------------------------------------------------------------
private static final String INTERFACE_TOKEN = "de.robv.android.xposed.IXposedService";
private static final int ACCESS_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
private static final int STAT_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
private static final int READ_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
private static final String[] SERVICE_NAMES = { "user.xposed.app", "user.xposed.system" };
private static final BinderService[] sServices = new BinderService[2];
private final IBinder mRemote;
private BinderService(int target) {
IBinder binder = ServiceManager.getService(SERVICE_NAMES[target]);
if (binder == null)
throw new IllegalStateException("Service " + SERVICE_NAMES[target] + " does not exist");
this.mRemote = binder;
}
}

View File

@ -1,74 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed.services;
import java.io.IOException;
import java.util.Arrays;
/** @hide */
@SuppressWarnings("JniMissingFunction")
public final class ZygoteService extends BaseService {
@Override
public native boolean checkFileAccess(String filename, int mode);
@Override
public native FileResult statFile(String filename) throws IOException;
@Override
public native byte[] readFile(String filename) throws IOException;
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
return new FileResult(readFile(filename), stat.size, stat.mtime);
}
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), stat.size, stat.mtime);
// Check range
if (offset > 0 && offset >= stat.size) {
throw new IllegalArgumentException("offset " + offset + " >= size " + stat.size + " for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > stat.size) {
throw new IllegalArgumentException("offset " + offset + " + length " + length + " > size " + stat.size + " for " + filename);
} else if (length <= 0) {
length = (int) (stat.size - offset);
}
byte[] content = readFile(filename);
return new FileResult(Arrays.copyOfRange(content, offset, offset + length), stat.size, stat.mtime);
}
}

View File

@ -1,24 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
/**
* Contains file access services provided by the Xposed framework.
*/
package de.robv.android.xposed.services;

View File

@ -18,7 +18,7 @@
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed.annotation;
package io.github.lsposed.lspd.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -18,7 +18,7 @@
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed.annotation;
package io.github.lsposed.lspd.annotation;
public enum Level {
LOW, MIDDLE, HIGH;

View File

@ -22,8 +22,8 @@ package io.github.lsposed.lspd.deopt;
import java.util.HashMap;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
/**
* Providing a whitelist of methods which are the callers of the target methods we want to hook.

View File

@ -37,8 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
public abstract class BaseRouter implements Router {
@ -60,7 +60,7 @@ public abstract class BaseRouter implements Router {
return;
}
startBootstrapHook(isSystem, appDataDir);
XposedInit.initForZygote(isSystem);
XposedInit.initForZygote();
} catch (Throwable t) {
Utils.logE("error during Xposed initialization", t);
}

View File

@ -28,8 +28,6 @@ import io.github.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
import io.github.lsposed.lspd.util.Utils;
import de.robv.android.xposed.SELinuxHelper;
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
public class NormalProxy extends BaseProxy {
@ -51,7 +49,6 @@ public class NormalProxy extends BaseProxy {
private void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
// init logger
ModuleLogger.initLogger(serviceClient.getModuleLogger());
SELinuxHelper.initOnce();
mRouter.initResourcesHook();
mRouter.prepare(isSystem);
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote

View File

@ -35,8 +35,8 @@ import dalvik.system.PathClassLoader;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
@ApiSensitive(Level.LOW)
public class ClassLoaderUtils {

View File

@ -27,8 +27,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
public class ClassUtils {

View File

@ -28,8 +28,8 @@ import java.lang.reflect.Field;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexClassLoader;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
/**
* For 6.0 only.

View File

@ -28,8 +28,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
@ApiSensitive(Level.LOW)
public class ProcessUtils {

View File

@ -25,8 +25,8 @@ import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
@ApiSensitive(Level.LOW)
public final class Unsafe {

View File

@ -25,8 +25,8 @@ import android.util.Log;
import io.github.lsposed.lspd.BuildConfig;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.annotation.ApiSensitive;
import de.robv.android.xposed.annotation.Level;
import io.github.lsposed.lspd.annotation.ApiSensitive;
import io.github.lsposed.lspd.annotation.Level;
public class Utils {

View File

@ -20,6 +20,8 @@
package hidden;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.os.Binder;
import android.os.IBinder;
@ -31,4 +33,8 @@ public class HiddenApiBridge {
public static IBinder Binder_allowBlocking(IBinder binder) {
return Binder.allowBlocking(binder);
}
public static void Resources_setImpl(Resources resources, ResourcesImpl impl) {
resources.setImpl(impl);
}
}

View File

@ -10,4 +10,9 @@ public class Resources {
public Resources(ClassLoader classLoader) {
throw new UnsupportedOperationException("STUB");
}
public void setImpl(ResourcesImpl impl) {
throw new UnsupportedOperationException("STUB");
}
}

View File

@ -1,15 +0,0 @@
package android.os;
public class SELinux {
public static final String getContext() {
throw new UnsupportedOperationException("STUB");
}
public static final boolean isSELinuxEnabled() {
throw new UnsupportedOperationException("STUB");
}
public static final boolean isSELinuxEnforced() {
throw new UnsupportedOperationException("STUB");
}
}