Skip to content

Latest commit

 

History

History
158 lines (123 loc) · 5.63 KB

23-minecRaft.md

File metadata and controls

158 lines (123 loc) · 5.63 KB

minecRaft

打开网页,我们发现了一个微型 Minecraft 游戏,然后就开始愉快的摸起鱼来

咳咳,这是一道 web 题,并且「本题解法与原版 Minecraft 游戏无关」。故 F12 打开,然后可以看到 html 内嵌的 js 中有这么一段:

if (cinput.length >= 32) {
    let tbool = gyflagh(cinput.join(''));
    if (tbool) {
        pressplateList[65].TurnOn_redstone_lamp();
        content.innerText = 'Congratulations!!!';
        return;
    }
    cinput.length = 0;
}

并且 gyflagh 这个函数是定义在 flag.js 里面的,很难不猜测这就是最终涉及到 flag 的地方。

打开 flag.js,然后我们看到了一堆乱码…………

首先我们可以把 0x 打头的十六进制数转成十进制数,不然的话跟变量的命名很像,看着太瞎眼了。

然后观察一下参数是 422439 之类的函数,可以发现都跟一个叫 _0x2c9e 的函数有关。

把这个 js 贴到 Devtools Console 里面,多执行几次,发现返回值不会变,并且都是 charCodeAtslice, fromCharCode 这种函数名,不妨可以猜测这个函数只是一个 string mapping。所以我们就把这个 mapping 手动给它转回去。

替换完了之后,应该就只剩下变量名比较难看了。所以根据上下文,给这些变量换个名字。

这波操作下来之后,这个代码就好看了不少:

String.prototype.encrypt = function(key_str) {
    const arr = new Array(2), key = new Array(4);
    let result = '';
    const plaintext = escape(this);
    for (var i = 0; i < 4; i++)
        key[i] = Str4ToLong(key_str.slice(i * 4, (i + 1) * 4));
    for (i = 0; i < plaintext.length; i += 8) {
        arr[0] = Str4ToLong(plaintext.slice(i    , i + 4));
        arr[1] = Str4ToLong(plaintext.slice(i + 4, i + 8));
        code(arr, key);
        result += LongToBase16(arr[0]) + LongToBase16(arr[1]);
    }
    return result;
};

function code(arr, key) {
    let a = arr[0], b = arr[1];
    const STEP = 2654435769, END = STEP * 32;
    let cur = 0;
    while (cur != END) {
        a += ((b << 4) ^ (b >>> 5)) + (b ^ cur) + key[cur & 3];
        cur += STEP;
        b += ((a << 4) ^ (a >>> 5)) + (a ^ cur) + key[(cur >>> 11) & 3];
    }
    arr[0] = a;
    arr[1] = b;
}

function Str4ToLong(val) {
    let result = 0;
    for (let i = 0; i < 4; i++)
        result |= val.charCodeAt(i) << (i * 8);
    return isNaN(result) ? 0 : result;
}

function LongToBase16(val) {
    let result = '';
    for (let i = 3; i >= 0; i--) {
        let tmp = ((val >> (8 * i)) & 0xff)['toString'](16);
        if (parseInt('0x' + tmp) <= 0xf)
            tmp = '0' + tmp;
        result += tmp;
    }
    return result;
}

function Base16ToLong(str) {
    let result = 0;
    for (let i = 0; i < 8; i += 2) {
        let tmp = parseInt('0x' + str.slice(i, i + 2));
        result = (result << 8) + tmp;
    }
    return result;
}

function LongToStr4(val) {
    return String.fromCharCode(val & 0xff, val >> 8 & 0xff, val >> 16 & 0xff, val >> 24 & 0xff);
}

function gyflagh(text) {
    let encrypted = text.encrypt('1356853149054377');
    return encrypted === '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c';
}

我们先把目光放在 String.prototype.encrypt 上,这个函数首先将 key_str 分成了 4 段(1356 8531 4905 4377),然后将每一段通过 Str4ToLong 转为整数。

接着,再把 text(64 个字符,每 8 个分一段)分成 4 段,每一段中再分成前后两个部分(不妨称为 a, b),然后把 a, b 扔给 code 做变换,再把变换后的 a, b LongToBase16 后加到 result(字符串)后面。

上面这段话可能有点难理解,拿 gyflagh 中的例子来说,假设 text = "mInecRaft#-ls_900d_f0r_$ntEr1ain",就是:

        Str4ToLong                   code                      LongToBase16

mIne cRaf  ->  1701726573 1717654115  -->   1874716276 -2120590913  ->  6fbde674 819a59bf
t#-l s_90  ->  1814897524 0809066355  -->  -1591700906  1531749031  ->  a1209256 5b4ca2a7
0d_f 0r_$  ->  1717527600 0610234928  -->  -1591884176  -965187555  ->  a11dc670 c678681d
ntEr 1ain  ->  1917154414 1852399921  -->  -1354040473    79179532  ->  af4afb67 04b82f0c

但是实际上,text 是什么我们是不知道的,我们要用 encrypted 来反推 text,其关键显然在 code 这个函数中。

然后现在的话,我们要完成的目标就是(右边的数值可以用 encrypted 进行 Base16ToLong 得到):

code([?, ?], key)  ->   1874716276 -2120590913
code([?, ?], key)  ->  -1591700906  1531749031
code([?, ?], key)  ->  -1591884176  -965187555
code([?, ?], key)  ->  -1354040473    79179532

然后我们可以注意到,code 函数的计算是可逆的,可以通过最终的结果反推出输入的值。

于是随便写一个逆向程序:

const KEYS: [Wrapping<u32>; 4] = [ Wrapping(909456177), Wrapping(825439544), Wrapping(892352820), Wrapping(926364468) ];
const ADD: Wrapping<u32> = Wrapping(2654435769);

fn uncode(a: u32, b: u32) -> (u32, u32) {
    let mut val = ADD * Wrapping(32);

    let mut a = Wrapping(a);
    let mut b = Wrapping(b);

    for _i in 0..32 {
        b -= ((a << 4) ^ (a >> 5)) + a ^ val + KEYS[((val >> 11).0 & 3) as usize];
        val -= ADD;
        a -= ((b << 4) ^ (b >> 5)) + b ^ val + KEYS[(val.0 & 3) as usize];
    }

    return (a.0, b.0);
}

然后就不难推算出 code 的输入是:

[ 1700225869, 1598378594 ]
[ 1817013865, 1098007406 ]
[  861893681, 1601779041 ]
[ 1967743793, 1765372015 ]

然后再把这些值用 LongToStr4 拼回去,得到 McWebRE_inMlnCrA1t_3a5y_1cIuop9i