前端解密-CDP调用-笔记
This_is_Y Lv6

屁话

​ 在面对前端加密http参数的时候,如果需要解密,以前我一直是两步:

  1. 首先在js代码中找到加密逻辑,这一步一般比较快,通过搜索加密关键字比如text,data,或者encode,encrypt等,然后全部打上断点,一步一步分析。如果没找到关键字就麻烦一点,只能硬着头皮看。
  2. 第二步一般是比较蛋疼的,在找到加密逻辑后,就需要在本地还原js代码以实现在bp或者python中调用,碍于js代码的层层嵌套结构,这一步往往花费了最多的时间和精力

但是如果把断点停在调用加密函数前,在浏览器的console中就可以运行加密代码。如下

image-20230425222922840

1处是加密参数的代码。2处是我在console中进行调用,可以看到完成了加密,但是加密代码 m.encode((0,o.default)(s)) 只能在断点停在这一步时才能运行。所以现在需要做的就是把浏览器停在这里,然后借助cdp远程运行代码。

操作

(前提:需要先手动找到加解密代码并且在浏览器中测试成功)

1.启动浏览器,配置断点

需要一个chrome浏览器,找到文件位置,使用以下命令启动

1
\chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\Users\This_is_Y\Desktop\test"  --remote-allow-origins=http://127.0.0.1:9222 --ignore-certificate-errors

–remote-debugging-port : 启远程调试,端口9222和我们之前转发出来的端口一致

–user-data-dir :设置独立的文件保存目录,建议一个网站一个目录

–remote-allow-origins :允许远程调试

–ignore-certificate-errors :强制忽略掉证书错误

启动浏览器后,在设置中开启并打开protocol monitor,

image-20230425224624917

image-20230425224652960

2.拿到webSocketDebuggerUrl和callFrameId

在确认protocol monitor启动后,访问网站,下好断点,然后让流程停在断点前。可以先在浏览器中测试,看看加解密代码能不能正常运行,如图,

image-20230425224455605

随后跳到协议监视器来,过滤Debugger.evaluateOnCallFrame,拿到参数callFrameId

image-20230425224747849

然后打开http://127.0.0.1:9222/json,找到目标网站的webSocketDebuggerUrl

image-20230425225607568

3.测试代码

python代码如下:

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
import json
import requests
import websocket


def main(callFrameId,js_code):
# 目标网站的webSocketDebuggerUrl
ws_url = "ws://127.0.0.1:9222/devtools/page/916C5F171E538078844D5D10770465BB"
conn = websocket.create_connection(ws_url,header={"User-Agent": "this_is_y"})


# 配置cdp命令
# 解决一些小bug
false = False
true = True
# 随意
request_id = 1
method = "Debugger.evaluateOnCallFrame"

# js_code = "m.decode('eyJVc2VyTmFtZSI6ImFkbWluIiwiUGFzc1dvcmQiOiIxMjM0IiwiTG9naW5UeXBlIjowLCJUb2tlbiI6IiIsInRpbWVzdGFtcCI6MTY4MjM0OTk5MTcxOX0=')"

# 注意换上callFrameId
param = {"callFrameId":callFrameId,"expression":js_code,"objectGroup":"console","includeCommandLineAPI":true,"silent":false,"returnByValue":false,"generatePreview":true}

command = {'method': method,
'id': request_id,
'params': param}

# 发送命令
conn.send(json.dumps(command))
# 接受websocket的响应,并将字符串转换为 dict()
result = json.loads(conn.recv())
print(result)


if __name__ == '__main__':
js_code = "m.decode('eyJVc2VyTmFtZSI6ImFkbWluIiwiUGFzc1dvcmQiOiIxMjM0IiwiTG9naW5UeXBlIjowLCJUb2tlbiI6IiIsInRpbWVzdGFtcCI6MTY4MjM0OTk5MTcxOX0=')"
callFrameId = "5749928402698829430.1.0"
main(callFrameId,js_code)


image-20230425230523470

这里有一些错误消息:可供参考

4.mitmproxy代码

worker.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
import mitmproxy.http
from mitmproxy import ctx, http
import json
import websocket

ws_url = "ws://127.0.0.1:9222/devtools/page/5CE4DFECA307706DCFFA590DCE41FA85"
callFrameId = "-338402750453211968.1.0"
count = 0

# error 消息:
# {'code': -32000, 'message': 'Can only perform operation while paused.'} 断点没下好
# {'code': -32000, 'message': 'Cannot find context with specified id'}   callFrameId可能有点问题


def main(ws_url,callFrameId,js_code):
conn = websocket.create_connection(ws_url,header={"User-Agent": "this_is_y"})


false = False
true = True
request_id = 1

method = "Debugger.evaluateOnCallFrame"
param = {"callFrameId":callFrameId,"expression":js_code,"objectGroup":"console","includeCommandLineAPI":true,"silent":false,"returnByValue":false,"generatePreview":true}
command = {'method': method,
'id': request_id,
'params': param}

# 发送命令
conn.send(json.dumps(command))
# 接受websocket的响应,并将字符串转换为 dict()
result = json.loads(conn.recv())
# print("result:",result)
return result


def decrypt(ws_url,callFrameId,ctext):
js_code = "m.decode('{}')".format(ctext)
print("Js_Code:",js_code)
return main(ws_url,callFrameId,js_code)




def encrypt(ws_url,callFrameId,ptext):
js_code = "m.encode('{}')".format(ptext)
print("Js_Code:",js_code)
return main(ws_url,callFrameId,js_code)





class Worker:
def __init__(self):
self.num = 0

def request(self, flow: mitmproxy.http.HTTPFlow):

if flow.request.host != "211.83.160.61":
return

print("Request >>>>>>>>>>>")
print("body:",flow.request.method)
print("body:",flow.request.path)
print("body:",flow.request.get_text())
body = flow.request.get_text()
if "{" in body and "}" in body:
# 未加密的明文,需要对其加密
result = encrypt(ws_url,callFrameId,body)
else:
result = decrypt(ws_url,callFrameId,body)
try:
error = result['error']
print("error:",error)
return
except:
pass

v = result['result']['result']['value']
print("V:",self.num,v,type(v))
self.num += 1
flow.request.content = v.encode('utf-8')

def response(self, flow: mitmproxy.http.HTTPFlow):
if flow.request.host != "211.83.160.61":
return

print("Response >>>>>>>>>>>")
print("body:",flow.response.get_text())
body = flow.response.get_text()
result = decrypt(ws_url,callFrameId,body)
try:
error = result['error']
print("error:",error)
return
except:
pass
v = result['result']['result']['value']
print("V:",self.num,v,type(v))
self.num += 1
flow.response.content = v.encode('utf-8')



addons = [
Worker()
]

使用该代码,配合mitmproxy的正向代理模式,

  • mitmweb -s .\3.py -p 8082 –mode regular

同时在User options -> UpStream Proxy Servers 中添加mitmproxy监听的8082端口

image-20230427010258503

已经可以做到在burp-repeater中发送明文请求,自动解密返回

image-20230427010102577

但是没法做到burp抓包自动解密,可以用mitm上游代理的方式转发数据包到burp,但是那样子burp不能发出包

最后还是放弃了用python结合 mitmproxy的方法,

理想状态

image-20230427011105760

BURP开发

最好的还是写一个burp插件来完成这件事,所以我花了一个月的时间来写这个插件虽然每天就写半小时。最后简单完成了一个丑陋的的插件

  • 加密参数选择
  • 动态添加参数
  • 工具目标地址自动获取wsurl并可以发送cdp命令到服务器
  • 可以发送http请求并回显
  • 可以修改请求并解密回显
  • 自动获取callFrameId
  • 优化ui页面

image-20230531224445531

还顺便写了一个测试CDP的小工具

image-20230531220339150

autoDecoder

https://github.com/f0ng/autoDecoder

在朋友帮助下知道了这个插件,发现可以将cdp调用的过程打包为python的web接口,然后通过这个工具完成解密。

注意事项

1.Handshake status 403 Forbidden

这一步卡了我非常久,打算写细致一点。在运行别人的代码的时候,发现一直报错

1
websocket._exceptions.WebSocketBadStatusException: Handshake status 403 Forbidden

唯一一个有帮助的就是在**websocket.create_connection()前加上websocket.enableTrace(True)**查看数据包细节。这引导我使用wireshark去抓包查看原始数据包是什么样子

image-20230425223833826

然后在wireshark中发现了这个。可以看到右边的提示信息

1
Rejected an incoming WebSocket connection from the http://127.0.0.1:9222 origin.  Use the command line flag --remote-allow-origins=http://127.0.0.1:9222 to allow connections from this origin or --remote-allow-origins=* to allow all origins.

在简单查了点资料和测试下,我在浏览器启动的时候加上了**–remote-allow-origins=http://127.0.0.1:9222** 参数,才解决问题

2.callFrameId

在发送cdp命令的时候,需要用到callFrameId参数,这个参数我还没找到怎么使用代码自动获取,先留个坑

参考

https://1oid.github.io/2023/02/15/frontend-encrypt-crack-cdp-debug-function-eval/
https://chromedevtools.github.io/devtools-protocol/tot/Debugger/
https://blog.csdn.net/freeking101/article/details/110213782

 Comments