一开始搜到有几个博客说用拦截器去实现,我也自己摸索下用拦截器
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;
}
}