Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,46 @@
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.os.Build;
import android.os.RemoteException;
import android.system.Os;
import android.util.Log;

import com.google.gson.Gson;

import org.json.JSONArray;
import org.json.JSONObject;
import org.lsposed.lspatch.loader.util.FileUtils;
import org.lsposed.lspatch.loader.util.XLog;
import org.lsposed.lspatch.service.LocalApplicationService;
import org.lsposed.lspatch.service.RemoteApplicationService;
import org.lsposed.lspatch.share.PatchConfig;
import org.lsposed.lspd.core.Startup;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;

import dalvik.system.DexFile;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import hidden.HiddenApiBridge;

Expand All @@ -57,7 +63,7 @@ public class LSPApplication {
private static LoadedApk stubLoadedApk;
private static LoadedApk appLoadedApk;

private static JSONObject config;
private static PatchConfig config;

public static boolean isIsolated() {
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
Expand All @@ -77,29 +83,47 @@ public static void onLoad() throws RemoteException, IOException {

Log.d(TAG, "Initialize service client");
ILSPApplicationService service;
if (config.optBoolean("useManager")) {
service = new RemoteApplicationService(context);
if (config.useManager) {
try {
service = new RemoteApplicationService(context);
List<Module> m = service.getLegacyModulesList();
JSONArray moduleArr = new JSONArray();
for (Module module : m) {
JSONObject moduleObj = new JSONObject();
moduleObj.put("path",module.apkPath);
moduleObj.put("packageName",module.packageName);
moduleArr.put(moduleObj);
}
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
shared.edit().putString("modules",moduleArr.toString()).commit();
Log.e(TAG, "Success update module scope");
}catch (Exception e){
Log.e(TAG, "Failed to connect to manager, fallback to fixed local service");
service = new LocalApplicationService(context);
}

} else {
service = new LocalApplicationService(context);
}

disableProfile(context);
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
Startup.bootstrapXposed();
// WARN: Since it uses `XResource`, the following class should not be initialized
// before forkPostCommon is invoke. Otherwise, you will get failure of XResources

Log.i(TAG, "Load modules");
LSPLoader.initModules(appLoadedApk);
Log.i(TAG, "Modules initialized");

switchAllClassLoader();
SigBypass.doSigBypass(context, config.optInt("sigBypassLevel"));
SigBypass.doSigBypass(context, config.sigBypassLevel);

Log.i(TAG, "LSPatch bootstrap completed");
}

private static Context createLoadedApkWithContext() {
try {
var timeStart = System.currentTimeMillis();
var mBoundApplication = XposedHelpers.getObjectField(activityThread, "mBoundApplication");

stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info");
Expand All @@ -109,13 +133,13 @@ private static Context createLoadedApkWithContext() {

try (var is = baseClassLoader.getResourceAsStream(CONFIG_ASSET_PATH)) {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
config = new JSONObject(streamReader.lines().collect(Collectors.joining()));
} catch (Throwable e) {
Log.e(TAG, "Failed to parse config file", e);
config = new Gson().fromJson(streamReader, PatchConfig.class);
} catch (IOException e) {
Log.e(TAG, "Failed to load config file");
return null;
}
Log.i(TAG, "Use manager: " + config.optBoolean("useManager"));
Log.i(TAG, "Signature bypass level: " + config.optInt("sigBypassLevel"));
Log.i(TAG, "Use manager: " + config.useManager);
Log.i(TAG, "Signature bypass level: " + config.sigBypassLevel);

Path originPath = Paths.get(appInfo.dataDir, "cache/lspatch/origin/");
Path cacheApkPath;
Expand All @@ -125,9 +149,7 @@ private static Context createLoadedApkWithContext() {

appInfo.sourceDir = cacheApkPath.toString();
appInfo.publicSourceDir = cacheApkPath.toString();
if (config.has("appComponentFactory")) {
appInfo.appComponentFactory = config.optString("appComponentFactory");
}
appInfo.appComponentFactory = config.appComponentFactory;

if (!Files.exists(cacheApkPath)) {
Log.i(TAG, "Extract original apk");
Expand All @@ -137,10 +159,13 @@ private static Context createLoadedApkWithContext() {
Files.copy(is, cacheApkPath);
}
}

cacheApkPath.toFile().setWritable(false);

var mPackages = (Map<?, ?>) XposedHelpers.getObjectField(activityThread, "mPackages");
mPackages.remove(appInfo.packageName);
appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);

XposedHelpers.setObjectField(mBoundApplication, "info", appLoadedApk);

var activityClientRecordClass = XposedHelpers.findClass("android.app.ActivityThread$ActivityClientRecord", ActivityThread.class.getClassLoader());
Expand All @@ -165,70 +190,23 @@ private static Context createLoadedApkWithContext() {
Log.i(TAG, "hooked app initialized: " + appLoadedApk);

var context = (Context) XposedHelpers.callStaticMethod(Class.forName("android.app.ContextImpl"), "createAppContext", activityThread, stubLoadedApk);
if (config.has("appComponentFactory")) {
if (config.appComponentFactory != null) {
try {
context.getClassLoader().loadClass(appInfo.appComponentFactory);
} catch (Throwable e) { // 捕捉更廣泛的類載入錯誤, 可能可以兼容部分加固如 360
Log.w(TAG, "Original AppComponentFactory not found: " + appInfo.appComponentFactory, e);
context.getClassLoader().loadClass(config.appComponentFactory);
} catch (Throwable e) {
Log.w(TAG, "Original AppComponentFactory not found: " + config.appComponentFactory, e);
appInfo.appComponentFactory = null;
}
}
Log.i(TAG,"createLoadedApkWithContext cost: " + (System.currentTimeMillis() - timeStart) + "ms");

return context;
} catch (Throwable e) {
Log.e(TAG, "createLoadedApk", e);
return null;
}
}

public static void disableProfile(Context context) {
final ArrayList<String> codePaths = new ArrayList<>();
var appInfo = context.getApplicationInfo();
var pkgName = context.getPackageName();
if (appInfo == null) return;
if ((appInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
codePaths.add(appInfo.sourceDir);
}
if (appInfo.splitSourceDirs != null) {
Collections.addAll(codePaths, appInfo.splitSourceDirs);
}

if (codePaths.isEmpty()) {
// If there are no code paths there's no need to setup a profile file and register with
// the runtime,
return;
}

var profileDir = HiddenApiBridge.Environment_getDataProfilesDePackageDirectory(appInfo.uid / PER_USER_RANGE, pkgName);

var attrs = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("r--------"));

for (int i = codePaths.size() - 1; i >= 0; i--) {
String splitName = i == 0 ? null : appInfo.splitNames[i - 1];
File curProfileFile = new File(profileDir, splitName == null ? "primary.prof" : splitName + ".split.prof").getAbsoluteFile();
Log.d(TAG, "Processing " + curProfileFile.getAbsolutePath());
try {
if (!curProfileFile.exists()) {
Files.createFile(curProfileFile.toPath(), attrs);
continue;
}
if (!curProfileFile.canWrite() && Files.size(curProfileFile.toPath()) == 0) {
Log.d(TAG, "Skip profile " + curProfileFile.getAbsolutePath());
continue;
}
if (curProfileFile.exists() && !curProfileFile.delete()) {
try (var writer = new FileOutputStream(curProfileFile)) {
Log.d(TAG, "Failed to delete, try to clear content " + curProfileFile.getAbsolutePath());
} catch (Throwable e) {
Log.e(TAG, "Failed to delete and clear profile file " + curProfileFile.getAbsolutePath(), e);
}
Os.chmod(curProfileFile.getAbsolutePath(), 00400);
}
} catch (Throwable e) {
Log.e(TAG, "Failed to disable profile file " + curProfileFile.getAbsolutePath(), e);
}
}
}

private static void switchAllClassLoader() {
var fields = LoadedApk.class.getDeclaredFields();
for (Field field : fields) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,89 +1,85 @@
package org.lsposed.lspatch.service;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import org.lsposed.lspatch.loader.util.FileUtils;
import org.lsposed.lspatch.share.Constants;
import org.json.JSONArray;
import org.json.JSONObject;
import org.lsposed.lspatch.util.ModuleLoader;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;

public class LocalApplicationService extends ILSPApplicationService.Stub {

private static final String TAG = "NPatch";

private final List<Module> modules = new ArrayList<>();

public LocalApplicationService(Context context) {
private final List<Module> cachedModule;
public LocalApplicationService(Context context){
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
cachedModule = new ArrayList<>();
try {
for (var name : context.getAssets().list("lspatch/modules")) {
String packageName = name.substring(0, name.length() - 4);
String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/";
String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
cacheApkPath = modulePath + sourceFile.getEntry(Constants.EMBEDDED_MODULES_ASSET_PATH + name).getCrc() + ".apk";
}

if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "Extract module apk: " + packageName);
FileUtils.deleteFolderIfExists(Paths.get(modulePath));
Files.createDirectories(Paths.get(modulePath));
try (var is = context.getAssets().open("lspatch/modules/" + name)) {
Files.copy(is, Paths.get(cacheApkPath));
JSONArray mArr = new JSONArray(shared.getString("modules", "{}"));
Log.i(TAG,"use fixed local application service:"+shared.getString("modules", "{}"));
for (int i = 0; i < mArr.length(); i++) {
JSONObject mObj = mArr.getJSONObject(i);
Module m = new Module();
String path = mObj.getString("path");
String packageName = mObj.getString("packageName");
m.apkPath = path;
m.packageName = packageName;
if (!new File(m.apkPath).exists()){
Log.i("NPatch","Module:" + m.packageName + " path not available, reset.");
try {
ApplicationInfo info = context.getPackageManager().getApplicationInfo(m.packageName, 0);
m.apkPath = info.sourceDir;
Log.i("NPatch","Module:" + m.packageName + " path reset to " + m.apkPath);
}catch (Exception e){
Log.e("NPatch",Log.getStackTraceString(e));
}
}

var module = new Module();
module.apkPath = cacheApkPath;
module.packageName = packageName;
module.file = ModuleLoader.loadModule(cacheApkPath);
modules.add(module);
m.file = ModuleLoader.loadModule(m.apkPath);
cachedModule.add(m);
}
} catch (IOException e) {
Log.e(TAG, "Error when initializing LocalApplicationServiceClient", e);
}catch (Exception e){
Log.e(TAG,Log.getStackTraceString(e));
}
}

@Override
public boolean isLogMuted() throws RemoteException {
return false;
}

@Override
public List<Module> getLegacyModulesList() {
return modules;
public List<Module> getLegacyModulesList() throws RemoteException {
return cachedModule;
}

@Override
public List<Module> getModulesList() {
public List<Module> getModulesList() throws RemoteException {
return new ArrayList<>();
}

@Override
public String getPrefsPath(String packageName) {
public String getPrefsPath(String packageName) throws RemoteException {
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath();
}

@Override
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
return null;
}

@Override
public IBinder asBinder() {
return this;
}

@Override
public boolean isLogMuted() throws RemoteException {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.widget.Toast;

import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspd.models.Module;
Expand Down Expand Up @@ -48,7 +47,7 @@ public RemoteApplicationService(Context context) throws RemoteException {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.i(TAG, "Manager binder received");
service = Stub.asInterface(binder);
service = ILSPApplicationService.Stub.asInterface(binder);
latch.countDown();
}

Expand Down Expand Up @@ -76,7 +75,6 @@ public void onServiceDisconnected(ComponentName name) {
if (!success) throw new TimeoutException("Bind service timeout");
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
InterruptedException | TimeoutException e) {
Toast.makeText(context, "Unable to connect to Manager", Toast.LENGTH_SHORT).show();
var r = new RemoteException("Failed to get manager binder");
r.initCause(e);
throw r;
Expand Down