xposed-okhttp-logger
xposed-okhttp-logger copied to clipboard
Xposed module logging HTTP(S) requests made via OkHttp
Xposed Logger Module for OkHttp
This is a simple module for the Xposed Framework that logs all HTTP(S) requests made via OkHttp. Supports all applications which are not using advanced source code obfuscation techniques (name mangling as done by e.g. ProGuard is fine).
Building
Built with gradle and most recent version of Android SDK build tooks v29.0.0 (as of 2019/10). To build, run the following command in the root directory of this repository:
./gradlew build
The generated APK is in RequestLogger/build/outputs/apk/<buildtype>/.
Once installed, it should show up as RequestLogger in the Xposed Installer.
The module's only dependency is Xposed Bridge.
Tested on an Android Oreo 8.0.0 virtual device (x86) with Xposed Framework v90-beta3.
Internals
Placing the Hooks
There are several points during the lifetime of a OkHttp request at which we can hook in order to log each HTTP(S) request:
- Hook
okhttp3.Requestconstructor orokhttp3.Request.Buildermethods: might yield some false positives, since Request objects can be created without issuing an HTTP call. Further,okhttp3.Requestobjects can be (re)created several times by interceptors before the actual request is issued, leading to duplicates. - Hook
okhttp3.OkHttpClient.newCall(Request r): similar to above, but guarantees to log each request only once. - Hook internal implementations of
okhttp3.Call.execute()andokhttp3.Call.enqueue(Callback c)byokhttp3.RealCall: logs all HTTP(s) requests only when they are scheduled to be executed. This also includes calls that were only scheduled but not executed because the app terminated before the call was issued. - Hook
okhttp3.Responseconstructor orokhttp3.Buildermethods: has the same effect as above option, but only logs when a request is finished (successfully or not). Further, similar to the first option,okhttp3.Responseobjects can be (re)created several times after a request has been issued, leading to duplicates.
This module implements the 3rd option, hooking okhttp3.RealCall.execute() and okhttp3.RealCall.enqueue(Callback c) since it yields the most accurate results and no duplicates.
It obtains the okhttp3.Request object from the issued call by reading the call's originalRequest field.
The URL of the request can then be read from the url field of the request object.
All detected requests are logged with timestamps in a logfile _requests.log in the private storage of each application.
Overcoming name mangling obfuscation
This module also implements a crude heuristic to find the required classes and field/method names when all identifiers from the original source code have been replaced with random ones. Specifically, this module is able to detect the standard name mangling scheme where each identifier is replaced with the shortest alphabetical string (in ascending order) such that no collusion occur. The implemented deobfuscator can identify obfuscated OkHttp code as follows:
-
Enumerate all classes and interfaces of the root
okhttp3package by generating the class names until we find one Xposed cannot find. In the tested OkHttp versions this was the case atokhttp.ae. -
Identify the
okhttp3.Callinterface, depending on OkHttp version:-
newer ones: based on its factory interface subclass and this class' only method, which accepts some
okhttp3.*class as parameter and creates anokhttp3.Callobject. In the same step we can also identify theokhttp3.Requestclass and theCall.request()getter. -
old ones (< v2.7.1 from 01/2016): based on signature of methods in the interface (taking one argument max, one method can throw an exception). The
okhttp3.Requestclass will be resolved later in step 6 when we know theokhttp3.RealCallclass.
-
-
Find the
okhttp3.Call.execute()andokhttp3.Call.enqueue(Callback c)methods among the methods in theCallinterface based on their signature. -
Find the
okhttp3.RealCallclass, which should be the only class implementing theokhttp3.Callinterface. -
Get the implementations of
execute()andenqueue(Callback c)inRealCall, which have the same name as their abstract declaration in theCallinterface. -
(Old versions of OkHttp only: Find the
okhttp3.Requestclass based on the fact it has no superclass and the field types in theRealCallclass). Then, find theoriginalRequestfield of theRealCallclass based on its type. -
Find the
okhttp3.HttpUrlclass based on one of its method's return typejava.net.URI. No other class in the package uses this class. Given theHttpUrlclass we can finally find theurlfield inokhttp3.RealCall. In order to reduce the number of classes we have to check, we can limit the search to all class types of the fields inokhttp3.Request. This means that we will only searchokhttp3.CacheControl,okhttp3.Headers,okhttp3.HttpUrlandokhttp3.RequestBody.
Tricking this module
After a RealCall instance is created from a Request object using okhttp3.OkHttpClient.newCall(Request r), interceptors registered with the client are able to transform the call before it is sent on the network (see Interceptors Documentation).
This means that an interceptor could change the requested URL after a RealCall has been created and before the request is sent.
This could be abused to make this module log a different URL than the one which is actually requested.
The only way to prevent this is to inject a network interceptor after the last user-supplied network interceptor and before the okhttp3.internal.http.CallServerInterceptor, which issues the actual request on the network. (see relevant code, lines 176-197).
Alternatively to injecting a network interceptor, one could also hook the okhttp3.internal.http.RealInterceptorChain.proceed methods to log the request when the CallServerInterceptor is detected.
If requests served from OkHttp's cache should be logged as well, one also needs to log requested URLs between the last user-supplied application interceptor and the OkHttp core.