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).
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.
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.Request
constructor orokhttp3.Request.Builder
methods: might yield some false positives, since Request objects can be created without issuing an HTTP call. Further,okhttp3.Request
objects 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.Response
constructor orokhttp3.Builder
methods: 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.Response
objects 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.
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
okhttp3
package 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.Call
interface, 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.Call
object. In the same step we can also identify theokhttp3.Request
class 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.Request
class will be resolved later in step 6 when we know theokhttp3.RealCall
class.
-
-
Find the
okhttp3.Call.execute()
andokhttp3.Call.enqueue(Callback c)
methods among the methods in theCall
interface based on their signature. -
Find the
okhttp3.RealCall
class, which should be the only class implementing theokhttp3.Call
interface. -
Get the implementations of
execute()
andenqueue(Callback c)
inRealCall
, which have the same name as their abstract declaration in theCall
interface. -
(Old versions of OkHttp only: Find the
okhttp3.Request
class based on the fact it has no superclass and the field types in theRealCall
class). Then, find theoriginalRequest
field of theRealCall
class based on its type. -
Find the
okhttp3.HttpUrl
class based on one of its method's return typejava.net.URI
. No other class in the package uses this class. Given theHttpUrl
class we can finally find theurl
field 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.HttpUrl
andokhttp3.RequestBody
.
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.