Commit 3615f339 authored by swift_gan's avatar swift_gan

done compat for xposed

parent 8e0774b6
......@@ -27,4 +27,5 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':hooklib')
implementation project(':xposedcompat')
}
package com.swift.sandhook;
import android.app.Activity;
import android.app.Application;
import android.util.Log;
import com.swift.sandhook.testHookers.ActivityHooker;
import com.swift.sandhook.testHookers.CtrHook;
......@@ -9,13 +11,17 @@ import com.swift.sandhook.testHookers.JniHooker;
import com.swift.sandhook.testHookers.LogHooker;
import com.swift.sandhook.testHookers.ObjectHooker;
import com.swift.sandhook.wrapper.HookErrorException;
import com.swift.sandhook.xposedcompat.XposedCompat;
import dalvik.system.DexClassLoader;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
try {
SandHook.addHookClass(JniHooker.class,
CtrHook.class,
......@@ -27,6 +33,26 @@ public class MyApp extends Application {
e.printStackTrace();
}
//setup for xposed
XposedCompat.cacheDir = getCacheDir();
XposedCompat.context = this;
XposedCompat.classLoader = getClassLoader();
XposedCompat.isFirstApplication= true;
XposedHelpers.findAndHookMethod(Activity.class, "onResume", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.e("XposedCompat", "beforeHookedMethod: " + param.method.getName());
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Log.e("XposedCompat", "afterHookedMethod: " + param.method.getName());
}
});
try {
ClassLoader classLoader = getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/hookers-debug.apk",
......
......@@ -10,9 +10,13 @@ import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SandHook {
static Map<Member,HookWrapper.HookEntity> globalHookEntityMap = new ConcurrentHashMap<>();
public static Class artMethodClass;
public static Field nativePeerField;
......@@ -55,6 +59,15 @@ public class SandHook {
HookWrapper.addHookClass(classLoader, hookWrapperClass);
}
public static void hook(HookWrapper.HookEntity entity) throws HookErrorException {
if (entity.target != null && entity.hook != null) {
if (!SandHook.hook(entity.target, entity.hook, entity.backup)) {
throw new HookErrorException("hook method <" + entity.target.getName() + "> error in native!");
}
globalHookEntityMap.put(entity.target, entity);
}
}
public static boolean hook(Member target, Method hook, Method backup) {
if (target == null || hook == null)
return false;
......
......@@ -21,8 +21,6 @@ import java.util.concurrent.ConcurrentHashMap;
public class HookWrapper {
static Map<Member,HookEntity> globalHookEntityMap = new ConcurrentHashMap<>();
public static void addHookClass(Class<?>... classes) throws HookErrorException {
addHookClass(null, classes);
}
......@@ -40,12 +38,7 @@ public class HookWrapper {
Map<Member,HookEntity> hookEntityMap = getHookMethods(classLoader, targetHookClass, clazz);
fillBackupMethod(classLoader, clazz, hookEntityMap);
for (HookEntity entity:hookEntityMap.values()) {
if (entity.target != null && entity.hook != null) {
if (!SandHook.hook(entity.target, entity.hook, entity.backup)) {
throw new HookErrorException("hook method <" + entity.target.getName() + "> error in native!");
}
globalHookEntityMap.put(entity.target, entity);
}
SandHook.hook(entity);
}
}
......@@ -285,6 +278,11 @@ public class HookWrapper {
this.target = target;
}
public HookEntity(Member target, Method hook, Method backup) {
this.target = target;
this.hook = hook;
this.backup = backup;
}
public boolean isCtor() {
return target instanceof Constructor;
......
......@@ -25,6 +25,7 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.linkedin.dexmaker:dexmaker:2.21.0'
compileOnly project(':hooklib')
}
package com.swift.sandhook.xposedcompat;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Created by swift_gan on 2017/11/23.
*/
public class ProcessUtils {
private static volatile String processName = null;
public static String getProcessName(Context context) {
if (!TextUtils.isEmpty(processName))
return processName;
processName = doGetProcessName(context);
return processName;
}
private static String doGetProcessName(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo proInfo : runningApps) {
if (proInfo.pid == android.os.Process.myPid()) {
if (proInfo.processName != null) {
return proInfo.processName;
}
}
}
return context.getPackageName();
}
public static boolean isMainProcess(Context context) {
String processName = getProcessName(context);
String pkgName = context.getPackageName();
if (!TextUtils.isEmpty(processName) && !TextUtils.equals(processName, pkgName)) {
return false;
} else {
return true;
}
}
public static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
final PackageManager packageManager = context.getPackageManager();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setPackage(packageName);
final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
return apps != null ? apps : new ArrayList<ResolveInfo>();
}
}
package com.swift.sandhook.xposedcompat;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import java.io.File;
public class XposedCompat {
public static File cacheDir;
public static Context context;
public static ClassLoader classLoader;
public static boolean isFirstApplication;
}
package com.swift.sandhook.xposedcompat.methodgen;
import android.util.Log;
import com.swift.sandhook.xposedcompat.BuildConfig;
public class DexLog {
public static final String TAG = "SandXposed-dexmaker";
public static int v(String s) {
return Log.v(TAG, s);
}
public static int i(String s) {
return Log.i(TAG, s);
}
public static int d(String s) {
if (BuildConfig.DEBUG) {
return Log.d(TAG, s);
}
return 0;
}
public static int w(String s) {
return Log.w(TAG, s);
}
public static int e(String s) {
return Log.e(TAG, s);
}
public static int e(String s, Throwable t) {
return Log.e(TAG, s, t);
}
}
......@@ -225,6 +225,7 @@ public class DexMakerUtils {
if (addInstMethod == null) {
try {
addInstMethod = Code.class.getDeclaredMethod("addInstruction", Insn.class);
addInstMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
......@@ -240,6 +241,7 @@ public class DexMakerUtils {
if (specMethod == null) {
try {
specMethod = Local.class.getDeclaredMethod("spec");
specMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
......
package com.swift.sandhook.xposedcompat.methodgen;
import com.swift.sandhook.xposedcompat.XposedCompat;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import de.robv.android.xposed.XposedBridge;
public final class DynamicBridge {
private static final HashMap<Member, Method> hookedInfo = new HashMap<>();
private static final HookerDexMaker dexMaker = new HookerDexMaker();
private static final AtomicBoolean dexPathInited = new AtomicBoolean(false);
private static File dexDir;
private static File dexOptDir;
public static void onForkPost() {
dexPathInited.set(false);
}
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
if (!checkMember(hookMethod)) {
return;
}
if (hookedInfo.containsKey(hookMethod)) {
DexLog.w("already hook method:" + hookMethod.toString());
return;
}
DexLog.d("start to generate class for: " + hookMethod);
try {
// using file based DexClassLoader
if (dexPathInited.compareAndSet(false, true)) {
// delete previous compiled dex to prevent potential crashing
// TODO find a way to reuse them in consideration of performance
try {
// we always choose to use device encrypted storage data on android N and later
// in case some app is installing hooks before phone is unlocked
String fixedAppDataDir = XposedCompat.cacheDir.getAbsolutePath();
dexDir = new File(fixedAppDataDir, "/sandxposed/");
dexOptDir = new File(dexDir, "oat");
dexDir.mkdirs();
try {
FileUtils.delete(dexOptDir);
} catch (Throwable throwable) {
}
} catch (Throwable throwable) {
DexLog.e("error when init dex path", throwable);
}
}
dexMaker.start(hookMethod, additionalHookInfo,
hookMethod.getDeclaringClass().getClassLoader(), dexDir == null ? null : dexDir.getAbsolutePath());
hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod());
} catch (Exception e) {
DexLog.e("error occur when generating dex. dexDir=" + dexDir, e);
}
}
private static boolean checkMember(Member member) {
if (member instanceof Method) {
return true;
} else if (member instanceof Constructor<?>) {
return true;
} else if (member.getDeclaringClass().isInterface()) {
DexLog.e("Cannot hook interfaces: " + member.toString());
return false;
} else if (Modifier.isAbstract(member.getModifiers())) {
DexLog.e("Cannot hook abstract methods: " + member.toString());
return false;
} else {
DexLog.e("Only methods and constructors can be hooked: " + member.toString());
return false;
}
}
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
throws InvocationTargetException, IllegalAccessException {
Method callBackup = hookedInfo.get(method);
if (callBackup == null) {
throw new IllegalStateException("method not hooked, cannot call original method.");
}
if (!Modifier.isStatic(callBackup.getModifiers())) {
throw new IllegalStateException("original method is not static, something must be wrong!");
}
callBackup.setAccessible(true);
if (args == null) {
args = new Object[0];
}
final int argsSize = args.length;
if (Modifier.isStatic(method.getModifiers())) {
return callBackup.invoke(null, args);
} else {
Object[] newArgs = new Object[argsSize + 1];
newArgs[0] = thisObject;
for (int i = 1; i < newArgs.length; i++) {
newArgs[i] = args[i - 1];
}
return callBackup.invoke(null, newArgs);
}
}
}
package com.swift.sandhook.xposedcompat.methodgen;
import android.os.Build;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileUtils {
public static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
/**
* Delete a file or a directory and its children.
*
* @param file The directory to delete.
* @throws IOException Exception when problem occurs during deleting the directory.
*/
public static void delete(File file) throws IOException {
for (File childFile : file.listFiles()) {
if (childFile.isDirectory()) {
delete(childFile);
} else {
if (!childFile.delete()) {
throw new IOException();
}
}
}
if (!file.delete()) {
throw new IOException();
}
}
public static String readLine(File file) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return reader.readLine();
} catch (Throwable throwable) {
return "";
}
}
public static void writeLine(File file, String line) {
try {
file.createNewFile();
} catch (IOException ex) {
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(line);
writer.flush();
} catch (Throwable throwable) {
DexLog.e("error writing line to file " + file + ": " + throwable.getMessage());
}
}
public static String getPackageName(String dataDir) {
if (TextUtils.isEmpty(dataDir)) {
DexLog.e("getPackageName using empty dataDir");
return "";
}
int lastIndex = dataDir.lastIndexOf("/");
if (lastIndex < 0) {
return dataDir;
}
return dataDir.substring(lastIndex + 1);
}
public static String getDataPathPrefix() {
return IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data/";
}
}
......@@ -11,6 +11,8 @@ import com.android.dx.Label;
import com.android.dx.Local;
import com.android.dx.MethodId;
import com.android.dx.TypeId;
import com.swift.sandhook.SandHook;
import com.swift.sandhook.wrapper.HookWrapper;
import java.io.File;
import java.lang.reflect.Constructor;
......@@ -37,7 +39,7 @@ public class HookerDexMaker {
public static final String METHOD_NAME_SETUP = "setup";
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
private static final String CLASS_DESC_PREFIX = "L";
private static final String CLASS_NAME_PREFIX = "EdHooker";
private static final String CLASS_NAME_PREFIX = "SandHooker";
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
private static final String FIELD_NAME_METHOD = "method";
private static final String PARAMS_FIELD_NAME_METHOD = "method";
......@@ -203,7 +205,7 @@ public class HookerDexMaker {
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
//HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod);
SandHook.hook(new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod));
}
public Method getHookMethod() {
......
package com.swift.sandhook.xposedcompat.methodgen;
import android.os.Process;
import android.text.TextUtils;
//import com.elderdrivers.riru.xposed.Main;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class ProcessUtils {
public static String getCurrentProcessName() {
String prettyName = null;//Main.sAppProcessName;
if (!TextUtils.isEmpty(prettyName)) {
return prettyName;
}
return getProcessName(Process.myPid());
}
/**
* a common solution from https://stackoverflow.com/a/21389402
* <p>
* use {@link com.elderdrivers.riru.xposed.Main#sAppProcessName} to get current process name
*/
public static String getProcessName(int pid) {
BufferedReader cmdlineReader = null;
try {
cmdlineReader = new BufferedReader(new InputStreamReader(
new FileInputStream(
"/proc/" + pid + "/cmdline"),
"iso-8859-1"));
int c;
StringBuilder processName = new StringBuilder();
while ((c = cmdlineReader.read()) > 0) {
processName.append((char) c);
}
return processName.toString();
} catch (Throwable throwable) {
DexLog.w("getProcessName: " + throwable.getMessage());
} finally {
try {
if (cmdlineReader != null) {
cmdlineReader.close();
}
} catch (Throwable throwable) {
DexLog.e("getProcessName: " + throwable.getMessage());
}
}
return "";
}
public static boolean isLastPidAlive(File lastPidFile) {
String lastPidInfo = FileUtils.readLine(lastPidFile);
try {
String[] split = lastPidInfo.split(":", 2);
return checkProcessAlive(Integer.parseInt(split[0]), split[1]);
} catch (Throwable throwable) {
DexLog.w("error when check last pid " + lastPidFile + ": " + throwable.getMessage());
return false;
}
}
public static void saveLastPidInfo(File lastPidFile, int pid, String processName) {
try {
if (!lastPidFile.exists()) {
lastPidFile.getParentFile().mkdirs();
lastPidFile.createNewFile();
}
} catch (Throwable throwable) {
}
FileUtils.writeLine(lastPidFile, pid + ":" + processName);
}
public static boolean checkProcessAlive(int pid, String processName) {
String existsPrcName = getProcessName(pid);
DexLog.w("checking pid alive: " + pid + ", " + processName + ", processName=" + existsPrcName);
return existsPrcName.equals(processName);
}
}
package de.robv.android.xposed;
import android.content.res.XResources;
//import android.content.res.XResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
......
package de.robv.android.xposed;
import android.os.SELinux;
//import android.os.SELinux;
import de.robv.android.xposed.services.BaseService;
import de.robv.android.xposed.services.DirectAccessService;
......@@ -25,18 +25,18 @@ public final class SELinuxHelper {
*
* @return A boolean indicating whether SELinux is enforcing.
*/
public static boolean isSELinuxEnforced() {
return sIsSELinuxEnabled && SELinux.isSELinuxEnforced();
}
// public static boolean isSELinuxEnforced() {
// return sIsSELinuxEnabled && SELinux.isSELinuxEnforced();
// }
/**
* Gets the security context of the current process.
*
* @return A String representing the security context of the current process.
*/
public static String getContext() {
return sIsSELinuxEnabled ? SELinux.getContext() : null;
}
// public static String getContext() {
// return sIsSELinuxEnabled ? SELinux.getContext() : null;
// }
/**
* Retrieve the service to be used when accessing files in {@code /data/data/*}.
......
......@@ -7,7 +7,6 @@ import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
......@@ -121,14 +120,12 @@ public final class XSharedPreferences implements SharedPreferences {
try {
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
if (result.stream != null) {
map = XmlUtils.readMapXml(result.stream);
//map = XmlUtils.readMapXml(result.stream);
result.stream.close();
} else {
// The file is unchanged, keep the current values
map = mMap;
}
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
} catch (IOException e) {
......
......@@ -4,6 +4,8 @@ import android.annotation.SuppressLint;
import android.util.Log;
import com.swift.sandhook.xposedcompat.methodgen.DynamicBridge;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
......@@ -151,7 +153,6 @@ public final class XposedBridge {
* @see #hookAllConstructors
*/
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
hookMethod = MethodHookUtils.preCheck(hookMethod);
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
} else if (hookMethod.getDeclaringClass().isInterface()) {
......@@ -196,16 +197,7 @@ public final class XposedBridge {
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
MethodInfo methodInfo = new MethodInfo(hookMethod);
declaringClass = methodInfo.getClassForSure();
Member reflectMethod = (Member) HookMain.findMethod(
declaringClass, methodInfo.methodName, methodInfo.methodSig);
if (reflectMethod == null) {
Log.e(TAG, "method not found: name="
+ methodInfo.methodName + ", sig=" + methodInfo.methodSig);
reflectMethod = hookMethod;
}
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
......
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
//import android.content.res.XResources;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
......@@ -45,7 +45,7 @@ public abstract class XC_InitPackageResources extends XCallback implements IXpos
* Reference to the resources that can be used for calls to
* {@link XResources#setReplacement(String, String, String, Object)}.
*/
public XResources res;
//public XResources res;
}
/** @hide */
......
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
import android.content.res.XResources.ResourceNames;
//import android.content.res.XResources;
//import android.content.res.XResources.ResourceNames;
import android.view.View;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
......@@ -41,13 +41,13 @@ public abstract class XC_LayoutInflated extends XCallback {
public View view;
/** Container with the ID and name of the underlying resource. */
public ResourceNames resNames;
// public ResourceNames resNames;
/** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */
public String variant;
// public String variant;
/** Resources containing the layout. */
public XResources res;
// public XResources res;
}
/** @hide */
......@@ -92,7 +92,7 @@ public abstract class XC_LayoutInflated extends XCallback {
@Override
public void unhook() {
XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
// XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
}
}
......
......@@ -2,6 +2,9 @@ package de.robv.android.xposed.callbacks;
import android.content.pm.ApplicationInfo;
import com.swift.sandhook.xposedcompat.ProcessUtils;
import com.swift.sandhook.xposedcompat.XposedCompat;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
......@@ -39,19 +42,19 @@ public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoa
}
/** The name of the package being loaded. */
public String packageName;
public String packageName = XposedCompat.context.getPackageName();
/** The process in which the package is executed. */
public String processName;
public String processName = ProcessUtils.getProcessName(XposedCompat.context);
/** The ClassLoader used for this package. */
public ClassLoader classLoader;
public ClassLoader classLoader = XposedCompat.classLoader;
/** More information about the application being loaded. */
public ApplicationInfo appInfo;
public ApplicationInfo appInfo = XposedCompat.context.getApplicationInfo();
/** Set to {@code true} if this is the first (and main) application for this process. */
public boolean isFirstApplication;
public boolean isFirstApplication = XposedCompat.isFirstApplication;
}
/** @hide */
......
......@@ -3,7 +3,7 @@ package de.robv.android.xposed.services;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
//import android.os.ServiceManager;
import java.io.IOException;
......@@ -158,7 +158,7 @@ public final class BinderService extends BaseService {
private final IBinder mRemote;
private BinderService(int target) {
IBinder binder = ServiceManager.getService(SERVICE_NAMES[target]);
IBinder binder = null; //ServiceManager.getService(SERVICE_NAMES[target]);
if (binder == null)
throw new IllegalStateException("Service " + SERVICE_NAMES[target] + " does not exist");
this.mRemote = binder;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment