目测重点在于cookie:mz和m
获取mz.js: https://match.yuanrenxue.com/static/match/match14/m.js
获取设置m: https://match.yuanrenxue.com/api/match/14/m
一、还原16进制
const fs = require('fs');
const parser = require('@babel/parser');
const generator = require('@babel/generator').default;
const jsCode = fs.readFileSync('./mz.js', {encoding: 'utf-8'})
let ast = parser.parse(jsCode);//parse 一下就可以了
let code = generator(ast, {minified: true, jsescOption: {minimum: true}}).code;
ast = parser.parse(code);
code = generator(ast).code;
fs.writeFile('./mz1.js', code, (err) => {
});
初步看到mz赋值位置:
n["Nu" + "mK" + "J"] = "mz" + "=", n["ZD" + "Kb" + "v"] = ";p" + "at" + "h=" + "/", n["Dn" + "NJ" + "W"] = "/a" + "pi" + "/m" + "at" + "ch" + "/1" + "4/" + "m", n["tl" + "pB" + "r"] = function (K, Y) {
二、字符串拼接
目标:n["cW" + "QT" + "T"] -> n["cWQTT"]
import json
import os
def concat_obj_property_name(node):
if type(node) == list:
for item in node:
concat_obj_property_name(item)
return
elif type(node) != dict:
return
# 捕获一个二元运算节点
if 'type' in node and node['type'] == 'BinaryExpression':
if not (node['left']['type'] == 'Literal' and node['right']['type'] == 'Literal'):
concat_obj_property_name(node['left'])
concat_obj_property_name(node['right'])
if node['left']['type'] == 'Literal' and node['right']['type'] == 'Literal':
# 构造新节点
new_node = {'type': 'Literal', 'value': node['left']['value'] + node['right']['value']}
node.clear()
node.update(new_node)
return
for key in node.keys():
concat_obj_property_name(node[key])
if __name__ == '__main__':
with open('mz1.json', 'r', encoding='utf8') as f:
data = json.loads(f.read())
concat_obj_property_name(data)
with open('mz2.json', 'w', encoding='utf8') as f:
f.write(json.dumps(data))
os.system('/usr/local/bin/node JsonToJs mz2.json mz2.js')
三、数值运算还原
目标:window['n'] = 7317 + -8779 + 1462; -> window['n'] = 0;
使用Python处理要考虑的情况太多。使用js更方便
const fs = require('fs');
//js转AST代码
const parser = require('@babel/parser');
//遍历ASR节点
const traverse = require('@babel/traverse').default;
//用来判断节点类型产生新的节点
const t = require('@babel/types');
//用来把AST转换成js代码
const generator = require('@babel/generator').default;
const jscode = fs.readFileSync("./mz2.js", {
encoding: "utf-8"
});
const visitor = {
"BinaryExpression"(path) {
let left = path.node.left;
let right = path.node.right;
if (left.type == "NumericLiteral" | left.type == "UnaryExpression" && right.type == "NumericLiteral" | right.type == "UnaryExpression") {
path.replaceWith(t.valueToNode(path.evaluate().value));
}
}
}
const revertUnaryExpression = {
"UnaryExpression"(path) {
let argument = path.node.argument;
if (argument.type == 'NumericLiteral' | argument.type == 'UnaryExpression' && generator(path.node).code.indexOf(' -') != -1) {
path.replaceWith(t.valueToNode(eval(generator(path.node).code)));
}
}
}
let ast = parser.parse(jscode);
for (let i = 0; i < 10; i++) {
traverse(ast, visitor);
}
traverse(ast, revertUnaryExpression)
let code = generator(ast).code;
fs.writeFile('./mz3.js', code, (err) => {
});
四、对象调用还原
个人想法:这一步才是备而后动-勿使有变这个题目的精髓。
var L = {};
L['aKzhL'] = function (S, P) {
return S + P;
}
var G = L;
function C(S, P) {
var F = G['aKzhL'](G['IboVR'](65535, S), G['IboVR'](65535, P));
return G['DNzZE'](G['QpEsx'](G['aKzhL'](G['imxsL'](G['Ggivm'](S, 16), G['Ggivm'](P, 16)), G['Ggivm'](F, 16)), 16), G['IboVR'](65535, F));
}
以L来说,在后面替换并未直接使用,而是使用var G = L;进行替换,这还只是针对一个块语句来说,若是放到全局,L不知道被污染多少次了。观察结构,可以在块语句中进行替换。
目标:还原前如上述代码。
还原后:
function C(S, P) {
var F = (65535 & S) + (65535 & P);
return (S >> 16) + (P >> 16) + (F >> 16) << 16 | 65535 & F;
}
import copy
import json
import os
obj_name_list = []
obj_property_dict = {}
def get_property_dict(node):
global obj_property_dict
if type(node) == list:
for item in node:
get_property_dict(item)
return
elif type(node) != dict:
return
# 因为存在变量更新的情况,所以不能遍历全文,在一个块语句中就做替换
if 'type' in node and node['type'] == 'BlockStatement':
try:
# 观察发现,代码中都是 赋值-表达式语句-赋值 这三种格式
if len(node['body']) >= 3 and \
node['body'][0]['type'] == 'VariableDeclaration' and \
node['body'][1]['type'] == 'ExpressionStatement' and \
node['body'][2]['type'] == 'VariableDeclaration':
obj_property_dict.clear()
if 'init' in node['body'][2]['declarations'][0] and \
node['body'][2]['declarations'][0]['init']['type'] == 'Identifier':
obj_name = node['body'][2]['declarations'][0]['id']['name']
if obj_name not in obj_property_dict:
obj_property_dict[obj_name] = {}
for i in node['body'][1]['expression']['expressions']:
if i['type'] == 'AssignmentExpression':
property_name = i['left']['property']['value']
obj_property_dict[obj_name][property_name] = i['right']
obj_property_reload(node)
node['body'][2]['declarations'][0]['init'] = {'type': 'Literal', 'value': 'Nothing'}
node['body'] = node['body'][2:]
except KeyError as e:
print('error', e, node['body'][2]['declarations'])
for key in node.keys():
get_property_dict(node[key])
# 对象调用还原
def obj_property_reload(node):
if type(node) == list:
for item in node:
obj_property_reload(item)
return
elif type(node) != dict:
return
try:
if node['type'] == 'MemberExpression':
# 处理形似:_0x5243e3['UhBgk']return '666'
try:
obj_name = node['object']['name']
obj_property_name = node['property']['value']
new_node = obj_property_dict[obj_name][obj_property_name]
if new_node['type'] != 'FunctionExpression':
node.clear()
node.update(new_node)
except Exception:
pass
# 捕获一个函数调用节点,且子节点callee的类型是一个MemberExpression
if node['type'] == 'CallExpression' and node['callee']['type'] == 'MemberExpression':
argument_list = node['arguments']
for para in argument_list:
if para['type'] == 'CallExpression': # 递归调用
obj_property_reload(para)
obj_name = node['callee']['object']['name']
obj_property_name = node['callee']['property']['value'] # 获取需要调用的对象属性名称
function_node = obj_property_dict[obj_name][obj_property_name] # 获取函数定义节点,即对象的属性值(该属性值是一个函数定义)
# 获取形参
param_list = [item['name'] for item in function_node['params']]
# 获取实参
param_argument_dict = dict(zip(param_list, argument_list))
return_node = copy.deepcopy(function_node['body']['body'][0])
# print(return_node)
if return_node['argument']['type'] == 'BinaryExpression' or \
return_node['argument']['type'] == 'LogicalExpression':
if return_node['argument']['left']['type'] == 'Identifier':
return_node['argument']['left'] = param_argument_dict[return_node['argument']['left']['name']]
if return_node['argument']['right']['type'] == 'Identifier':
return_node['argument']['right'] = param_argument_dict[return_node['argument']['right']['name']]
node.clear()
node.update(return_node['argument'])
elif return_node['argument']['type'] == 'CallExpression':
if return_node['argument']['callee']['type'] != 'MemberExpression':
function_name = return_node['argument']['callee']['name']
if function_name in param_argument_dict:
return_node['argument']['callee'] = param_argument_dict[function_name]
for i in range(len(return_node['argument']['arguments'])):
if return_node['argument']['arguments'][i]['type'] == 'Identifier':
argument_name = return_node['argument']['arguments'][i]['name']
return_node['argument']['arguments'][i] = param_argument_dict[argument_name]
node.clear()
node.update(return_node['argument'])
except Exception as e:
print('???', e)
for key in node.keys():
obj_property_reload(node[key])
if __name__ == '__main__':
with open('mz3.json', 'r', encoding='utf8') as f:
data = json.loads(f.read())
get_property_dict(data)
print(obj_property_dict.keys())
with open('mz4.json', 'w', encoding='utf8') as f:
f.write(json.dumps(data))
os.system('/usr/local/bin/node JsonToJs mz4.json mz4.js')
五、控制流平坦化
import json
import os
def sort_code(node):
if type(node) == list:
try:
if len(node) == 2 and node[1]['type'] == 'WhileStatement' and node[0]['type'] == 'VariableDeclaration':
for i in node[0]['declarations']:
if i['type'] == 'VariableDeclarator' and i['init'] and \
i['init']['type'] == 'CallExpression':
sort_list = i['init']['callee']['object']['value'].split('|')
cases_list = node[1]['body']['body'][0]['cases']
result_list = [cases_list[int(i)]['consequent'][0] for i in sort_list]
node.clear()
node.extend(result_list)
except (KeyError, TypeError):
for item in node:
sort_code(item)
return
elif type(node) == dict:
for key in node.keys():
sort_code(node[key])
return
else:
return
for item in node:
sort_code(item)
if __name__ == '__main__':
with open('mz5.json', 'r', encoding='utf8') as f:
data = json.loads(f.read())
sort_code(data)
with open('mz6.json', 'w', encoding='utf8') as f:
f.write(json.dumps(data))
os.system('/usr/local/bin/node JsonToJs mz6.json mz6.js')
经过以上几个步骤,逻辑已经基本清晰:
function E(K) {
function d(h, b) {
var D = b;
var I = _n('jsencrypt');
var u = new I();
var Q = u['encode'](h, D);
// if (m5['toString']()['indexOf']('\n') != -(1507 + -311 * -11 + -4927)) while (!![]) {
// console['log'](x['pSnMY'](x['pSnMY'](x['pSnMY']('生而', '为虫'), ',我'), '很抱') + '歉');
// }
return Q;
}
return result = d(K, K), result;
}
let a, p;
a = Date['parse'](new Date()) * 8;
p = E(parseInt(a / 8)); //rsa加密
let b = Date['parse'](new Date());
let aa = m5(p);
let bb = m5(b);
let d = 'Mozilla,Netscape,5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36,[object NetworkInformation],true,,[object Geolocation],8,zh-CN,zh-CN,zh,0,[object MediaCapabilities],[object MediaSession],[object MimeTypeArray],true,[object Permissions],MacIntel,[object PluginArray],Gecko,20030107,[object UserActivation],Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36,Google Inc.,,[object DeprecatedStorageQuota],[object DeprecatedStorageQuota],875,0,25,1440,30,900,[object ScreenOrientation],30,1440,[object DOMStringList],function assign() { [native code] },,match.yuanrenxue.com,match.yuanrenxue.com,https://match.yuanrenxue.com/match/14,https://match.yuanrenxue.com,/match/14,,https:,function reload() { [native code] },function replace() { [native code] },,function toString() { [native code] },function valueOf() { [native code] }'
let b64_zw = 'TW96aWxsYSxOZXRzY2FwZSw1LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTVfNykgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMC4wLjAuMCBTYWZhcmkvNTM3LjM2LFtvYmplY3QgTmV0d29ya0luZm9ybWF0aW9uXSx0cnVlLCxbb2JqZWN0IEdlb2xvY2F0aW9uXSw4LHpoLUNOLHpoLUNOLHpoLDAsW29iamVjdCBNZWRpYUNhcGFiaWxpdGllc10sW29iamVjdCBNZWRpYVNlc3Npb25dLFtvYmplY3QgTWltZVR5cGVBcnJheV0sdHJ1ZSxbb2JqZWN0IFBlcm1pc3Npb25zXSxNYWNJbnRlbCxbb2JqZWN0IFBsdWdpbkFycmF5XSxHZWNrbywyMDAzMDEwNyxbb2JqZWN0IFVzZXJBY3RpdmF0aW9uXSxNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvMTEwLjAuMC4wIFNhZmFyaS81MzcuMzYsR29vZ2xlIEluYy4sLFtvYmplY3QgRGVwcmVjYXRlZFN0b3JhZ2VRdW90YV0sW29iamVjdCBEZXByZWNhdGVkU3RvcmFnZVF1b3RhXSw4NzUsMCwyNSwxNDQwLDMwLDkwMCxbb2JqZWN0IFNjcmVlbk9yaWVudGF0aW9uXSwzMCwxNDQwLFtvYmplY3QgRE9NU3RyaW5nTGlzdF0sZnVuY3Rpb24gYXNzaWduKCkgeyBbbmF0aXZlIGNvZGVdIH0sLG1hdGNoLnl1YW5yZW54dWUuY29tLG1hdGNoLnl1YW5yZW54dWUuY29tLGh0dHBzOi8vbWF0Y2gueXVhbnJlbnh1ZS5jb20vbWF0Y2gvMTQsaHR0cHM6Ly9tYXRjaC55dWFucmVueHVlLmNvbSwvbWF0Y2gvMTQsLGh0dHBzOixmdW5jdGlvbiByZWxvYWQoKSB7IFtuYXRpdmUgY29kZV0gfSxmdW5jdGlvbiByZXBsYWNlKCkgeyBbbmF0aXZlIGNvZGVdIH0sLGZ1bmN0aW9uIHRvU3RyaW5nKCkgeyBbbmF0aXZlIGNvZGVdIH0sZnVuY3Rpb24gdmFsdWVPZigpIHsgW25hdGl2ZSBjb2RlXSB9';
m = m5(gee(aa, bb, c, d, e, b64_zw)) + '|' + b + '|' + a + '|';
return m
但是直接运行还是会陷入死循环,需要手动处理如:补环境,干掉格式检查,干掉本地环境检查。如以下示例:
// eval('delete document'), eval('delete window');
// bp = eval('CanvasCaptureMediaStreamTrack');
// global替换为Node环境下不存在的属性如:f**e['you']
try {
global && (cl[cs >>> 2] &= 25 << 32 - cs % 4 * 8, cl['length'] = Math['ceil'](cs / 4));
} catch (cM) {
cM[cs >>> 2] &= 4294967295 << 32 - cs % 4 * 8, cM['length'] = Math['ceil'](cs / 4);
}
经过以上操作,发现还缺少v14和v142参数,是在m.js文件中进行的赋值,又发现每次请求,m.js中的大数组是随机变化的。
window[$_0x3469('\x30\x78\x31\x32\x35', '\x65\x45\x30\x5a')] = '\x35\x37\x6f' + '\x38\x75\x33' + '\x70\x6b\x62' + '\x74';
window[$_0x3469('\x30\x78\x33\x34', '\x66\x56\x53\x75') + '\x32'] = $_0x3469('\x30\x78\x33\x31', '\x49\x44\x69\x5a') + '\x33\x36\x35' + $_0x3469('\x30\x78\x37\x34', '\x23\x64\x73\x53') + '\x32\x34';
so等到需要时动态请求即可:
def get_v():
"""
get v14 and v142
:return:
"""
res = session.get('https://match.yuanrenxue.com/api/match/14/m')
v = re.findall("window\[\${0,1}.*?=(\${0,1}.*?);", res.text)
if len(v) != 2:
get_v()
with open('./m.js', 'wb') as f:
f.write(res.content)
os.system('/usr/local/bin/node JsToJson m.js m.json')
with open('./m.json', 'r', encoding='utf8') as f:
node = json.loads(f.read())
# 提取数组还原的代码及还原代码中的常量,其它代码无用
array_revert_node = {
'type': 'Program',
'body': node['body'][:3],
'sourceType': 'script'
}
with open('m_array_revert.json', 'w', encoding='utf8') as f:
f.write(json.dumps(array_revert_node))
os.system('/usr/local/bin/node JsonToJs m_array_revert.json m_array_revert.js')
return v
完整代码:
py:
import json
import os
import re
import subprocess
import execjs
import requests
node = execjs.get()
session = requests.session()
headers = {'User-Agent': 'yuanrenxue.project'}
sum_num = 0
def login():
data = {
'username': 'lllll',
'pwd': '********'
}
session.post('https://match.yuanrenxue.com/api/login', data=data)
session.get('https://match.yuanrenxue.com/api/loginInfo')
def get_v():
"""
get v14 and v142
:return:
"""
res = session.get('https://match.yuanrenxue.com/api/match/14/m')
v = re.findall("window\[\${0,1}.*?=(\${0,1}.*?);", res.text)
if len(v) != 2:
get_v()
with open('./m.js', 'wb') as f:
f.write(res.content)
os.system('/usr/local/bin/node JsToJson m.js m.json')
with open('./m.json', 'r', encoding='utf8') as f:
node = json.loads(f.read())
array_revert_node = {
'type': 'Program',
'body': node['body'][:3],
'sourceType': 'script'
}
with open('m_array_revert.json', 'w', encoding='utf8') as f:
f.write(json.dumps(array_revert_node))
os.system('/usr/local/bin/node JsonToJs m_array_revert.json m_array_revert.js')
return v
def get_cookie(page_num, v):
with open('m_array_revert.js', 'r', encoding='utf-8') as f:
ctx = node.compile(f.read())
v = [ctx.eval(i) for i in v]
p = subprocess.Popen(['/usr/local/bin/node', './mz6.js', str(page_num), v[0], v[1]], stdout=subprocess.PIPE)
m = p.stdout.read().decode('UTF-8').replace('\n', '')
cookies = {
'mz': 'TW96aWxsYSxOZXRzY2FwZSw1LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTVfNykgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMC4wLjAuMCBTYWZhcmkvNTM3LjM2LFtvYmplY3QgTmV0d29ya0luZm9ybWF0aW9uXSx0cnVlLCxbb2JqZWN0IEdlb2xvY2F0aW9uXSw4LHpoLUNOLHpoLUNOLHpoLDAsW29iamVjdCBNZWRpYUNhcGFiaWxpdGllc10sW29iamVjdCBNZWRpYVNlc3Npb25dLFtvYmplY3QgTWltZVR5cGVBcnJheV0sdHJ1ZSxbb2JqZWN0IFBlcm1pc3Npb25zXSxNYWNJbnRlbCxbb2JqZWN0IFBsdWdpbkFycmF5XSxHZWNrbywyMDAzMDEwNyxbb2JqZWN0IFVzZXJBY3RpdmF0aW9uXSxNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvMTEwLjAuMC4wIFNhZmFyaS81MzcuMzYsR29vZ2xlIEluYy4sLFtvYmplY3QgRGVwcmVjYXRlZFN0b3JhZ2VRdW90YV0sW29iamVjdCBEZXByZWNhdGVkU3RvcmFnZVF1b3RhXSw4NzUsMCwyNSwxNDQwLDMwLDkwMCxbb2JqZWN0IFNjcmVlbk9yaWVudGF0aW9uXSwzMCwxNDQwLFtvYmplY3QgRE9NU3RyaW5nTGlzdF0sZnVuY3Rpb24gYXNzaWduKCkgeyBbbmF0aXZlIGNvZGVdIH0sLG1hdGNoLnl1YW5yZW54dWUuY29tLG1hdGNoLnl1YW5yZW54dWUuY29tLGh0dHBzOi8vbWF0Y2gueXVhbnJlbnh1ZS5jb20vbWF0Y2gvMTQsaHR0cHM6Ly9tYXRjaC55dWFucmVueHVlLmNvbSwvbWF0Y2gvMTQsLGh0dHBzOixmdW5jdGlvbiByZWxvYWQoKSB7IFtuYXRpdmUgY29kZV0gfSxmdW5jdGlvbiByZXBsYWNlKCkgeyBbbmF0aXZlIGNvZGVdIH0sLGZ1bmN0aW9uIHRvU3RyaW5nKCkgeyBbbmF0aXZlIGNvZGVdIH0sZnVuY3Rpb24gdmFsdWVPZigpIHsgW25hdGl2ZSBjb2RlXSB9',
'm': m + str(page_num)
}
print(v)
print(cookies)
return cookies
def get_page_info():
global sum_num
url = 'https://match.yuanrenxue.com/api/match/14?'
for i in range(1, 6):
v = get_v()
cookies = get_cookie(i, v)
res = session.get(f'{url}page={i}', headers={'User-Agent': 'yuanrenxue.project'}, cookies=cookies).json()
for j in res['data']:
sum_num += j['value']
if __name__ == '__main__':
login()
get_page_info()
print(sum_num)
js:
GOOD-GOOD-STUDY/mz6.js at master · tianmingbo/GOOD-GOOD-STUDY · GitHub