代理 mitmproxy Python启动 非命令行启动(多种方式) 使用笔记(一)

代理 mitmproxy Python启动 非命令行启动(多种方式) 使用笔记(一)

在进行 APP 应用操作时,难免会遇到抓包操作,于是我们这里使用 mitmproxy 来完成这能力

目录

  1. mitmproxy 简介
  2. mitmproxy 常用的命令行启动
  3. 方式一:通过main.mitmdump启动,会阻塞主进程
  4. 方式二:通过dump.DumpMaster类加asyncio.run启动,会阻塞主进程
  5. 方式三:改进dump.DumpMaster类加asyncio.run启动,不会阻塞主进程,同一个进程内,提供一个开启、关闭 mitmproxy的方案,但是仍有问题需要处理
  6. 方式四:使用 multiprocessing 多进程启动不会阻塞主进程,用多进程启动,并可开启、关闭
  7. 方式五:使用QProcess启动,PyQt5,6/PySide2,6适用不会阻塞主进程,并可开启、关闭

简介

mitmproxy 是一组工具,为 HTTP/1、HTTP/2 和 WebSocket 提供交互式、支持 SSL/TLS 的拦截代理,官方文档见这里


常用的命令行启动

  1. 基础启动
mitmweb  # 启动带WEB 可视化界面的代理,默认:host=127.0.0.1,port=8080
mitmproxy  # 启动无可视化代理,默认:host=127.0.0.1,port=8080
  1. 自定义地址端口,允许公共 IP 地址的连接,带脚本插件启动
# host=192.167.6.120,port=8888,允许公共 IP 地址的连接:block_global=false,脚本插件:test_addon.py
mitmweb --listen-host=192.167.6.120 -p=8888 --set block_global=false -s .\test_addon.py
mitmproxy --listen-host=192.167.6.120 -p=8888 --set block_global=false -s .\test_addon.py

注意:为什么要加 block_global=false?,若不加,则只能本地拦截,而移动设备请求无法被拦截
报错如下:Client connection from 192.167.6.166 killed by block_global option


非命令行脚本直接启动

方式一:通过main.mitmdump启动

通过main.mitmdump函数, main.mitmdump启动后,会阻塞主进程,这点需要注意

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File        : test_mitmproxy.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : 通过`main.mitmdump`函数
"""

from mitmproxy.http import HTTPFlow
from mitmproxy.tools import main


class Counter:
    def request(self, flow: HTTPFlow):
        request = flow.request  # 获取请求对象
        print("request data = ", request.data)

    def response(self, flow: HTTPFlow):
        if flow.response.status_code != 200:
            return
        response = flow.response  # 获取响应对象
        print(f"response content = {response.content}")


addons = [
    Counter()
]


if __name__ == '__main__':
    main.mitmdump(['-s', __file__, '--listen-host', '192.167.6.120', '-p', '8888', '--set', 'block_global=false'])


方式二:通过dump.DumpMaster类加asyncio.run启动

通过dump.DumpMaster类, asyncio.run启动后,会阻塞主进程,这点需要注意

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File        : test_mitmproxy.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : 通过`dump.DumpMaster`类
"""
import asyncio

from mitmproxy import http, options, optmanager
from mitmproxy.tools import dump


class Counter:
    ...  # 同方式一,无改动


async def func_temp(host: str = '127.0.0.1', port: int = 8080, conf_file: str = None):
    opts = options.Options(listen_host=host, listen_port=port)
    # print(opts.keys())  # 输出选项名称
    # 加载配置文件,如果你的参数在 opts.keys() 中不存在,则必须使用配置文件,否则不需要用
    if conf_file:
        optmanager.load_paths(opts, conf_file)
    dm = dump.DumpMaster(opts)
    dm.addons.add(Counter())
    try:
        await dm.run()
    except BaseException as e:
        print(e)
        dm.shutdown()


if __name__ == '__main__':
    asyncio.run(func_temp(conf_file=r'.\.mitmproxy\config.yaml'))

配置文件如下,配置文件中的选项,请参考官网,见这里

%YAML 1.2
---
listen_host: 192.167.6.120
listen_port: 8888
block_global: false
...

方式三:改进asyncio.run启动

这种方式是对第2种的改进,主要目的是不阻塞主进程,此处提供一个开启、关闭的方案,但是仍有问题需要处理

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File        : test_mitmproxy.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : 通过`dump.DumpMaster`类
"""
import asyncio
import threading

from mitmproxy import http, options, optmanager
from mitmproxy.tools import dump


class Counter:
    ...  # 同方式一,无改动


async def func_temp(host: str = '127.0.0.1', port: int = 8080, conf_file: str = None):
    ...  # 同方式二,无改动


def start_loop(loop):
    """创建事件循环loop的函数"""
    asyncio.set_event_loop(loop)
    loop.run_forever()


async def stop_loop():
    """关闭事件循环"""
    loop = asyncio.get_event_loop()
    loop.stop()
    loop.close()


def start_mitmproxy() -> (threading.Thread, asyncio.AbstractEventLoop, Future)::
    """启动mitmproxy"""
    new_loop = asyncio.new_event_loop()
    t = threading.Thread(target=start_loop, args=(new_loop,))  # 开启新的线程去启动事件循环
    t.start()
    conf_file = r'../.mitmproxy/config.yaml'
    future = asyncio.run_coroutine_threadsafe(func(conf_file=conf_file), new_loop)
    return t, new_loop, future


# TODO 这会停止事件循环,mitmproxy也就会关闭代理,但是所占用的端口无法被释放,除非关掉主进程,
# 若你不想关闭主进程,又想在同一个端口多次启停 mitmproxy,就需要注意这一点
def stop_mitmproxy(thread: threading.Thread, loop: asyncio.AbstractEventLoop, future: Future) -> None:
    """关闭mitmproxy"""
    try:
        asyncio.run_coroutine_threadsafe(stop_loop(), loop)
        future.cancel()
        thread.join()
    except BaseException:
        pass


if __name__ == '__main__':
    import time

    th, loop, futur = start_mitmproxy()
    time.sleep(30)
    stop_mitmproxy(th, loop, futur)


方式四:使用 multiprocessing 多进程启动

使用 multiprocessing 多进程启动与管理,不阻塞主进程,并可开启、关闭

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File        : test_mitmproxy.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : multiprocessing
"""
from multiprocessing import Process

from mitmproxy.tools import main


class Counter:
    ...  # 同方式一,无改动


addons = [
    Counter()
]


def start_mitmproxy():
    main.mitmdump(['-s', __file__, '--listen-host', '0.0.0.0', '-p', '8888', '--set', 'block_global=false'])


def run() -> Process:
    """运行多进程"""
    p = Process(target=start_mitmproxy, name='mitmproxy')
    p.start()
    return p


def stop(p: Process) -> None:
    """终止多进程"""
    p.terminate()
    p.join()


if __name__ == '__main__':
    import time

    pro = run()
    time.sleep(30)
    stop(pro)

方式五:使用QProcess启动

PyQt5,6/PySide2,6适用,因为使用QProcess实现,QProcess用于启动外部程序,并加以管理,不会阻塞主进程

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ File        : test_mitmproxy.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : PyQt5 适用 QProcess
"""
from PyQt5.QtCore import QObject, QProcess
from mitmproxy import http


class Counter:
    ...  # 同方式一,无改动


addons = [
    Counter()
]


class MitmdumpQProcess(QObject):
    def __init__(self, parent, basic_signal: BaseSignal):
        super().__init__(parent=parent)
        self.basic_signal = basic_signal  # 调度者的信号,如果需要的话
        self.p = None  # 默认值

    def start_process(self):
        """启动"""
        if self.p is not None:
            return
        print("Executing process")
        self.p = QProcess()
        self.p.readyReadStandardOutput.connect(self.__handle_stdout)  # 捕获消息,并输出
        self.p.readyReadStandardError.connect(self.__handle_stderr)  # 捕获消息,并输出
        self.p.stateChanged.connect(self.__handle_state)
        self.p.finished.connect(self.__process_finished)  # 结束时清理
        options = [
            '--listen-host', '0.0.0.0',
            '-p', '8888',
            '--set', 'block_global=false',
            '-s', f'{__file__}',
        ]
        # 请注意,若使用 PyInstaller 打包,需要将 mitmdump.exe 存放在同级目录内
        self.p.start("mitmdump", options)

    def __handle_stderr(self):
        data = self.p.readAllStandardError()
        stderr = bytes(data).decode("utf8")
        print(stderr)

    def __handle_stdout(self):
        data = self.p.readAllStandardOutput()
        stdout = bytes(data).decode("utf8")
        print(stdout)

    @staticmethod
    def __handle_state(state):
        states = {
            QProcess.NotRunning: 'Not running',
            QProcess.Starting: 'Starting',
            QProcess.Running: 'Running',
            QProcess.NormalExit: 'Normal Exit'
        }
        state_name = states[state]
        print(f"State changed: {state_name}")

    def __process_finished(self):
        print("Process finished.")
        self.p = None

    def kill_process(self):
        """杀死"""
        self.p.terminate()
        self.p.kill()

参考文章:
mitmproxy 官方文档
mitmproxy入门三、直接python脚本运行&常用http处理方式
PyCharm中直接启动mitmproxy并自动打开&关闭系统代理
本文章的原文地址
GitHub主页

posted @ 2024-05-06 20:11  星尘的博客  阅读(1492)  评论(0编辑  收藏  举报