[core]Load module in memory (#809)
This commit is contained in:
parent
bbd5c36fe0
commit
e2a0d2f05a
|
|
@ -46,6 +46,7 @@ import android.util.Log;
|
|||
|
||||
import org.lsposed.lspd.nativebridge.NativeAPI;
|
||||
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
||||
import org.lsposed.lspd.util.InMemoryDelegateLastClassLoader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
|
|
@ -373,7 +374,7 @@ public final class XposedInit {
|
|||
librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator);
|
||||
}
|
||||
ClassLoader initLoader = XposedInit.class.getClassLoader();
|
||||
ClassLoader mcl = new DelegateLastClassLoader(apk, librarySearchPath.toString(), initLoader);
|
||||
ClassLoader mcl = InMemoryDelegateLastClassLoader.loadApk(new File(apk), librarySearchPath.toString(), initLoader);
|
||||
|
||||
try {
|
||||
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package org.lsposed.lspd.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import sun.net.www.ParseUtil;
|
||||
|
||||
public final class ClassPathURLStreamHandler extends Handler {
|
||||
private final String fileUri;
|
||||
private final JarFile jarFile;
|
||||
|
||||
public ClassPathURLStreamHandler(String jarFileName) throws IOException {
|
||||
jarFile = new JarFile(jarFileName);
|
||||
fileUri = new File(jarFileName).toURI().toString();
|
||||
}
|
||||
|
||||
public URL getEntryUrlOrNull(String entryName) {
|
||||
if (jarFile.getEntry(entryName) != null) {
|
||||
try {
|
||||
String encodedName = ParseUtil.encodePath(entryName, false);
|
||||
return new URL("jar", null, -1, fileUri + "!/" + encodedName, this);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Invalid entry name", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
return new ClassPathURLConnection(url);
|
||||
}
|
||||
|
||||
private class ClassPathURLConnection extends JarURLConnection {
|
||||
private JarFile connectionJarFile = null;
|
||||
private ZipEntry jarEntry = null;
|
||||
private InputStream jarInput = null;
|
||||
private boolean closed = false;
|
||||
|
||||
private ClassPathURLConnection(URL url) throws MalformedURLException {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (closed) {
|
||||
throw new IllegalStateException("JarURLConnection has been closed");
|
||||
}
|
||||
if (!connected) {
|
||||
jarEntry = jarFile.getEntry(getEntryName());
|
||||
if (jarEntry == null) {
|
||||
throw new FileNotFoundException("URL=" + url + ", zipfile=" + jarFile.getName());
|
||||
}
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarFile getJarFile() throws IOException {
|
||||
connect();
|
||||
if (connectionJarFile != null) return connectionJarFile;
|
||||
return connectionJarFile = new JarFile(jarFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
connect();
|
||||
if (jarInput != null) return jarInput;
|
||||
return jarInput = new FilterInputStream(jarFile.getInputStream(jarEntry)) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
closed = true;
|
||||
jarFile.close();
|
||||
if (connectionJarFile != null) connectionJarFile.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
String cType = guessContentTypeFromName(getEntryName());
|
||||
if (cType == null) {
|
||||
cType = "content/unknown";
|
||||
}
|
||||
return cType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
try {
|
||||
connect();
|
||||
return (int) getJarEntry().getSize();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package org.lsposed.lspd.util;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class CompoundEnumeration<E> implements Enumeration<E> {
|
||||
private final Enumeration<E>[] enums;
|
||||
private int index = 0;
|
||||
|
||||
public CompoundEnumeration(Enumeration<E>[] enums) {
|
||||
this.enums = enums;
|
||||
}
|
||||
|
||||
private boolean next() {
|
||||
while (index < enums.length) {
|
||||
if (enums[index] != null && enums[index].hasMoreElements()) {
|
||||
return true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
return next();
|
||||
}
|
||||
|
||||
public E nextElement() {
|
||||
if (!next()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return enums[index].nextElement();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package org.lsposed.lspd.util;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public abstract class Handler extends java.net.URLStreamHandler {
|
||||
|
||||
private static final String separator = "!/";
|
||||
|
||||
private static int indexOfBangSlash(String spec) {
|
||||
int indexOfBang = spec.length();
|
||||
while ((indexOfBang = spec.lastIndexOf('!', indexOfBang)) != -1) {
|
||||
if ((indexOfBang != (spec.length() - 1)) &&
|
||||
(spec.charAt(indexOfBang + 1) == '/')) {
|
||||
return indexOfBang + 1;
|
||||
} else {
|
||||
indexOfBang--;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean sameFile(URL u1, URL u2) {
|
||||
if (!u1.getProtocol().equals("jar") || !u2.getProtocol().equals("jar"))
|
||||
return false;
|
||||
|
||||
String file1 = u1.getFile();
|
||||
String file2 = u2.getFile();
|
||||
int sep1 = file1.indexOf(separator);
|
||||
int sep2 = file2.indexOf(separator);
|
||||
|
||||
if (sep1 == -1 || sep2 == -1) {
|
||||
return super.sameFile(u1, u2);
|
||||
}
|
||||
|
||||
String entry1 = file1.substring(sep1 + 2);
|
||||
String entry2 = file2.substring(sep2 + 2);
|
||||
|
||||
if (!entry1.equals(entry2))
|
||||
return false;
|
||||
|
||||
URL enclosedURL1, enclosedURL2;
|
||||
try {
|
||||
enclosedURL1 = new URL(file1.substring(0, sep1));
|
||||
enclosedURL2 = new URL(file2.substring(0, sep2));
|
||||
} catch (MalformedURLException unused) {
|
||||
return super.sameFile(u1, u2);
|
||||
}
|
||||
|
||||
return super.sameFile(enclosedURL1, enclosedURL2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashCode(URL u) {
|
||||
int h = 0;
|
||||
|
||||
String protocol = u.getProtocol();
|
||||
if (protocol != null)
|
||||
h += protocol.hashCode();
|
||||
|
||||
String file = u.getFile();
|
||||
int sep = file.indexOf(separator);
|
||||
|
||||
if (sep == -1)
|
||||
return h + file.hashCode();
|
||||
|
||||
URL enclosedURL;
|
||||
String fileWithoutEntry = file.substring(0, sep);
|
||||
try {
|
||||
enclosedURL = new URL(fileWithoutEntry);
|
||||
h += enclosedURL.hashCode();
|
||||
} catch (MalformedURLException unused) {
|
||||
h += fileWithoutEntry.hashCode();
|
||||
}
|
||||
|
||||
String entry = file.substring(sep + 2);
|
||||
h += entry.hashCode();
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void parseURL(URL url, String spec, int start, int limit) {
|
||||
String file = null;
|
||||
String ref = null;
|
||||
// first figure out if there is an anchor
|
||||
int refPos = spec.indexOf('#', limit);
|
||||
boolean refOnly = refPos == start;
|
||||
if (refPos > -1) {
|
||||
ref = spec.substring(refPos + 1);
|
||||
if (refOnly) {
|
||||
file = url.getFile();
|
||||
}
|
||||
}
|
||||
// then figure out if the spec is
|
||||
// 1. absolute (jar:)
|
||||
// 2. relative (i.e. url + foo/bar/baz.ext)
|
||||
// 3. anchor-only (i.e. url + #foo), which we already did (refOnly)
|
||||
boolean absoluteSpec = false;
|
||||
if (spec.length() >= 4) {
|
||||
absoluteSpec = spec.substring(0, 4).equalsIgnoreCase("jar:");
|
||||
}
|
||||
spec = spec.substring(start, limit);
|
||||
|
||||
if (absoluteSpec) {
|
||||
file = parseAbsoluteSpec(spec);
|
||||
} else if (!refOnly) {
|
||||
file = parseContextSpec(url, spec);
|
||||
|
||||
// Canonize the result after the bangslash
|
||||
int bangSlash = indexOfBangSlash(file);
|
||||
String toBangSlash = file.substring(0, bangSlash);
|
||||
String afterBangSlash = file.substring(bangSlash);
|
||||
afterBangSlash = canonizeString(afterBangSlash);
|
||||
file = toBangSlash + afterBangSlash;
|
||||
}
|
||||
setURL(url, "jar", "", -1, file, ref);
|
||||
}
|
||||
|
||||
private String canonizeString(String file) {
|
||||
int i;
|
||||
int lim;
|
||||
|
||||
// Remove embedded /../
|
||||
while ((i = file.indexOf("/../")) >= 0) {
|
||||
if ((lim = file.lastIndexOf('/', i - 1)) >= 0) {
|
||||
file = file.substring(0, lim) + file.substring(i + 3);
|
||||
} else {
|
||||
file = file.substring(i + 3);
|
||||
}
|
||||
}
|
||||
// Remove embedded /./
|
||||
while ((i = file.indexOf("/./")) >= 0) {
|
||||
file = file.substring(0, i) + file.substring(i + 2);
|
||||
}
|
||||
// Remove trailing ..
|
||||
while (file.endsWith("/..")) {
|
||||
i = file.indexOf("/..");
|
||||
if ((lim = file.lastIndexOf('/', i - 1)) >= 0) {
|
||||
file = file.substring(0, lim + 1);
|
||||
} else {
|
||||
file = file.substring(0, i);
|
||||
}
|
||||
}
|
||||
// Remove trailing .
|
||||
if (file.endsWith("/."))
|
||||
file = file.substring(0, file.length() - 1);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private String parseAbsoluteSpec(String spec) {
|
||||
int index;
|
||||
// check for !/
|
||||
if ((index = indexOfBangSlash(spec)) == -1) {
|
||||
throw new NullPointerException("no !/ in spec");
|
||||
}
|
||||
// test the inner URL
|
||||
try {
|
||||
String innerSpec = spec.substring(0, index - 1);
|
||||
new URL(innerSpec);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new NullPointerException("invalid url: " +
|
||||
spec + " (" + e + ")");
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
private String parseContextSpec(URL url, String spec) {
|
||||
String ctxFile = url.getFile();
|
||||
// if the spec begins with /, chop up the jar back !/
|
||||
if (spec.startsWith("/")) {
|
||||
int bangSlash = indexOfBangSlash(ctxFile);
|
||||
if (bangSlash == -1) {
|
||||
throw new NullPointerException("malformed " + "context url:" + url + ": no !/");
|
||||
}
|
||||
ctxFile = ctxFile.substring(0, bangSlash);
|
||||
}
|
||||
if (!ctxFile.endsWith("/") && (!spec.startsWith("/"))) {
|
||||
// chop up the last component
|
||||
int lastSlash = ctxFile.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
throw new NullPointerException("malformed " + "context url:" + url);
|
||||
}
|
||||
ctxFile = ctxFile.substring(0, lastSlash + 1);
|
||||
}
|
||||
return (ctxFile + spec);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package org.lsposed.lspd.util;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.TAG;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import hidden.ByteBufferDexClassLoader;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public final class InMemoryDelegateLastClassLoader extends ByteBufferDexClassLoader {
|
||||
private final String apk;
|
||||
|
||||
private InMemoryDelegateLastClassLoader(ByteBuffer[] dexBuffers,
|
||||
String librarySearchPath,
|
||||
ClassLoader parent,
|
||||
String apk) {
|
||||
super(dexBuffers, librarySearchPath, parent);
|
||||
this.apk = apk;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
var cl = findLoadedClass(name);
|
||||
if (cl != null) {
|
||||
return cl;
|
||||
}
|
||||
try {
|
||||
return Object.class.getClassLoader().loadClass(name);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
ClassNotFoundException fromSuper;
|
||||
try {
|
||||
return findClass(name);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
fromSuper = ex;
|
||||
}
|
||||
try {
|
||||
return getParent().loadClass(name);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw fromSuper;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL findResource(String name) {
|
||||
try {
|
||||
var urlHandler = new ClassPathURLStreamHandler(apk);
|
||||
return urlHandler.getEntryUrlOrNull(name);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Enumeration<URL> findResources(String name) {
|
||||
var result = new ArrayList<URL>();
|
||||
var url = findResource(name);
|
||||
if (url != null) result.add(url);
|
||||
return Collections.enumeration(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
var resource = Object.class.getClassLoader().getResource(name);
|
||||
if (resource != null) return resource;
|
||||
resource = findResource(name);
|
||||
if (resource != null) return resource;
|
||||
final var cl = getParent();
|
||||
return (cl == null) ? null : cl.getResource(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
@SuppressWarnings("unchecked")
|
||||
final var resources = (Enumeration<URL>[]) new Enumeration<?>[]{
|
||||
Object.class.getClassLoader().getResources(name),
|
||||
findResources(name),
|
||||
getParent() == null ? null : getParent().getResources(name)};
|
||||
return new CompoundEnumeration<>(resources);
|
||||
}
|
||||
|
||||
public static InMemoryDelegateLastClassLoader loadApk(File apk, String librarySearchPath, ClassLoader parent) {
|
||||
var byteBuffers = new ArrayList<ByteBuffer>();
|
||||
try (var apkFile = new ZipFile(apk)) {
|
||||
int secondaryNumber = 2;
|
||||
for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null;
|
||||
dexFile = apkFile.getEntry("classes" + secondaryNumber + ".dex"), secondaryNumber++) {
|
||||
try (var in = apkFile.getInputStream(dexFile)) {
|
||||
var byteBuffer = ByteBuffer.allocate(in.available());
|
||||
byteBuffer.mark();
|
||||
Channels.newChannel(in).read(byteBuffer);
|
||||
byteBuffer.reset();
|
||||
byteBuffers.add(byteBuffer);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Can not read " + dexFile + " in " + apk, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can not open " + apk, e);
|
||||
}
|
||||
var dexBuffers = new ByteBuffer[byteBuffers.size()];
|
||||
return new InMemoryDelegateLastClassLoader(byteBuffers.toArray(dexBuffers),
|
||||
librarySearchPath, parent, apk.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package hidden;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import dalvik.system.BaseDexClassLoader;
|
||||
|
||||
public class ByteBufferDexClassLoader extends BaseDexClassLoader {
|
||||
public ByteBufferDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
|
||||
super(dexFiles, librarySearchPath, parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ package dalvik.system;
|
|||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class BaseDexClassLoader extends ClassLoader {
|
||||
|
|
@ -14,6 +15,10 @@ public class BaseDexClassLoader extends ClassLoader {
|
|||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package sun.net.www;
|
||||
|
||||
public class ParseUtil {
|
||||
public static String encodePath(String path, boolean flag) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue