[分析]對"無影坦克"的解密算法分析


聲明:本文只是對javascript文件進行了學習性的注釋,源文件是從網站上獲取的。

  • 如果此文章侵犯了你的權益,請盡快聯系我刪除
  • 為了防止引流,這里就不放原作者的url了。
  • 可以依據此代碼寫一個對應的解密過程程序
  • 為了簡化原文和方便閱讀,對一些函數進行了重命名。
  • 由於簡化了輸入等控件,這個JavaScript不能運行,請知悉

在此感謝大佬 ybzjdsd(哆啦可尼夫)


let IMG1 = new Image();
let IMGINFO = [];
let img = new Image();
let MODE = 4;
let SRC1 = "";
let SRC2 = "";

function readFile() {
    var oFReader = new FileReader();
    var ofile = document.getElementById("ipt2").files[0];//指定讀入
    oFReader.readAsDataURL(ofile);//將png轉換為data:開頭的base64編碼
    oFReader.onloadend = function (oFRevent) {//在讀取結束時觸發下述事件
        var base64_img = oFRevent.target.result;//將讀取器內的結果復制到變量中
        img.src = base64_img;//確定圖片源便於下一步讀取
        img.onload = function () {//在圖片讀取時操作,在頁面或圖像加載完成后立即發生
            start()//開始進行解碼的預備
        }
    }
}
function start() {
    try {
        let f = sol();
        //至此結束
        //下面是處理HTML的過程
        if (SRC2) {
            URL.revokeObjectURL(SRC2)
        }
        SRC2 = URL.createObjectURL(f[0]);
        document.getElementById("a2").href = SRC2;
        document.getElementById("img2").src = SRC2;
        document.getElementById("info2a").style.display = "block";
        document.getElementById("a2").download = f[1];
        document.getElementById("info2").innerHTML = f[1]
    } catch (e) {
        alert("圖片讀取失敗")
    }
}
function utf8Decode(inputStr) {
    var outputStr = "";
    var code1,
    code2,
    code3,
    code4;
    for (var i = 0; i < inputStr.length; i++) {
        code1 = inputStr.charCodeAt(i);
        if (code1 < 128) {
            outputStr += String.fromCharCode(code1);
        } else if (code1 < 224) {
            code2 = inputStr.charCodeAt(++i);
            outputStr += String.fromCharCode(((code1 & 31) << 6) | (code2 & 63));
        } else if (code1 < 240) {
            code2 = inputStr.charCodeAt(++i);
            code3 = inputStr.charCodeAt(++i);
            outputStr += String.fromCharCode(((code1 & 15) << 12) | ((code2 & 63) << 6) | (code3 & 63));
        } else {
            code2 = inputStr.charCodeAt(++i);
            code3 = inputStr.charCodeAt(++i);
            code4 = inputStr.charCodeAt(++i);

            outputStr += String.fromCharCode(((code1 & 7) << 18) | ((code2 & 63) << 12) | ((code3 & 63) << 6) | (code2 & 63));
        }
    }
    return outputStr;
}

function sol() {
    let cv = document.createElement("canvas");
    let cvd = cv.getContext("2d");
    cv.width = img.width;
    cv.height = img.height;
    cvd.drawImage(img, 0, 0);
    let imgdata = cvd.getImageData(0, 0, img.width, img.height);//imgdata是圖像的RGBA數據,使用
    /*imgdata數據結構
     * 
     * R - 紅色 (0-255)
     * G - 綠色 (0-255)
     * B - 藍色 (0-255)
     * A - alpha 通道 (0-255; 0 是透明的,255 是完全可見的)
     * 其中第n個像素的RGBA值為
     * (imgdata[(n-1)*4 + 0], 
     *  imgdata[(n-1)*4 + 1] ,...)
     **/
    let klist = decodeFile(imgdata.data[2] % 8, imgdata);//圖像的解密模式在第一像素的藍色通道的8余數里
    //這里返回的klist具有如下數據結構:
    /**
     * klist[0]文件詳細信息數組,,第一個參數([0])是字節數,第二個是文件名,第三個是文件類型
     * klist[1]為Uint8Array類型
     * */
    let file = new File([klist[1].buffer], utf8Decode(klist[0][1]), {
        //生成一個文件,文件的內容是buffer,名字為源文件名
        //buffer:ArrayBuffer 對象用來表示通用的、固定長度的原始二進制數據緩沖區。它是一個字節數組,通常在其他語言中稱為“byte array”
        type: klist[0][2]//類型也是源文件的類型
    })
    //返回一個數組,第一參數是生成的解密文件,第二參數是文件名
        return [file, utf8Decode(klist[0][1])]
}
function decodeFile(mode, imgdata) {//這個mode似乎是表圖壓縮度
    let aa = Math.ceil(3 * mode / 8);//ceil:向上取整
    let n = imgdata.width * imgdata.height;//像素數
    let j = 0;
    let k = "";
    let current_pixel_index = 1;
    let mlist = [1, 2, 4, 8, 16, 32, 64, 128];  //冗余代碼
    let word = "";
    let blist
    //=new Uint8Array();
    let blength = 0;
    //對每個像素點進行處理
    while (current_pixel_index < n && (word.length == 0 || word.slice(-1).charCodeAt(0) > 0)) {
        //如果i小於像素數並且
        //word長度為零 或 word沒有結束(最后一個字符不為空

        //關鍵在於此處運算,對RGB的值二進制序列化之后取后四位得到目標序列,即隱寫
        k = k + (imgdata.data[4 * current_pixel_index] + 256).toString(2).slice(-mode);//將R值運算之后,以2進制表示並取后mode位,然后加和到k上,下同

        k = k + (imgdata.data[4 * current_pixel_index + 1] + 256).toString(2).slice(-mode);

        k = k + (imgdata.data[4 * current_pixel_index + 2] + 256).toString(2).slice(-mode);

        current_pixel_index++//像素前移

        for (let ii = 0; ii < aa; ii++) {

            if (k.length >= 8 && (word.length == 0 || word.slice(-1).charCodeAt(0) > 0)) {
                //如果k的長度大於等於8位 並且
        //word長度為零 或 word沒有結束(最后一個字符不為空
                word = word + String.fromCharCode(parseInt(k.slice(0, 8), 2));//將k的前8位轉換為字符,加到word里

                k = k.slice(8);//k遞減
            }
        }
    }
    //像素處理結束
    //從word里獲取源文件的信息,這里容易看到word的格式如下
    /**
     * 字節數(大小)\u0001 源文件名稱 \u0001 源文件類型(
     */
    blength = parseInt(word.split(String.fromCharCode(1))[0]);//獲取源文件大小
    if (!(blength > -1)) {
        //如果源文件大小出錯則報錯
        throw "error"
    }
    if (!(word.split(String.fromCharCode(1)).length > 2)) {
        //如果不符合上面的數據結構,也就是找不到三個參數則報錯
        throw "error"
    }
    blist = new Uint8Array(blength);//8位無符號整型數組,長度為源文件字節長度
    //此處上述算法執行結束后k的值應該是8位2進制,

    //將k的值按照2進制寫入blist[0]
    if (k.length >= 8 && j < blength) {
        blist[j] = parseInt(k.slice(0, 8), 2);
        k = k.slice(8);
        j++
    }
    //繼續讀取,直至讀完源文件字節數
    while (current_pixel_index < n && j < blength) {
        k = k + (imgdata.data[4 * current_pixel_index] + 256).toString(2).slice(-mode);
        k = k + (imgdata.data[4 * current_pixel_index + 1] + 256).toString(2).slice(-mode);
        k = k + (imgdata.data[4 * current_pixel_index + 2] + 256).toString(2).slice(-mode);
        current_pixel_index++

        for (let ii = 0; ii < aa; ii++) {
            if (k.length >= 8 && j < blength) {
                //同樣的,從此處獲取源文件的二進制值轉成RGB值存到目標組里
                blist[j] = parseInt(k.slice(0, 8), 2);
                k = k.slice(8);
                j++
            }
        }
    }
    //返回一個組,第一參數是源文件信息,第二參數是文件的組
    return [word.split(String.fromCharCode(0))[0].split(String.fromCharCode(1)), blist]
}

可以對應的寫出Python代碼:

import cv2
import numpy
import pylab
import matplotlib.pyplot as plt
import math
import struct
import array
import sys
import time

starttime = time.time()

argv = sys.argv
if(len(argv) != 2):
    print("用法: dewytk [filename]")
    print("將在執行路徑生成圖片")
    endtime = time.time()
    print('[!-1]耗時', str(round(endtime - starttime, 2)),'秒')
    exit(-1)
else:
    imgfile = argv[1]

try:
    img = cv2.imread(imgfile,cv2.IMREAD_UNCHANGED)
    imgdata = []
    rawdata = dict()
    
    #print("圖像的形狀,返回一個圖像的(行數,列數,通道數):",img.shape)
    
    n = img.size
    
    a = 1
    mode = 4
    
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            p = img[i][j]
            #BGRA
            rawdata = dict()
            rawdata['R'] = p[0]
            rawdata['G'] = p[1]
            rawdata['B'] = p[2]
            imgdata.append(rawdata)
            if(i == 0 and j == 0):
                mode = p[0] % 8
                if ( mode <= 0 or mode >4):

                    endtime = time.time()
                    print('[!-3]耗時', str(round(endtime - starttime, 2)),'秒')
                    exit(-3)
    
    aa = math.ceil(3*mode/8)
    n = img.size
    j = 0
    k = ""
    current_pixel_index = 1
    word = ""
    blist = []
    blength = 0
    while(current_pixel_index < n and (len(word) == 0 or ord(word[-1])>0 )):
        k = k+str(bin(imgdata[current_pixel_index]['B'] + 256))[-mode:]
        k = k+str(bin(imgdata[current_pixel_index]['G'] + 256))[-mode:]
        k = k+str(bin(imgdata[current_pixel_index]['R'] + 256))[-mode:]
        current_pixel_index = current_pixel_index +1
        for i in range(0,aa):
            if(len(k) >= 8 and (len(word) == 0 or ord(word[-1]) > 0)):
                word = word + chr(int(k[0:8],2))
                k = k[8:]
    #ref to lines#139
    blength = int(word.split(chr(1))[0])
    if(blength <= -1 or not (len(word.split(chr(1))) >2)):
        endtime = time.time()
        print('[!-2]無法處理未知的隱寫參數。耗時', str(round(endtime - starttime, 2)),'秒')
        exit(-2)
    blist = []
    
    if(len(k) >= 8 and j < blength):
        blist.append(int(k[0:8],2))
        k = k[8:]
        j = j + 1
    while (current_pixel_index < n and j < blength):
        k = k+str(bin(imgdata[current_pixel_index]['B'] + 256))[-mode:]
        k = k+str(bin(imgdata[current_pixel_index]['G'] + 256))[-mode:]
        k = k+str(bin(imgdata[current_pixel_index]['R'] + 256))[-mode:]
        current_pixel_index = current_pixel_index +1
        for i in range(0,aa):
            if(len(k) >= 8 and j<blength):
                blist.append(int(k[0:8],2))
                k = k[8:]
                j = j + 1
    
    
    with open(str(word.split(chr(1))[1]), 'wb')as fp:
        for x in blist:
            a = struct.pack('B', x)
            fp.write(a)
    endtime = time.time()
    print("已經向源文件 " + str(word.split(chr(1))[1]) + "\t寫入了 " + str(word.split(chr(1))[0]) + "\t字節,耗時 " + str(round(endtime - starttime, 2))+ "\t秒")
    exit(0)
    pass
except:
    endtime = time.time()
    print("[!-5]" + '耗時', str(round(endtime - starttime, 2)),'秒')
    exit(-5)
    pass

要解密整個文件夾下的所有加密文件,可以使用下述指令(cmd)
要提前將上述腳本保存為decode.py

@echo off
for %%i in (*) do ( python decode.py %%i)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM