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

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

mitmproxy Python启动 非命令行启动(多种方式)

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

目录

  1. mitmproxy 简介
  2. mitmproxy 常用的命令行启动
  3. mitmproxy 非命令行脚本直接启动,方式一(会阻塞主进程,这点需要注意)
  4. mitmproxy 非命令行脚本直接启动,方式二(会阻塞主进程,这点需要注意)
  5. mitmproxy 非命令行脚本直接启动,方式三(不阻塞主进程,同一个进程内,提供一个开启、关闭 mitmproxy的方案,但是仍有问题需要处理)
  6. mitmproxy 非命令行脚本直接启动,方式四(不阻塞主进程,用多进程启动,并可开启、关闭
  7. mitmproxy 非命令行脚本直接启动,方式五(不阻塞主进程PyQt5,6/PySide2,6适用,使用QProcess实现,并可开启、关闭

简介

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启动后,会阻塞主进程,这点需要注意

#!/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启动后,会阻塞主进程,这点需要注意

#!/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
...

方式三

这种方式是对第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 多进程启动与管理,不阻塞主进程,并可开启、关闭

#!/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)

方式五

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  星尘的博客  阅读(350)  评论(0编辑  收藏  举报