Skip to content

Commit

Permalink
[add] 拦截okhttp请求图片
Browse files Browse the repository at this point in the history
  • Loading branch information
vitvm committed Oct 27, 2024
1 parent 8dfda0e commit 9b0db12
Show file tree
Hide file tree
Showing 58 changed files with 1,772 additions and 46 deletions.
10 changes: 5 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ this.afterEvaluate {

android {
namespace 'com.pic.catcher'
compileSdk 35
compileSdk COMPILE_SDK_VERSION.toInteger()

defaultConfig {
applicationId "com.pic.catcher"
minSdk 24
targetSdk 35
targetSdk TARGET_SDK_VERSION.toInteger()
versionCode 1
versionName "1.0-gaoji"

Expand All @@ -85,7 +85,7 @@ android {
// }
signingConfigs {
alpha {
storeFile file("${rootDir}/app/key-store-test.jks")
storeFile file("${rootDir}/keys/key-store-test.jks")
storePassword "123456"
keyAlias "piccatch"
keyPassword "123456"
Expand All @@ -94,8 +94,8 @@ android {
buildTypes {
debug {
debuggable true
minifyEnabled true
shrinkResources true
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro','proguard-rules-debug.pro'
signingConfig signingConfigs.alpha
}
Expand Down
9 changes: 3 additions & 6 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,17 @@
#}

-keepclassmembers class * extends com.pic.catcher.base.BaseFragment {
<init>();
<init>();
}
-keepclassmembers class * extends com.pic.catcher.base.BaseViewModel {
<init>();
}
-keepclassmembers class * extends com.pic.catcher.base.BaseFragment {
<init>(*);
<init>();
}
-keepclassmembers class * extends com.pic.catcher.base.BaseViewModel {
<init>(*);
<init>();
}
-keepclassmembers class * extends com.lu.lposed.plugin.IPlugin {
<init>(*);
}
-keepclassmembers class * extends com.lu.lposed.plugin.IPlugin {
<init>();
}
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.PicCatcher"
tools:targetApi="31">
android:theme="@style/Theme.PicCatcher">
<meta-data
android:name="xposedmodule"
android:value="true" />
Expand Down
39 changes: 23 additions & 16 deletions app/src/main/java/com/pic/catcher/MainHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
import com.lu.magic.util.log.SimpleLogger;
import com.pic.catcher.plugin.BitmapCatcherPlugin;
import com.pic.catcher.plugin.GlideCatcherPlugin;
import com.pic.catcher.plugin.OKHttpPlugin;
import com.pic.catcher.plugin.WebViewCatcherPlugin;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -87,7 +89,11 @@ public void onLog(int level, @NonNull Object[] objects) {
return (short) 0;
}
if (BuildConfig.DEBUG) {
LogUtil.w("setOnErrorReturnFallback", throwable);
if (throwable instanceof InvocationTargetException) {
LogUtil.w("setOnErrorReturnFallback", ((InvocationTargetException) throwable).getTargetException());
} else {
LogUtil.w("setOnErrorReturnFallback2", throwable);
}
}
return null;
});
Expand Down Expand Up @@ -119,18 +125,18 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// }
// );
//
unhook = XposedHelpers2.findAndHookMethod(
Instrumentation.class.getName(),
lpparam.classLoader,
"callApplicationOnCreate",
Application.class.getName(),
new XC_MethodHook2() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
initPlugin((Context) param.args[0], lpparam);
}
}
);
// unhook = XposedHelpers2.findAndHookMethod(
// Instrumentation.class.getName(),
// lpparam.classLoader,
// "callApplicationOnCreate",
// Application.class.getName(),
// new XC_MethodHook2() {
// @Override
// protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// initPlugin((Context) param.args[0], lpparam);
// }
// }
// );
initUnHookList.add(unhook);
//
// XposedHelpers2.findAndHookMethod(
Expand Down Expand Up @@ -182,9 +188,10 @@ private void initSelfPlugins(Context context, XC_LoadPackage.LoadPackageParam lp
private void initTargetPlugins(Context context, XC_LoadPackage.LoadPackageParam lpparam) {
//目前生成的plugin都是单例的
PluginRegistry.register(
BitmapCatcherPlugin.class,
GlideCatcherPlugin.class,
WebViewCatcherPlugin.class
// BitmapCatcherPlugin.class,
// GlideCatcherPlugin.class,
// WebViewCatcherPlugin.class
OKHttpPlugin.class
).handleHooks(context, lpparam);


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

import android.content.Context;
import android.text.TextUtils;
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.AppUtil;
import com.lu.magic.util.log.LogUtil;
import com.pic.catcher.ClazzN;
import com.pic.catcher.util.Regexs;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.util.Arrays;

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

/**
* @author Lu
* @date 2024/10/26 23:16
* @description
*/
public class OKHttpPlugin implements IPlugin {
@Override
public void handleHook(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
handleHookOkHttp3(context, loadPackageParam);
// handleHookAndroidOkHttp(context, loadPackageParam);
}

private void handleHookAndroidOkHttp(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
XposedHelpers2.findAndHookMethod(
ClazzN.from("com.android.okhttp.internal.http.HttpEngine"),
"readResponse",
new XC_MethodHook2() {

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object response = XposedHelpers2.getObjectField(param.thisObject, "userResponse");

LogUtil.d("response", response);

String contentType = (String) XposedHelpers2.callMethod(response, "header", "Content-Type");
if (TextUtils.isEmpty(contentType)) {
LogUtil.d("content-type is empty");
return;
}
String guessFileEx = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
if (TextUtils.isEmpty(guessFileEx) || !Regexs.PIC_EXT.matcher(guessFileEx).find()) {
//不是图片
return;
}
Object body = XposedHelpers2.callMethod(response, "body");
LogUtil.d("body", body);
if (body == null) {
return;
}
//com.android.okhttp.internal.http.Http1xStream$FixedLengthSource
Object source = XposedHelpers2.callMethod(body, "source");
LogUtil.d("bufferedSource", source);
if (source == null) {
return;
}
//读取二进制
Object bytes = XposedHelpers2.callMethod(source, "readByteArray");
// Object buffer = XposedHelpers2.getObjectField(source, "buffer");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write((byte[]) bytes);
Object sink = XposedHelpers2.callStaticMethod(ClazzN.from("com.android.okhttp.okio.Okio"), "sink", outputStream);
Object buffer = XposedHelpers2.callMethod(ClazzN.from("com.android.okhttp.okio.Okio"), "buffer", sink);
XposedHelpers2.setObjectField(body, "buffer", buffer);

//com.android.okhttp.internal.http.Http1xStream$FixedLengthSource
// Object sourceField = XposedHelpers2.getObjectField(source, "source");
// XposedHelpers2.callMethod(buffer, "read", bytes);
//读完就废了,所以复制一个给原先的结果

// LogUtil.d("readByteArray is ok", bytes + "", sourceField);
if (bytes != null) {
LogUtil.d("readByteArray is bytes. start download");
PicExportManager.getInstance().exportByteArray((byte[]) bytes, guessFileEx);
}
}
}
);

}

private void handleHookOkHttp3(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
ClassLoader clazzLoader = AppUtil.getClassLoader();
LogUtil.d("OKHttpPlugin", "handleHook", clazzLoader);
Class<?> realCallClazz = ClazzN.from("okhttp3.RealCall", clazzLoader);
if (realCallClazz == null) {
LogUtil.d("can not find RealCall class");
return;
}
XposedHelpers2.findAndHookMethod(
realCallClazz,
// "execute",
"getResponseWithInterceptorChain",
new XC_MethodHook2() {

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object response = param.getResult();
String contentType = (String) XposedHelpers2.callMethod(response, "header", "Content-Type");
if (TextUtils.isEmpty(contentType)) {
LogUtil.d("content-type is empty");
return;
}
String guessFileEx = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
if (TextUtils.isEmpty(guessFileEx) || !Regexs.PIC_EXT.matcher(guessFileEx).find()) {
//不是图片
return;
}

// 制造一个新的body,不影响原始数据读写
Object response2 = XposedHelpers2.callMethod(response, "peekBody", Long.MAX_VALUE);
if (response2 == null) {
LogUtil.d("response2 is null");
return;
}
Object bytes = XposedHelpers2.callMethod(response2, "bytes");
if (bytes instanceof byte[]) {
LogUtil.d("readByteArray is bytes. start download");
PicExportManager.getInstance().exportByteArray((byte[]) bytes, guessFileEx);
}
}
}
);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,24 @@ public void exportByteArray(final byte[] dataBytes, String lastName) {
final String finalLastName = lastName;
runOnIo(() -> {
if (dataBytes == null || dataBytes.length == 0) {
LogUtil.d("exportByteArray: dataBytes is empty");
return;
}
FileOutputStream fileOutputStream = null;
try {
String fileName = Md5Util.get(dataBytes) + finalLastName;
File file = new File(this.exportDir, fileName);
if (file.exists()) {
LogUtil.d("exportByteArray: file already exists:", file);
return;
}
this.exportDir.mkdirs();
fileOutputStream = new FileOutputStream(file);
IOUtil.writeByByte(dataBytes, fileOutputStream);
LogUtil.d("exportByteArray: ", file);
} catch (Throwable th) {
th.printStackTrace();
// th.printStackTrace();
LogUtil.d(th);
} finally {
IOUtil.closeQuietly(fileOutputStream);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.pic.catcher.plugin;

import android.content.Context;
import android.webkit.WebView;

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 com.pic.catcher.ClazzN;

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

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

@Override
public void handleHook(Context context, XC_LoadPackage.LoadPackageParam loadPackageParam) {
Class<?> webViewClientClazz = ClazzN.from("com.tencent.smtt.sdk.WebViewClient");
Class<?> webViewClazz = ClazzN.from("com.tencent.smtt.sdk.WebView");
if (webViewClientClazz == null || webViewClazz == null) {
return;
}
XposedHelpers2.findAndHookMethod(
webViewClientClazz,
"onLoadResource",
webViewClazz,
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);
}
}
);
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/pic/catcher/util/ext/AnyX.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fun TextView.setTextColorTheme(color: Int) {
}


inline fun <T> T?.takeNotNull(fallback: T): T {
fun <T> T?.takeNotNull(fallback: T): T {
if (this == null) {
return fallback
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<string name="app_name">PicCatcher</string>
<string name="app_name">图片捕手</string>
<string name="app_xposed_description">App图片抓取器</string>
<string name="module_not_active">模块未激活</string>
<string name="module_have_active">模块已激活</string>
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ buildscript {
}

plugins {
id 'com.android.application' version '8.5.2' apply false
id 'com.android.library' version '8.5.2' apply false
id 'com.android.application' version '8.6.0' apply false
id 'com.android.library' version '8.6.0' apply false
id 'org.jetbrains.kotlin.android' version '2.0.20' apply false
}
9 changes: 7 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#?debug apk???????
android.injected.testOnly = false
#设置debug apk也可以点击直接安装
android.injected.testOnly=false

TARGET_SDK_VERSION=35
COMPILE_SDK_VERSION=35

#org.gradle.java.home=C\:\\Programing\\Android\\android-studio\\jbr
File renamed without changes.
Loading

0 comments on commit 9b0db12

Please sign in to comment.