js算法1
爬虫需要token,需要js转写为python,原js代码
const generateSign = (payload) => {
const ts = Math.floor(Date.now() / 1000).toString();
const magic = "wEb_sEcReT";
const raw = `${payload}_${ts}_${magic}`;
const enc = raw.split('').map(c => {
// charCodeAt(0) 获取字符的 ASCII 码,然后 +3 进行偏移,最后使用 fromCharCode 将新的 ASCII 码转换回字符
return String.fromCharCode(c.charCodeAt(0) + 3);
}).join('');
// 4. btoa 是浏览器自带的 Base64 编码函数
const sign = btoa(enc);
return {
'Timestamp': ts,
'X-Sign': sign
};
};
需要注意的是js的Data.now()返回的是毫秒级时间戳,而python的time.time()返回的是秒级时间戳,python用int转整数就行
import time
import math
import base64
payload = input()
tm = (int(time.time()))
magic = "wEb_sEcReT"
raw = (f"{payload}_{tm}_{magic}")
sign = ''.join(chr(ord(c)+3) for c in raw)
token = base64.b64encode(sign.encode('UTF-8')).decode()
print(token)
js算法2
这道题考js混淆还原
const _0x5a2f = ['\x6c\x6f\x67', '\x4f\x4b', '\x45\x52\x52\x4f\x52', '\x6c\x65\x6e\x67\x74\x68'];
const checkPassword = function(_0x1b2c) {
let _0x3a4b = _0x1b2c[_0x5a2f[3]];
if (_0x3a4b === 0x5) {
let _0x4c5d = _0x1b2c[0x0] + _0x1b2c[0x4];
if (_0x4c5d === '\x68\x6f') {
console[_0x5a2f[0]](_0x5a2f[1]);
return !![];
}
}
console[_0x5a2f[0]](_0x5a2f[2]);
return ![];
};
0x开头的数是十六进制数,\x开头的数是十六进制的ASCII码
!![]表示true,![]表示false
数组对象的访问除了用.还可以用[]
我们先换复杂变量名,再写个python脚本还原\x字符
while True:
inp = input().split('\\x')
for i in range(0,len(inp)):
if inp[i]:
raw = int(inp[i],16)
# 注意int函数的第二个参数是进制,这里是16进制,必须要加上
inp[i] = chr(raw)
print(f"{''.join(inp)}")
还原出来
const con = ['log', 'OK', 'ERROR', 'length'];
const checkPassword = function(args) {
let arg1 = args[con[3]];
if (arg1 === 5) {
let arg2 = args[0] + args[4];
if (arg2 === 'ho') {
console[con[0]](con[1]);
return true;
}
}
console[con[0]](con[2]);
return false;
};
js算法3
js代码
const con = ['split', 'length', 'charCodeAt'];
function verifyKey(args) {
let arr = '2|4|0|1|3'[con[0]]('|');
let num = 0;
let var2 = [];
while (true) {
switch (arr[num++]) {
case '0':
for (let var1 = 0; var1 < args[con[1]]; var1++) {
var2.push(args[con[2]](var1) ^ 0x2a);
}
continue;
case '1':
let var3 = [0x44, 0x42, 0x4b, 0x45, 0x51, 0x45, 0x41, 0x57];
if (var2[con[1]] !== var3[con[1]]) return false;
continue;
case '2':
if (!args) return false;
continue;
case '3':
for (let var4 = 0; var4 < var3[con[1]]; var4++) {
if (var2[var4] !== var3[var4]) return false;
}
return true;
case '4':
if (typeof args !== 'string') return false;
continue;
}
break;
}
}
控制流平坦化是一种常见的代码混淆技术,旨在通过改变程序的控制流结构来增加代码的复杂性和难以理解性。它通过将原本清晰的控制流转换为更复杂的形式,使得代码难以被分析和逆向工程
这个js本质就是简单的异或加解密,但通过使用控制流平坦化遍历多个case来增加代码的复杂度
'2|4|0|1|3'[con[0]]('|');
// 本质意思是将字符串'2|4|0|1|3'按照'|'分割成数组['2','4','0','1','3'],case的顺序就是按照这个数组的顺序来执行的
解密脚本
let var3 = [0x44, 0x42, 0x4b, 0x45, 0x51, 0x45, 0x41, 0x57];
for (let num = 0; num < var3.length; num++) {
var3[num] = String.fromCharCode(var3[num] ^ 0x2a);
}
console.log(var3.join(''));
A ^ B = C,A ^ C = B,B ^ C = A
js算法4
js代码
const trap = new Proxy({}, {
get: function(target, prop) {
return function(str) {
return str.split('').map(c => String.fromCharCode(c.charCodeAt(0) ^ prop.length)).join('');
};
}
});
const checkRoot = function(input) {
let check = function () { /* check */ };
let env = check['toString']()['length'] === 27;
let runner = [][trap.one('ejowfq')][trap.two('`lmpwqv`wlq')]('return btoa')();
if (!env) {
return input === runner('fake_flag');
} else {
return input === trap.magic('gidf~uw5}|Z4vZf5ax');
}
};
Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。它允许你创建一个对象,并为其指定一个处理程序对象,这个处理程序对象可以拦截和定义对目标对象的各种操作
先看懂proxy。他的第一个参数应该为该对象自身的属性方法,这里定义为空{}。第二个参数是proxy的拦截操作,当调用属性的值时就会触发proxy的get方法。
再看get方法的参数,target是被代理的对象,这里是空对象{},prop是被访问的属性名,内部还会传参str。如在访问trap.one时,prop就是'one',‘ejowfq’就是str,函数会将str每一个字与prop的长度进行异或运算,最后返回一个字符串
写一个运行脚本
inp = input()
raw = input()
res = ''.join((chr(ord(x)^len(inp)) for x in raw))
print(res)
checkRoot函数里
let env = check['toString']()['length'] === 27;
// 赋值优先级比较低,所以check['toString']()===27会先执行返回布尔值
.toString()无论遇到什么都会变为字符串,function () { /* check */ }刚好是27个字符,所以env为true
let runner = [][trap.one('ejowfq')][trap.two('`lmpwqv`wlq')]('return btoa')();
按照上面的算法。trap.one('ejowfq')返回filter,trap.two('lmpwqvwlq')返回constructor,所以runner就是[].filter.constructor('return btoa')()。filter是方法,他的constructor就是Function,所以runner就是Function('return btoa')(),返回一个base64编码函数(无意义,放在这里混淆)
filter负责过滤数组,他会只保留返回true的元素,用法如下
let arr = [1, 2, 3, 4, 5]; let evenNumbers = arr.filter(num => num % 2 === 0); const newArr = array.filter((item, i, arr) => { return item % 2 !== 0 }); // 保留数组中所有奇数元素
return input === trap.magic('gidf~uw5}|Z4vZf5ax');
// trap.magic('gidf~uw5}|Z4vZf5ax')返回一个字符串,input需要与这个字符串相等才会返回true
脚本解密为blac{pr0xy_1s_c0d}
js算法5
js代码
function customEncrypt(text) {
let encrypted = [];
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
if (i % 2 === 0) {
charCode = charCode + 5;
} else {
charCode = charCode - 3;
}
let highNibble = (charCode & 0xF0) >> 4;
let lowNibble = (charCode & 0x0F) << 4;
let finalByte = highNibble | lowNibble;
encrypted.push(finalByte);
}
return encrypted;
}
// 这是你从风控网络请求中截获的加密数组:
const targetArray = [182, 150, 102, 70, 8, 246, 131, 55, 131, 246, 135, 38, 40];
这是纯粹的js逆向
逆向中很常见& 0xF0和& 0x0F分别是取高四位和低四位
&是有0则0,|是有1则1
0x0F的二进制是00001111
0xF0的二进制是11110000
十六进制二位数满位是0xFF,二进制是11111111,刚好一字节
意思就是把ascii码的高四位和低四位交换了位置,所以解密时需要先交换回去再进行加减运算
| & 满足交换律:A|B = B|A,A&B = B&A,所以highNibble | lowNibble和lowNibble | highNibble是一样的
解密脚本
const targetArray = [182, 150, 102, 70, 8, 246, 131, 55, 131, 246, 135, 38, 40];
let decryptedText = [];
for (let i = 0;i < targetArray.length;i++) {
let highNibble = (targetArray[i] >> 4 ) & 0x0F;
let lowNibble = (targetArray[i] << 4 ) & 0xF0;
let finalByte = lowNibble | highNibble;
var raw = finalByte
if (i % 2 === 0){
raw = String.fromCharCode(raw - 5)
}
else {
raw = String.fromCharCode(raw + 3)
}
decryptedText.push(raw)
}
console.log(decryptedText.join(''));
& | 都不存在可逆运算。这里& 0x0F和& 0xF0是为了保证交换后高四位和低四位的值不会混在一起
十分注意的点,一开始我本想用parseInt(finalByte,2)来把交换后的二进制字符串转换为十进制数。结果在js中& | >> <<等位运算符的操作数会被自动转换为32位整数,所以可以直接进行ascii转码
flag{r3v3rse}
js算法6
js代码
function customEncode(text) {
let iv = 0x42;
let encrypted = [];
let prevCipher = iv;
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
let shifted = ((charCode << 3) & 0xFF) | (charCode >> 5);
let cipherByte = (shifted + prevCipher) % 256;
encrypted.push(cipherByte);
prevCipher = cipherByte;
}
return encrypted;
}
// 这是你抓包拿到的密文数组:
const targetArray = [117, 216, 227, 30, 249, 20, 39, 66, 60, 167, 40, 75, 228, 207];
考点cipherByte环取模256CBC(状态滚动)加密
一般取模是没有逆解,但环取模是可以逆原位的,好比时钟,逆运算就是变号取模
假设你现在在 2点钟,我让你往前走 5个小时
计算很简单:(2 + 5) % 12 = 7。你停在了 7点钟
向后退(解密)
现在你停在 7点钟,我想让你倒带(退回 5 个小时)
计算也很简单:(7 - 5) % 12 = 2。你完美回到了 2点钟
但js里负数取模也会得到负数,所以需要加一轮模数来保证结果为正数
const targetArray = [117, 216, 227, 30, 249, 20, 39, 66, 60, 167, 40, 75, 228, 207];
let decrypyed = []
let prevCipher = 0x42
for (let i = 0;i < targetArray.length;i++){
let shifted = (targetArray[i] - prevCipher + 256) % 256;
prevCipher = targetArray[i]
let charCode = ( shifted >> 3 ) | (( shifted << 5) & 0xFF)
decrypyed.push(String.fromCharCode(charCode))
}
console.log(decrypyed.join(''))
flag{cbc_m0d3}
js算法7
js代码
function verifyVM(input) {
const memory = [
0, 0, 1, 42, 3, 76,
0, 1, 2, 5, 3, 103,
0, 2, 1, 115, 3, 18,
0, 3, 2, 10, 3, 93,
0, 4, 1, 88, 3, 43,
0, 5, 2, 12, 3, 106,
0, 6, 1, 66, 3, 47,
0, 7, 2, 33, 3, 79,
0, 8, 1, 99, 3, 28,
4, 0
];
let pc = 0; // Program Counter (指令指针)
let acc = 0; // Accumulator (累加器寄存器)
// 虚拟机的 CPU 核心循环 (Fetch -> Decode -> Execute)
while (true) {
let opcode = memory[pc++]; // 抓取操作码
let operand = memory[pc++]; // 抓取操作数
switch (opcode) {
case 0:
acc = input.charCodeAt(operand);
break;
case 1:
acc ^= operand;
break;
case 2:
acc -= operand;
break;
case 3:
if (acc !== operand) return false;
break;
case 4:
return true;
}
}
}
这是一套简易的JSVMP。利用此技术可以用自己的指令集来编写一个js虚拟机
memory就是该虚拟机的指令集,pc++会优先运算并重赋值,所以opcode就是序偶的第一个数,operand就是序偶的第二个数
发现九行很有规律,每一行都是先取input的一个字符的ascii码放入acc寄存器,再进行异或或减法运算,最后与每行最后一个序偶的操作数进行比较,如果相等则通过。
逆过来,我们提出每行最后一个数,然后根据第二部进行逆解密再转为字符。一共九次
memory = [
0, 0, 1, 42, 3, 76,
0, 1, 2, 5, 3, 103,
0, 2, 1, 115, 3, 18,
0, 3, 2, 10, 3, 93,
0, 4, 1, 88, 3, 43,
0, 5, 2, 12, 3, 106,
0, 6, 1, 66, 3, 47,
0, 7, 2, 33, 3, 79,
0, 8, 1, 99, 3, 28,
]
pc = 2
decryto = []
for i in range(1,10):
times = memory[pc]
if(times == 1):
raw = chr(memory[pc+3] ^ memory[pc+1])
else:
raw = chr(memory[pc+3] + memory[pc+1])
decryto.append(raw)
pc += 6
print(''.join(decryto))
flagsvmp
