just copy loader
This commit is contained in:
parent
64a9076931
commit
cb7b40cbd3
|
|
@ -0,0 +1,16 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
/out
|
||||
/.idea
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[submodule "mmp"]
|
||||
path = mmp
|
||||
url = https://github.com/MMPosed/MMPosed.git
|
||||
branch = mmpatch
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Readme
|
||||
LSPosed as hook framework
|
||||
|
||||
There some major change since xpatch
|
||||
|
||||
1. use LSPosed as hook framework
|
||||
1. keep loader simple, clear not nesseacry things, like bypass signature. let developer do this part
|
||||
|
||||
|
||||
`Maybe perform force push if some private data leak in project. Sorry for the confusion.`
|
||||
|
||||
# Useage
|
||||
1. You need do signature bypass by yourself
|
||||
1. Orignal signature saved to assets/original_signature_info.ini
|
||||
1. Orignal apk saved to assets/original_apk.bin
|
||||
|
||||
For example, you may need to replace signatures from getPackageInfo and redirect `/data/app/{your apk}/base.apk` to `original_apk.bin` to bypass normally signature check.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
/target
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "com.storm.wind.xposed"
|
||||
minSdkVersion 27
|
||||
targetSdkVersion 28
|
||||
versionCode version_code as Integer
|
||||
versionName version_name
|
||||
|
||||
multiDexEnabled false
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
debuggable false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
applicationVariants.all { variant ->
|
||||
def buildType = variant.name.capitalize()
|
||||
def variantLowered = variant.name.toLowerCase()
|
||||
|
||||
variant.outputs.all {
|
||||
outputFileName = "${variant.getFlavorName()}-${variant.versionName}.apk"
|
||||
}
|
||||
|
||||
task "copyDex$buildType"(type: Copy) {
|
||||
dependsOn("assemble$buildType")
|
||||
def dexFilePath = "$buildDir/intermediates/dex/${variantLowered}/mergeDex${buildType}/classes.dex"
|
||||
from dexFilePath
|
||||
rename "(.*).dex", "classes-${version_name}.dex"
|
||||
into "$rootProject.projectDir/out/list-dex"
|
||||
}
|
||||
|
||||
task "copySo$buildType"(type: Copy) {
|
||||
dependsOn("assemble$buildType")
|
||||
from "$buildDir/intermediates/merged_native_libs/${variantLowered}/out/lib"
|
||||
into "$rootProject.projectDir/out/list-so"
|
||||
}
|
||||
|
||||
task "copy$buildType"() {
|
||||
dependsOn("copySo$buildType")
|
||||
dependsOn("copyDex$buildType")
|
||||
|
||||
doLast {
|
||||
System.out.println("Dex and so files has been copy to ${rootProject.projectDir}${File.separator}out")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(':xpatchcore')
|
||||
implementation("androidx.core:core:1.3.2")
|
||||
implementation project(path: ':lspcore')
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
-keep class com.wind.xposed.entry.MMPEntry {
|
||||
public <init>();
|
||||
public void initAndLoadModules();
|
||||
}
|
||||
|
||||
-keep class com.wind.xpatch.proxy.**{*;}
|
||||
|
||||
-keep class de.robv.android.xposed.**{*;}
|
||||
|
||||
-keep class android.app.**{*;}
|
||||
-keep class android.content.**{*;}
|
||||
-keep class android.os.**{*;}
|
||||
|
||||
-keep class android.view.**{*;}
|
||||
-keep class com.lody.whale.**{*;}
|
||||
-keep class com.android.internal.**{*;}
|
||||
-keep class xposed.dummy.**{*;}
|
||||
-keep class com.wind.xposed.entry.util.**{*;}
|
||||
|
||||
-keep class com.swift.sandhook.**{*;}
|
||||
-keep class com.swift.sandhook.xposedcompat.**{*;}
|
||||
|
||||
-dontwarn android.content.res.Resources
|
||||
-dontwarn android.content.res.Resources$Theme
|
||||
-dontwarn android.content.res.AssetManager
|
||||
-dontwarn android.content.res.TypedArray
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.storm.wind.xposed">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".XposedApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/sample_app_title"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1 @@
|
|||
palceholder
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.storm.wind.xposed;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
|
||||
//import android.support.v4.app.ActivityCompat;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
private static final int REQUEST_PERMISSION_CODE = 1;
|
||||
private static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
XposedHelpers.findAndHookMethod(this.getClass(), "checkXposed", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
param.setResult(true);
|
||||
}
|
||||
});
|
||||
|
||||
TextView textView = findViewById(R.id.msg);
|
||||
if (checkXposed()) {
|
||||
textView.setText("ok");
|
||||
}
|
||||
else {
|
||||
textView.setText("fail");
|
||||
}
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
}
|
||||
|
||||
public boolean checkXposed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.storm.wind.xposed;
|
||||
|
||||
import static com.wind.xposed.entry.MMPLoader.initAndLoadModules;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
|
||||
public class XposedApplication extends Application {
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
}
|
||||
|
||||
static {
|
||||
System.loadLibrary("lspd");
|
||||
YahfaHooker.init();
|
||||
XposedInit.startsSystemServer = false;
|
||||
initAndLoadModules();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="onClick"
|
||||
android:text="Hello World!" />
|
||||
</RelativeLayout>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sample_app_title">Xposed Module Loader</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url "https://jcenter.bintray.com" }
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.0-alpha12'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
androidCompileSdkVersion = 30
|
||||
androidCompileNdkVersion = "22.1.7171670"
|
||||
androidBuildToolsVersion = "30.0.3"
|
||||
androidMinSdkVersion = 27
|
||||
androidTargetSdkVersion = 28
|
||||
verCode = 1
|
||||
verName = "mmpatch"
|
||||
apiCode = 93
|
||||
defaultManagerPackageName = "org.github.mmpatch"
|
||||
androidSourceCompatibility = JavaVersion.VERSION_11
|
||||
androidTargetCompatibility = JavaVersion.VERSION_11
|
||||
zipPathMagiskReleasePath = project(":lspcore").projectDir.path + "/build/tmp/release/magisk/"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url "https://jcenter.bintray.com" }
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.androidMinSdkVersion
|
||||
targetSdkVersion rootProject.ext.androidTargetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
debuggable false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':lspcore')
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#-keep class com.wind.xposed.entry.XposedModuleEntry {
|
||||
# public <init>();
|
||||
# public void init();
|
||||
#}
|
||||
#-keep class de.robv.android.xposed.**{*;}
|
||||
#-keep class com.swift.sandhook.**{*;}
|
||||
#-keep class com.swift.sandhook.xposedcompat.**{*;}
|
||||
#
|
||||
#-dontwarn de.robv.android.xposed.XposedHelper
|
||||
-keep class com.wind.xposed.entry.MMPEntry {
|
||||
public <init>();
|
||||
public void initAndLoadModules();
|
||||
}
|
||||
-keep class de.robv.android.xposed.**{*;}
|
||||
|
||||
-dontwarn de.robv.android.xposed.XposedHelper
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.wind.xposed.entry">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="de.robv.android.xposed" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:supportsRtl="true"></application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package android.app;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.CompatibilityInfo;
|
||||
|
||||
public final class ActivityThread {
|
||||
public static ActivityThread currentActivityThread() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public static Application currentApplication() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public static String currentPackageName() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package android.app;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
public final class LoadedApk {
|
||||
public ApplicationInfo getApplicationInfo() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public ClassLoader getClassLoader() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public String getResDir() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package android.content.res;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class CompatibilityInfo implements Parcelable {
|
||||
public static final Parcelable.Creator<CompatibilityInfo> CREATOR = null;
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
package com.wind.xposed.entry;
|
||||
|
||||
import static com.wind.xposed.entry.MMPLoader.initAndLoadModules;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.wind.xposed.entry.util.FileUtils;
|
||||
import com.wind.xposed.entry.util.ReflectionApiCheck;
|
||||
import com.wind.xposed.entry.util.XLog;
|
||||
import com.wind.xposed.entry.util.XpatchUtils;
|
||||
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
|
||||
/**
|
||||
* Created by Windysha
|
||||
*/
|
||||
public class MMPApplication extends Application {
|
||||
private static final String ORIGINAL_APPLICATION_NAME_ASSET_PATH = "original_application_name.ini";
|
||||
private static final String TAG = "XpatchProxyApplication";
|
||||
private static String originalApplicationName = null;
|
||||
private static Application sOriginalApplication = null;
|
||||
private static ClassLoader appClassLoader;
|
||||
private static Object activityThread;
|
||||
|
||||
final static public int FIRST_ISOLATED_UID = 99000;
|
||||
final static public int LAST_ISOLATED_UID = 99999;
|
||||
final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
|
||||
final static public int LAST_APP_ZYGOTE_ISOLATED_UID = 98999;
|
||||
final static public int SHARED_RELRO_UID = 1037;
|
||||
final static public int PER_USER_RANGE = 100000;
|
||||
|
||||
static public boolean isIsolated() {
|
||||
int uid = android.os.Process.myUid();
|
||||
uid = uid % PER_USER_RANGE;
|
||||
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID) || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
|
||||
}
|
||||
|
||||
static {
|
||||
ReflectionApiCheck.unseal();
|
||||
|
||||
System.loadLibrary("lspd");
|
||||
YahfaHooker.init();
|
||||
XposedInit.startsSystemServer = false;
|
||||
|
||||
Context context = XpatchUtils.createAppContext();
|
||||
originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH);
|
||||
XLog.d(TAG, "original application name " + originalApplicationName);
|
||||
|
||||
if (isIsolated()) {
|
||||
XLog.d(TAG, "skip isolated process");
|
||||
}
|
||||
else {
|
||||
if (isApplicationProxied()) {
|
||||
doHook();
|
||||
initAndLoadModules(context);
|
||||
}
|
||||
else {
|
||||
XLog.e(TAG, "something wrong");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MMPApplication() {
|
||||
super();
|
||||
|
||||
if (isApplicationProxied()) {
|
||||
createOriginalApplication();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isApplicationProxied() {
|
||||
if (originalApplicationName != null && !originalApplicationName.isEmpty() && !("android.app.Application").equals(originalApplicationName)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassLoader getAppClassLoader() {
|
||||
if (appClassLoader != null) {
|
||||
return appClassLoader;
|
||||
}
|
||||
try {
|
||||
Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication");
|
||||
Object loadedApkObj = XposedHelpers.getObjectField(mBoundApplication, "info");
|
||||
appClassLoader = (ClassLoader) XposedHelpers.callMethod(loadedApkObj, "getClassLoader");
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return appClassLoader;
|
||||
}
|
||||
|
||||
private static void doHook() {
|
||||
hookContextImplSetOuterContext();
|
||||
hookInstallContentProviders();
|
||||
hookActivityAttach();
|
||||
hookServiceAttach();
|
||||
}
|
||||
|
||||
private static void hookContextImplSetOuterContext() {
|
||||
XposedHelpers.findAndHookMethod("android.app.ContextImpl", getAppClassLoader(), "setOuterContext", Context.class, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||
replaceApplicationParam(param.args);
|
||||
// XposedHelpers.setObjectField(param.thisObject, "mOuterContext", sOriginalApplication);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void hookInstallContentProviders() {
|
||||
XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.ActivityThread", getAppClassLoader()), "installContentProviders", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
replaceApplicationParam(param.args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void hookActivityAttach() {
|
||||
XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Activity", getAppClassLoader()), "attach", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
replaceApplicationParam(param.args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void hookServiceAttach() {
|
||||
XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Service", getAppClassLoader()), "attach", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
||||
replaceApplicationParam(param.args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void replaceApplicationParam(Object[] args) {
|
||||
if (args == null || args.length == 0) {
|
||||
return;
|
||||
}
|
||||
for (Object para : args) {
|
||||
if (para instanceof MMPApplication) {
|
||||
para = sOriginalApplication;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getActivityThread() {
|
||||
if (activityThread == null) {
|
||||
try {
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
activityThread = XposedHelpers.callStaticMethod(activityThreadClass, "currentActivityThread");
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return activityThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
|
||||
// 将applicationInfo中保存的applcation class name还原为真实的application class name
|
||||
if (isApplicationProxied()) {
|
||||
modifyApplicationInfoClassName();
|
||||
}
|
||||
|
||||
super.attachBaseContext(base);
|
||||
|
||||
if (isApplicationProxied()) {
|
||||
attachOrignalBaseContext(base);
|
||||
setLoadedApkField(base);
|
||||
}
|
||||
|
||||
// setApplicationLoadedApk(base);
|
||||
}
|
||||
|
||||
private void attachOrignalBaseContext(Context base) {
|
||||
try {
|
||||
XposedHelpers.callMethod(sOriginalApplication, "attachBaseContext", base);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoadedApkField(Context base) {
|
||||
// mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
|
||||
try {
|
||||
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
|
||||
Object contextImpl = XposedHelpers.callStaticMethod(contextImplClass, "getImpl", base);
|
||||
Object loadedApk = XposedHelpers.getObjectField(contextImpl, "mPackageInfo");
|
||||
XposedHelpers.setObjectField(sOriginalApplication, "mLoadedApk", loadedApk);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// setLoadedApkField(sOriginalApplication);
|
||||
// XposedHelpers.setObjectField(sOriginalApplication, "mLoadedApk", XposedHelpers.getObjectField(this, "mLoadedApk"));
|
||||
super.onCreate();
|
||||
|
||||
if (isApplicationProxied()) {
|
||||
// replaceApplication();
|
||||
replaceLoadedApkApplication();
|
||||
replaceActivityThreadApplication();
|
||||
|
||||
sOriginalApplication.onCreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceLoadedApkApplication() {
|
||||
try {
|
||||
// replace LoadedApk.java makeApplication() mActivityThread.mAllApplications.add(app);
|
||||
ArrayList<Application> list = (ArrayList<Application>) XposedHelpers.getObjectField(getActivityThread(), "mAllApplications");
|
||||
list.add(sOriginalApplication);
|
||||
|
||||
Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication"); // AppBindData
|
||||
Object loadedApkObj = XposedHelpers.getObjectField(mBoundApplication, "info"); // info
|
||||
|
||||
// replace LoadedApk.java makeApplication() mApplication = app;
|
||||
XposedHelpers.setObjectField(loadedApkObj, "mApplication", sOriginalApplication);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceActivityThreadApplication() {
|
||||
try {
|
||||
XposedHelpers.setObjectField(getActivityThread(), "mInitialApplication", sOriginalApplication);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private Application createOriginalApplication() {
|
||||
if (sOriginalApplication == null) {
|
||||
try {
|
||||
sOriginalApplication = (Application) getAppClassLoader().loadClass(originalApplicationName).newInstance();
|
||||
}
|
||||
catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return sOriginalApplication;
|
||||
}
|
||||
|
||||
private void modifyApplicationInfoClassName() {
|
||||
try {
|
||||
Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication"); // AppBindData
|
||||
Object applicationInfoObj = XposedHelpers.getObjectField(mBoundApplication, "appInfo"); // info
|
||||
|
||||
XposedHelpers.setObjectField(applicationInfoObj, "className", originalApplicationName);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
package com.wind.xposed.entry;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.wind.xposed.entry.util.FileUtils;
|
||||
import com.wind.xposed.entry.util.XLog;
|
||||
import com.wind.xposed.entry.util.XpatchUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dalvik.system.DelegateLastClassLoader;
|
||||
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage;
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelper;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
|
||||
public class MMPLoader {
|
||||
|
||||
private static final String TAG = MMPLoader.class.getSimpleName();
|
||||
private static final String DIR_BASE = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
private static final String XPOSED_MODULE_FILE_PATH = "xpmodules.list";
|
||||
private static AtomicBoolean hasInited = new AtomicBoolean(false);
|
||||
private static Context appContext;
|
||||
|
||||
|
||||
@SuppressLint("DiscouragedPrivateApi")
|
||||
public static boolean loadModule(final String moduleApkPath, String moduleOdexDir, String moduleLibPath, final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) {
|
||||
|
||||
XLog.i(TAG, "Loading modules from " + moduleApkPath);
|
||||
|
||||
if (!new File(moduleApkPath).exists()) {
|
||||
XLog.e(TAG, moduleApkPath + " does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
ClassLoader mcl = new DelegateLastClassLoader(moduleApkPath, null, appClassLoader);
|
||||
|
||||
try {
|
||||
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != appClassLoader) {
|
||||
Log.e(TAG, " Cannot load module:");
|
||||
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
|
||||
Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
|
||||
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
try (InputStream is = mcl.getResourceAsStream("assets/xposed_init")) {
|
||||
if (is == null) {
|
||||
XLog.e(TAG, "assets/xposed_init not found in the APK");
|
||||
return false;
|
||||
}
|
||||
|
||||
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
|
||||
String moduleClassName;
|
||||
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
|
||||
moduleClassName = moduleClassName.trim();
|
||||
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
XLog.i(TAG, "Loading class " + moduleClassName);
|
||||
Class<?> moduleClass = mcl.loadClass(moduleClassName);
|
||||
|
||||
if (!XposedHelper.isIXposedMod(moduleClass)) {
|
||||
Log.w(TAG, "This class doesn't implement any sub-interface of IXposedMod, skipping it");
|
||||
continue;
|
||||
}
|
||||
else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
|
||||
Log.w(TAG, "This class requires resource-related hooks (which are disabled), skipping it.");
|
||||
continue;
|
||||
}
|
||||
|
||||
final Object moduleInstance = moduleClass.newInstance();
|
||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||
XposedHelper.callInitZygote(moduleApkPath, moduleInstance);
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
||||
// hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
|
||||
IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance, moduleApkPath);
|
||||
XposedBridge.CopyOnWriteSortedSet<XC_LoadPackage> xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>();
|
||||
xc_loadPackageCopyOnWriteSortedSet.add(wrapper);
|
||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet);
|
||||
lpparam.packageName = currentApplicationInfo.packageName;
|
||||
lpparam.processName = (String) Class.forName("android.app.ActivityThread").getDeclaredMethod("currentProcessName").invoke(null);
|
||||
lpparam.classLoader = appClassLoader;
|
||||
lpparam.appInfo = currentApplicationInfo;
|
||||
lpparam.isFirstApplication = true;
|
||||
XC_LoadPackage.callAll(lpparam);
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
||||
XLog.w(TAG, "unsupport resource hook");
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
XLog.e(TAG, "", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
XLog.e(TAG, "", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void initAndLoadModules() {
|
||||
Context context = XpatchUtils.createAppContext();
|
||||
initAndLoadModules(context);
|
||||
}
|
||||
|
||||
public static void initAndLoadModules(Context context) {
|
||||
if (!hasInited.compareAndSet(false, true)) {
|
||||
XLog.w(TAG, "has been init");
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
XLog.e(TAG, "try to init with context null");
|
||||
return;
|
||||
}
|
||||
|
||||
appContext = context;
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (!FileUtils.isSdcardPermissionGranted(context)) {
|
||||
XLog.e(TAG, "file permission is not granted, can not control xposed module by file " + XPOSED_MODULE_FILE_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
initSELinux(context);
|
||||
|
||||
ClassLoader originClassLoader = context.getClassLoader();
|
||||
List<String> modulePathList = loadAllInstalledModule(context);
|
||||
|
||||
for (String modulePath : modulePathList) {
|
||||
String dexPath = context.getDir("xposed_plugin_dex", Context.MODE_PRIVATE).getAbsolutePath();
|
||||
if (!TextUtils.isEmpty(modulePath)) {
|
||||
MMPLoader.loadModule(modulePath, dexPath, null, context.getApplicationInfo(), originClassLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initSELinux(Context context) {
|
||||
XposedHelper.initSeLinux(context.getApplicationInfo().processName);
|
||||
}
|
||||
|
||||
private static List<String> loadAllInstalledModule(Context context) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<String> modulePathList = new ArrayList<>();
|
||||
|
||||
List<String> packageNameList = loadPackageNameListFromFile(true);
|
||||
List<Pair<String, String>> installedModuleList = new ArrayList<>();
|
||||
|
||||
boolean configFileExist = configFileExist();
|
||||
|
||||
for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) {
|
||||
ApplicationInfo app = pkg.applicationInfo;
|
||||
if (!app.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (app.metaData != null && (app.metaData.containsKey("xposedmodule"))) {
|
||||
String apkPath = pkg.applicationInfo.publicSourceDir;
|
||||
String apkName = context.getPackageManager().getApplicationLabel(pkg.applicationInfo).toString();
|
||||
if (TextUtils.isEmpty(apkPath)) {
|
||||
apkPath = pkg.applicationInfo.sourceDir;
|
||||
}
|
||||
if (!TextUtils.isEmpty(apkPath) && (!configFileExist || packageNameList == null || packageNameList.contains(app.packageName))) {
|
||||
XLog.d(TAG, "query installed module path " + apkPath);
|
||||
modulePathList.add(apkPath);
|
||||
}
|
||||
installedModuleList.add(Pair.create(pkg.applicationInfo.packageName, apkName));
|
||||
}
|
||||
}
|
||||
|
||||
final List<Pair<String, String>> installedModuleListFinal = installedModuleList;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> savedPackageNameList = loadPackageNameListFromFile(false);
|
||||
if (savedPackageNameList == null) {
|
||||
savedPackageNameList = new ArrayList<>();
|
||||
}
|
||||
List<Pair<String, String>> addPackageList = new ArrayList<>();
|
||||
for (Pair<String, String> packgagePair : installedModuleListFinal) {
|
||||
if (!savedPackageNameList.contains(packgagePair.first)) {
|
||||
XLog.d(TAG, "append " + packgagePair + " to " + XPOSED_MODULE_FILE_PATH);
|
||||
addPackageList.add(packgagePair);
|
||||
}
|
||||
}
|
||||
try {
|
||||
appendPackageNameToFile(addPackageList);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
return modulePathList;
|
||||
}
|
||||
|
||||
// 从sd卡中加载指定文件,以加载指定的xposed module
|
||||
private static List<String> loadPackageNameListFromFile(boolean loadActivedPackages) {
|
||||
File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
if (!moduleFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
List<String> modulePackageList = new ArrayList<>();
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
fileInputStream = new FileInputStream(moduleFile);
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
|
||||
String modulePackageName;
|
||||
while ((modulePackageName = bufferedReader.readLine()) != null) {
|
||||
modulePackageName = modulePackageName.trim();
|
||||
if (modulePackageName.isEmpty() || (modulePackageName.startsWith("#") && loadActivedPackages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modulePackageName.startsWith("#")) {
|
||||
modulePackageName = modulePackageName.substring(1);
|
||||
}
|
||||
int index = modulePackageName.indexOf("#");
|
||||
if (index > 0) {
|
||||
modulePackageName = modulePackageName.substring(0, index);
|
||||
}
|
||||
XLog.d(TAG, "load " + XPOSED_MODULE_FILE_PATH + " file result, modulePackageName " + modulePackageName);
|
||||
modulePackageList.add(modulePackageName);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
closeStream(fileInputStream);
|
||||
closeStream(bufferedReader);
|
||||
}
|
||||
return modulePackageList;
|
||||
}
|
||||
|
||||
private static void appendPackageNameToFile(List<Pair<String, String>> packageNameList) throws IOException {
|
||||
|
||||
if (isEmpty(packageNameList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
if (!moduleFile.exists()) {
|
||||
if (!moduleFile.createNewFile()) {
|
||||
throw new IllegalStateException("create " + XPOSED_MODULE_FILE_PATH + " err");
|
||||
}
|
||||
}
|
||||
FileOutputStream outputStream = null;
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
outputStream = new FileOutputStream(moduleFile, true);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
|
||||
|
||||
for (Pair<String, String> packageInfo : packageNameList) {
|
||||
String packageName = packageInfo.first;
|
||||
String appName = packageInfo.second;
|
||||
writer.write(packageName + "#" + appName);
|
||||
writer.write("\n");
|
||||
XLog.d(TAG, "append new pkg to " + XPOSED_MODULE_FILE_PATH);
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
closeStream(outputStream);
|
||||
closeStream(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean configFileExist() {
|
||||
File moduleConfigFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
return moduleConfigFile.exists();
|
||||
}
|
||||
|
||||
private static void closeStream(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEmpty(Collection<?> collection) {
|
||||
if (collection == null || collection.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Context getAppContext() {
|
||||
return appContext;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.wind.xposed.entry.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
//读写权限
|
||||
private static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
public static boolean isSdcardPermissionGranted(Context context) {
|
||||
int pid = android.os.Process.myPid();
|
||||
int uid = Process.myUid();
|
||||
return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED && context.checkPermission(PERMISSIONS_STORAGE[1], pid, uid) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static String readTextFromAssets(Context context, String assetsFileName) {
|
||||
if (context == null) {
|
||||
throw new IllegalStateException("context null");
|
||||
}
|
||||
try {
|
||||
InputStream is = context.getAssets().open(assetsFileName);
|
||||
return readTextFromInputStream(is);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String readTextFromInputStream(InputStream is) {
|
||||
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String str;
|
||||
while ((str = bufferedReader.readLine()) != null) {
|
||||
builder.append(str);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.wind.xposed.entry.util;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class ReflectionApiCheck {
|
||||
|
||||
private static final String TAG = ReflectionApiCheck.class.getSimpleName();
|
||||
private static final int ERROR_EXEMPT_FAILED = -21;
|
||||
private static Object sVmRuntime;
|
||||
private static Method setHiddenApiExemptions;
|
||||
|
||||
static {
|
||||
if (SDK_INT >= Build.VERSION_CODES.P) {
|
||||
try {
|
||||
Method forName = Class.class.getDeclaredMethod("forName", String.class);
|
||||
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
|
||||
|
||||
Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
|
||||
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
|
||||
if (getRuntime == null) {
|
||||
throw new IllegalStateException("getRuntime method null");
|
||||
}
|
||||
setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
|
||||
sVmRuntime = getRuntime.invoke(null);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Log.e(TAG, "reflect bootstrap failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int unseal() {
|
||||
if (SDK_INT < 28) {
|
||||
// Below Android P, ignore
|
||||
return 0;
|
||||
}
|
||||
|
||||
// try exempt API first.
|
||||
if (exemptAll()) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return ERROR_EXEMPT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* make the method exempted from hidden API check.
|
||||
*
|
||||
* @param method the method signature prefix.
|
||||
* @return true if success.
|
||||
*/
|
||||
public static boolean exempt(String method) {
|
||||
return exempt(new String[]{method});
|
||||
}
|
||||
|
||||
/**
|
||||
* make specific methods exempted from hidden API check.
|
||||
*
|
||||
* @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L"
|
||||
* @return true if success
|
||||
*/
|
||||
public static boolean exempt(String... methods) {
|
||||
if (sVmRuntime == null || setHiddenApiExemptions == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods});
|
||||
return true;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make all hidden API exempted.
|
||||
*
|
||||
* @return true if success.
|
||||
*/
|
||||
public static boolean exemptAll() {
|
||||
return exempt(new String[]{"L"});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.wind.xposed.entry.util;
|
||||
|
||||
import com.wind.xposed.entry.BuildConfig;
|
||||
|
||||
public class XLog {
|
||||
|
||||
private static boolean enableLog = BuildConfig.DEBUG;
|
||||
|
||||
public static void d(String tag, String msg) {
|
||||
if (enableLog) {
|
||||
android.util.Log.d(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void v(String tag, String msg) {
|
||||
if (enableLog) {
|
||||
android.util.Log.v(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String tag, String msg) {
|
||||
if (enableLog) {
|
||||
android.util.Log.w(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String tag, String msg) {
|
||||
if (enableLog) {
|
||||
android.util.Log.i(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg) {
|
||||
if (enableLog) {
|
||||
android.util.Log.e(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String tag, String msg, Throwable tr) {
|
||||
if (enableLog) {
|
||||
android.util.Log.e(tag, msg, tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.wind.xposed.entry.util;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class XpatchUtils {
|
||||
final static String TAG = "MMP" + XpatchUtils.class.getSimpleName();
|
||||
|
||||
public static Context createAppContext() {
|
||||
try {
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
|
||||
currentActivityThreadMethod.setAccessible(true);
|
||||
|
||||
Object activityThreadObj = currentActivityThreadMethod.invoke(null);
|
||||
|
||||
Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
boundApplicationField.setAccessible(true);
|
||||
Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData
|
||||
if (mBoundApplication == null) {
|
||||
Log.e(TAG, "mBoundApplication null");
|
||||
return null;
|
||||
}
|
||||
Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info
|
||||
infoField.setAccessible(true);
|
||||
Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk
|
||||
if (loadedApkObj == null) {
|
||||
Log.e(TAG, "loadedApkObj null");
|
||||
return null;
|
||||
}
|
||||
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
|
||||
Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass());
|
||||
createAppContextMethod.setAccessible(true);
|
||||
|
||||
Object context = createAppContextMethod.invoke(null, (ActivityThread) activityThreadObj, loadedApkObj);
|
||||
|
||||
if (context instanceof Context) {
|
||||
return (Context) context;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isApkDebugable(Context context) {
|
||||
try {
|
||||
ApplicationInfo info = context.getApplicationInfo();
|
||||
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package de.robv.android.xposed;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
import de.robv.android.xposed.IXposedMod;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
public class XposedHelper {
|
||||
|
||||
private static final String TAG = "XposedHelper";
|
||||
|
||||
public static void initSeLinux(String processName) {
|
||||
// SELinuxHelper.initOnce();
|
||||
// SELinuxHelper.initForProcess(processName);
|
||||
}
|
||||
|
||||
public static boolean isIXposedMod(Class<?> moduleClass) {
|
||||
return IXposedMod.class.isAssignableFrom(moduleClass);
|
||||
}
|
||||
|
||||
|
||||
public static XC_MethodHook.Unhook newUnHook(XC_MethodHook XC_MethodHook, Member member) {
|
||||
return XC_MethodHook.new Unhook(member);
|
||||
}
|
||||
|
||||
public static void callInitZygote(String modulePath, Object moduleInstance) throws Throwable {
|
||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||
param.modulePath = modulePath;
|
||||
param.startsSystemServer = false;
|
||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">library</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
version_name=1.0
|
||||
version_code=1
|
||||
|
||||
android.useAndroidX=true
|
||||
|
||||
|
||||
Binary file not shown.
|
|
@ -0,0 +1,6 @@
|
|||
#Thu Mar 25 19:56:14 CST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
include ':app'
|
||||
rootProject.name='MMPLoader'
|
||||
include ':xpatchcore'
|
||||
project(':xpatchcore').projectDir = new File('core')
|
||||
include ':lspcore'
|
||||
project(':lspcore').projectDir = new File('mmp/core')
|
||||
include ':hiddenapi-stubs'
|
||||
project(':hiddenapi-stubs').projectDir = new File('mmp/hiddenapi-stubs')
|
||||
include ':interface'
|
||||
project(':interface').projectDir = new File('mmp/service/interface')
|
||||
include ':hiddenapi-bridge'
|
||||
project(':hiddenapi-bridge').projectDir = new File('mmp/hiddenapi-bridge')
|
||||
include ':manager-service'
|
||||
project(':manager-service').projectDir = new File('mmp/manager-service')
|
||||
Loading…
Reference in New Issue