JS VM + HOOK

题目给了一个html 一个js源码 粗略查看是VM 说到VM 我们可以手动逆过去 但是那样很耗时间也费神 所以我们要使用一些技巧 题目源码如下

function createRegisters(obj){
	obj.registers = [];
	for(i=0; i < 256; ++i){
		obj.registers.push(0);
	}
};

function Machine() {
	createRegisters(this);
	this.code = [0]
	this.PC = 0;
	this.callstack = [];
	this.pow = Math.pow(2,32)
};

Machine.prototype = {
	opcodesCount: 16,
	run: run,
	loadcode: function(code){this.code = code},
	end: function(){this.code=[]}
};


function run(){
	while(this.PC < this.code.length){
		var command = parseCommand.call(this)
		command.execute(this);
	}
	//this.end()
}

function getOpcodeObject(){
	var opNum = (this.code[this.PC] % this.opcodesCount);
	this.PC += 1;
	return eval('new Opcode'+opNum);
}

function parseCommand(){
	var opcode = getOpcodeObject.call(this);
	opcode.consumeArgs(this);
	return opcode;
}

var opcCreate = "";
for(i=0;i<16;++i){
	opcCreate += "function Opcode"+i+"(){this.args=[]}\n";
}


eval(opcCreate);


function makeFromImm(obj) {
	var res = obj.code[obj.PC + 2];
	res <<=8;
	res += obj.code[obj.PC + 1];
	res <<=8;
	res += obj.code[obj.PC];
	res <<=8;
	res += obj.code[obj.PC+3];
	res = res >>> 0;
	return res;
}

function getRegImm(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = makeFromImm(obj);
	obj.PC += 4;
}

function getImm(obj){
	this.args[0] = makeFromImm(obj);
	obj.PC += 4;	
}

function getTwoRegs(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = obj.code[obj.PC];
	obj.PC += 1;
}

function getThreeRegs(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[2] = obj.code[obj.PC];
	obj.PC += 1;
}

function getRegString(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = getString(obj);
}

function getRegRegString(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[2] = getString(obj);
}

function getRegTwoString(obj){
	this.args[0] = obj.code[obj.PC];
	obj.PC += 1;
	this.args[1] = getString(obj);
	this.args[2] = getString(obj);
}

function getString(obj){
	var res = "";
	while(obj.code[obj.PC] != 0) {
		res += String.fromCharCode(obj.code[obj.PC]);
		obj.PC += 1;
	}
	obj.PC += 1;
	return res;
}

Opcode0.prototype = {
	consumeArgs : function(obj){},
	execute: function(){}
};

Opcode1.prototype = {
	consumeArgs: getRegImm,
	execute: function(obj){
		obj.registers[this.args[0]] = (obj.registers[this.args[0]] +  this.args[1]) % 0x100000000;
	}
}


Opcode2.prototype = {
	consumeArgs: getTwoRegs,	
	execute: function(obj){
		obj.registers[this.args[0]] = (obj.registers[this.args[0]] + obj.registers[this.args[1]]) % 0x100000000;
	}
}

Opcode3.prototype = {
	consumeArgs: getRegImm,
	execute: function(obj){
		obj.registers[this.args[0]] = ((obj.registers[this.args[0]] -  this.args[1]) % 0x100000000) >>> 0;
	}
}

Opcode4.prototype = {
	consumeArgs: getTwoRegs,
	execute: function(obj){
		obj.regsiters[this.args[0]] = ((obj.registers[this.args[0]] - this.registers[this.args[1]])%100000000) >>> 0
	}
}

Opcode5.prototype = {
	consumeArgs: getThreeRegs,
	execute: function(obj){
		var mult = obj.registers[this.args[0]] * obj.registers[this.args[1]];
		console.log(mult.toString(16));
		obj.registers[this.args[2]] = (mult / obj.pow) >>> 0;
		obj.registers[this.args[2]+1] = (mult & 0xffffffff) >>> 0;
	}
}

Opcode6.prototype = {
	consumeArgs: getThreeRegs,
	execute: function(obj){
		var divs = obj.registers[this.args[0]] * obj.pow + obj.registers[this.args[0]+1];
		obj.registers[this.args[2]]  = (divs / obj.registers[this.args[1]]) >>> 0;
		obj.registers[this.args[2]+1]= (divs % obj.registers[this.args[1]]) >>> 0;
	}
}

Opcode7.prototype = {
	consumeArgs: getRegImm,
	execute: function(obj) {
		obj.registers[this.args[0]] = this.args[1];
	}	
}

Opcode8.prototype = {
	consumeArgs: getImm,
	execute: function(obj){
		obj.callstack.push(obj.PC);
		obj.PC = this.args[0];
	}
}

Opcode9.prototype = {
	consumeArgs: getImm,
	execute: function(obj){
		obj.PC = (obj.PC +  this.args[0]) % obj.code.length;
	}
}

Opcode10.prototype = {
	consumeArgs: function(){},
	execute: function(obj){
		obj.PC = obj.callstack.pop();
	}
}

Opcode11.prototype = {
	consumeArgs: getRegString,
	execute: function(obj){
		obj.registers[this.args[0]] = eval('new '+this.args[1]);
	}
}

Opcode12.prototype = {
	consumeArgs: getRegTwoString,
	execute: function(obj){
		obj.registers[this.args[0]][this.args[1]] = Function(this.args[2]);
	}
}

Opcode13.prototype = {
	consumeArgs: getRegRegString,
	execute: function(obj){
		obj.registers[this.args[0]] = obj.registers[this.args[1]][this.args[2]];
	}
}

Opcode14.prototype = {
	consumeArgs: getRegRegString,
	execute: function(obj){
		obj.registers[this.args[1]][this.args[2]] = obj.registers[this.args[0]];
	}
}

Opcode15.prototype = {
	consumeArgs: getRegRegString,
	execute: function(obj){
		obj.registers[this.args[0]] = obj.registers[this.args[1]][this.args[2]]();
	}
}
function check(){
machine = new Machine;
machine.loadcode([11, 1, 79, 98, 106, 101, 99, 116, 0, 12, 1, 120, 0, 114, 101, 116, 117, 114, 110, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 39, 105, 110, 112, 117, 116, 39, 41, 91, 48, 93, 46, 118, 97, 108, 117, 101, 47, 47, 0, 15, 3, 1, 120, 0, 14, 3, 1, 117, 115, 101, 114, 105, 110, 112, 117, 116, 0, 12, 1, 121, 0, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 101, 110, 100, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 123, 116, 104, 105, 115, 46, 99, 111, 100, 101, 61, 91, 93, 59, 116, 104, 105, 115, 46, 80, 67, 61, 49, 55, 51, 125, 47, 47, 0, 15, 3, 1, 121, 0, 12, 1, 122, 0, 97, 108, 101, 114, 116, 40, 49, 41, 59, 47, 47, 11, 234, 79, 98, 106, 101, 99, 116, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 50, 41, 59, 47, 47, 12, 234, 120, 255, 118, 97, 114, 32, 102, 61, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 114, 101, 103, 105, 115, 116, 101, 114, 115, 91, 49, 93, 46, 117, 115, 101, 114, 105, 110, 112, 117, 116, 47, 47, 10, 118, 97, 114, 32, 105, 32, 61, 32, 102, 46, 108, 101, 110, 103, 116, 104, 47, 47, 10, 118, 97, 114, 32, 110, 111, 110, 99, 101, 32, 61, 32, 39, 103, 114, 111, 107, 101, 39, 59, 47, 47, 10, 118, 97, 114, 32, 106, 32, 61, 32, 48, 59, 47, 47, 10, 118, 97, 114, 32, 111, 117, 116, 32, 61, 32, 91, 93, 59, 47, 47, 10, 118, 97, 114, 32, 101, 113, 32, 61, 32, 116, 114, 117, 101, 59, 47, 47, 10, 119, 104, 105, 108, 101, 40, 106, 32, 60, 32, 105, 41, 123, 47, 47, 10, 111, 117, 116, 46, 112, 117, 115, 104, 40, 102, 46, 99, 104, 97, 114, 67, 111, 100, 101, 65, 116, 40, 106, 41, 32, 94, 32, 110, 111, 110, 99, 101, 46, 99, 104, 97, 114, 67, 111, 100, 101, 65, 116, 40, 106, 37, 53, 41, 41, 47, 47, 10, 106, 43, 43, 59, 47, 47, 10, 125, 47, 47, 10, 118, 97, 114, 32, 101, 120, 32, 61, 32, 32, 91, 49, 44, 32, 51, 48, 44, 32, 49, 52, 44, 32, 49, 50, 44, 32, 54, 57, 44, 32, 49, 52, 44, 32, 49, 44, 32, 56, 53, 44, 32, 55, 53, 44, 32, 53, 48, 44, 32, 52, 48, 44, 32, 51, 55, 44, 32, 52, 56, 44, 32, 50, 52, 44, 32, 49, 48, 44, 32, 53, 54, 44, 32, 53, 53, 44, 32, 52, 54, 44, 32, 53, 54, 44, 32, 54, 48, 93, 59, 47, 47, 10, 105, 102, 32, 40, 101, 120, 46, 108, 101, 110, 103, 116, 104, 32, 61, 61, 32, 111, 117, 116, 46, 108, 101, 110, 103, 116, 104, 41, 32, 123, 47, 47, 10, 106, 32, 61, 32, 48, 59, 47, 47, 10, 119, 104, 105, 108, 101, 40, 106, 32, 60, 32, 101, 120, 46, 108, 101, 110, 103, 116, 104, 41, 123, 47, 47, 10, 105, 102, 40, 101, 120, 91, 106, 93, 32, 33, 61, 32, 111, 117, 116, 91, 106, 93, 41, 47, 47, 10, 101, 113, 32, 61, 32, 102, 97, 108, 115, 101, 59, 47, 47, 10, 106, 32, 43, 61, 32, 49, 59, 47, 47, 10, 125, 47, 47, 10, 105, 102, 40, 101, 113, 41, 123, 47, 47, 10, 97, 108, 101, 114, 116, 40, 39, 89, 79, 85, 32, 87, 73, 78, 33, 39, 41, 59, 47, 47, 10, 125, 101, 108, 115, 101, 123, 10, 97, 108, 101, 114, 116, 40, 39, 78, 79, 80, 69, 33, 39, 41, 59, 10, 125, 125, 101, 108, 115, 101, 123, 97, 108, 101, 114, 116, 40, 39, 78, 79, 80, 69, 33, 39, 41, 59, 125, 47, 47, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 51, 41, 59, 47, 47, 15, 1, 234, 120, 255, 9, 255, 255, 255, 12, 10, 97, 108, 101, 114, 116, 40, 52, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 53, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 54, 41, 59, 47, 47, 10, 97, 108, 101, 114, 116, 40, 55, 41, 59, 47, 47, 0, 12, 1, 103, 0, 118, 97, 114, 32, 105, 32, 61, 48, 59, 119, 104, 105, 108, 101, 40, 105, 60, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 46, 108, 101, 110, 103, 116, 104, 41, 123, 105, 102, 40, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 91, 105, 93, 32, 61, 61, 32, 50, 53, 53, 32, 41, 32, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 99, 111, 100, 101, 91, 105, 93, 32, 61, 32, 48, 59, 105, 43, 43, 125, 47, 47, 0, 12, 1, 104, 0, 119, 105, 110, 100, 111, 119, 46, 109, 97, 99, 104, 105, 110, 101, 46, 80, 67, 61, 49, 55, 50, 47, 47, 0, 15, 0, 1, 103, 0, 15, 0, 1, 104, 0])
machine.run();
}

能看到源码是这样的 我们要知道这是在一个html中 我们可以通过js来调试这个程序 我也做了一个opcode的列表 以防有些不理解的

Opcode 作用
Opcode1Opcode7 加减乘除、赋值
Opcode8Opcode10 跳转、调用、返回
Opcode11 new Object
Opcode12 Function(字符串)动态编译代码
Opcode13Opcode14 对象属性读写
Opcode15 调用对象中的函数

由于 Opcode12 会调用 Function(this.args[2]),将字符串动态编译成 JavaScript 函数,因此我们没有必要把整个 VM 一条条手动还原。只要在动态编译发生之前,把传入 Function() 的字符串打印出来,就能直接拿到 VM 即将执行的真实代码。

原始的 Opcode12 如下:

Opcode12.prototype = {
    consumeArgs: getRegTwoString,
    execute: function(obj){
        obj.registers[this.args[0]][this.args[1]] = Function(this.args[2]);
    }
}

其中:

this.args[0]

表示寄存器编号:

this.args[1]

表示对象属性名:

this.args[2]

则是即将被动态编译的 JavaScript 源码字符串。

因此,我们可以直接对 Opcode12 进行 hook,在执行 Function() 之前输出源码:

Opcode12.prototype = {
    consumeArgs: getRegTwoString,
    execute: function(obj){
        console.log("[Opcode12] register =", this.args[0]);
        console.log("[Opcode12] property =", this.args[1]);
        console.log("[Opcode12] source =\n" + this.args[2]);

        debugger;

        obj.registers[this.args[0]][this.args[1]] = Function(this.args[2]);
    }
}

修改完成后,重新加载 HTML 文件,在输入框中随便输入一些字符并点击提交。程序运行到 Opcode12 时会暂停,同时控制台会输出动态生成的源码。

这里会打印出多个函数,其中部分只是干扰代码。例如:

return document.getElementsByTagName('input')[0].value//

它的作用是获取输入框内容。

还会看到:

window.machine.end = function(){this.code=[];this.PC=173}//

以及:

var i =0;
while(i<window.machine.code.length){
    if(window.machine.code[i] == 255)
        window.machine.code[i] = 0;
    i++
}//

最后一个函数非常关键。它会将字节码中的 255 修改为 0

题目中的字符串读取函数是:

function getString(obj){
    var res = "";
    while(obj.code[obj.PC] != 0) {
        res += String.fromCharCode(obj.code[obj.PC]);
        obj.PC += 1;
    }
    obj.PC += 1;
    return res;
}

可以看到,字符串以 0 作为结束标志。题目作者提前在字节码中放入了一些 255,程序运行后再将其替换为 0。这样一来,字节码会在运行过程中改变解析方式。

随后程序还会执行:

window.machine.PC=172//

将 PC 指针重新设置到隐藏代码所在的位置,使 VM 再次解析修改后的字节码。

这是一种简单的自修改字节码技巧。

为了减少无关输出,我们可以进一步优化 hook,只打印包含关键字 nonce 的函数:

Opcode12.prototype = {
    consumeArgs: getRegTwoString,
    execute: function(obj){
        if (
            typeof this.args[2] === "string" &&
            this.args[2].includes("nonce")
        ) {
            console.log("[+] Found check function:");
            console.log(this.args[2]);
            debugger;
        }

        obj.registers[this.args[0]][this.args[1]] = Function(this.args[2]);
    }
}

再次运行程序后,可以得到真正的校验逻辑:

var f=window.machine.registers[1].userinput//
var i = f.length//
var nonce = 'groke';//
var j = 0;//
var out = [];//
var eq = true;//
while(j < i){//
out.push(f.charCodeAt(j) ^ nonce.charCodeAt(j%5))//
j++;//
}//
var ex =  [1, 30, 14, 12, 69, 14, 1, 85, 75, 50, 40, 37, 48, 24, 10, 56, 55, 46, 56, 60];//
if (ex.length == out.length) {//
j = 0;//
while(j < ex.length){//
if(ex[j] != out[j])//
eq = false;//
j += 1;//
}//
if(eq){//
alert('YOU WIN!');//
}else{
alert('NOPE!');
}}else{alert('NOPE!');}//

虽然每一行末尾都有 //,但下一行前面存在换行符,因此每一行仍然可以正常执行。这里的 // 主要用于吞掉字符串拼接过程中可能混入的干扰数据。

接下来分析校验逻辑:

out.push(
    f.charCodeAt(j) ^
    nonce.charCodeAt(j % 5)
)

用户输入字符串中的每一个字符,都会与字符串:

groke

循环异或。

异或结果必须等于数组:

[
    1, 30, 14, 12, 69,
    14, 1, 85, 75, 50,
    40, 37, 48, 24, 10,
    56, 55, 46, 56, 60
]

由于异或运算具有自反性:

A ^ B ^ B = A

因此使用相同的密钥再次异或即可恢复原始输入。

exp

ex = [
    1, 30, 14, 12, 69,
    14, 1, 85, 75, 50,
    40, 37, 48, 24, 10,
    56, 55, 46, 56, 60
]

nonce = "groke"

flag = "".join(
    chr(value ^ ord(nonce[index % len(nonce)]))
    for index, value in enumerate(ex)
)

print(flag)

这道题虽然套了一层 js VM,并且还加入了自修改字节码,但是并不需要完整分析全部 opcode。只要抓住 Opcode12 这个动态编译点,在 Function() 执行前 dump 参数,就可以直接得到真实校验逻辑。

当然 还有一个方法就是 在这里添加一个 console.log('new 0pcode'+command.args)

function run(){
	while(this.PC < this.code.length){
		var command = parseCommand.call(this)
		console.log('new 0pcode'+command.args)
		command.execute(this);
	}
	//this.end()
}

效果是一样的

当然还有更简单的

一把梭

image

flag

WOW_so_EASY