[TOC]
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
下的核心概念:
➜ ~ 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
输入上面命令,启动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
应用
➜ ~ 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
- 1.启动mitmproxy
➜ ~ mitmproxy
- 2.点开
东方头条App
到幸运大转盘
界面 - 3.点击'领取金币';点击'立即领取';
- 问题:这时候mitmrpoxy的
Flow界面
已包含上面的网络请求,网络请求非常多,怎么找到需要的请求 - 解答:应用mitmrpoxy的
Filter expressions
- 4.按
f
快捷键:设置view_filter
这个Option
按f 快捷键, 设置view_filter
|
---|
- 5.输入
~u zhuanpan
, 按回车执行命令
输入~u zhuanpan
|
---|
知识点:
~u zhuanpan
是Filter expressions
:~u regex
,用来过滤URL符合regex正则表达式的网络请求;可以按?
跳转到Help界面
查看全部的Filter expressions
6.用
j
导航快捷键定位到zhuanpan/get_zhuanpan_new
网络请求;按下m
快捷键将这条网络请求标记
按下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
➜ ~ mitmproxy --no-server
快捷键 | 界面 | command | 说明 |
---|---|---|---|
L | flowlist | console.command view.load | Load flows from file |
- 3.按
L
快捷键, 把步骤1保存的文件加载进来
按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
先用在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内容类似,不再列出]
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
- 把
#!/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脚本以编写完成
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/mda-jfpeu3azyxp3yxjr/mda-jfpeu3azyxp3yxjr.mp4"}'
# 代码里可以调用
ctx.master.commands.call("replay.client", [flow])
ctx.master.commands.execute("view.focus.go 0")
代码阅读
源码地址:https://github.com/mitmproxy/mitmproxy
mitmproxy/tools/_main.py
入口方法:
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)
开始运行:
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()
线程:
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)
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