mitmproxy教程

mitmproxy是一款类似于wireshark,fiddler的功能强大的抓包工具,它包括以下三个功能:

  1. mitmproxy    HTTP和HTTPS的交互式中间人代理,带有控制台界面(windows不支持mitmproxy的控制台界面)
  2. mitmdump    mitmproxy的命令行版本,提供Python API
  3. mitmweb    基于Web的mitmproxy接口

本文主要以实现自动化的抓包(中间人代理)程序为目的。主要任务可以分为三个部分:

  1. 启动mitmproxy
  2. 自动配置系统代理
  3. 自动安装CA证书

启动mitmproxy

安装mitmproxy

pip install mitmproxy

打开cmd直接输入mitmdump

它会默认在8080端口打开一个代理服务,拦截所有的HTTP和HTTPS请求。

我们可以使用mitmdump的API查看或修改HTTP、HTTPS的请求和响应。新建一个test.py文件,代码如下:

class Monitor:
    def request(self, flow):
        flow.request.headers["User-Agent"] = 'mitmproxy'
        ctx.log.info(flow.request.url)

    def response(self, flow):
        ctx.log.info(flow.response.text)


addons = [
    Monitor()
]

使用方法:

mitmdump -s test.py

这是启动mitmproxy的常用方式。难道只能通过命令行调用吗?在以前的版本,mitmproxy提供了libmproxy以便调用扩展,但现在应该是直接合并到了mitmproxy中,所以还有另外一种方式:

from mitmproxy import proxy, options
from mitmproxy.tools.dump import DumpMaster

opts = options.Options(listen_host='127.0.0.1', listen_port=8080)
pconf = proxy.config.ProxyConfig(opts)
m = DumpMaster(opts)
m.server = proxy.server.ProxyServer(pconf)
m.addons.add(Monitor())
m.run()

代码中的m相当于是mitmproxy的主干,给它做好配置,加上代理服务器和前面写的Monitor类,最后调用run方法启动服务。

代理服务成功启动只是第一步,我们还需要配置系统代理和安装证书,虽然在本地可以很轻松地手动完成这两件事,但如果你打算将程序偷偷用在别人电脑上,或者写成客户端软件,必须自动完成这些操作。

自动配置系统代理

这里我使用ctypes模块下的wininet实现windows的系统代理配置,直接上代码。

LPWSTR = POINTER(WCHAR)
HINTERNET = LPVOID

INTERNET_PER_CONN_PROXY_PAC = 4
INTERNET_OPTION_REFRESH = 37
INTERNET_OPTION_SETTINGS_CHANGED = 39
INTERNET_OPTION_PER_CONNECTION_OPTION = 75
INTERNET_PER_CONN_FLAGS = 1


class INTERNET_PER_CONN_OPTION(Structure):
    class Value(Union):
        _fields_ = [
            ('dwValue', DWORD),
            ('pszValue', LPWSTR),
            ('ftValue', FILETIME),
        ]

    _fields_ = [
        ('dwOption', DWORD),
        ('Value', Value),
    ]


class INTERNET_PER_CONN_OPTION_LIST(Structure):
    _fields_ = [
        ('dwSize', DWORD),
        ('pszConnection', LPWSTR),
        ('dwOptionCount', DWORD),
        ('dwOptionError', DWORD),
        ('pOptions', POINTER(INTERNET_PER_CONN_OPTION)),
    ]


def set_proxy_settings(pac, on=True):
    if on:
        setting = create_unicode_buffer(pac)
    else:
        setting = None

    InternetSetOption = windll.wininet.InternetSetOptionW
    InternetSetOption.argtypes = [HINTERNET, DWORD, LPVOID, DWORD]
    InternetSetOption.restype = BOOL

    List = INTERNET_PER_CONN_OPTION_LIST()
    Option = (INTERNET_PER_CONN_OPTION * 2)()
    nSize = c_ulong(sizeof(INTERNET_PER_CONN_OPTION_LIST))

    Option[0].dwOption = INTERNET_PER_CONN_FLAGS
    Option[0].Value.dwValue = (5 if on else 1)
    Option[1].dwOption = INTERNET_PER_CONN_PROXY_PAC
    Option[1].Value.pszValue = setting

    List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST)
    List.pszConnection = None
    List.dwOptionCount = 2
    List.dwOptionError = 0
    List.pOptions = Option

    InternetSetOption(None, INTERNET_OPTION_PER_CONNECTION_OPTION, byref(List), nSize)
    InternetSetOption(None, INTERNET_OPTION_SETTINGS_CHANGED, None, 0)
    InternetSetOption(None, INTERNET_OPTION_REFRESH, None, 0)

代码中的set_proxy_settings传入PAC的脚本地址,和是否启动系统代理的标志on。PAC脚本的编写就不说了,这里稍微解释一下:

  • Option[0].dwOption = INTERNET_PER_CONN_FLAGS,它的值为1,表示代理模式的key(无代理、自动检测、脚本设置、手动设置)
  • Option[0].Value.dwValue = (5 if on else 1),就是代理模式的value,5表示脚本设置代理,1表示无代理
  • Option[1].dwOption = INTERNET_PER_CONN_PROXY_PAC,它的值为4,表示脚本地址的key
  • Option[1].Value.pszValue = setting,就是脚本地址的value

不同的数字代表不同的配置,大家可以试着改一下数字,看看配置结果有什么不同。

自动安装证书

前两步已经完成了,运行程序会发现,我们已经可以成功地抓到HTTP请求了,但对于HTTPS,浏览器需要验证证书,首次启动mitmdump的时候,会在用户目录下生成.mitmproxy文件夹,其中mitmproxy-ca-cert.cer就是mitmproxy代理伪造的CA证书,我们需要做的就是将该证书安装到受信任的根证书颁发机构的存储区。本地手动安装很容易,网上有不少教程,这里主要讲的是用程序来完成自动安装证书的操作。

由于Python似乎并没有相应的API可以实现该操作,因此考虑使用windows自带的命令。查了一下,certutil可以满足需求。首先检测是否已经安装该证书:

certutil -verifystore root "mitmproxy"

可以用Python中的os.popen执行该命令,并判断输出结果。

如果未安装,则执行安装命令:

certutil -addstore -f root "mitmproxy-ca-cert.cer"

这里遇到了一个难题,该命令需要管理员权限才能执行,但要通过程序而非人为操作获取到管理员权限似乎不是一件容易的事。经查,用bat脚本可以实现,代码如下:

@echo off

>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" 
 
if '%errorlevel%' NEQ '0' (  
    goto UACPrompt  
) else ( goto gotAdmin )  
   
:UACPrompt  
    echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" 
    echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs" 
    "%temp%\getadmin.vbs" 
    exit /B  
   
:gotAdmin  
    if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )  
    pushd "%CD%" 
    CD /D "%~dp0" 
 
:begin
    certutil -addstore -f root "mitmproxy-ca-cert.cer"

上面代码的逻辑是先获取到管理员权限,然后在 :begin 中执行带有管理员权限的命令。该脚本同样用os.popen运行。

总结

综上,三部分已全部实现,接下来就是将这些代码合并在一起,组成完整的程序。最终的代码后续会发布到Github上。

点赞 分享

发表评论

共有 2 条评论

  • 回复

    little_people

    2019-04-17 10:19

    老哥,mitmproxy的这种方式libmproxy,我报了in script test.py: Cannot close a running event loop这个错误,你的没有吗?能直接运行?
  • 回复

    matteo

    2018-10-28 07:53

    请问已经将代码发布到GitHub上了吗? 想参考一下,谢谢