Skip to content

Commit

Permalink
[add] 1、拦截 glide 图片加载框架函数,捕捉部分图片;2、拦截webView资源加载函数,捕抓app中的网页图片
Browse files Browse the repository at this point in the history
  • Loading branch information
Mingyueyixi committed Oct 13, 2024
1 parent a5f0498 commit 8dfda0e
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 21 deletions.
6 changes: 5 additions & 1 deletion app/src/main/java/com/pic/catcher/MainHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.lu.magic.util.log.LogUtil;
import com.lu.magic.util.log.SimpleLogger;
import com.pic.catcher.plugin.BitmapCatcherPlugin;
import com.pic.catcher.plugin.GlideCatcherPlugin;
import com.pic.catcher.plugin.WebViewCatcherPlugin;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -180,7 +182,9 @@ private void initSelfPlugins(Context context, XC_LoadPackage.LoadPackageParam lp
private void initTargetPlugins(Context context, XC_LoadPackage.LoadPackageParam lpparam) {
//目前生成的plugin都是单例的
PluginRegistry.register(
BitmapCatcherPlugin.class
BitmapCatcherPlugin.class,
GlideCatcherPlugin.class,
WebViewCatcherPlugin.class
).handleHooks(context, lpparam);


Expand Down
89 changes: 89 additions & 0 deletions app/src/main/java/com/pic/catcher/plugin/GlideCatcherPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.pic.catcher.plugin;


import android.content.Context;
import android.webkit.MimeTypeMap;

import com.lu.lposed.api2.XC_MethodHook2;
import com.lu.lposed.api2.XposedHelpers2;
import com.lu.lposed.plugin.IPlugin;
import com.lu.magic.util.IOUtil;
import com.lu.magic.util.log.LogUtil;
import com.pic.catcher.ClazzN;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
* @author Lu
* @date 2024/10/13 15:18
* @description
*/
public class GlideCatcherPlugin implements IPlugin {
@Override
public void handleHook(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
LogUtil.d("GlideCatcherPlugin", "handleHook", ClazzN.from("com.bumptech.glide.load.resource.gif.StreamGifDecoder"));
XposedHelpers2.findAndHookMethod(ClazzN.from("com.bumptech.glide.load.resource.gif.StreamGifDecoder"), "inputStreamToBytes", InputStream.class, new XC_MethodHook2() { // from class: com.pic.catcher.plugin.GlideCatcherPlugin.1
@Override // com.lu.lposed.api2.XC_MethodHook2
public void afterHookedMethod(XC_MethodHook.MethodHookParam param) {
Object result = param.getResult();
LogUtil.d("GlideCatcherPlugin", "inputStreamToBytes", result);
if (result != null) {
PicExportManager.getInstance().exportByteArray((byte[]) result, ".gif");
}
}
});
XposedHelpers2.hookAllMethods(ClazzN.from("com.bumptech.glide.load.model.FileLoader"), "buildLoadData", new XC_MethodHook2() { // from class: com.pic.catcher.plugin.GlideCatcherPlugin.2
@Override // com.lu.lposed.api2.XC_MethodHook2
public void beforeHookedMethod(XC_MethodHook.MethodHookParam param) {
Object arg = param.args[0];
if (arg instanceof File) {
PicExportManager.getInstance().exportBitmapFile((File) arg);
}
}
});
XposedHelpers2.hookAllMethods(ClazzN.from("com.bumptech.glide.load.data.HttpUrlFetcher"), "loadData", new XC_MethodHook2() { // from class: com.pic.catcher.plugin.GlideCatcherPlugin.3
@Override // com.lu.lposed.api2.XC_MethodHook2
public void beforeHookedMethod(XC_MethodHook.MethodHookParam param) {
Object glideUrl;
Object callMethod;
Object callback = param.args[1];
if (Proxy.isProxyClass(callback.getClass())) {
return;
}
glideUrl = XposedHelpers2.getObjectField(param.thisObject, "glideUrl");
String lastName = null;
if (glideUrl != null) {
callMethod = XposedHelpers2.callMethod(glideUrl, "toStringUrl", new Object[0]);
String url = (String) callMethod;
lastName = MimeTypeMap.getFileExtensionFromUrl(url);
}
final String url2 = lastName;
Object callback2 = Proxy.newProxyInstance(callback.getClass().getClassLoader(), callback.getClass().getInterfaces(), new InvocationHandler() { // from class: com.pic.catcher.plugin.GlideCatcherPlugin.3.1
@Override // java.lang.reflect.InvocationHandler
public Object invoke(Object o, Method method, Object[] objects) throws InvocationTargetException, IllegalAccessException {
if ("onDataReady".equals(method.getName())) {
Object iStream = objects[0];
if (iStream instanceof InputStream) {
byte[] data = IOUtil.readToBytes((InputStream) iStream);
PicExportManager.getInstance().exportByteArray(data, url2);
ByteArrayInputStream iStream2 = new ByteArrayInputStream(data);
objects[0] = iStream2;
}
}
return method.invoke(o, objects);
}
});
param.args[1] = callback2;
}
});
}
}
109 changes: 106 additions & 3 deletions app/src/main/java/com/pic/catcher/plugin/PicExportManager.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package com.pic.catcher.plugin;

import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.os.Looper;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;

import com.lu.magic.util.AppUtil;
import com.lu.magic.util.IOUtil;
import com.lu.magic.util.log.LogUtil;
import com.lu.magic.util.thread.AppExecutor;
import com.pic.catcher.util.FileUtils;
import com.pic.catcher.util.Md5Util;
import com.pic.catcher.util.PicUtil;
import com.pic.catcher.util.Regexs;
import com.pic.catcher.util.http.HttpConnectUtil;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.URI;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import kotlin.Unit;
import kotlin.jvm.functions.Function1;

/**
* @author Mingyueyixi
Expand Down Expand Up @@ -56,7 +71,6 @@ public void exportBitmap(Bitmap bitmap) {
if (bitmap == null || bitmap.isRecycled()) {
return;
}
String tempName = bitmap.hashCode() + ".webp";
FileOutputStream tempStream = null;
try {
exportTempDir.mkdirs();
Expand All @@ -67,7 +81,7 @@ public void exportBitmap(Bitmap bitmap) {
String exportFileName = md5 + ".webp";
File exportFile = new File(exportDir, exportFileName);
if (exportFile.exists()) {
exportFile.deleteOnExit();
return;
}
tempStream = new FileOutputStream(exportFile);
IOUtil.writeByByte(bitmapBytes, tempStream);
Expand All @@ -87,4 +101,93 @@ public void runOnIo(Runnable runnable) {
runnable.run();
}
}
}

private void exportHttpPicUrlIfNeed(String url, String fileEx) {
HttpConnectUtil.request("GET", url, null, null, true,
response -> {
if (!Regexs.PIC_EXT.matcher(fileEx).find()) {
//图片不知道
Map<String, List<String>> headers = response.getHeader();
List<String> contentType = headers.get("Content-Type");
if (contentType == null || contentType.isEmpty()) {
//不能判断是不是图片
return null;
}
String guessFileEx = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType.get(0));
if (!Regexs.PIC_EXT.matcher(guessFileEx).find()) {
//不是图片
return null;
}
exportByteArray(response.getBody(), fileEx);
IOUtil.closeQuietly(response);
return null;
}
byte[] body = response.getBody();
exportByteArray(body, fileEx);
return null;
}
);
}

public void exportUrlIfNeed(String url) {
runOnIo(() -> {
try {
if (TextUtils.isEmpty(url)) {
return;
}
String fileEx = MimeTypeMap.getFileExtensionFromUrl(url);
fileEx = fileEx.toLowerCase(Locale.ROOT);
if (!URLUtil.isHttpUrl(url) || URLUtil.isHttpsUrl(url)) {
exportHttpPicUrlIfNeed(url, fileEx);
} else if (URLUtil.isFileUrl(url)) {
File file = new File(URI.create(url));
if (Regexs.PIC_EXT.matcher(fileEx).find()) {
exportBitmapFile(file);
} else {
//判断文件内容是否是图片格式
fileEx = PicUtil.detectImageType(file, "");
if (Regexs.PIC_EXT.matcher(fileEx).find()) {
exportBitmapFile(file);
}
}
}
} catch (Exception e) {
LogUtil.d(e);
}
});

}

public void exportByteArray(final byte[] dataBytes, String lastName) {
if (TextUtils.isEmpty(lastName)) {
lastName = PicUtil.detectImageType(dataBytes, "bin");
}
if (!lastName.startsWith(".")) {
lastName = "." + lastName;
}
final String finalLastName = lastName;
runOnIo(() -> {
if (dataBytes == null || dataBytes.length == 0) {
return;
}
FileOutputStream fileOutputStream = null;
try {
String fileName = Md5Util.get(dataBytes) + finalLastName;
File file = new File(this.exportDir, fileName);
if (file.exists()) {
return;
}
this.exportDir.mkdirs();
fileOutputStream = new FileOutputStream(file);
IOUtil.writeByByte(dataBytes, fileOutputStream);
LogUtil.d("exportByteArray: ", file);
} catch (Throwable th) {
th.printStackTrace();
} finally {
IOUtil.closeQuietly(fileOutputStream);
}
});
}


}
40 changes: 40 additions & 0 deletions app/src/main/java/com/pic/catcher/plugin/WebViewCatcherPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.pic.catcher.plugin;

import android.content.Context;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.lu.lposed.api2.XC_MethodHook2;
import com.lu.lposed.api2.XposedHelpers2;
import com.lu.lposed.plugin.IPlugin;
import com.lu.magic.util.log.LogUtil;

import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
* @author Lu
* @date 2024/10/13 14:48
* @description webview 网页图片加载获取
*/
public class WebViewCatcherPlugin implements IPlugin {

@Override
public void handleHook(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
XposedHelpers2.findAndHookMethod(
WebViewClient.class,
"onLoadResource",
WebView.class,
String.class,
new XC_MethodHook2() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
WebView webView = (WebView) param.args[0];
String url = (String) param.args[1];
LogUtil.d("WebViewClient.onLoadResource", "url=" + url);
PicExportManager.getInstance().exportUrlIfNeed(url);
}
}
);
}
}
77 changes: 77 additions & 0 deletions app/src/main/java/com/pic/catcher/util/PicUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.pic.catcher.util;


import com.lu.magic.util.IOUtil;
import com.lu.magic.util.log.LogUtil;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.ByteBuffer;


public abstract class PicUtil {
public static String detectImageType(File file, String fallback) {
if (!file.exists()) {
return fallback;
}
String result = fallback;
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
result = detectImageType(inputStream, fallback);
} catch (FileNotFoundException e) {
LogUtil.d(e);
} finally {
IOUtil.closeQuietly(inputStream);
}
return result;
}

public static String detectImageType(InputStream inputStream, String fallback) {
byte[] headerBytes = null;
try {
headerBytes = new byte[12];
inputStream.read(headerBytes);
} catch (Exception e) {
e.printStackTrace();
}
if ((headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8) {
return "jpg";
} else if (headerBytes[0] == (byte) 0x89 && headerBytes[1] == (byte) 0x50 && headerBytes[2] == (byte) 0x4E && headerBytes[3] == (byte) 0x47) {
return "png";
} else if (headerBytes[0] == (byte) 0x47 && headerBytes[1] == (byte) 0x49 && headerBytes[2] == (byte) 0x46 && headerBytes[3] == (byte) 0x38) {
return "gif";
} else if (isWebP(headerBytes)) {
return "webp";
}
return fallback;
}

public static String detectImageType(byte[] data, String fallback) {
if (data == null || data.length < 8) {
return fallback;
}
return detectImageType(new ByteArrayInputStream(data), fallback);
}

public static boolean isWebP(byte[] data) {
if (data == null || data.length < 12) {
return false;
}

// 检查前4个字节是否为 "RIFF"
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') {
return false;
}

// 检查第8到第11个字节是否为 "WEBP"
if (data[8] != 'W' || data[9] != 'E' || data[10] != 'B' || data[11] != 'P') {
return false;
}

return true;
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/pic/catcher/util/Regexs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.pic.catcher.util;

import java.util.regex.Pattern;

/**
* @author Lu
* @date 2024/10/13 15:03
* @description 正则
*/
public interface Regexs {
/**
* 图片后缀
*/
Pattern PIC_EXT = Pattern.compile("jpg|jpeg|png|gif|webp|png|tif|ico|bmp|jng");
}
Loading

0 comments on commit 8dfda0e

Please sign in to comment.