[TOC]
mitmproxy的使用 mitmproxy is a free and open source interactive HTTPS proxy. 官网:https://mitmproxy.org/ 安装:pip3 install mitmproxy
或 brew install mitmproxy
安装后有3个命令行工具:mitmproxy, mitmdump, mitmweb
这里不介绍mitmweb
的使用,mitmproxy
与mitmdump
的功能重点:mitmproxy
:交互式;查看流量数据(请求与响应);执行自定义脚本mitmdump
:执行自定义脚本,脚本在Mitmproxy中叫做Addon
使用mitmproxy
:因为是命令行界面,所以需要记住一些快捷键 使用mitmdump
:偏向编写python代码
通过一个典型的调用,来认识下Mitmproxy
下的核心概念:
1 ➜ ~ mitmproxy --set scripts=ad_short_mitm.py '~u baidu\.com'
在Mitmproxy的叫法
set
Command
scripts
Options
ad_short_mitm.py
Addon
‘~u baidu\.com’
Filter expressions
mitmproxy
输入上面命令,启动mitmproxy并显示Flows界面
:
Flows界面
快捷键
第1个也是最重要的快捷键:?
: 进入Help界面
:
进入Help界面
第2个重要的快捷键::
: Command prompt,进入命令输入模式
进入命令输入模式
可以输入的命令:可以在Command Reference界面
查看
可以按tab
来命令补全:比如输入flow.m;再按tab; 补全为flow.mark
可以按tab
来路径补全
按enter
执行命令
常用的命令可以用快捷键,不用进入命令输入模式,省去输入的时间
快捷键
界面
截图
?
Help界面
K
Key Bindings界面
P
Flow Details界面
E
Events界面
C
Command Reference界面
O
Options界面
注意:上面的快捷键,都是大写字母,mitmproxy
的快捷键是区分大小写的
Flows界面居然没有快捷键?
快捷键
command
说明
q
console.view.pop
返回:界面间的返回
g
console.nav.start
跳到第一行
G
console.nav.end
跳到最后一行
h
console.nav.left
j
console.nav.down
跳到下一行
k
console.nav.up
跳到上一行
l
console.nav.right
space
console.nav.pagedown
ctrl b
console.nav.pageup
ctrl f
console.nav.pagedown
tab
console.nav.next
g\G\j\k等这样的导航键是通用的:在Flows、Events、Command、Options等界面都能用
刚开始学命令行界面时,有这么命令、快捷键要记,没记住怎么办? 这里介绍下mitmproxy的--no-server, -n
应用
1 2 3 4 5 6 ➜ ~ mitmproxy --help usage: mitmproxy [options] ... Proxy Options: --no-server, -n --server Start a proxy server. Enabled by default.
第1个Terminal窗口里正常启动mitmproxy:➜ ~ mitmproxy
开启第2个Terminal窗口带--no-server
选项启动mitmproxy:➜ ~ mitmproxy --no-server
;按K
/C
/O
/?
查看快捷键、Command、Options、帮助
第2个mitmproxy专门用于查看快捷键、Command、Options、帮助
案例实战 以东方头条App - 幸运大转盘
这个游戏为实战
东方头条App - 幸运大转盘
1.点击’领取金币’:会发出https://…/zhuanpan/get_zhuanpan_new网络请求
2.点击’立即领取’:会发出https://…/zhuanpan/get_gold网络请求
应用目的:通过mitmproxy的replay功能来减少手动操作时间 知识点:Filter expressions
, Options
, Command
2.点开东方头条App
到幸运大转盘
界面
3.点击’领取金币’;点击’立即领取’;
问题:这时候mitmrpoxy的Flow界面
已包含上面的网络请求,网络请求非常多,怎么找到需要的请求
解答:应用mitmrpoxy的Filter expressions
4.按f
快捷键:设置view_filter
这个Option
按f
快捷键, 设置view_filter
输入~u zhuanpan
按下m
标记网络请求
7.用同样的操作,标记zhuanpan/get_gold
网络请求
按下m
标记网络请求
8.按:
快捷键, 进入命令输入模式;输入rep
, 按tab
补全命令; 输入@marked
; 按回车执行命令
按tab
补全命令
出于演示使用mitmrpoxy的目的,才增加了许多不必要的步骤;简洁方法:
去除步骤4、5、6、7
步骤8改为: replay.client "(~u zhuanpan/get_zhuanpan_new) | (~u zhuanpan/get_gold)"
案例到此结束,小结下用到的快捷键、命令:
快捷键
command
说明
f
: set view_fliter=
只显示符合条件的网络请求
m
flow.mark.toggle @focus
Toggle mark on this flow
: replay.client @marked
重放多条标记的网络请求
相关快捷键:
快捷键
界面
command
说明
M
flowlist
view.marked.toggle
Toggle viewing marked flows
U
flowlist
flow.mark @all false
Un-set all marks
r
flowlist
replay.client @focus
Replay this flow
一些用到Filter expressions
的Options
: view_filter、save_stream_filter、intercept
相关文档:https://docs.mitmproxy.org/stable/concepts-options/ https://docs.mitmproxy.org/stable/concepts-filters/
问题 上面的实战有以下几个问题:
第1次收集操作时,不是每次都抽到金币,也有可能抽到广告;怎样每次都跳过广告?
游戏有20次机会,要手动输入多次replay.client @marked
才能把20次机会用完;怎样才能减少手动操作?
这些问题我们通过编写脚本来解决。这里使用mitmproxy
的其它功能为编写脚本提供方便 把实战的已被标记的2个网络请求保存为文件,方便查看:
快捷键
界面
command
说明
w
flowlist
console.command save.file @shown
Save listed flows to file
1.按w
快捷键, 把@shown
修改为@marked
; 指定保存路径;按回车执行命令
按w
保存为文件
输入路径时,可以按tab
来补全路径
最好不要使用~
:像我自己Mac上输入~/zhuanpan.mitm
,没有保存成功;当然你也可以测试下使用~
的路径能否保存成功
输入的文件的后缀名是可以随意指定的;保存的文件为二进制格式
2.开启第2个Terminal窗口带–no-server选项启动mitmproxy
1 ➜ ~ mitmproxy --no-server
快捷键
界面
command
说明
L
flowlist
console.command view.load
Load flows from file
按L
加载文件
好了,编写脚本的准备工作结束! 小结下用到的快捷键、命令:
快捷键
界面
command
说明
w
flowlist
console.command save.file @shown
Save listed flows to file
L
flowlist
console.command view.load
Load flows from file
相关快捷键:
快捷键
界面
command
说明
e
flowlist
console.command export.file {choice} @focus
Export this flow to file
快捷键w
与e
的区别
w
e
文件为二进制文件
文件为文本文件
保存的信息完整
只保存请求信息,不保存响应信息
能一次保存多条网络请求信息
一次只能保存一条网络请求信息
mitmdump Mitmproxy
是用python实现的,编写相应的Addon
脚本也是用python
shell脚本 先用在mitmproxy
的e
快捷键来辅助编写shell脚本,来解决下上面的实战问题
用e
快捷键分别保存zhuanpan/get_zhuanpan_new
、zhuanpan/get_gold
网络请求为文件get_zhuanpan_new.sh
、get_gold.sh
get_zhuanpan_new.sh文件内容:[get_gold.sh内容类似,不再列出]
1 curl -H 'Host:zhuanpan.dftoutiao.com' -H 'Content-Type:application/x-www-form-urlencoded' -H 'Connection:keep-alive' -H 'Accept:*/*' -H 'User-Agent:DFTT/2.4.8 (iPhone; iOS 12.3.1; Scale/3.00)' -H 'Accept-Language:zh-Hans-CN;q=1, en-CN;q=0.9, zh-Hant-CN;q=0.8' -H 'Content-Length:484' -H 'Accept-Encoding:br, gzip, deflate' -X POST 'https://zhuanpan.dftoutiao.com/zhuanpan/get_zhuanpan_new' --data-binary 'accid=834536089&appqid=AppStore190602&apptypeid=DFTT&appver=2.4.8&device=iPhone%206s%20Plus%20%28A1634/A1687%29&deviceid=AE9418A1-561A-4F5C-AF05-1EC222A50CF3&fr=rwzx&ime=F2B14555-E2EB-4556-B757-2C55799C92C2<=d2RlWExGb015UjRqSkxMZk0rRkYwcTAzd0I3RmErMWRLbzZsYTc4dkFtakxLMmgvdW9xWFhYUEFNdU9XTHZMV3F6cWNhVXRPalBSMkJNUHlvTktRbnc9PQ%3D%3D&network=wifi&num=57&os=iOS%2012.3.1&position=%E6%B5%99%E6%B1%9F&sign=5aac4e159e8d205c084c9f9e6cf4e41f&softname=DFTTIOS&softtype=TouTiao&ts=1564368354'
把get_zhuanpan_new.sh
、get_gold.sh
的内容合并到最终的文件中zhuanpan.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # !/usr/bin/env bash function zhuanpan { # mitmproxy用快捷键e导出的get_zhuanpan_new.sh文件内容原样写到这 curl -H 'Host:zhuanpan.dftoutiao.com' -H 'Content-Type:application/x-www-form-urlencoded' -H 'Connection:keep-alive' -H 'Accept:*/*' -H 'User-Agent:DFTT/2.4.8 (iPhone; iOS 12.3.1; Scale/3.00)' -H 'Accept-Language:zh-Hans-CN;q=1, en-CN;q=0.9, zh-Hant-CN;q=0.8' -H 'Content-Length:484' -H 'Accept-Encoding:br, gzip, deflate' -X POST 'https://zhuanpan.dftoutiao.com/zhuanpan/get_zhuanpan_new' --data-binary 'accid=834536089&appqid=AppStore190602&apptypeid=DFTT&appver=2.4.8&device=iPhone%206s%20Plus%20%28A1634/A1687%29&deviceid=AE9418A1-561A-4F5C-AF05-1EC222A50CF3&fr=rwzx&ime=F2B14555-E2EB-4556-B757-2C55799C92C2<=d2RlWExGb015UjRqSkxMZk0rRkYwcTAzd0I3RmErMWRLbzZsYTc4dkFtakxLMmgvdW9xWFhYUEFNdU9XTHZMV3F6cWNhVXRPalBSMkJNUHlvTktRbnc9PQ%3D%3D&network=wifi&num=57&os=iOS%2012.3.1&position=%E6%B5%99%E6%B1%9F&sign=5aac4e159e8d205c084c9f9e6cf4e41f&softname=DFTTIOS&softtype=TouTiao&ts=1564368354' # mitmproxy用快捷键e导出的get_gold.sh文件内容原样写到这 curl -H 'Host:zhuanpan.dftoutiao.com' -H 'Content-Type:application/x-www-form-urlencoded' -H 'Connection:keep-alive' -H 'Accept:*/*' -H 'User-Agent:DFTT/2.4.8 (iPhone; iOS 12.3.1; Scale/3.00)' -H 'Accept-Language:zh-Hans-CN;q=1, en-CN;q=0.9, zh-Hant-CN;q=0.8' -H 'Content-Length:487' -H 'Accept-Encoding:br, gzip, deflate' -X POST 'https://zhuanpan.dftoutiao.com/zhuanpan/get_gold' --data-binary 'accid=834536089&appqid=AppStore190602&apptypeid=DFTT&appver=2.4.8&device=iPhone%206s%20Plus%20%28A1634/A1687%29&deviceid=AE9418A1-561A-4F5C-AF05-1EC222A50CF3&fr=rwzx&ime=F2B14555-E2EB-4556-B757-2C55799C92C2&isfirst=0<=d2RlWExGb015UjRqSkxMZk0rRkYwcTAzd0I3RmErMWRLbzZsYTc4dkFtakxLMmgvdW9xWFhYUEFNdU9XTHZMV3F6cWNhVXRPalBSMkJNUHlvTktRbnc9PQ%3D%3D&network=wifi&os=iOS%2012.3.1&position=%E6%B5%99%E6%B1%9F&sign=c6f61f80d1c001ac5382ef73632e0e9e&softname=DFTTIOS&softtype=TouTiao&ts=1564368376' } for ((i=0; i<20; i++)); do zhuanpan done
ok,shell脚本以编写完成
python脚本 关于Addon
的概念可以查看:https://docs.mitmproxy.org/stable/addons-overview/
编写Addon
脚本写些什么呢?先上一下模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from mitmproxy import ctx from mitmproxy import flowfilter from mitmproxy import http from mitmproxy import addonmanager class Myaddon(object): def __init__(self): pass def load(self, entry: addonmanager.Loader): pass def request(self, flow: http.HTTPFlow): pass def response(self, flow: http.HTTPFlow): pass addons = [ Myaddon() ]
编写Addon
脚本:就是选择性的实现上面的方法
具体都有哪些方法可以选择性实现,可以查看如下文档:
开始实现Addon
脚本:
新建文件zhuangpan_mitm.py
, 实现__init__
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import json import re from mitmproxy import ctx from mitmproxy import flowfilter from mitmproxy import http class Zhuangpan(object): def __init__(self): self.filter = flowfilter.parse(r'(~u zhuanpan/get_zhuanpan_new) | (~u zhuanpan/get_gold)') self.new_fliter = flowfilter.parse(r'~u zhuanpan/get_zhuanpan_new') self.get_fliter = flowfilter.parse(r'~u zhuanpan/get_gold') self.flows = [] self.urls = set() self.remain = 0 addons = [ Zhuangpan() ]
这里用到了Mitmproxy的api:flowfilter.parse
:
1 2 3 4 5 6 7 8 9 10 # mitmproxy/flowfilter.py文件 def parse(s: str) -> TFilter: # 还定义了: def match(flt, flow): """ Matches a flow against a compiled filter expression. Returns True if matched, False if not. .... """
1 2 3 4 5 6 7 8 9 10 class Zhuangpan(object): ... def request(self, flow: http.HTTPFlow): if flowfilter.match(self.filter, flow): url = flow.request.url if not url in self.urls: ctx.log.alert(url) self.flows.append(flow) self.urls.add(url)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Zhuangpan(object): ... def response(self, flow: http.HTTPFlow): if flowfilter.match(self.new_fliter, flow): flow.response.replace(r'"gold":0', '"gold":999') text = flow.response.text data = json.loads(text) self.remain = data.get('data').get('cur_num') ctx.log.alert('remain count:{}'.format(self.remain)) if flowfilter.match(self.get_fliter, flow): if self.remain > 0 and len(self.urls) >= 2: flows = [f.copy() for f in self.flows] ctx.master.commands.call("replay.client", flows)
使用ctx.log.xxx
等方法来代替使用print
或logging.warning
等方法:
在mitmproxy中,ctx.log.xxx
记录的信息会出现在Event
界面, 而其它方法不会出现在Event
界面
在mitmdump中,ctx.log.xxx
记录的信息事件显示的顺序正确, 而其它方法显示的顺序不正确
使用 1 2 3 4 5 6 ➜ ~ mitmdump --scripts zhuangpan_mitm.py Loading script zhuangpan_mitm.py Proxy server listening at http://*:8080 # --scripts SCRIPT, -s SCRIPT Execute a script. May be passed multiple times.
1 2 3 4 5 6 7 8 mitmdump --set userid=zhj -s "mitm_user_xxx.py" -s math_mitm.py '~u mapi.hddgood.com' mitmdump --set replacements='/~s/"video_url":"(.+)"}/"video_url":"https://vd3.bdstatic.com/abc.mp4"}' # 代码里可以调用 ctx.master.commands.call("replay.client", [flow]) ctx.master.commands.execute("view.focus.go 0")
其它 问题:只关注某个域名下的流量,怎么设置? 解决:ignore_hostshttps://docs.mitmproxy.org/stable/howto-ignoredomains/
1 2 3 4 5 6 # Ignore everything but example.com and mitmproxy.org: --ignore-hosts '^(?!example\.com)(?!mitmproxy\.org)' 正则表达式:反前瞻 反前瞻:要匹配某个模式时,需要在它 后面找不到含有给定前瞻模式的内容 foo(?!bar) Negative lookahead assertion. The pattern foo will only match if not followed by a match of pattern bar.
代码阅读 源码地址:https://github.com/mitmproxy/mitmproxy
mitmproxy/tools/_main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 入口方法: def mitmproxy(args=None) -> typing.Optional[int]: run(console.master.ConsoleMaster) def mitmdump(args=None) -> typing.Optional[int]: run(dump.DumpMaster) 主要代码 def run(master_cls): opts = options.Options() master = master_cls(opts) pconf = proxy.config.ProxyConfig(opts) server = proxy.server.ProxyServer(pconf) master.server = server master.run() return master master.Master console.master.ConsoleMaster dump.DumpMaster web.master.WebMaster Server proxy.server.ProxyServer proxy.server.DummyServer Master与Server关系: master.server = server Master和Server对象生成: Master(opts: options.Options) Server(config: config.ProxyConfig) ProxyConfig与Options关系: ProxyConfig(options: options.Options)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 开始运行: master.run() master.start() def start(self): if self.server: ServerThread(self.server).start() class ServerThread(basethread.BaseThread): def __init__(self, server): self.server = server address = getattr(self.server, "address", None) super().__init__( "ServerThread ({})".format(repr(address)) ) def run(self): self.server.serve_forever()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 线程: ServerThread connection_thread def connection_thread(self, connection, client_address): with self.handler_counter: try: self.handle_client_connection(connection, client_address) finally: close_socket(connection) def handle_client_connection(self, conn, client_address): h = ConnectionHandler( conn, client_address, self.config, self.channel ) h.handle() def handle(self): self.log("clientconnect", "info") root_layer = None root_layer = self._create_root_layer() root_layer = self.channel.ask("clientconnect", root_layer) root_layer() self.log("clientdisconnect", "info") def _create_root_layer(self): root_ctx = ... mode = self.config.options.mode if mode.startswith("upstream:"): return modes.HttpUpstreamProxy elif mode == "transparent": return modes.TransparentProxy(root_ctx) elif mode == "regular": return modes.HttpProxy(root_ctx)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 addons的运行过程[生命周期] [1]. "load" [2]. "running" [3]. "configure" [1]. "load" DumpMaster.__init__(self,options): super().__init__(options) self.addons.add(*addons.default_addons()) AddonManager.add(self, *addons): for i in addons: self.chain.append(self.register(i)) AddonManager.register(self, addon): l = Loader(self.master) self.invoke_addon(addon, "load", l) [2]. "running" master.run(): loop = asyncio.get_event_loop() self.run_loop(loop.run_forever) master.run_loop(self, loop): asyncio.ensure_future(self.running()) master.running(self): self.addons.trigger("running") [3]. "configure" class AddonManager: def __init__(self, master): self.lookup = {} self.chain = [] self.master = master master.options.changed.connect(self._configure_all) def _configure_all(self, options, updated): self.trigger("configure", updated)
参考 https://stackoverflow.com/questions/51893788/using-mitmproxy-inside-python-script
https://dev.to/kevcui/3-mitmproxy-tips-you-might-not-know-about-5dbg
https://github.com/KevCui/mitm-scripts