Add support for resources hooking. (1/2)

This commit is contained in:
solohsu 2019-04-21 11:46:58 +08:00
parent 1006a3a034
commit 711c589088
8 changed files with 390 additions and 135 deletions

View File

@ -203,7 +203,9 @@ static void ensureMethodCached(void *hookMethod, void *backupMethod,
int methodIndex = read32(
(void *) ((char *) backupMethod + OFFSET_dex_method_index_in_ArtMethod));
LOGI("methodIndex = %d", methodIndex);
if (methodIndex >= 512) {
LOGW("methodIndex = %d", methodIndex);
}
// update the cached method manually
// first we find the array of cached methods

View File

@ -25,6 +25,14 @@ public class Resources {
throw new UnsupportedOperationException("STUB");
}
public Resources(ClassLoader classLoader) {
throw new UnsupportedOperationException("STUB");
}
public void setImpl(ResourcesImpl impl) {
throw new UnsupportedOperationException("STUB");
}
public static Resources getSystem() {
throw new UnsupportedOperationException("STUB");
}

View File

@ -0,0 +1,4 @@
package android.content.res;
public class ResourcesImpl {
}

View File

@ -8,6 +8,10 @@ public class TypedArray {
throw new UnsupportedOperationException("STUB");
}
protected TypedArray(Resources resources) {
throw new UnsupportedOperationException("STUB");
}
protected TypedArray(Resources resources, int[] data, int[] indices, int len) {
throw new UnsupportedOperationException("STUB");
}

View File

@ -34,8 +34,6 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
import de.robv.android.xposed.callbacks.XC_LayoutInflated;
import de.robv.android.xposed.callbacks.XC_LayoutInflated.LayoutInflatedParam;
import de.robv.android.xposed.callbacks.XCallback;
import xposed.dummy.XResourcesSuperClass;
import xposed.dummy.XTypedArraySuperClass;
import static de.robv.android.xposed.XposedHelpers.decrementMethodDepth;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
@ -52,7 +50,7 @@ import static de.robv.android.xposed.XposedHelpers.incrementMethodDepth;
* be set using the methods made available via the API methods in this class.
*/
@SuppressWarnings("JniMissingFunction")
public class XResources extends XResourcesSuperClass {
public class XResources extends Resources {
private static final SparseArray<HashMap<String, Object>> sReplacements = new SparseArray<>();
private static final SparseArray<HashMap<String, ResourceNames>> sResourceNames = new SparseArray<>();
@ -80,11 +78,19 @@ public class XResources extends XResourcesSuperClass {
private String mResDir;
private String mPackageName;
/** Dummy, will never be called (objects are transferred to this class only). */
private XResources() {
throw new UnsupportedOperationException();
public XResources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
super(assets, metrics, config);
}
public XResources(ClassLoader classLoader) {
super(classLoader);
}
/** Dummy, will never be called (objects are transferred to this class only). */
// private XResources() {
// throw new UnsupportedOperationException();
// }
/** @hide */
public void initObject(String resDir) {
if (mIsObjectInited)
@ -168,7 +174,7 @@ public class XResources extends XResourcesSuperClass {
pkgInfo = PackageParser.parsePackageLite(resDir, 0);
}
if (pkgInfo != null && pkgInfo.packageName != null) {
Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser");
// Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser");
packageName = pkgInfo.packageName;
setPackageNameForResDir(packageName, resDir);
return packageName;
@ -624,28 +630,28 @@ public class XResources extends XResourcesSuperClass {
}
}
/** @hide */
@Override
public XmlResourceParser getAnimation(int id) throws NotFoundException {
Object replacement = getReplacement(id);
if (replacement instanceof XResForwarder) {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();
boolean loadedFromCache = isXmlCached(repRes, repId);
XmlResourceParser result = repRes.getAnimation(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
return result;
}
return super.getAnimation(id);
}
// /** @hide */
// @Override
// public XmlResourceParser getAnimation(int id) throws NotFoundException {
// Object replacement = getReplacement(id);
// if (replacement instanceof XResForwarder) {
// Resources repRes = ((XResForwarder) replacement).getResources();
// int repId = ((XResForwarder) replacement).getId();
//
// boolean loadedFromCache = isXmlCached(repRes, repId);
// XmlResourceParser result = repRes.getAnimation(repId);
//
// if (!loadedFromCache) {
// long parseState = (Build.VERSION.SDK_INT >= 21)
// ? getLongField(result, "mParseState")
// : getIntField(result, "mParseState");
// rewriteXmlReferencesNative(parseState, this, repRes);
// }
//
// return result;
// }
// return super.getAnimation(id);
// }
/** @hide */
@Override
@ -937,76 +943,76 @@ public class XResources extends XResourcesSuperClass {
return super.getIntArray(id);
}
/** @hide */
@Override
public XmlResourceParser getLayout(int id) throws NotFoundException {
XmlResourceParser result;
Object replacement = getReplacement(id);
if (replacement instanceof XResForwarder) {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();
boolean loadedFromCache = isXmlCached(repRes, repId);
result = repRes.getLayout(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
} else {
result = super.getLayout(id);
}
// Check whether this layout is hooked
HashMap<String, CopyOnWriteSortedSet<XC_LayoutInflated>> inner;
synchronized (sLayoutCallbacks) {
inner = sLayoutCallbacks.get(id);
}
if (inner != null) {
CopyOnWriteSortedSet<XC_LayoutInflated> callbacks;
synchronized (inner) {
callbacks = inner.get(mResDir);
if (callbacks == null && mResDir != null)
callbacks = inner.get(null);
}
if (callbacks != null) {
String variant = "layout";
TypedValue value = (TypedValue) getObjectField(this, "mTmpValue");
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
String[] components = value.string.toString().split("/", 3);
if (components.length == 3)
variant = components[1];
else
XposedBridge.log("Unexpected resource path \"" + value.string.toString()
+ "\" for resource id 0x" + Integer.toHexString(id));
} else {
XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id));
}
synchronized (sXmlInstanceDetails) {
synchronized (sResourceNames) {
HashMap<String, ResourceNames> resNamesInner = sResourceNames.get(id);
if (resNamesInner != null) {
synchronized (resNamesInner) {
XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(mResDir), variant, callbacks);
sXmlInstanceDetails.put(result, details);
// if we were called inside LayoutInflater.parseInclude, store the details for it
MethodHookParam top = sIncludedLayouts.get().peek();
if (top != null)
top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details);
}
}
}
}
}
}
return result;
}
// /** @hide */
// @Override
// public XmlResourceParser getLayout(int id) throws NotFoundException {
// XmlResourceParser result;
// Object replacement = getReplacement(id);
// if (replacement instanceof XResForwarder) {
// Resources repRes = ((XResForwarder) replacement).getResources();
// int repId = ((XResForwarder) replacement).getId();
//
// boolean loadedFromCache = isXmlCached(repRes, repId);
// result = repRes.getLayout(repId);
//
// if (!loadedFromCache) {
// long parseState = (Build.VERSION.SDK_INT >= 21)
// ? getLongField(result, "mParseState")
// : getIntField(result, "mParseState");
// rewriteXmlReferencesNative(parseState, this, repRes);
// }
// } else {
// result = super.getLayout(id);
// }
//
// // Check whether this layout is hooked
// HashMap<String, CopyOnWriteSortedSet<XC_LayoutInflated>> inner;
// synchronized (sLayoutCallbacks) {
// inner = sLayoutCallbacks.get(id);
// }
// if (inner != null) {
// CopyOnWriteSortedSet<XC_LayoutInflated> callbacks;
// synchronized (inner) {
// callbacks = inner.get(mResDir);
// if (callbacks == null && mResDir != null)
// callbacks = inner.get(null);
// }
// if (callbacks != null) {
// String variant = "layout";
// TypedValue value = (TypedValue) getObjectField(this, "mTmpValue");
// getValue(id, value, true);
// if (value.type == TypedValue.TYPE_STRING) {
// String[] components = value.string.toString().split("/", 3);
// if (components.length == 3)
// variant = components[1];
// else
// XposedBridge.log("Unexpected resource path \"" + value.string.toString()
// + "\" for resource id 0x" + Integer.toHexString(id));
// } else {
// XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id));
// }
//
// synchronized (sXmlInstanceDetails) {
// synchronized (sResourceNames) {
// HashMap<String, ResourceNames> resNamesInner = sResourceNames.get(id);
// if (resNamesInner != null) {
// synchronized (resNamesInner) {
// XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(mResDir), variant, callbacks);
// sXmlInstanceDetails.put(result, details);
//
// // if we were called inside LayoutInflater.parseInclude, store the details for it
// MethodHookParam top = sIncludedLayouts.get().peek();
// if (top != null)
// top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details);
// }
// }
// }
// }
// }
// }
//
// return result;
// }
/** @hide */
@Override
@ -1094,28 +1100,28 @@ public class XResources extends XResourcesSuperClass {
return super.getTextArray(id);
}
/** @hide */
@Override
public XmlResourceParser getXml(int id) throws NotFoundException {
Object replacement = getReplacement(id);
if (replacement instanceof XResForwarder) {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();
boolean loadedFromCache = isXmlCached(repRes, repId);
XmlResourceParser result = repRes.getXml(repId);
if (!loadedFromCache) {
long parseState = (Build.VERSION.SDK_INT >= 21)
? getLongField(result, "mParseState")
: getIntField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
return result;
}
return super.getXml(id);
}
// /** @hide */
// @Override
// public XmlResourceParser getXml(int id) throws NotFoundException {
// Object replacement = getReplacement(id);
// if (replacement instanceof XResForwarder) {
// Resources repRes = ((XResForwarder) replacement).getResources();
// int repId = ((XResForwarder) replacement).getId();
//
// boolean loadedFromCache = isXmlCached(repRes, repId);
// XmlResourceParser result = repRes.getXml(repId);
//
// if (!loadedFromCache) {
// long parseState = (Build.VERSION.SDK_INT >= 21)
// ? getLongField(result, "mParseState")
// : getIntField(result, "mParseState");
// rewriteXmlReferencesNative(parseState, this, repRes);
// }
//
// return result;
// }
// return super.getXml(id);
// }
private static boolean isXmlCached(Resources res, int id) {
int[] mCachedXmlBlockIds = (int[]) getObjectField(res, "mCachedXmlBlockIds");
@ -1253,12 +1259,17 @@ public class XResources extends XResourcesSuperClass {
* Mainly used when inflating layouts.
* @hide
*/
public static class XTypedArray extends XTypedArraySuperClass {
/** Dummy, will never be called (objects are transferred to this class only). */
private XTypedArray() {
super(null, null, null, 0);
throw new UnsupportedOperationException();
}
public static class XTypedArray extends TypedArray {
public XTypedArray(Resources resources) {
super(resources);
}
/** Dummy, will never be called (objects are transferred to this class only). */
// private XTypedArray() {
// super(null, null, null, 0);
// throw new UnsupportedOperationException();
// }
@Override
public boolean getBoolean(int index, boolean defValue) {

View File

@ -378,10 +378,9 @@ public final class XposedBridge {
* @hide
*/
public static void hookInitPackageResources(XC_InitPackageResources callback) {
// TODO not supported yet
// synchronized (sInitPackageResourcesCallbacks) {
// sInitPackageResourcesCallbacks.add(callback);
// }
synchronized (sInitPackageResourcesCallbacks) {
sInitPackageResourcesCallbacks.add(callback);
}
}
public static void clearInitPackageResources() {

View File

@ -1,6 +1,16 @@
package de.robv.android.xposed;
import android.app.ActivityThread;
import android.app.AndroidAppHelper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.TypedArray;
import android.content.res.XResources;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@ -12,20 +22,34 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XCallback;
import de.robv.android.xposed.services.BaseService;
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.closeSilently;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static de.robv.android.xposed.XposedHelpers.findClass;
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
import static de.robv.android.xposed.XposedHelpers.getObjectField;
import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField;
import static de.robv.android.xposed.XposedHelpers.setStaticLongField;
import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
public final class XposedInit {
private static final String TAG = XposedBridge.TAG;
@ -34,7 +58,7 @@ public final class XposedInit {
private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
// TODO not supported yet
private static boolean disableResources = true;
private static boolean disableResources = false;
private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"};
private XposedInit() {
@ -58,11 +82,214 @@ public final class XposedInit {
} catch (NoSuchFieldError ignored) {
}
}
findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication",
ApplicationInfo.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
ApplicationInfo app = (ApplicationInfo) param.args[0];
XResources.setPackageNameForResDir(app.packageName,
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir);
}
});
hookResources();
}
/*package*/
static void hookResources() throws Throwable {
// ed: not for now
public static void hookResources() throws Throwable {
String BASE_DIR = EdXpConfigGlobal.getConfig().getInstallerBaseDir();
if (SELinuxHelper.getAppDataFileService().checkFileExists(BASE_DIR + "conf/disable_resources")) {
Log.w(TAG, "Found " + BASE_DIR + "conf/disable_resources, not hooking resources");
disableResources = true;
return;
}
/*
* getTopLevelResources(a)
* -> getTopLevelResources(b)
* -> key = new ResourcesKey()
* -> r = new Resources()
* -> mActiveResources.put(key, r)
* -> return r
*/
final Class<?> classGTLR;
final Class<?> classResKey;
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
if (Build.VERSION.SDK_INT <= 18) {
classGTLR = ActivityThread.class;
classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
} else {
classGTLR = Class.forName("android.app.ResourcesManager");
classResKey = Class.forName("android.content.res.ResourcesKey");
}
if (Build.VERSION.SDK_INT >= 24) {
hookAllMethods(classGTLR, "getOrCreateResources", 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);
}
@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();
}
}
}
});
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() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (param.getResult() instanceof XResources.XTypedArray) {
return;
}
if (!(param.args[0] instanceof XResources)) {
return;
}
XResources.XTypedArray newResult =
new XResources.XTypedArray((Resources) param.args[0]);
int len = (int) param.args[1];
Method resizeMethod = XposedHelpers.findMethodBestMatch(
TypedArray.class, "resize", new Class[]{int.class});
resizeMethod.setAccessible(true);
resizeMethod.invoke(newResult, len);
param.setResult(newResult);
}
});
// Replace system resources
XResources systemRes = new XResources(
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
systemRes.initObject(null);
setStaticObjectField(Resources.class, "mSystem", systemRes);
XResources.init(latestResKey);
//custom
hookAllConstructors(PackageParser.PackageParserException.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log(new Throwable());
}
});
}
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) {
Object result = param.getResult();
if (result == null || result instanceof XResources ||
Arrays.binarySearch(XRESOURCES_CONFLICTING_PACKAGES, AndroidAppHelper.currentPackageName()) == 0) {
return null;
}
// Replace the returned resources with our subclass.
XResources newRes = new XResources(
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
newRes.initObject(resDir);
// Invoke handleInitPackageResources().
if (newRes.isFirstLoad()) {
String packageName = newRes.getPackageName();
XC_InitPackageResources.InitPackageResourcesParam resparam = new XC_InitPackageResources.InitPackageResourcesParam(XposedBridge.sInitPackageResourcesCallbacks);
resparam.packageName = packageName;
resparam.res = newRes;
XCallback.callAll(resparam);
}
param.setResult(newRes);
return newRes;
}
private static boolean needsToCloseFilesForFork() {