如何利用mitmproxy来批量修改Android中HTTP流量

背景

有时候我们常常在调试Android程序时,常常需要对API返回的response进行修改,以达到测试特殊情况的目的。比如有时候我们需要某个字符串显示超过某个限制来看看此时Android上面显示是否正常,有时候我们需要特定高度的图片来显示页面是否异常。

对于这些场景,其实通过调试工具mitmproxy就能完成。利用mitmproxy可以让我们的APP开发调试事半功倍。对于从来没有听说过mitmproxy的朋友,推荐查看我之前的两篇博客:如何调试 Android 上 HTTP(S) 流量 和 mitmproxy基础实践教程

之前所讲的关于mitmproxy的使用教程都是比较基础的。比如我们可以拦截某些特定的request,然后修改这个request的某些属性,有必要还可以修改response的某些属性,这样就可以解决本文开头所讲的场景里面的问题。但是,这样做的效率很低,我在手机上发起某个API请求,然后被电脑上的mitmproxy截获了,然后我更改这个请求返回的response,然后这个response就会到了手机上了。

Mitmproxy的Inline Scripts 特性介绍

这样听起来好像有点麻烦。可以不可以自动的修改这些resquest和respones。当然可以!这就是我们今天要说的主题,mitmproxy还一个非常极客的特性,叫做 Inline Scripts。有了它,我们就不要在手机和电脑上来来回回地调试了。mitmproxy的Inline Scripts可以让我们对通过mitmproxy的所有HTTP(S)流量进行可编程的定制,这里的script指的是python脚本。Inline Scripts是一种靠HTTP中的事件进行驱动的API,这有点类似于Android的Activity的生命周期。Inline Scripts提供了HTTP请求中各个时间点的hook函数,比如HTTP请求启动的时候,request到达mitmproxy的时候,response到达mitmproxy的时候等等。下面我就简单介绍一下Inline Scripts中的一些Event:

Inline Scripts 中常用的Event介绍

  1. start(context, argv)
    HTTP请求开始启动的时候,这个event在所有event之前。
  2. clientconnect(context, root_layer)
    客户端向代理(mitmproxy)建立一个connection的时候,一个connection可以对应多个request。
  3. request(context, flow)
    客户端的HTTP请求被代理(mitmproxy)接收到的时候。flow里面包含了request对象,比如request的方法,request的url,参数等等。
  4. serverconnect(context, server_conn)
    代理(mitmproxy)向服务器端建立一个connection的时候,同理一个connection可以对应多个request。
  5. responseheaders(context, flow)
    当服务器的response header被代理(mitmproxy)接收的时候,这个event在接下来的response event之前。
  6. response(context, flow)
    当服务器的response被代理(mitmproxy)接收的时候,这个event在responseheaders事件之后。
  7. error(context, flow)
    当flow出现异常的时候会产生该事件。比如connection被中断等。
  8. serverdisconnect(context, server_conn)
    当代理(mitmproxy)断开到服务器的连接时
  9. clientdisconnect(context, root_layer)
    当客户端断开到代理(mitmproxy)的连接时
  10. done(context, argv)
    Inline Scripts被关闭的时候。

Inline Scripts 使用示例

 

下面的示例为了说明 mitmproxy 中的 Inline Scripts是如何使用的。具体思路:

  1. 首先我们将已有的相关API 流量通过mitmproxy截获
  2. 将这些API中response中特性属性的value全部改为我们想要的value

假设我们要测试的某个请求返回的数据格式如下:

{"data":[{"key":"value1"},{"key":"value2"},{"key":"value3"}]}

因此接下来我们就可以写相应的python脚本(change.py)了:

from libmproxy.protocol.http import decoded  #来自mitmproxy中的库
from libmproxy.protocol.http import HTTPResponse
import json

#mitmproxy中代理所有HTTP请求的response全部会经过这里
def response(context, flow):
    #如果request的url里面包含了某个关键字(需要按照你的需要设置)
    if 'KEYWORD' in flow.request.pretty_url(hostheader=True):
        #解码请求的response
        with decoded(flow.response):
           #使用json封装response
           body = json.loads(flow.response.content)  
           if body['data']:  
               list = body['data']
               for item in list:
                   if item['key']:
                       #将符合条件的属性进行更改
                       item['key']='value_special'  
           #将更改后的数据重新封装为response 
           flow.response.content = json.dumps(body)

然后咱们再结合之前两篇教程,我们只需执行以下命令就可以完成我们的需求:

./mitmproxy -b YOUR_LOCAL_IP_ADDRESS -p PORT -s change.py

当这个命令行运行的时候,每次手机请求这个api,请求经过mitmproxy的时候,mitmproxy会把请求转发给服务器端,接着服务器端讲response返回给mitmproxy。mitmproxy拿到数据之后,将response中的数据按照前面的python脚本进行了更改,最后这个被修改过的response回到手机上了。

这一切都是自动的,你只需拿着手机发起请求就行了。