[分析]对"无影坦克"的解密算法分析

声明:本文只是对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)
posted @ 2021-09-28 09:55  二氢茉莉酮酸甲酯  阅读(1688)  评论(0编辑  收藏  举报