绕过反调试
每xx ms执行一次debugger
setInterval(function () {
debugger
}, 500)
在debugger处右键 永不在此处暂停(或:添加条件断点 false)
每xx ms打印一个值 干扰控制台输出
- 单一js文件形态:
setInterval(function () {
if (eval.toString() !== 'function eval() { [native code] }'){
w();
dd();
while (1){
console.error('苦蟲');
console.error('闆蘭')
}
}
if (setInterval.toString() !== 'function setInterval() { [native code] }'){
w();
dd();
console.error('總難煉為');
debugger
}
console.error('永不言棄,jy');
}, 500);
控制台处定位到代码,源代码处右键 在网络面板中显示,找到对应条目右键 阻止请求URL
- 嵌入js中作为函数的形态:
function oo0O0(){
window.a = 'xxxxxxxxxxxxxxxxxxxxxxxxxx' // 一堆乱码
for (var i = 0, len = window.a.length; i < len; i++) {
console.log(window.a[i]);
}
}
在该函数处下断点,让它在被调用之前就修改掉内部的赋值
- 存在类似以下的函数
var a = "deb"
var b = "bugger"
// 拼接执行
本地重写a和b,用ReRes插件替换
禁止格式化代码(正则匹配)
将代码压缩,或者注释掉正则部分的代码
obfuscator特征&解混淆
ob混淆:JavaScript Obfuscator Tool
解混淆:ob混淆还原工具 | ob混淆专解 测试版 V0.6
以基础代码举例
function hi() {
console.log("Hello World!");
}
hi();
旧版obfuscator加密后的结果是这样的:
var _0x30bb = ['log', 'Hello\x20World!']; // 定义数组 [1]
(function (_0x38d89d, _0x30bbb2) {
var _0xae0a32 = function (_0x2e4e9d) {
while (--_0x2e4e9d) {
_0x38d89d['push'](_0x38d89d['shift']()); // 对数组内变量做移位操作 [2]
}
};
_0xae0a32(++_0x30bbb2);
}(_0x30bb, 0x153));
var _0xae0a = function (_0x38d89d, _0x30bbb2) { // 解密函数 [3]
_0x38d89d = _0x38d89d - 0x0;
var _0xae0a32 = _0x30bb[_0x38d89d];
return _0xae0a32;
};
function hi() {
console[_0xae0a('0x1')](_0xae0a('0x0')); // 加密后的函数 [4]
}
hi(); // 调用
对于[2]可以直接执行,获得正确顺序下的数组内容
正确数组有了,[3]中还提供了现成的解密方法,我们只需要用它就可以还原数据了
import re
import execjs
decrypt = '''var _0x30bb = ['Hello World!', 'log'] // 处理过移位的数组
var _0xae0a = function (_0x38d89d, _0x30bbb2) { // 解密函数
_0x38d89d = _0x38d89d - 0x0;
var _0xae0a32 = _0x30bb[_0x38d89d];
return _0xae0a32;
};'''
decrypt_func = '_0xae0a'
ctx = execjs.compile(decrypt)
with open('a.js', 'r', encoding='utf-8') as f:
code = f.read()
res = code
for i in set(re.findall(decrypt_func + '\([\s\S]+?\)', code)):
args = re.findall('\(([\s\S]+?)\)', i)[0]
arg1 = eval(args.split(',')[0])
_res = ctx.call(decrypt_func, arg1)
res = res.replace(i, "'" + _res + "'")
print(res)
脚本其实是完成了批量匹配和解密的过程
而目前4.0.0版obfuscator的默认加密已经没有上面可读性那么好了(看github上说不会再更新了)
function _0x3b94() { // 定义数组 [1]
var _0x47a143 = ['log', '1914804MUXMLC', 'Hello\x20World!', '13668568PxihTU', '3072916cTlxVO', '1319280YhFVKJ', '20xdoBic', '374500AdEIEJ', '565449UDQpof', '1069183kuRgHD'];
_0x3b94 = function () {
return _0x47a143;
};
return _0x3b94(); // 从原先的赋值变量改为函数加载 并进行嵌套 同时增加了数组内的干扰项 排列顺序与之后解密有关系
}
(function (_0x41ac74, _0x1a5714) {
var _0x59b7d7 = _0x1e16, _0x75200a = _0x41ac74();
while (!![]) { // 对原先流程复杂化
try {
var _0x28ad51 = -parseInt(_0x59b7d7(0x122)) / 0x1 + -parseInt(_0x59b7d7(0x120)) / 0x2 + parseInt(_0x59b7d7(0x121)) / 0x3 + -parseInt(_0x59b7d7(0x124)) / 0x4 + parseInt(_0x59b7d7(0x11f)) / 0x5 * (parseInt(_0x59b7d7(0x11e)) / 0x6) + -parseInt(_0x59b7d7(0x127)) / 0x7 + parseInt(_0x59b7d7(0x126)) / 0x8;
if (_0x28ad51 === _0x1a5714) break; else _0x75200a['push'](_0x75200a['shift']()); // 对数组内变量做移位操作 [2]
} catch (_0x4accb5) {
_0x75200a['push'](_0x75200a['shift']());
}
}
}(_0x3b94, 0x93154));
function _0x1e16(_0xf3f526, _0x3900a2) { // 解密函数 [3]
var _0x3b9493 = _0x3b94();
return _0x1e16 = function (_0x1e16b4, _0x52b1ed) { // 仍旧是嵌套 核心执行部分在这里
_0x1e16b4 = _0x1e16b4 - 0x11e;
var _0x17070a = _0x3b9493[_0x1e16b4];
return _0x17070a;
}, _0x1e16(_0xf3f526, _0x3900a2);
}
function hi() {
var _0x2ffbfb = _0x1e16; // 再次替换变量名
console[_0x2ffbfb(0x123)](_0x2ffbfb(0x125)); // 加密后的函数 [4]
}
hi();
但仔细看每一个存在差异的地方就能发现改动是有限的,核心逻辑上并没有太大区别,修改后的脚本(也仅有两处需要改):
import re
import execjs
decrypt = '''[1] + [2] + [3]'''
decrypt_func = '*见下注'
ctx = execjs.compile(decrypt)
with open('a.js', 'r', encoding='utf-8') as f:
code = f.read()
res = code
for i in set(re.findall(decrypt_func + '\([\s\S]+?\)', code)):
args = re.findall('\(([\s\S]+?)\)', i)[0]
arg1 = eval(args.split(',')[0])
_res = ctx.call(decrypt_func, arg1)
res = res.replace(i, "'" + _res + "'")
print(res)
注:由于最后解密的部分又重新替换了个名字,所以要么在js处进行替换 要么在我们解密脚本中进行替换,结果是一样的
当然,这里展示的只是一个非常简单的单一函数Ob混淆后再解混淆的实例,实际情况要更复杂更麻烦,建议直接用工具嗦~
添加hook
油猴
一个简单的在cookie生成处下断点的例子
(function () {
'use strict';
Object.defineProperty(document,'cookie',{
set:function(val){
debugger;
return val;
}
});})();
油猴前面的注释有其特殊的含义,主要注意@match
,它确定了这段代码的作用域 支持正则
- version2.0
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie',{
get: function(){
return cookie_cache;
},
set: function(val){
console.log('Setting cookie', val);
// 填写cookie名
if(val.indexOf('m') !== -1){
// if(val.indexOf('RM4hZBv0dDon443M') !== -1){
debugger;
}
var cookie = val.split(",")[0];
var ncookie = cookie.split("=");
var flag = false;
var cache = cookie_cache.split("; ");
cache = cache.map(function(a){
if(a.split("=")[0] === ncookie[0]){
flag = true;
return cookie;
}
return a;
})
cookie_cache = cache.join("; ");
if(!flag){
cookie_cache += cookie + "; ";
}
return cookie_cache;
}
});
})();
浏览器插件
manifest.json
{
"name": "Injection",
"version": "1.0",
"description": "RequestHeader钩子",
"manifest_version": 1,
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"inject.js"
],
"all_frames": true,
"permissions": [
"tabs"
],
"run_at": "document_start"
}
]
}
- header
var code = function(){
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
if(key=='Authorization'){
debugger;
}
return org.apply(this,arguments);
}
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
- cookie
var code = function(){
var org = document.cookie.__lookupSetter__('cookie');
document.__defineSetter__("cookie",function(cookie){
if(cookie.indexOf('abcdefghijk')>-1){
debugger;
}
org = cookie;
});
document.__defineGetter__("cookie",function(){return org;});
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
- url参数
var code = function(){
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async){
if (url.indexOf("AbCdE")>-1){
debugger;
}
return open.apply(this, arguments);
};
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);
*补环境
对js逆向来说最重要的内容之一了,一般涉及这些内容
window = global
navigator = {}
*待补充
对js进行ast/json转换
- js2ast: https://astexplorer.net/
- js2json
// js2json.js
// cmd: node js2json <jsfile> <outjsonfile>
const fs = require('fs')
const esprima = require('esprima')
const input_text = process.argv[2]
const output_text = process.argv[3]
const data = fs.readFileSync(input_text)
const ast = esprima.parseScript(data.toString())
const ast_to_json = JSON.stringify(ast)
fs.writeFileSync(output_text, ast_to_json)
- json2js
// json2js
// cmd: node json2js <jsonfile> <outjsfile>
const fs = require('fs')
const escodegen = require('escodegen')
const input_text = process.argv[2]
const output_text = process.argv[3]
const data = fs.readFileSync(input_text)
const ast = JSON.parse(data.toString())
const code = escodegen.generate(ast, {
format: {
compact: true,
escapeless: true
}
})
fs.writeFileSync(output_text, code)
切分js
转AST进行操作
可以先对js转json,再操作json
举例:
import json
import os
os.system('node js2json 1.js 1.json')
with open('1.json', 'r', encoding='utf-8') as f:
node = json.loads(f.read())
left_node = {
'type': 'Program',
'body': node['body'][:3],
'sourceType': 'script'
}
right_node = {
'type': 'Program',
'body': node['body'][3:],
'sourceType': 'script'
}
with open('1_left.json', 'w', encoding='utf-8') as f1, open('1_right.json', 'w', encoding='utf-8') as f2:
f1.write(json.dumps(left_node))
f2.write(json.dumps(right_node))
os.system('node json2js 1_left.json 1_left.js')
os.system('node json2js 1_right.json 1_right.js')
execjs库相关问题
如果遇到编码相关问题,两种解决方式:
- 修改subprocess.py中
__init__
方法中的encoding参数为’utf-8'
!!!但之后记得改回来!以免影响到其他库的正常运行
- 在引入execjs库之前加上这几行
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
requests库tricks
确保headers顺序不变
用requests.Session()
对象,对这个session对象设置headers,还能减少后续继续赋值cookie的过程
cookie转字典
cookie_dict = requests.utils.dict_from_cookiejar(response.cookies)