0%

[TOC]

目标

做为一个程序员,已成中年油腻大叔了?那怎么行,来趣消除App扶我起来学数学App成语消消乐游戏battle下

做为已毕业十几年的理科生,在这个游戏中的优势肯定不是成语容量,而是工作专业技能 – 上代码

测试环境

App: 趣消除AppiOS版本、扶我起来学数学App版本
工具: mitmproxy、python、Charles
背景知识:mitmproxy、python、抓包

解决

分析

游戏界面上呈现的:
App游戏界面

网络呈现:
游戏是通过websocket协议来传输json格式的字符串,举asking消息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"ask_string": "步不之来疏笔口平去重学青字斟浅之论暗明尊易伐道不云句来刊酌诛才师",
"type": "asking",
"scores": [
{
"nick": "吕耀辉",
....
},
{
"nick": "xxxx",
......
}
]
}

成语答案:answer消息

1
2
3
4
5
6
7
8
9
10
{
"answer": "口诛笔伐",
"answer_index": [
"6",
"29",
"5",
"21"
],
"type": "answer"
}

目的是把上面的"ask_string": "步不之来疏笔口平去重学青字斟浅之论暗明尊易伐道不云句来刊酌诛才师"解析为一个个成语

工作原理

写2个文件:chengyu_mitm.py[代码文件]、chengyu.text[数据文件]

  • chengyu_mitm.pyasking消息里解析出ask_stringchengyu.text文件里查找是否包含相应的成语
  • 如果包含成语,图形化、格式化显示结果
  • chengyu.text文件刚开始是空的;在每局游戏结束时,游戏都会发送game_result消息给我们,里面有这局游戏的答案成语,把这些成语写到文件中
  • 玩的局数越多,chengyu.text文件包含的成语越多,查找到答案的可能性越大

所以我们只要关注:asking消息、game_result消息
如果要程序回复答案,可以关注下answer消息[客户端发给服务器的]

代码

chengyu_mitm.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
import json
import time
import collections

from mitmproxy import ctx

'''
> mitmdump -s chengyu_mitm.py '~u websock_m/websock_message'
'''

Red = '\033[0;31m'
Green = '\033[0;32m'
Yellow = '\033[0;33m'
Blue = '\033[0;34m'
Purple = '\033[0;35m'
Cyan = '\033[0;36m'
White = '\033[0;37m'

colors = {
0:Red,
1:Purple,
2:Yellow,
3:Blue,
4:White,
}

class Chengyu(object):
def __init__(self):
self.dictpath = '/Users/zhoujie/chengyu.text'
self.chengyu = set()
with open(self.dictpath, 'rt') as f:
for line in f.readlines():
self.chengyu.add(line.strip())

self.answers = list()
self.ask_string = ''

# {'和':[1,8], '我':[11]}
self.char_indexs_dict = dict()

# {1:'和', 8:'和', 11:'我'}
self.index_char_dict = dict()

self.count = 0

# 自动提交答案的网络发送次数
self.auto_send_count = 0

# 自动发送的成语
self.auto_send_list = list()

# 服务器确定正确的成语
self.ack_true_list = list()

# {'中流砥柱':[1,9,21,25]}
self.answer_indexs_dict = dict()

# 查找到的错误答案
self.error_answers = []

# 玩了多少局
self.play_times = 0

self.firt_auto_answer = True



# General lifecycle
def load(self, loader):
ctx.log.info('\033[1;31mevent: load\033[0m')

def configure(self, updated):
ctx.log.info('\033[1;31mevent: configure\033[0m')



# Websocket lifecycle
def websocket_message(self, flow):
"""
Called when a WebSocket message is received from the client or
server. The most recent message will be flow.messages[-1]. The
message is user-modifiable. Currently there are two types of
messages, corresponding to the BINARY and TEXT frame types.
"""

ctx.log.info('\033[1;31m websocket_message \033[0m')

# get the latest message
message = flow.messages[-1]

# simply print the content of the message
ctx.log.info('')
ctx.log.info(message.content)
ctx.log.info('')

m = json.loads(message.content)
message_type = m['type']
if m.get('ask_string'):
self.ask_string = m['ask_string']
# 计算答案
self.find_answers_v2(self.ask_string)
self.play_times += 1

# {"answer": "\u957f\u9a71\u76f4\u5165", "answer_index": ["0", "20", "43", "41"], "type": "answer"}
if message_type == 'answer':
self.answer_indexs_dict[m['answer']] = m['answer_index']

# 删除已回答正确的答案
ack = False

# {"answer":"长驱直入","type":"answer_ack","seq":0,"ack":1}
if m.get('ack') == 1:

answer = m['answer']
self.ack_true_list.append(answer)
answer_index = self.answer_indexs_dict.get(answer,[])
for i in answer_index:
self.index_char_dict[int(i)] = ' '

for c,i in zip(answer,answer_index):
indexs = self.char_indexs_dict[c]
indexs.remove(int(i))

try:
ack = True
self.answers.remove(m['answer'])
except:
pass

if message_type == 'answer_ack' and m.get('ack') == 0:
pass


# 自动答题
if message_type == 'answer_ack' or self.firt_auto_answer:

self.auto_answer(flow)

# 显示答案
if len(self.ask_string):
self.print_answers()


if message_type == 'game_result':
# 把答案增加到内存字典中
self.__add_new_worlds_to_memory(m)

self.reset_data_to_init()


def websocket_end(self, flow):
"""
A websocket connection has ended.
"""
ctx.log.info('\033[1;31m websocket_end \033[0m')

self.reset_data_to_init()

if self.play_times % 5 == 0:
with open(self.dictpath, 'wt') as f:
l = list(self.chengyu)
l.sort()
for item in l:
f.write(item)
f.write('\n')


# ---------------------------------------------
def find_answers_v2(self, ask_string):
'''
在内存成语字典查找答案
'''
ask_set = set(ask_string)
for i, c in enumerate(ask_string):
self.char_indexs_dict.setdefault(c, []).append(i)
self.index_char_dict = dict( zip(range(len(ask_string)), ask_string))

used_chars = set()
# 找到的的成语中各异字符为2个的答案数量:如 [真真假假]
answer_2chars_count = 0
max_count = len(ask_string) / 4
for item in self.chengyu:
item_set = set(item)
if not (item_set - ask_set):
self.answers.append(item)
used_chars.update(item_set)
if len(item_set)<4:
answer_2chars_count += 1
if len(self.answers) - answer_2chars_count >= max_count and used_chars >= ask_set:
self.count = len(self.answers)
return
self.count = len(self.answers)

def auto_answer(self, flow):
while len(self.answers) > 0:
item = self.answers[0]
answer_index = []
counter = collections.Counter(item)

b_continue = False
for char, count in counter.items():
if len(self.char_indexs_dict[char]) < count:
self.error_answers.append(item)
self.answers.remove(item)
b_continue = True
break # break for

if b_continue:
continue

char_index = {}
for c in item:
x = char_index.get(c, 0)
index = self.char_indexs_dict[c][x]
answer_index.append( str(index) )
char_index[c] = x+1

if len(set(answer_index)) < 4:
ctx.log.error(f'?算法有错误:{answer_index} 小于4')
continue

send_message = {
'answer': item,
'answer_index': answer_index,
'type': 'answer'
}
mm = json.dumps(send_message)
# -----------------------
print(mm)
# -----------------------
self.answer_indexs_dict[item] = answer_index
# 向服务器发送消息
if not flow.ended and not flow.error:
self.firt_auto_answer = False
self.auto_send_count += 1
self.auto_send_list.append(item)
self.answers.remove(item)
time.sleep(1.5)
flow.inject_message(flow.server_conn, mm)
break




def __add_new_worlds_to_memory(self, m):
'''
把答案增加到内存字典中
'''
for answer in m['all_answer']:
self.chengyu.add(answer['phrase'])

ctx.log.info('\033[1;31m 共收录{}个成语 \033[0m'.format(len(self.chengyu)))
ctx.log.info(f'\033[1;31m 共玩了 {self.play_times} 局\033[0m')

def print_answers(self):
'''
图形化、色彩化显示答案
'''
self.print_color(self.ask_string)
self.print_color('共找到 {}/{} 个成语'.format(self.count, len(self.ask_string)//4))
self.print_color('错误成语 {}'.format(self.error_answers))
self.print_color('共自动 {} 次提交'.format(self.auto_send_count))
self.print_color('自动{:2}个:{}'.format(len(self.auto_send_list),self.auto_send_list))
self.print_color('确认{:2}个:{}'.format(len(self.ack_true_list),self.ack_true_list))
for item in self.answers:
self.print_color(item)
self.print_matrix(item)

if (not self.answers) and self.index_char_dict:
self.print_matrix()


def print_matrix(self, item = []):
'''
item: '腊尽春回' or []
'''
chars_in_line = 6
length = len(self.ask_string)

lines = (length + chars_in_line - 1) // chars_in_line
PADDING = ' '*(lines * chars_in_line - length)
is_need_padding = len(PADDING) != 0

global colors, White

self.print_color('--'*chars_in_line)

for i, c in self.index_char_dict.items():
end = ''
if (i+1) % chars_in_line == 0 or (i+1) == length:
end = '\n'

color = White
if c in item:
color = colors[item.index(c)]

line, first = divmod(i, chars_in_line)
if is_need_padding and first == 0 and (line + 1 == lines):
c = PADDING + c

self.print_color(c, end=end, front_color=color)

self.print_color('--'*chars_in_line)

def print_color(self, message, end='\n', front_color=Red):
print(f'{front_color}{message}\033[0m', end=end)


def reset_data_to_init(self):
self.ask_string = ''
self.answers.clear()
self.index_char_dict.clear()

self.count = 0
self.auto_send_count = 0

self.answer_indexs_dict.clear()
self.char_indexs_dict.clear()
self.error_answers.clear()
self.ack_true_list.clear()
self.auto_send_list.clear()
self.firt_auto_answer = True


addons = [
Chengyu()
]

if __name__ == "__main__":
c = Chengyu()

ask_string = '腊见家义降德若功赎仁判悲生升道肘两身乐极尽立罪春命明回人捉襟性暗'
ask_string = '来戴恶自唇而怙到生二不马亡竹锲寒擒梅香听寒星圆其十贼其鸟舍悛口一自不月披青擒王说曝手然擒一齿齿石'
ask_string = '侧拨扮相大拔挈然纲打富提敲如涌门见助济行踽全弄轩责刀危踽击贫独波可云岌日班思求斧乔备泉领旁岌劫装'
c.ask_string = ask_string
c.find_answers_v2(ask_string)
print(c.answers)
c.print_answers()

chengyu.text

1
2
3
4
5
6
7
8
一劳永逸
一掷千金
一曝十寒
一石二鸟
一筹莫展
一落千丈
一衣带水
一语破的

注意:self.dictpath = '/Users/xxx/chengyu.text' 一定要修改成你自己的chengyu.text所在路径

运行效果

运行效果

参考

[TOC]

问题

mitmproxy是命令行界面,不太方便查看,自己遇到的问题有:

  • 在Flow Details界面不知道用哪个快捷键查看下一条或上一条网络请求详情
  • json中的中文显示为 \ua9b4 或 ?不能看清是哪个中文
  • 不知道怎么复制界面中显示的内容

Charles的编程能力弱

把Charles和mitmproxy结合起来使用,可以查看方便、编程扩展

环境

电脑和手机都连接到同一个Wi-Fi, 比如TP_LINK_629F
电脑: ip=192.168.1.100; 运行着Charles、mitmproxy
Charles: 192.168.1.100:8888
mitmproxy: 192.168.1.100:8080
手机: 设置代理地址为Charles的地址192.168.1.100:8888

Charles - External Proxy Settings功能开启:

  • Web Proxy(HTTP) - Web Proxy Server设置为mitmproxy的地址192.168.1.100:8080
  • Secure Web Proxy(HTTPS) - Secure Web Proxy Server设置为mitmproxy的地址192.168.1.100:8080
手机
Charles - External Proxy Settings

补充: mitmproxy设置上游代理

1
mitmdump --mode=upstream:http://127.0.0.1:8888 --ssl-insecure

应用

应用:自动生成接口的python代码

趣头条App为例:
阅读得积分的接口为: https://api.1sapp.com/readtimer/report
自动生成的代码大约如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def readtimer_report(self):

headers = {
'User-Agent': 'qukan_android;retrofit/2.4.0 okhttp/3.11.0;os/7.0 M5 Note Meizu;device/862546036581345;version/3.9.41.000.0904.1121;channel/012',
'Host': 'api.1sapp.com',
'Accept-Encoding': 'gzip',
'Connection': 'keep-alive',
}

params = {
'qdata': 'NzVFNkE3Mjc0RjEzRkYwRTM5OTEzNjAyRUZBMDMzQTAuY0dGeVlXMGZPR0kyTldKalltTXRNVGczT1MwME9EZGlMVGszT0RBdE1EUmlOVEV3TWpOak9UazRIblpsY25OcGIyNGZNVEVlY0d4aGRHWnZjbTBmWVc1a2NtOXBaQjVsWXg4eC46LxvyIvgD62/T93SlANmywpigTwlOwcfCHe0iZ1D8mH1zpslt2JCRPdiHOj1M20bU0zDX0odUOBig6Kt51mheNJuQYeDvp15R8RSGTT3LR9s55nBCGvWyTLq+3pjEvkyERElR9E2I384/nHQR4iqxqv7LKQ4rBA0R6bNG8sksHqNl1izSbF87G/4/Qw5vVYcuNUfU0BM6vvIbsy2CTPWlJ51YCzadQZZLONuaYTpyhuOiUV4vnx6qkvpYDNp9XpPPjbXxJAb7fikqjWSdyx167hXDPzUkNZGndjZsv7kQANDkIk2Dm+g5YW1I49xnkOzJkmxvyrLevnsSb9S5fSEUEyfq0GlPHE0RRBeSjFxVltH1zdZraTtk13Z+MvA7HBYQONz/0OwyMnujc1Ety91uKh6YCCVEDvBO+RTOzoRDa3nlRo3FTo9OeRBsyL20qIP24977MMYXEoxinTuwNonipCjnjSYIrhOu6cyv7uxuLd6FxtmTsydawNGMPI/K+habIKXNUFsQMCUcAGhYpoKQJvkQqHEq6lPyZZzXDot1EsN6bsGj56xQdiuJZLMFyZaGNR6E9FmVlI0LAVT8ttVpOvs+5f08T0iMxMNb0VQk6DOySpYHp7EVjc9YFpPcVxj8aXvuTjoPkaNGhSKQ0fgBd8HVRLslcnzX0QLJkTuU7NQ9aili6m2M2hWvh/q8ghWrvtLT+izCiFNKHE+4GTC9J6jqgyjHsXkAjcOSBAaIXMQKnOd664hdoR2GqV+GAy95fc5zZCJ7EFvzmTbJQrpMOwW+Y2NvYPZtgjw1uJEyU7AR7nVw7VqMjPpCDYeWBWoQ1W4OjlXTqgBR4MIu1sTag6a+my/0hItf91SNa58zCN3YmE2NnsWwwiCC+ZP91moV/KqPwX3vMLKW4/3Vsziqe8gl',
}

data = {
}

url = 'http://api.1sapp.com/readtimer/report'
result = self._get(url, headers=headers, params=params, data=data)
return result

应用步骤:

  1. 手机按上面环境设置好代理地址
  2. 在电脑上启动Charles,Charles按上面环境设置
  3. 在电脑上启动mitimproxy, 启动命令如下:mitmproxy -s gen_code_mitm.py
  4. 文件gen_code_mitm.py内容参考下面
  5. 在手机趣头条App阅读文章或看视频一定时间
  6. 会在~/Desktop/api/目录下生成一个叫readtimer_report.text
  7. readtimer_report.text的相应代码复制粘帖到文件qu_tou_tiao.py
  8. 按需要修改自动生成的代码为希望的样子
  9. 运行qu_tou_tiao.py

注意:

  • readtimer_report.text的名字和所在目录是由代码gen_code_mitm.py决定的,请自行阅读修改
  • readtimer_report方法名由代码gen_code_mitm.py决定的,请自行阅读修改
  • 对body为复杂json格式的代码自动生成会有错误,有能力的自行修改
  • readtimer/report这个接口自动生成的代码数据是不能重复获取积分的,这里只是演示

文件

文件qu_tou_tiao.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
43
44
'''
代码模板
'''
import requests
import json
import logging

logging.basicConfig(format='%(asctime)s:%(message)s', datefmt='%m-%d %H:%M:%S', level=logging.INFO)

class User(object):
def __init__(self):
pass

def api_need_implement(self):
pass

def _header(self):
return {
'User-Agent': '',
'Cookie':self.cookie
}

@staticmethod
def _post(url, data=None, json=None, p=logging.warning, **kwargs):
res = requests.post(url, data=data, **kwargs)
result = res.text
p(res.json())
logging.info('')
return result

@staticmethod
def _get(url, params=None, p=logging.warning, **kwargs):
res = requests.get(url, params=params, **kwargs)
result = res.text
p(json.loads(result))
logging.info('')
return result

def genUsers():
yield User()

if __name__ == "__main__":
for user in genUsers():
user.api_need_implement()

文件gen_code_mitm.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import json
import re
from urllib.parse import urlparse

from mitmproxy import ctx
from mitmproxy import flowfilter
from mitmproxy import http

'''
生成接口python代码
'''

class GenCode(object):
def __init__(self):
ctx.log.info('__init__')

# 趣头条
urls = [
r'taskcenter/getListV2',#tab页:任务
r'readtimer/report',
]
self.qu_tou_tiao = flowfilter.parse('|'.join(urls))

# 百度 - 全民小视频
urls = [
r'mvideo/api', # 每日签到
]
self.quan_ming = flowfilter.parse('|'.join(urls))

self.flowfilters = [
self.qu_tou_tiao,
self.quan_ming,
]

def load(self, loader):
ctx.log.info('event: load')

def configure(self, updated):
ctx.log.info('event: configure')

def running(self):
ctx.log.info('event: running')

def done(self):
ctx.log.info('event: done')



def response(self, flow: http.HTTPFlow):
if any( [ filter(flow) for filter in self.flowfilters ] ):

request: http.HTTPRequest = flow.request

parse_result = urlparse(request.url)
url_path = parse_result.path

function_name = re.sub(r'[/-]','_', url_path).strip('_')
headers_code = self.headers_string(flow)
params_code = self.params_string(flow)
data_code = self.data_string(flow)

path = f'''/Users/zhoujie/Desktop/api/{function_name}.text'''
with open(path, 'a') as f:
print(f'''# ---------------------''',file=f)

code = f'''
def {function_name}(self):

{headers_code}

{params_code}

{data_code}

url = '{request.scheme}://{request.pretty_host}{url_path}'
result = self._{request.method.lower()}(url, headers=headers, params=params, data=data)
return result

'''
f.write(code)

print(f'''Response:''',file=f)
print(f'''{flow.response.text}''',file=f)
print(f'''# ---------------------\n\n''',file=f)

def headers_string(self, flow: http.HTTPFlow):
lines = ''
for key,value in flow.request.headers.items():
lines += f"\n\t\t'{key}': '{value}',"
s = f'''headers = {{{lines}\n\t}}'''
return s


def params_string(self, flow: http.HTTPFlow):
lines = ''
for key,value in flow.request.query.items():
lines += f"\n\t\t'{key}': '{value}',"
s = f'''params = {{{lines}\n\t}}'''
return s

def data_string(self, flow: http.HTTPFlow):
'''
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json; charset=utf-8
Content-Type: text/plain;charset=utf-8
'''
lines = ''

# [urlencoded_form, multipart_form, plan, json]取其一
for key,value in flow.request.urlencoded_form.items():
lines += f"\n\t\t'{key}': '{value}',"

for key,value in flow.request.multipart_form.items():
key = key.decode(encoding='utf-8')
value = value.decode(encoding='utf-8')
lines += f"\n\t\t'{key}': '{value}',"

# Todo:复杂json数据还不能代码化
if 'application/json' in flow.request.headers.get('content-type',''):
d = json.loads(flow.request.text)
for key,value in d.items():
lines += f"\n\t\t'{key}': {value},"

s = f'''data = {{{lines}\n\t}}'''
return s




addons = [
GenCode()
]

[TOC]

Rewrite应用

案例01

问题:今日头条极速版App每天的阅读推送文章任务因为每天收到的推送很少,因而不能得很高的积分,怎样把普通文章的阅读变为推送文章的阅读呢?

思考:对比普通文章的阅读与推送文章的阅读发出的网络数据,找出差异

普通文章的阅读与推送文章的阅读达到奖励标准时,都用相同的接口https://is.snssdk.com/score_task/v1/task/get_read_bonus/

1
2
3
4
5
# 普通阅读文章/视频
https://is.snssdk.com/score_task/v1/task/get_read_bonus/?fp=xxx&...&group_id=6689697061983486472

# 推送文章的阅读
https://is.snssdk.com/score_task/v1/task/get_read_bonus/?fp=xxx&...&&impression_type=push&group_id=6689697061983486472

对比上面的接口数据发现:
推送阅读只比普通阅读多出了impression_type=push的Query String

解决:用Charles的Rewrite功能Add Query Param来增加impression_type=push解决问题

Rewrite功能-Type:Add Query Param

上面的get_read_bonus重写规则Export导出的get_read_bonus.xml文件内容如下:

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
<?xml version='1.0' encoding='UTF-8' ?>
<?charles serialisation-version='2.0' ?>
<rewriteSet-array>
<rewriteSet>
<active>true</active>
<name>get_read_bonus</name>
<hosts>
<locationPatterns>
<locationMatch>
<location>
<protocol>https</protocol>
<host>is.snssdk.com</host>
<path>/score_task/v1/task/get_read_bonus/</path>
</location>
<enabled>true</enabled>
</locationMatch>
</locationPatterns>
</hosts>
<rules>
<rewriteRule>
<active>true</active>
<ruleType>8</ruleType>
<matchHeader></matchHeader>
<matchValue></matchValue>
<matchHeaderRegex>false</matchHeaderRegex>
<matchValueRegex>false</matchValueRegex>
<matchRequest>false</matchRequest>
<matchResponse>false</matchResponse>
<newHeader>impression_type</newHeader>
<newValue>push</newValue>
<newHeaderRegex>false</newHeaderRegex>
<newValueRegex>false</newValueRegex>
<matchWholeValue>false</matchWholeValue>
<caseSensitive>false</caseSensitive>
<replaceType>2</replaceType>
</rewriteRule>
</rules>
</rewriteSet>
</rewriteSet-array>

重写规则设置正确与否验证:
规则设置正确与否验证

在上面的Notes列上会显示Rewrite Tool: query added "impression_type: push"

应用:

  1. 打开Charles并生效上面的Rewrite设置
  2. 手机设置代理为Charles的代理地址
  3. 正常阅读普通文章\视频达到奖励时点
  4. 查找我的收益页面,验证成功与否

案例02

问题:趣消除趣键盘东方头条等App都看广告得金币,怎样减少广告的时间?

思考:广告是哪里来的?广告时长是怎么来的?
当点击App上的按钮弹出广告时,3个App都调用了接口:

1
https://is.snssdk.com/api/ad/union/sdk/get_ads/

响应如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
......
"video": {
"cover_height": 1280,
"cover_url": "http://sf1-ttcdn-tos.pstatp.com/img/mosaic-legacy/1be91000a8c62c6ba6221~noop.jpg",
"cover_width": 720,
"endcard": "https://www.toutiaopage.com/union/endcard/1629848424707111/?rit=909946692\u0026req_id=ED6EC127-C359-4C18-A41E-3A5F6F499250u3183\u0026ad_sdk_version=1.9.9.0\u0026os=ios\u0026lang=cn\u0026style_id=1104\u0026ad_id=1629844369912839\u0026_toutiao_params=%7B%22cid%22%3A1629848424707111%2C%22device_id%22%3A9724339963504202%2C%22log_extra%22%3A%22%7B%5C%22ad_price%5C%22%3A%5C%22XOub4AAGRWZc65vgAAZFZgz-hMMMgth42hwxAg%5C%22%2C%5C%22convert_id%5C%22%3A1629408290774020%2C%5C%22orit%5C%22%3A900000000%2C%5C%22req_id%5C%22%3A%5C%22ED6EC127-C359-4C18-A41E-3A5F6F499250u3183%5C%22%2C%5C%22rit%5C%22%3A909946692%7D%22%2C%22orit%22%3A900000000%2C%22req_id%22%3A%22ED6EC127-C359-4C18-A41E-3A5F6F499250u3183%22%2C%22rit%22%3A909946692%2C%22sign%22%3A%22D41D8CD98F00B204E9800998ECF8427E%22%2C%22uid%22%3A9724339963504202%2C%22ut%22%3A14%7D\u0026append=%7B%22openurl%22%3A%22%22%2C%22postdata%22%3A%5B%7B%22__type__%22%3A%22req_id%22%2C%22cid%22%3A1629848424707111%2C%22req_id%22%3A%22ED6EC127-C359-4C18-A41E-3A5F6F499250u3183%22%2C%22rit%22%3A909946692%7D%5D%7D",
"resolution": "720x1280",
"size": 5628226,
"video_duration": 29.04,
"video_url": "http://vd2.bdstatic.com/mda-jesntzw6569xqudw/mda-jesntzw6569xqudw.mp4"
}
}],
......
}

广告就是从上面的接口获取而来的,广告时长由video_url字段对应的mp4的时长决定

解决:用Charles的Rewrite功能Body替换video_url字段的值
Rewrite功能-Type:Body

1
2
3
4
5
Match Value:
"video_url":"(.+)"}

Replace Value:
"video_url":"http://vd2.bdstatic.com/mda-jesntzw6569xqudw/mda.mp4"}

提供一个只有3秒的素材:

1
http://vd2.bdstatic.com/mda-jesntzw6569xqudw/mda-jesntzw6569xqudw.mp4

案例03

问题:章鱼输入法App有看广告得金币,没有像案例02那样找到相应的接口api返回广告视频的URL,怎样减少广告的时间?

思考:尝试替换广告视频的请求
比如广告视频的请求如下:

1
2
3
4
Get https://v3-ad.ixigua.com/.../video/m/.../toutiao.mp4

替换为只有3秒的视频地址
Get http://vd2.bdstatic.com/.../3seconds.mp4

解决:用Charles的Rewrite功能URL替换请求
Rewrite功能-Type:URL

案例04

问题:扶我起来学数学App的作战休息区有一个游戏伪装者,在上报成绩时,接口有hash字段,修改成绩字段,hash会验证不通过,达到了防止伪造成绩的功能,怎样在hash前伪造成绩?
成绩上报接口
思考:hash算法一般难破解,与其破解hash算法,不如转变思路:修改传入hash的值

1
2
value肯定与成绩相关
hash(value)

那value具体是怎么样的呢?通过抓包的数据可以判定为是个h5游戏,在js代码中可能包含相要的答案
image

在浏览器中打开上面中的game.html验证确实是一个h5游戏:
image

解决:

  • 用Charles的Mirror功能把抓包的数据自动保存为文件,再在文本编辑器中检查代码
  • game.html文件中查找rest/game_report,因为有这个网络包,所以先查找这个关键字,结果如下:
1
2
3
4
5
6
function _gameReport(score, callBack, hash, time){
var oAjax = null;
//这里进行HTTP请求
oAjax = new XMLHttpRequest();
oAjax.open('post',HOSTURLAPI+"/rest/game_report"+"?uid="+UID+"&gameid="+GAMEID+"&score="+score+"&tm="+time+"&hash="+hash,true);
}
  • 查找_gameReport函数的调用者
1
2
3
4
5
6
7
8
9
10
11
function gameReport(score, callBack){
var timeData = new Date().getTime();
var hashValue = UID+GAMEID+score+timeData;
var hash = '';
dsBridge.call('hashCode',hashValue,function(data){
hash = data;
_gameReport(score, callBack, hash, timeData);
});
}

gameReport(b[0], function(success, old_score){})

js代码调用到App的hashCode方法,hashValue = UID+GAMEID+score+timeData
用Charles的Rewrite功能Body替换:

1
2
3
4
5
Match Value:
gameReport(b[0]

Replace Value:
gameReport('99'

image

  • 成绩已修改,hash验证已通过

Mirror应用

文档:https://www.charlesproxy.com/documentation/tools/mirror/

The Mirror tool saves responses to disk as they are received, creating a mirror copy of websites as you browse them.

Mirror把响应保存为文件到硬盘上

image

Mirror保存下来的文件

No Caching 和 Block Cookies应用

在案例:扶我起来学数学App的伪装者游戏通过Rewrite功能修改了js文件中的内容;但有时js文件不是每次都会传输,而是使用了缓存,这时Rewrite功能就失效了,因为没有发生网络请求;通过No Caching 和 Block Cookies使网络请求每次都发生

Map Local应用

Map Local应用场景
修改js文件来改变App行为:
1. 使用No Caching 和 Block Cookies功能保证js文件通过网络请求加载到App
2. 使用Mirror功能把js文件保存到电脑上
3. 使用Map Local功能使App加载修改后的js文件

[TOC]

mitmproxy的使用

mitmproxy is a free and open source interactive HTTPS proxy.
官网:https://mitmproxy.org/
安装:pip3 install mitmproxybrew install mitmproxy
安装后有3个命令行工具:mitmproxy, mitmdump, mitmweb

这里不介绍mitmweb的使用,mitmproxymitmdump的功能重点:
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

1
➜  ~ mitmproxy

输入上面命令,启动mitmproxy并显示Flows界面

Flows界面
Flows界面

快捷键

  • 第1个也是最重要的快捷键:?: 进入Help界面
进入Help界面
Help界面
  • 第2个重要的快捷键::: Command prompt,进入命令输入模式
进入命令输入模式
命令输入模式
可以输入的命令:可以在Command Reference界面查看
可以按tab来命令补全:比如输入flow.m;再按tab; 补全为flow.mark
可以按tab来路径补全
enter执行命令
常用的命令可以用快捷键,不用进入命令输入模式,省去输入的时间
  • 界面间跳转快捷键
快捷键 界面 截图
? Help界面 Help界面
K Key Bindings界面 Key Bindings界面
P Flow Details界面 Flow Details界面
E Events界面 Events界面
C Command Reference界面 Command Reference界面
O Options界面 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

  • 1.启动mitmproxy
1
➜  ~ mitmproxy
  • 2.点开东方头条App幸运大转盘界面
  • 3.点击’领取金币’;点击’立即领取’;
  • 问题:这时候mitmrpoxy的Flow界面已包含上面的网络请求,网络请求非常多,怎么找到需要的请求
  • 解答:应用mitmrpoxy的Filter expressions
  • 4.按f快捷键:设置view_filter这个Option
f快捷键, 设置view_filter
  • 5.输入~u zhuanpan, 按回车执行命令
输入~u zhuanpan
  • 知识点:~u zhuanpanFilter 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 expressionsOptions:
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
  • 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

快捷键we的区别

w e
文件为二进制文件 文件为文本文件
保存的信息完整 只保存请求信息,不保存响应信息
能一次保存多条网络请求信息 一次只能保存一条网络请求信息

mitmdump

Mitmproxy是用python实现的,编写相应的Addon脚本也是用python

shell脚本

先用在mitmproxye快捷键来辅助编写shell脚本,来解决下上面的实战问题

    1. e快捷键分别保存zhuanpan/get_zhuanpan_newzhuanpan/get_gold网络请求为文件get_zhuanpan_new.shget_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&lt=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'
    1. get_zhuanpan_new.shget_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&lt=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&lt=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脚本:就是选择性的实现上面的方法

具体都有哪些方法可以选择性实现,可以查看如下文档:

文档 https://docs.mitmproxy.org/stable/addons-events/
源代码 docs/src/examples/addons/events.py
源代码 mitmproxy/eventsequence.py

开始实现Addon脚本:

    1. 新建文件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. 实现request方法:
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. 实现response方法:
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等方法来代替使用printlogging.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_hosts
https://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

问题

手动设置wifi的步骤非常长:

  1. 点击“设置”
  2. 点击“无线局域网”
  3. 点击已连接的wifi
  4. 点击“配置代理”
  5. 点击“手动”
  6. 定位服务器输入框,输入ip
  7. 定位端口输入框,输入port
  8. 点击“存储”

iOS解决

用iOS上的Shadowrocket和Mac上的Charles来快速设置代理
iOS和Mac在同一wifi

  1. 启动Charles,假设代理地址为:192.168.0.100:8888
  2. Shadowrocket设置全局路由代理
  3. Shadowrocket添加HTTP类型的节点
  4. Shadowrocket打开连接

添加HTTP类型的节点:
添加节点

结果:
结果

Android解决

用Android上的Kitsunebi和Mac上的Charles来快速设置代理
Android和Mac在同一wifi

  1. 开启Charles的Http Proxy和SOCKS Proxy
  2. Kitsunebi添加socks类型的节点
  3. Kitsunebi打开连接

开启Charles的Http Proxy和SOCKS Proxy
Kitsunebi-socks-proxy

[TOC]

概念

魔法方法

模块

并发 & 异步

其它

[TOC]

基础

Vim

算法

网络

Docker

爬虫

协议

Shell

Linux

图像

渗透

JavaScript

iOS

Python

Node.js

后端

前端

深度学习

图书

网站