JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数

本文仅供学习交流使用,请勿用于商业用途或不正当行为
如果侵犯到贵公司的隐私或权益,请联系我立即删除

前两篇文章:

js逆向:【硬干货】手把手实战某条_signature参数破解(上)

js逆向:【硬干货】手把手实战某条_signature参数破解(中)

我们介绍了如何分析定位关键加密函数的位置,以及如何抠出需要的js代码

之前我提到需要将Index页面源代码里的init代码扣下来,现在又发现,其实没有init生成的值也能用,而且调试起来更简单,需要补的环境代码更少。

我们先将之前扣的代码里的init这部分代码删掉

然后运行代码,这时报错:window未定义

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第1张图片

window是浏览器的全局对象,而Node环境下的全局对象是global,所以我们可以这样定义

window = global;

继续运行,报错:href未定义

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第2张图片

一般报错href未定义,是缺少Location对象下面的href属性

我们先看一下浏览器环境下href的值是什么

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第3张图片

所以我们这样定义

window.location = {
    "href": "https://www.toutiao.com/ch/news_hot/",
    "protocol": "https:",
};

继续运行,报错:userAgent未定义

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第4张图片

我们将浏览器的userAgent复制下来然后定义

window.navigator = {
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
};

继续运行…

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第5张图片

提示 please call init first!,但已经能看到加密结果了

先不用管这个提示,我们直接去测试下这个值是否能拿到数据

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第6张图片

顺利拿到数据,虽然不完美但是目的达到了,大家如果想要完美就自行去调试吧。

到这里,我想说,使用Node环境运行代码其实也挺麻烦的,我们都知道,只要能调用真实浏览器运行JS就能拿到加密结果了,但是web自动化工具被检测不能用,那还有什么办法能够控制浏览器运行JS呢?

有!WebSocket ,通过websocket可以实现浏览器与服务器进行双向通信,也就是说浏览器客户端可以给服务端发送数据,服务端能给浏览器客户端发送数据。

那么我们可以利用这一点让服务端将要加密的参数发送给浏览器客户端,浏览器将接收到的参数传入目标加密函数并运行,就能得到加密结果,最后将结果返回给服务器,实现不使用web自动化工具"操控"浏览器。

关于websocket我就不科普了,大神讲得很好了:

http://www.ruanyifeng.com/blog/2017/05/websocket.html

说下实现思路:

  • 同时开启一个http服务端和一个websocket服务端;
  • http客户端请求http服务端,服务端收到请求后触发websocket服务端给websocket客户端发送消息;
  • 利用中间人代理给浏览器注入js,让浏览器成为webcoket客户端,它收到websocket服务端发来的消息时,调用相关目标加密函数生成值,然后将值发送给websocket服务端
  • websocket服务端收到值后将值转交给Http服务端,然后返回给http客户端

mitmproxy很多小伙伴都熟悉吧,这是一个python下的中间人代理库,可以很方便地使用Python代码拦截、篡改请求和响应,本文使用mitmproxy进行js注入,不熟悉的小伙伴参考这篇文章:

https://www.cnblogs.com/20175211lyz/p/12255610.html

直接上代码:

http和websocket服务端:

from tornado import websocket, web, ioloop, gen

cl = []
msg = []
msg_flag = False


class IndexHandler(web.RequestHandler):
    def get(self):
        self.write("hello...")


class SocketHandler(websocket.WebSocketHandler):
    def check_origin(self, origin):
        return True

    def open(self):
        """新的websocket连接后被调动"""
        if self not in cl:
            cl.append(self)
        print('有新客户端连接!')

    def on_close(self):
        """websocket连接关闭后被调用"""
        if self in cl:
            cl.remove(self)

    def on_message(self, message):
        """接收到客户端消息时被调用"""
        global msg_flag
        print('收到客户端发送回来的加密值:%s' % message)
        msg.append(message)
        if not 'started' in message:
            msg_flag = True

class ApiHandler(web.RequestHandler):

    @gen.coroutine
    def post(self):
        global msg_flag,msg
        """接收参数,发送给ws客户端"""
        url = self.get_body_argument('url')
        print('http server收到参数:%s' % url)

        # 将URL推送给客户端
        for c in cl:
            c.write_message(url)

        while not msg_flag:
            yield gen.sleep(0.1)
        msg_flag = False
        self.write(msg[-1])


app = web.Application([
    (r'/', IndexHandler),
    (r'/ws', SocketHandler),  # http://127.0.0.1:8000/ws
    (r'/api', ApiHandler), # http://127.0.0.1:8000/api
])

if __name__ == '__main__':
    app.listen(8000)
    ioloop.IOLoop.instance().start()


Tornado框架的websocket服务参考:

https://www.xiaoqc.cn/detail/python-tornado-websocket/

代理端

import mitmproxy.http
from mitmproxy import options
from mitmproxy import proxy
from mitmproxy.tools.dump import DumpMaster

# 要注入到页面的js代码
inject = """
//连接websocket服务端
var ws = new WebSocket('ws://localhost:8000/ws');

//连接成功时执行
ws.onopen= function() {
    ws.send('browser started')
};

// 收到服务端消息时执行
ws.onmessage= function(evt) {
    // evt.data 是websocket服务端发送过来的值
    console.log(evt.data);

    //调用目标加密函数
    signature = window.byted_acrawler.sign({url: evt.data});

    //将生成的值发送给websocket服务端
    ws.send(signature);
    console.log(signature);
};
"""


class Myaddon():
    def response(self, flow: mitmproxy.http.HTTPFlow):
        if 'acrawler.js' in flow.request.url: # 注入到acrawler.js文件里
            flow.response.text = inject + flow.response.text

addons = [
    Myaddon()
]


def run():
    myaddon = Myaddon()
    port = 8080
    opts = options.Options(listen_port=port)
    pconf = proxy.config.ProxyConfig(opts)
    m = DumpMaster(opts)
    m.server = proxy.server.ProxyServer(pconf)
    m.addons.add(myaddon)

    print(f'启动监听 {port} 端口')
    try:
        m.run()
    except KeyboardInterrupt:
        m.shutdown()

if __name__ == "__main__":
    run()

以代理方式启动浏览器,访问目标URL:

./Chrome.exe http://www.xxxxxxxx.com --proxy-server=127.0.0.1:8080 --ignore-certificate-errors

浏览器启动后,成功注入js代码到acrawler.js文件

JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第7张图片

​请求http服务,将要加密的参数传过去,可以成功拿到加密结果了
JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第8张图片
测试加密结果是否可用
JS逆向:【硬干货】手把手实战某条_signature参数破解(下)——WebSocket与JS函数_第9张图片
可以看到成功拿到数据了

今天的分享先到这里,大家如果有更好的思路,欢迎大佬指点呀,拜~

公众号:一生向风

你可能感兴趣的:(JS逆向)