xposed实现对okhttp拦截网络

it2024-11-17  4

一开始搜到有几个博客说用拦截器去实现,我也自己摸索下用拦截器

XC_MethodHook xc_BuildHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); try { XpLog.INSTANCE.log(getTag(), "hookAllConstructors.param.thisObject:" + param.thisObject); List interceptors = handleInterceptor(param, classLoader, "interceptors"); if ("okhttp3.OkHttpClient$Builder".equals(param.thisObject.getClass().getName())) { List networkInterceptors = handleInterceptor(param, classLoader, "networkInterceptors"); } } catch (Throwable e) { XposedBridge.log(e); } } }; //hook okhttp对象 Class<?> builderClass = classLoader.loadClass("okhttp3.OkHttpClient$Builder"); XposedBridge.hookAllConstructors(builderClass, xc_BuildHook); XposedBridge.hookAllMethods(builderClass, "build", xc_BuildHook);

通过hook以上2个方法,做得到实例的interceptors成员变量,我们就可以修改拦截器的内容,当时我发现,如果interceptors空的话,我是没办法,好像是这样编,后来我实现编不下去,放弃hook OkHttpClient

后来我发现通过hook RealInterceptorChain,这个类在okhttp源码里面虽然会多次实例化,但是只有一个入口,就算代码混淆,也方便定位找到

XposedBridge.hookAllConstructors(aClassRealInterceptorChain, xc_BuildHook);

写上这代码,我能拿到okhttp默认添加的拦截器,现在我感觉有的活力去编下去了,利用动态代理做了一个对象加进去

private List handleInterceptor(XC_MethodHook.MethodHookParam param, ClassLoader classLoader, String interceptorsStr) { List interceptors = (List) XposedHelpers.getObjectField(param.thisObject, interceptorsStr); XpLog.INSTANCE.log(getTag(), "handleInterceptor.interceptors:" + interceptors); if (interceptors != null) { if (!interceptors.isEmpty()) { int i = interceptors.size() - 1; Object interceptor = interceptors.get(i); XpLog.INSTANCE.log(getTag(), "handleInterceptor.interceptor:" + interceptor.getClass()); //动态代理加进去 InterceptorProxyHandler interceptorProxyHandler = new InterceptorProxyHandler(interceptor); Object mObj = Proxy.newProxyInstance(classLoader, interceptor.getClass().getInterfaces(), interceptorProxyHandler); XpLog.INSTANCE.log(getTag(), "handleInterceptor.mObj:" + mObj); if (mObj != null) { //移除原本的 interceptors.remove(i); //加在最后一个地方 interceptors.add(mObj); } } //这里输出具体拦截器的类的名字 Field interceptorsField = XposedHelpers.findField(param.thisObject.getClass(), interceptorsStr); ParameterizedType type = (ParameterizedType) interceptorsField.getGenericType(); Type[] actualTypeArguments = type.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { XpLog.INSTANCE.log(getTag(), "interceptors actualTypeArgument:" + ((Class) actualTypeArgument).getName()); break; } } return interceptors; }

后来,我开心的编,编啊编,写上我的动态代理对象先

public class InterceptorProxyHandler implements InvocationHandler { final static String TAG = "InterceptorProxyHandler"; private Object mTarget; public InterceptorProxyHandler(Object target) { this.mTarget = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { XpLog.INSTANCE.log(TAG, "invoke.method:" + method.getName()); if ("intercept".equals(method.getName())) { //这里参考 HttpLoggingInterceptor 来实现,如果是混淆,需要自己分析类的每个方法 if (args != null) { XpLog.INSTANCE.log(TAG, "invoke.args:" + Arrays.toString(args)); Object realInterceptorChainObj = args[0]; Class<?> aClassRealInterceptorChain = realInterceptorChainObj.getClass(); //反射获取request对象 Method requestMethod = aClassRealInterceptorChain.getDeclaredMethod("request"); Object invokeRequest = requestMethod.invoke(realInterceptorChainObj); Class<?> aClassRequest = invokeRequest.getClass(); //获取body Method requestBodyMethod = aClassRequest.getDeclaredMethod("body"); Object invokeRequestBody = requestBodyMethod.invoke(invokeRequest); boolean hasRequestBody = invokeRequestBody != null; Method requestConnectionMethod = aClassRealInterceptorChain.getDeclaredMethod("connection"); Object invokeConnection = requestConnectionMethod.invoke(realInterceptorChainObj); Object protocol = invokeConnection != null ? invokeConnection.getClass().getDeclaredMethod("protocol").invoke(invokeConnection) : "http/1.1"; Method methodMethod = aClassRequest.getDeclaredMethod("method"); Object invokeMethod = methodMethod.invoke(invokeRequest); Method urlMethod = aClassRequest.getDeclaredMethod("url"); Object invokeUrl = urlMethod.invoke(invokeRequest); String requestStartMessage = "--> " + invokeMethod + ' ' + invokeUrl + ' ' + protocol; if (hasRequestBody) { Class<?> invokeBodyClass = invokeRequestBody.getClass(); requestStartMessage += " (" + invokeBodyClass.getDeclaredMethod("contentLength").invoke(invokeRequestBody) + "-byte body)"; } XpLog.INSTANCE.log(TAG, requestStartMessage); if (hasRequestBody) { Class<?> invokeBodyClass = invokeRequestBody.getClass(); // Request body headers are only present when installed as a network interceptor. Force // them to be included (when available) so there values are known. Method contentTypeMethod = invokeBodyClass.getDeclaredMethod("contentType"); Object invokeContentType = contentTypeMethod.invoke(invokeRequestBody); if (invokeContentType != null) { XpLog.INSTANCE.log(TAG, "Content-Type: " + invokeContentType); } Method contentLengthMethod = invokeBodyClass.getDeclaredMethod("contentLength"); Object invokeContentLength = contentLengthMethod.invoke(invokeRequestBody); if (invokeContentLength != null) { XpLog.INSTANCE.log(TAG, "Content-Length: " + invokeContentLength); } Method headersMethod = aClassRequest.getDeclaredMethod("headers"); Object invokeHeaders = headersMethod.invoke(invokeRequest); if (invokeHeaders != null) { Class<?> aClassHeaders = invokeHeaders.getClass(); Method sizeMethod = aClassHeaders.getDeclaredMethod("size"); int invokeSize = (int) sizeMethod.invoke(invokeHeaders); for (int i = 0; i < invokeSize; i++) { Method nameMethod = aClassHeaders.getDeclaredMethod("name", int.class); String name = (String) nameMethod.invoke(invokeHeaders, i); // Skip headers from the request body as they are explicitly logged above. if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { XpLog.INSTANCE.log(TAG, name + ": " + aClassHeaders.getDeclaredMethod("value", int.class).invoke(invokeHeaders, i)); } } //处理结束地方 if (!hasRequestBody) { XpLog.INSTANCE.log(TAG, "--> END " + invokeMethod); } else if (bodyEncoded(invokeHeaders)) { XpLog.INSTANCE.log(TAG, "--> END " + invokeMethod + " (encoded body omitted)"); } else { //逆向看某app这里 okio做了混淆,所以要特殊处理 // 混淆保留了这个,所以能看出是什么类 // /* compiled from: Buffer */ Class<?> aClassBuffer = mTarget.getClass().getClassLoader().loadClass("okio.c");//okio.Buffer Object objBuffer = aClassBuffer.newInstance(); ///* compiled from: BufferedSink */ Class<?> aClassBufferedSink = mTarget.getClass().getClassLoader().loadClass("okio.d"); Method writeToMethod = invokeBodyClass.getDeclaredMethod("writeTo", aClassBufferedSink); writeToMethod.invoke(invokeRequestBody, objBuffer); Method readStringMethod = aClassBuffer.getDeclaredMethod("a", Charset.class);//readString String invokeReadString = (String) readStringMethod.invoke(objBuffer, Charset.forName("UTF-8")); XpLog.INSTANCE.log(TAG, "request params:" + invokeReadString); XpLog.INSTANCE.log(TAG, "--> END " + invokeMethod + " (" + invokeContentLength + "-byte body)"); } } } } } } catch (Throwable e) { XpLog.INSTANCE.log(e); } Object invoke = method.invoke(mTarget, args); return invoke; } private boolean bodyEncoded(Object invokeHeaders) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { String contentEncoding = (String) invokeHeaders.getClass().getDeclaredMethod("get", String.class).invoke(invokeHeaders, "Content-Encoding"); return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); } }

把请求编好,但是发现去解析响应的时候,代码突然编不下去了,okhttp里面做了判断抛了异常有点难受,我在反射proceed这个方法 ,okhttp给我抛了AssertionError,源码我找了下看,还是大哥RealInterceptorChain这个类

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (this.index >= this.interceptors.size()) { throw new AssertionError(); } else { ++this.calls; if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must retain the same host and port"); } else if (this.httpCodec != null && this.calls > 1) { throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must call proceed() exactly once"); } else { RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request); Interceptor interceptor = (Interceptor)this.interceptors.get(this.index); Response response = interceptor.intercept(next); if (httpCodec != null && this.index + 1 < this.interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } else if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } else { return response; } } } }

一开始想着我能不能跟拦截器那样动态代理,翻了翻代码,发现这对象每次都new,不好hook,算了,只能这样了

心想RealInterceptorChain这类,整个就唯独一个类,就算混淆也好定位,那我来hook RealInterceptorChain 的proceed方法吧

//hook RealInterceptorChain /** * public Call newCall(Request request) { * return new RealCall(this, request, false); * } * * * public Response getResponseWithInterceptorChain() throws IOException { * ArrayList arrayList = new ArrayList(); * arrayList.addAll(this.client.interceptors()); * arrayList.add(this.retryAndFollowUpInterceptor); * arrayList.add(new BridgeInterceptor(this.client.cookieJar())); * arrayList.add(new CacheInterceptor(this.client.internalCache())); * arrayList.add(new ConnectInterceptor(this.client)); * if (!this.forWebSocket) { * arrayList.addAll(this.client.networkInterceptors()); * } * arrayList.add(new CallServerInterceptor(this.forWebSocket)); * return new RealInterceptorChain(arrayList, null, null, null, 0, this.originalRequest, this, this.client.eventListenerFactory().create(this)).proceed(this.originalRequest); * } */ Class<?> aClassRealInterceptorChain = classLoader.loadClass("okhttp3.internal.http.RealInterceptorChain"); //XposedBridge.hookAllConstructors(aClassRealInterceptorChain, xc_BuildHook); XposedHelpers.findAndHookMethod( aClassRealInterceptorChain, "proceed", classLoader.loadClass("okhttp3.Request"), new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); //入参 Object arg = param.args[0]; StringBuilder netStr = new StringBuilder(); netStr.append("\n"); netStr.append(">>>> start net"); netStr.append("\n"); netStr.append("procee@@@@@@arg.getClass:").append(arg.getClass()); netStr.append("\n"); netStr.append("proceed@@arg:").append(arg); Class<?> aClassRequest = arg.getClass(); //获取body Method requestBodyMethod = aClassRequest.getDeclaredMethod("body"); Object invokeRequestBody = requestBodyMethod.invoke(arg); boolean hasRequestBody = invokeRequestBody != null; Method methodMethod = aClassRequest.getDeclaredMethod("method"); Object invokeMethod = methodMethod.invoke(arg); if (hasRequestBody) { Class<?> invokeBodyClass = invokeRequestBody.getClass(); // Request body headers are only present when installed as a network interceptor. Force // them to be included (when available) so there values are known. Method contentTypeMethod = invokeBodyClass.getDeclaredMethod("contentType"); Object invokeContentType = contentTypeMethod.invoke(invokeRequestBody); if (invokeContentType != null) { netStr.append("\n"); netStr.append("Content-Type: ").append(invokeContentType); } Method contentLengthMethod = invokeBodyClass.getDeclaredMethod("contentLength"); Object invokeContentLength = contentLengthMethod.invoke(invokeRequestBody); if (invokeContentLength != null) { netStr.append("\n"); netStr.append("Content-Length: ").append(invokeContentLength); } Method headersMethod = aClassRequest.getDeclaredMethod("headers"); Object invokeHeaders = headersMethod.invoke(arg); if (invokeHeaders != null) { Class<?> aClassHeaders = invokeHeaders.getClass(); Method sizeMethod = aClassHeaders.getDeclaredMethod("size"); int invokeSize = (int) sizeMethod.invoke(invokeHeaders); for (int i = 0; i < invokeSize; i++) { Method nameMethod = aClassHeaders.getDeclaredMethod("name", int.class); String name = (String) nameMethod.invoke(invokeHeaders, i); // Skip headers from the request body as they are explicitly logged above. if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { netStr.append("\n"); netStr.append(name).append(": ").append(aClassHeaders.getDeclaredMethod("value", int.class).invoke(invokeHeaders, i)); } } //处理结束地方 if (!hasRequestBody) { netStr.append("\n"); netStr.append("--> END ").append(invokeMethod); } else if (bodyEncoded(invokeHeaders)) { netStr.append("\n"); netStr.append("--> END ").append(invokeMethod).append(" (encoded body omitted)"); } else { //逆向看某app这里 okio做了混淆,所以要特殊处理 // 混淆保留了这个,所以能看出是什么类 // /* compiled from: Buffer */ Class<?> aClassBuffer = null; try { aClassBuffer = classLoader.loadClass("okio.Buffer");//okio.Buffer } catch (Exception e) { aClassBuffer = classLoader.loadClass("okio.c");//okio.Buffer } Object objBuffer = aClassBuffer.newInstance(); ///* compiled from: BufferedSink */ Class<?> aClassBufferedSink = null; try { aClassBufferedSink = classLoader.loadClass("okio.BufferedSink");//okio.BufferedSink } catch (Exception e) { aClassBufferedSink = classLoader.loadClass("okio.d");//okio.BufferedSink } Method writeToMethod = invokeBodyClass.getDeclaredMethod("writeTo", aClassBufferedSink); writeToMethod.invoke(invokeRequestBody, objBuffer); Method readStringMethod; try { readStringMethod = aClassBuffer.getDeclaredMethod("readString", Charset.class);//readString } catch (Exception e) { readStringMethod = aClassBuffer.getDeclaredMethod("a", Charset.class);//readString } String invokeReadString = (String) readStringMethod.invoke(objBuffer, Charset.forName("UTF-8")); netStr.append("\n"); netStr.append("request params:").append(invokeReadString); netStr.append("\n"); netStr.append("--> END ").append(invokeMethod).append(" (").append(invokeContentLength).append("-byte body)"); } } } //出参 Object result = param.getResult(); if (result != null) { netStr.append("\n"); netStr.append("proceed@@result:").append(result); Class<?> aClassResponse = result.getClass(); Method headersMethod = aClassResponse.getDeclaredMethod("headers"); Object invokeHeaders = headersMethod.invoke(result); boolean isGzip = false; if (invokeHeaders != null) { Class<?> aClassHeaders = invokeHeaders.getClass(); Method sizeMethod = aClassHeaders.getDeclaredMethod("size"); int invokeSize = (int) sizeMethod.invoke(invokeHeaders); for (int i = 0; i < invokeSize; i++) { Method nameMethod = aClassHeaders.getDeclaredMethod("name", int.class); String name = (String) nameMethod.invoke(invokeHeaders, i); String value = (String) aClassHeaders.getDeclaredMethod("value", int.class).invoke(invokeHeaders, i); if ("Content-Encoding".equalsIgnoreCase(name) && "gzip".equals(value)) { //标记下数据是否压缩 isGzip = true; } netStr.append("\n"); netStr.append(name).append(": ").append(value); } } Method bodyResponseMethod = aClassResponse.getDeclaredMethod("body"); Object invokeResponseBody = bodyResponseMethod.invoke(result); Class<?> aClassResponseBody = invokeResponseBody.getClass(); Method sourceMethod = aClassResponseBody.getDeclaredMethod("source"); Object invokeBufferedSource = sourceMethod.invoke(invokeResponseBody); //okio.e BufferedSource Class<?> aClassBufferedSource = invokeBufferedSource.getClass(); //b request Method requestMethod;//request try { requestMethod = aClassBufferedSource.getDeclaredMethod("request", long.class);//request } catch (Exception e) { requestMethod = aClassBufferedSource.getDeclaredMethod("b", long.class);//request } requestMethod.invoke(invokeBufferedSource, Long.MAX_VALUE); // c b() buffer Method bufferMethod;//buffer try { bufferMethod = aClassBufferedSource.getDeclaredMethod("buffer");//buffer } catch (Exception e) { bufferMethod = aClassBufferedSource.getDeclaredMethod("b");//buffer } Object invokeBuffer = bufferMethod.invoke(invokeBufferedSource); Class<?> aClassBuffer = invokeBuffer.getClass(); Method cloneMethod = aClassBuffer.getDeclaredMethod("clone"); Object invoke = cloneMethod.invoke(invokeBuffer); Class<?> aClass = invoke.getClass(); Method readString = null;//readString try { readString = aClass.getDeclaredMethod("readString", Charset.class);//readString } catch (Exception e) { readString = aClass.getDeclaredMethod("a", Charset.class);//readString } String repResult = (String) readString.invoke(invoke, Charset.forName("UTF-8")); netStr.append("\n"); netStr.append("rep result:").append(isGzip ? new String(uncompress(repResult.getBytes())) : repResult); } netStr.append("\n"); netStr.append(">>>> end net"); XpLog.INSTANCE.log(getTag(), netStr.toString()); } });

这样一hook,我对所有app只要用okhttp我都能正常输出出入参数了,方便分析人家app,但是存在一个问题,出入的参数可能会加密或者压缩之类,这个就自己研究吧

最后会用到这2个方法

private boolean bodyEncoded(Object invokeHeaders) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { String contentEncoding = (String) invokeHeaders.getClass().getDeclaredMethod("get", String.class).invoke(invokeHeaders, "Content-Encoding"); return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); } public static byte[] uncompress(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(bytes); try { GZIPInputStream ungzip = new GZIPInputStream(in); byte[] buffer = new byte[256]; int n; while ((n = ungzip.read(buffer)) >= 0) { out.write(buffer, 0, n); } return out.toByteArray(); } catch (Exception e) { //e.printStackTrace(); return bytes; } }
最新回复(0)