[core]Load module in memory (#809)

This commit is contained in:
vvb2060 2021-07-12 14:29:52 +08:00 committed by GitHub
parent bbd5c36fe0
commit e2a0d2f05a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 474 additions and 1 deletions

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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!");
}

View File

@ -0,0 +1,7 @@
package sun.net.www;
public class ParseUtil {
public static String encodePath(String path, boolean flag) {
throw new RuntimeException("Stub!");
}
}