GFSJ0122-【secret-string-400】
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 | 作用 |
|---|---|
Opcode1–Opcode7 |
加减乘除、赋值 |
Opcode8–Opcode10 |
跳转、调用、返回 |
Opcode11 |
new Object |
Opcode12 |
Function(字符串)动态编译代码 |
Opcode13、Opcode14 |
对象属性读写 |
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()
}
效果是一样的
当然还有更简单的
一把梭
flag
WOW_so_EASY
评论