How to mitmproxy part 2: Modifying Android Network Traffic with mitmproxy
January 30, 2021
In the previous post, I talked about how to inspect your Android network traffic with mitmproxy. In this part 2, I will tell you how you can modify a network response body on your Android app with mitmproxy. You could also change a network request, but, for simplicity, I am not going to do it. I'll leave the rest for you to research on your own.
For a quick recap, mitmproxy is a free open-sourced and interactive tool to create an HTTPS proxy that behaves like a man-in-the-middle attack. It's similar to other application such as Charles, Fiddler, etc. There are two ways we can modify the network traffic with mitmproxy:
- Using intercept/breakpoint from the browser.
- Using addons.
I prefer to use the addon feature because you don't need to modify it manually every time a hit request is matched. Using addons also eliminates the chance of getting a timeout connection because we don't have to pause the request/response for too long and exceed the timeout threshold.
Before creating the addon, I recommend you to have basic knowledge of Python programming language. Why? Because an addon file is basically a Python file. In fact, mitmproxy is built on top of Python. You can check it on its Github page.
What is mitmproxy Addons
Mitmproxy addons is a feature that lets you listen to network events such as requests and responses. It allows you to do some action inside each event, like modifying the headers, changing the response body, etc.
Addons are a potent part of mitmproxy. I stated explicitly in part 1 mitmproxy addon is the reason why I choose mitmproxy among other proxy tools.
Example Use Case
I think it's better to have a scenario as to when I need to use mitmproxy addons. I will be using my android-mvvm-coroutine app as an example. Say that I want to check a Recyclerview item on the teams
page below. What happened if one of the items has a really long team name? How would it be displayed?
The response would come from this endpoint and we want to match this request in our mitmproxy addon script then change its response:
GEThttps://www.thesportsdb.com/api/v1/json/1/lookup_all_teams.php?id=4328HTTP/2.0
I know you can just hard coded the string then recompile the app or use breakpoint to change the string value before it is displayed. It would be nice to automatically change the string without the need to recompile the app or interrupting the running process.
Creating the Addon Script
Create an addon file anywhere you want. But I prefer to group them in a folder because it would be easier to find and reference them later if you have multiple addon files to execute together. Our addon file would look like this:
from mitmproxy import httpclass ChangeTeam:def __init__(self):pass# mitmproxy eventdef response(self, flow: http.HTTPFlow) -> None:if self.is_request_match(flow):modified_response = open("./responses/long_name_teams.json", "r").read()flow.response.text = modified_responseprint("----------------->Change teams responses<-----------------")def is_request_match(self, flow: http.HTTPFlow) -> bool:request_path = flow.request.pathrequest_query = flow.request.queryreturn "lookup_all_teams" in request_path and request_query["id"] == "4328"addons = [ChangeTeam()]
You can check the Addon example above on this GitHub repo I just created. An Addon is basically a Python class. In the Addon script above, we listen to the http/s response indicated by the def response(..)
function. Every time a response is given by a server from any whitelisted hosts, this function will get executed. Before we modify the response body, we need to check or verify if it is the request that it's the response we want to change. If not, all the network requests will get their response modified, and we don't want that.
Inside the response
function, we create a simple if statement to check if the request path has a substring of lookup_all_teams
and has query parameter id
with value 4328
. As you may have noticed, I created a new function def is_request_match(..)
that returns a bool
data type to separate the implementation and for readability. If the condition is true, it will execute the function inside, which modifies the response body.
In the Addon script above, I put the desired response I want in a .json
file. It then read the JSON file and replaces the entire response body with it. This eases me if I want to change something in the response. I just change the JSON file content without touching the code. You can also log some messages with the python print
function, and if the line gets executed, the message will appear on the terminal.
Running the Addon Script
We have our script ready, and it's time to load it to the mitmproxy process. To load the script, we just need to pass it as a command-line argument with the value of its relative or absolute path like so:
mitmweb -s ./change_teams.py
Remember from Part 1 we can ignore other hosts with RegEx? The full command would look like this
mitmweb --ignore-hosts '^(?![0-9\.]+:)(?!([^\.:]+\.)*thesportsdb\.(com|net):)' -s ./change_teams.py
Every time a server responds to the client and matches with our validator, the response will get modified automatically, and a message got printed on the terminal.
Working with Multiple Addon Scripts
You can load multiple Addon scripts to the process. The command line accepts multiple script arguments:
mitmweb -s {addon_1.py} -s {addon_2.py} -s {addon_3.py}
Modifying the Addon Script
Say you want to modify the Addon script, but the mitmweb process is still running, is it ok to modify it or should we stop the process and re-run it? Well, it turns out it's ok to change the loaded Addon script while the process is still running. Mitmproxy has a hot-reload mechanism, which means if you modify the loaded Addon script and then save it, it will immediately be reloaded without restarting the proxy server.
Note that the hot-reload feature is only working on the loaded Addon script. If you create a new Addon script, you need to restart the proxy server with an additional argument.
Exploring API Documentation
You are probably wondering what attributes you can get from the mitmproxy.http.HTTPFlow
class. It's pretty complete actually, you can get the headers, host address, request body in POST request, etc. Unfortunately, they don't have a documentation page for it. They recommend using pydoc in your own local computer to explore the API from command-line by using this command:
pydoc mitmproxy.http
On the other hand, I like to explore the API on the browser instead of the terminal. You can do so using pydoc
by starting up the local documentation server with this command:
pydoc -p 8085
Notice that I am using the port number 8085
. Suppose for some reason, the port is not available on your computer or used by another program. In that case, you can use another port number or use this command to let pydoc
automatically choose a port number.
pydoc -b
After the server is up and running, you can visit the documentation page on your browser. In our case it's http://localhost:8085/mitmproxy.http.html
. If you are using Python 3, change the binary command to pydoc3
instead of pydoc
.
Conclusion
mitmproxy
is a powerful tool for debugging network activity on an app, specifically on this post series, an Android app. It helps us to test several edge cases and making us less dependent on the back-end side while testing. It also could be used on other platforms such as iOS, macOS, Windows, etc. As long as the proxy server and network certification configurations are done right, you're good to go. mitmproxy
could also be used as a tool in black-box testing.