听圈子里的人说,现在都人均rs了,rs不再像20年那会那样,像一座大山。但我看了一些补环境方案,发现很多补环境方案连ActiveXObject对象还没搞明白,只是走了神奇的window.ActiveXObject=undefined 这个bug, 这种方案只能说能过,所以对人均rs我有些怀疑。

问了公司的配置开发,他们能解决4代和5代,不能解决vmp版本。公司的配置开发能力已相当不错, 会解决常用加密如AES,DES等,一些入门加密如 jsl, hexin-v也能做,有一些能力强的还学了下AST,但遇到vmp版本时就没法拿捏。

有一些人用了开源的sdenv, 不不的cynode, 挽风的node-sandbox,就觉得rs简单,但这只是因为工具的能力,并不代表自己会,得知其然,知其所以然才行。

就拿sdenv来说,如果你只会用sdenv, 万一遇到 aHR0cDovL2Nob25ncWluZy5jaGluYXRheC5nb3YuY24veHhna3h0L3BhZ2VzL3F5d2gvemR3ZmFqY3guaHRtbCAK,aHR0cDovL3d3dy5jemNlLmNvbS5jbi9jbi9qeXNqL3lkanloei9INzcwMzE1aW5kZXhfMS5odG0K 等站点怎么办?更不用说aHR0cHM6Ly96eGdrLmNvdXJ0Lmdvdi5jbi94Z2wvCg==,aHR0cHM6Ly93d3cuY2Vid20uY29tL3dlYWx0aC9qZ2xjMzkvaW5kZXguaHRtbCA=等站点了。

联系作者

之前在神奇的window.ActiveXObject=undefined里写过,加上window.ActiveXObject=undefined后,某数的校验就降级了,很多环境校验就都没走了。但神奇的是,在我的一个补环境方案里,去掉这行后,很多环境校验一样没走,一样能过反爬,于是我懵,我困惑了,这到底是什么鬼bug啊。

去网上查了下ActiveXObject这个对象,仅支持微软 Internet Explorer 浏览器,在其它现代浏览器(如 Chrome、Firefox、Safari 等)中无法使用,包括微软的 Edge 浏览器(Chromium 内核)也无法使用。也就是说,只有在微软 Internet Explorer 浏览器里,’ActiveXObject’ in window才是true, 在其它现代浏览器里,’ActiveXObject’ in window 都是false。

而在补环境的时候,userAgent 是Chrome, Edge等浏览器时,如果加上 window.ActiveXObject = undefined; 这行,’ActiveXObject’ in window 就会变成true了,就不符合上述的结果,是可以被检测出来的。

很多人补环境不严格,补环境代码里有 window.ActiveXObject = undefined; userAgent是 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0,这种是Chromium 内核的,换成是我做反爬,就要干它了,悄咪咪的把它记下来,然后在夜深人静的时候给它返回点假数据。

参考资料:

https://webaim.org/blog/user-agent-string-history/

联系作者

一个月前有道友反馈,过不去aHR0cHM6Ly93d3cuY2Vid20uY29tL3dlYWx0aC9qZ2xjMzkvaW5kZXguaHRtbCA=这个样本获取token的接口,于是掏出祖传补环境代码去测试,首页能过,接口竟然过不去,于是把所有的备用方案拿去测试,竟然都过不去,我惊了,这是难得一见的补环境过不去的网站了。

此时只好拿出算法方案,意外发现以前用的找20取4位数组的方法竟然失效了。于是拿出尘封已久的AST代码,也没办法还原控制流混淆了,估计是控制流混淆有点变化。于是索性不还原,直接在vmp循环里打日志慢慢调试,搞了一天,愣是没搞定。

于是求助小黄鱼大佬们,问了一圈补环境方案,竟然没找到一个能搞定的,看来大家的补环境水平都差不多了。于是求助算法大佬们,终于找到解决的办法。其实算法没变化,只是我用的20取4位数组的办法还不够通用,在这个站点上正好失效了。

有了算法方案后,对比之后发现这个站点上之所以过不去是因为校验指纹数组了,于是想起2年前曾研究过这个指纹数组,找到2年前的补环境,发现竟然能过,收工。

头疼的是,第二天补环境就要跑不动了,测试通过率只有60%,算法方案才能稳稳的跑通。结果前几天,补环境和算法方案都跑不动了,仔细一看,url加密,表单加密,响应加密都上了,这属实是全家桶了,头皮发麻。

联系作者

一般反爬在生成加密参数的时候,都会取navigator里的userAgent属性,然后生成加密参数。在使用requests库的时候,爬虫也会设置下headers里的User-Agent值。但很多时候爬虫并没有保持这两个值一致。

在使用补环境生成参数的时候,如果要支持动态传入userAgent,就得在调用参数生成接口的时候增加userAgent参数,并传到补环境代码里设置navigator.userAgent,爬虫偷懒的时候就会省掉这一步,直接固定写死navigator.userAgent。

还有在使用jsdom补环境生成参数的时候,会错误设置userAgent。如果像以下代码一样设置userAgent就是错的

1
2
3
4
5
6
7
8
9
10
11
const { JSDOM } = require('jsdom');

// 自定义的 userAgent 字符串
const myUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3';

// 创建 jsdom 实例并设置 userAgent
const dom = new JSDOM(``, {
userAgent: myUserAgent
});

console.log(dom.window.navigator.userAgent); // 输出类似Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/19.0.0 这种包含jsdom的userAgent

jsdom里正确的做法是使用ResourceLoader设置userAgent

1
2
3
4
5
6
7
8
9
10
11
12
13
const jsdom = require("jsdom");  // 引入 jsdom
const { JSDOM } = jsdom; // 引出 JSDOM 类, 等同于 JSDOM = jsdom.JSDOM
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
const resourceLoader = new jsdom.ResourceLoader({
userAgent: userAgent
});
const dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
resources: resourceLoader,
});
window = dom.window
console.log(window.navigator.userAgent) // 输出是Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

有一些反爬在不严格的时候即便userAgent不一致也让爬虫通过,但风控严格的时候userAgent不一致又不让爬虫通过,让爬虫摸不着头脑,得排查老半天。所以爬虫为了稳妥起见,还是得保持navigator里的userAgent属性和headers里的User-Agent值一致。

联系作者

通常我们写好一个接口后,需要做接口性能测试,知道接口的QPS(Queries Per Second),百分位响应时间,我们可以用Python库Locust来做

pip install locust库后,新建locustfile.py,写入要测试的接口,demo如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from locust import HttpUser, task, between
import json

class MyUser(HttpUser):
# 设置用户的等待时间(模拟用户在操作之间的思考时间)
wait_time = between(1, 2) # 每次任务之间等待 1~2 秒

@task
def post_request(self):
# 定义请求的 URL 和数据

url = 'https://www.baidu.com'

payload = {
"url": url,
}
headers = {
"Content-Type": "application/json"
}

# 发起 POST 请求
with self.client.post('http://%s/api/generate_cookies' % '127.0.0.1:5009', json=payload, headers=headers, catch_response=True) as response:
# 验证响应状态码是否为 200
if response.status_code == 200:
try:
# 如果需要验证返回内容,可以解析 JSON 数据
response.success() # 标记请求成功
except ValueError:
response.failure("Response is not valid JSON")
else:
# 如果状态码不是 200,标记请求失败
response.failure(f"Status code: {response.status_code}")

locust -f locustfile.py –web-port=8089,之后浏览器里打开 http://localhost:8089/ 就可以新建一个测试

测试结果中,RPS就是我们常说的QPS,95%ile 指的是 95百分位响应时间 (Percentile Response Times),也就是95%的请求的响应时间不超过某个值。

联系作者

JavaScript逆向时一些奇奇怪怪的检测里,我们写过一个使用Node v8内置函数来过 document.all 补环境检测的demo, 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const v8 = require("v8");
const vm = require("vm");

// 允许使用 V8 内置函数(需启用 --allow-natives-syntax 标志)
v8.setFlagsFromString('--allow-natives-syntax');

// 创建不可检测对象
let undetectable = vm.runInThisContext('%GetUndetectable()');

// 恢复标志禁用(可选)
v8.setFlagsFromString('--no-allow-natives-syntax');


function HTMLAllCollection() {
return undetectable
};
Object.defineProperties(HTMLAllCollection.prototype, {
[Symbol.toStringTag]: {
value: 'HTMLAllCollection',
configurable: true
}
});
undetectable.__proto__ = HTMLAllCollection.prototype;
document = {}
document.all = new HTMLAllCollection()

length = 3;
for (let i = 0; i < length; i++) {
document.all[i] = '1';
}
debugger;
document.all.length = length;
console.log(typeof document.all)
console.log(document.all)
console.log(document.all.length)

有道友说这种检测已经算简单了,可以回去看看document.all(), 测试了下这函数也没啥啊,就返回null, 后来去看了下document.all的特性,才发现它有和form表单一样的特性,例子如下

1
2
3
4
5
6
7
8
9
10
var input = document.createElement('input')
input.name = 'aaa'
document.body.append(input)
input = document.createElement('input')
input.id = 'bbb'
document.body.append(input)
document.all.aaa
document.all('aaa')
document.all.bbb
document.all('bbb')

这就令人头秃了。想来安全人员为了兼容还是留了一手了,要不然真的难搞了。以前刚学补环境的时候,就像拿到个锤子,总是锤来锤去,后面锤多了才发现越来越难锤,慢慢就回归到自动化了。毕竟算法,补环境,自动化都是工具,什么好用就用啥了。

联系作者

平时使用requests和gevent写爬虫的时候,是如下demo, 在代码最前面加上monkey.patch_all()即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import gevent
from gevent import pool, monkey
monkey.patch_all()
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import time
import requests
from gevent.lock import RLock

key_lock = RLock()
key = 0


def get_key():
global key
key_lock.acquire()
key += 1
key_lock.release()
return key

def test_request():
print("start ")
time.sleep(2)
print(get_key())
session = requests.session()
response = session.get('https://www.baidu.com', verify=False, timeout=10)
print(response.status_code, response.url)
time.sleep(3)
print('end')


if __name__ == "__main__":
n = 3
gevent_poll = pool.Pool(n)
jobs = []
for i in range(n):
job = gevent_poll.spawn(test_request)
jobs.append(job)
gevent.joinall(jobs)

输出结果里 0 1 2 是连续的, 请求是并发的

1
2
3
4
5
6
0
1
2
200 https://www.baidu.com/
200 https://www.baidu.com/
200 https://www.baidu.com/

为了过ja3指纹,我们可以使用curl_cffi这个库,demo如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import gevent
from gevent import pool, monkey
monkey.patch_all()
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import time
import requests
from curl_cffi import requests as cffi_requests
from gevent.lock import RLock

key_lock = RLock()
key = 0


def get_key():
global key
key_lock.acquire()
key += 1
key_lock.release()
return key

def test_request():
print("start ")
time.sleep(2)
print(get_key())
session = cffi_requests.Session()
response = session.get('https://www.baidu.com', verify=False, timeout=10)
print(response.status_code, response.url)
time.sleep(3)
print('end')


if __name__ == "__main__":
n = 3
gevent_poll = pool.Pool(n)
jobs = []
for i in range(n):
job = gevent_poll.spawn(test_request)
jobs.append(job)
gevent.joinall(jobs)

但这样并不能实现并发,输出结果里0 1 2是分开的,请求是顺序的

1
2
3
4
5
6
0
200 https://www.baidu.com/
1
200 https://www.baidu.com/
2
200 https://www.baidu.com/

解决办法是在创建session时加上thread参数,指定gevent即可,像session = cffi_requests.Session(thread=’gevent’)这样创建即可,最终代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from gevent import pool, monkey
monkey.patch_all()
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import time
import requests
from curl_cffi import requests as cffi_requests
from gevent.lock import RLock

key_lock = RLock()
key = 0


def get_key():
global key
key_lock.acquire()
key += 1
key_lock.release()
return key

def test_request():
print("start ")
time.sleep(2)
print(get_key())
session = cffi_requests.Session(thread='gevent')
response = session.get('https://www.baidu.com', verify=False, timeout=10)
print(response.status_code, response.url)
time.sleep(3)
print('end')


if __name__ == "__main__":
n = 3
gevent_poll = pool.Pool(n)
jobs = []
for i in range(n):
job = gevent_poll.spawn(test_request)
jobs.append(job)
gevent.joinall(jobs)

结果里0 1 2 是顺序的,请求是并发的。

1
2
3
4
5
6
0
1
2
200 https://www.baidu.com/
200 https://www.baidu.com/
200 https://www.baidu.com/

这又是一个小细节,被坑了。正常以为会像requests库一样用,但结果不是。文档 https://curl-cffi.readthedocs.io/en/latest/advanced.html#using-with-eventlet-gevent 里有说到这个用法,但没仔细去看了,这个库API和requests太像了,以为可以无缝衔接。

联系作者

JavaScript逆向时一些奇奇怪怪的检测

之前和同事讨论过一些检测,同事说可以记录下来,能帮到新人,于是有了这篇文章。

document.all

这是在浏览器里被特殊处理的一个特性,也就是typeof document.all是undefined, 但却document.all却能取到值。要在Node中实现这个特性,现在有两种方案,一种是Node C++插件,另一种是V8内置函数。一般场景用V8内置函数即可,移植性更高。Node C++插件和系统有关,也和Node版本绑定,移植性差。

要实现这个特性,GPT给出如下答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const v8 = require("v8");
const vm = require("vm");

// 允许使用 V8 内置函数(需启用 --allow-natives-syntax 标志)
v8.setFlagsFromString('--allow-natives-syntax');

// 创建不可检测对象
let undetectable = vm.runInThisContext('%GetUndetectable()');

// 恢复标志禁用(可选)
v8.setFlagsFromString('--no-allow-natives-syntax');

// 测试对象行为
undetectable.aaa = 'bbb';
console.log('typeof undetectable:', typeof undetectable); // 输出 'undefined'
console.log('undetectable.aaa:', undetectable.aaa); // 输出 'bbb'
console.log('undetectable instanceof Object:', undetectable instanceof Object); // 输出 'true'

修修改改,即可实现document.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const v8 = require("v8");
const vm = require("vm");

// 允许使用 V8 内置函数(需启用 --allow-natives-syntax 标志)
v8.setFlagsFromString('--allow-natives-syntax');

// 创建不可检测对象
let undetectable = vm.runInThisContext('%GetUndetectable()');

// 恢复标志禁用(可选)
v8.setFlagsFromString('--no-allow-natives-syntax');


function HTMLAllCollection() {
return undetectable
};
Object.defineProperties(HTMLAllCollection.prototype, {
[Symbol.toStringTag]: {
value: 'HTMLAllCollection',
configurable: true
}
});
undetectable.__proto__ = HTMLAllCollection.prototype;
document = {}
document.all = new HTMLAllCollection()

length = 3;
for (let i = 0; i < length; i++) {
document.all[i] = '1';
}
debugger;
document.all.length = length;
console.log(typeof document.all)
console.log(document.all)
console.log(document.all.length)

eval(‘!new function(){eval(“console.log(this);this.a=2”)}().a’)

这个主要用来检测vm2环境,在浏览器和Node里都是false, 在vm2里是true, 测试代码如下

1
2
3
4
5
6
7
8
9
10
11
const { VM } = require("vm2");
const vm = new VM();

vm.run(`
const obj = new function() {
eval("console.log(this);this.a=2");
}();
console.log(obj); // 输出空对象 {}
console.log(obj.a); // 输出 undefined
console.log(!obj.a); // 输出 true
`);

window[“Object”][“getOwnPropertyNames”](Function.prototype.toString)

这个主要考察严格模式和非严格模式, 正常用以下方法重定义

1
2
Function.prototype.toString  = function toString() {};
console.log(Object.getOwnPropertyNames(Function.prototype.toString));

会返回[‘length’, ‘name’, ‘arguments’, ‘caller’, ‘prototype’]

用以下方法重定义

1
2
Function.prototype.toString = {toString(){}}.toString
console.log(Object.getOwnPropertyNames(Function.prototype.toString));

则返回 [‘length’, ‘name’]

form表单特性

1
2
3
4
5
6
7
8
9
10
11
12
let form = document.createElement('form');
form.action = 'https://www.baidu.com/';
let input = document.createElement('input');
input.name = 'action';
form.appendChild(input)
input = document.createElement('input');
input.name = 'textContent';
input.id = 'password';
form.appendChild(input)
console.log(form.action)
console.log(form.textContent)
console.log(form.password)

正常form.action是一个url链接,但这里变成了HTMLInputElement。

参考资料

零点的 JavaScript toString 检测对抗

联系作者

听闻sdenv被反爬,垂死病中惊坐起,去看了下sdenv的源码, 整理了下以前写的针对jsdom检测的简单测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const jsdom = require("jsdom");  // 引入 jsdom
const { JSDOM } = jsdom; // 引出 JSDOM 类, 等同于 JSDOM = jsdom.JSDOM
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
const resourceLoader = new jsdom.ResourceLoader({
userAgent: userAgent
});
let dom = new JSDOM(``, {
url: "https://example.org/",
referrer: "https://example.com/",
resources: resourceLoader,
});
window = dom.window;
debugger;
console.log(window.navigator.userAgent);

function detectForm(document) {
let url = 'https://www.baidu.com/'
let form = document.createElement('form');
form.action = '';
let input = document.createElement('input');
input.name = 'action';
form.appendChild(input)
input = document.createElement('input');
input.name = 'textContent';
input.id = 'password';
form.appendChild(input)
return form.action != url && form.action.__proto__.toString() == '[object HTMLInputElement]'
}

console.log('test _globalObject', (typeof window._globalObject != "undefined" && typeof window != "undefined" && window._globalObject == window) === false);
console.log('test document ', window.document.toString() === '[object HTMLDocument]')
console.log('test open ', window.open.toString() === 'function open() { [native code] }')
console.log('test fetch ', window.fetch !== undefined && window.fetch.toString() === 'function fetch() { [native code] }')
console.log('test prompt ', window.prompt.toString() === 'function prompt() { [native code] }')
console.log('test Event ', window.Event.toString() === 'function Event() { [native code] }')
console.log('test Request', window.Request !== undefined && window.Request.toString() === 'function Request() { [native code] }')
console.log('test XPathException', window.XPathException === undefined)
console.log('test webdriver ', window.navigator.webdriver === false)
console.log('test webdriver ', (Object.getOwnPropertyDescriptor(window.navigator.__proto__, 'webdriver') && Object.getOwnPropertyDescriptor(window.navigator.__proto__, 'webdriver').get.toString()) === 'function get webdriver() { [native code] }')
console.log('test document.all ', typeof window.document.all === 'undefined')
console.log('test document.all ', window.document.all !== undefined && (window.document.all.__proto__.toString() === '[object HTMLAllCollection]'))
console.log('test form ', detectForm(window.document) === true)

针对sdenv, 写了一个简单的测试代码,放在sdenv源码的example目录下跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const {
jsdomFromText
} = require('../utils/jsdom');
const browser = require('../browser/');

let index_url = "https://www.example.com/"
const [jsdomer, cookieJar] = jsdomFromText({
url: `${index_url}`,
referrer: `${index_url}`,
contentType: "text/html",
runScripts: 'dangerously',
beforeParse(window) {
browser(window, 'chrome')
},
})

const dom = jsdomer('<html></html>');
window = dom.window;

debugger;
console.log(window.navigator.userAgent);
function detectForm(document) {
let url = 'https://www.baidu.com/'
let form = document.createElement('form');
form.action = '';
let input = document.createElement('input');
input.name = 'action';
form.appendChild(input)
input = document.createElement('input');
input.name = 'textContent';
input.id = 'password';
form.appendChild(input)
return form.action != url && form.action.__proto__.toString() == '[object HTMLInputElement]'
}

console.log('test _globalObject', (typeof window._globalObject != "undefined" && typeof window != "undefined" && window._globalObject == window) === false);
console.log('test document ', window.document.toString() === '[object HTMLDocument]')
console.log('test open ', window.open.toString() === 'function open() { [native code] }')
console.log('test fetch ', window.fetch !== undefined && window.fetch.toString() === 'function fetch() { [native code] }')
console.log('test prompt ', window.prompt.toString() === 'function prompt() { [native code] }')
console.log('test Event ', window.Event.toString() === 'function Event() { [native code] }')
console.log('test Request', window.Request !== undefined && window.Request.toString() === 'function Request() { [native code] }')
console.log('test XPathException', window.XPathException === undefined)
console.log('test webdriver ', window.navigator.webdriver === false)
console.log('test webdriver ', (Object.getOwnPropertyDescriptor(window.navigator.__proto__, 'webdriver') && Object.getOwnPropertyDescriptor(window.navigator.__proto__, 'webdriver').get.toString()) === 'function get webdriver() { [native code] }')
console.log('test document.all ', typeof window.document.all === 'undefined')
console.log('test document.all ', window.document.all !== undefined && (window.document.all.__proto__.toString() === '[object HTMLAllCollection]'))
console.log('test form ', detectForm(window.document) === true)

测试结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
test _globalObject true
test document false
test open false
test fetch true
test prompt false
test Event true
test Request true
test XPathException true
test webdriver true
test webdriver true
test document.all true
test document.all false
test form true

可以看到几个测试结果是false的,而浏览器里都是true, 得针对修改下

联系作者

JavaScript逆向时如何解决格式化反调试

在调试的时候,我们一般都会格式化代码再调试,这时会遇到无限debugger, 或者代码跑着一直不出结果,死循环了。此时一般是用正则去匹配,于是我们可以使用如下hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RegExp.prototype.my_test = RegExp.prototype.test

let my_regex = function(arguments) {
debugger ;
console.log(this, arguments)
if (this.source == `test`) {
return true
}
return this.my_test(arguments)
}

Object.defineProperty(RegExp.prototype, 'test', {
value: my_regex
})

后来在处理ob(全称JavaScript Obfuscator)混淆时,发现会使用String里的search函数去匹配,所以我们可以使用如下hook

1
2
3
4
5
6
7
8
9
10
11
12
let my_search = function(arguments) {
debugger ;
console.log(this, arguments)
if (arguments == '(((.+)+)+)+$') {
return false;
}
return this.my_search(arguments)
};

Object.defineProperty(String.prototype, 'search', {
value: my_search
});

测试代码例子

1
(function(_0x2af2c1,_0x5729ae){var _0x3bb80f=_0x2dcf,_0x136599=_0x2af2c1();while(!![]){try{var _0x32921f=parseInt(_0x3bb80f(0xbc))/(-0x2f*-0xb7+-0xc50+-0x38c*0x6)+-parseInt(_0x3bb80f(0xc1))/(-0xe3e+0x1484+-0x644)+parseInt(_0x3bb80f(0xbf))/(0x2bf+0x1*-0x1499+-0x11dd*-0x1)+-parseInt(_0x3bb80f(0x8d))/(0x1ff7+0x19e7+-0x39da)+-parseInt(_0x3bb80f(0xa5))/(0x16e3+0x1451+-0x2b2f)*(parseInt(_0x3bb80f(0xcd))/(-0x394*-0x3+0x908+0x85*-0x26))+-parseInt(_0x3bb80f(0x81))/(0x79*0x1+-0x569*0x1+0x4f7)*(-parseInt(_0x3bb80f(0x96))/(0x20e4+-0x161+-0x1f7b))+parseInt(_0x3bb80f(0xa3))/(-0x1*0x12e1+-0xeda+0x21c4);if(_0x32921f===_0x5729ae)break;else _0x136599['push'](_0x136599['shift']());}catch(_0xf76fcd){_0x136599['push'](_0x136599['shift']());}}}(_0x3e14,0xc9*0x12f8+0x1*-0xeb4d1+-0x952*-0x115));function hi(){var _0xf73b53=_0x2dcf,_0x21b2f2={'UFCQd':_0xf73b53(0x7e),'MyoAf':function(_0x101d17,_0x5db4cf){return _0x101d17(_0x5db4cf);},'WRAzg':function(_0x2fbc41,_0x5c91ad){return _0x2fbc41===_0x5c91ad;},'ROAZT':_0xf73b53(0xc9),'AFrmY':function(_0x3264b7,_0x3c5af7){return _0x3264b7===_0x3c5af7;},'lfcce':_0xf73b53(0x7f),'mzhRj':'kMBpD','AZbGw':function(_0x3bf8f2,_0x26a03f){return _0x3bf8f2!==_0x26a03f;},'oSFRf':_0xf73b53(0xaa),'xmjLA':_0xf73b53(0xcf),'voUVp':_0xf73b53(0x98),'TjDqc':function(_0x324314,_0x498551){return _0x324314+_0x498551;},'MjLdR':'chain','TbfQJ':_0xf73b53(0x92),'LVUpa':_0xf73b53(0xc3),'kLQio':_0xf73b53(0xb0),'ZEGgJ':function(_0x7f45b4){return _0x7f45b4();},'BCKVL':function(_0x2b4677,_0x2db01b){return _0x2b4677!==_0x2db01b;},'yBLFu':_0xf73b53(0x84),'FVWnM':function(_0x5dda0e,_0x3b7a03,_0x51f16c){return _0x5dda0e(_0x3b7a03,_0x51f16c);},'dGGJz':_0xf73b53(0xc2)},_0x1e42b5=(function(){var _0x76ca35=_0xf73b53,_0x2389f8={};_0x2389f8['CKmHh']=_0x21b2f2[_0x76ca35(0xc8)];var _0x187799=_0x2389f8,_0x287f4a=!![];return function(_0x410a03,_0x5485b2){var _0x71193a=_0x76ca35;if(_0x187799['CKmHh']===_0x187799[_0x71193a(0x76)]){var _0x1faa8c=_0x287f4a?function(){var _0x2c5b4d=_0x71193a;if(_0x5485b2){var _0x22c5a5=_0x5485b2[_0x2c5b4d(0xae)](_0x410a03,arguments);return _0x5485b2=null,_0x22c5a5;}}:function(){};return _0x287f4a=![],_0x1faa8c;}else{if(_0x498367){var _0x35b1f7=_0x548caa['apply'](_0x385275,arguments);return _0x28ff30=null,_0x35b1f7;}}};}()),_0xed3405=_0x1e42b5(this,function(){var _0x5de0ef=_0xf73b53,_0x43fad1={'udtfN':function(_0x31b48a,_0x5dd2e9){var _0x58b7c4=_0x2dcf;return _0x21b2f2[_0x58b7c4(0xa0)](_0x31b48a,_0x5dd2e9);}};if(_0x21b2f2[_0x5de0ef(0x83)](_0x5de0ef(0x9d),_0x5de0ef(0x9d)))return _0xed3405[_0x5de0ef(0x91)]()['search']('(((.+)+)+)+$')[_0x5de0ef(0x91)]()[_0x5de0ef(0x79)](_0xed3405)[_0x5de0ef(0x8b)]('(((.+)+)+)+$');else{if(_0x48b81e)return _0x144005;else UZhylY['udtfN'](_0x4ad49c,-0x1*0x1091+0xb8*0x2a+-0xd9f);}});_0x21b2f2[_0xf73b53(0x7d)](_0xed3405);var _0x56c8cd=(function(){var _0x131b47=_0xf73b53,_0x3139cc={'BmvIp':function(_0x133012,_0x3ba194){return _0x133012(_0x3ba194);}};if(_0x21b2f2[_0x131b47(0xa2)](_0x21b2f2[_0x131b47(0x8c)],_0x21b2f2[_0x131b47(0x8c)]))return _0x5082a5;else{var _0x509b4c=!![];return function(_0x1e2cf4,_0x5de51f){var _0x303c02=_0x131b47,_0x339326={};_0x339326['ynxTT']=_0x21b2f2[_0x303c02(0xbe)];var _0x1c595d=_0x339326;if(_0x21b2f2['AFrmY'](_0x21b2f2[_0x303c02(0xb6)],_0x21b2f2[_0x303c02(0x87)]))zKrqIw['BmvIp'](_0x1ed5b8,'0');else{var _0x46aa46=_0x509b4c?function(){var _0x128723=_0x303c02;if(_0x5de51f){if(_0x1c595d[_0x128723(0x7b)]===_0x128723(0xc9)){var _0x10e124=_0x5de51f[_0x128723(0xae)](_0x1e2cf4,arguments);return _0x5de51f=null,_0x10e124;}else{var _0x55a8b6=_0x29c4c5[_0x128723(0xae)](_0x59aea0,arguments);return _0x57be67=null,_0x55a8b6;}}}:function(){};return _0x509b4c=![],_0x46aa46;}};}}());(function(){var _0x312c6e=_0xf73b53,_0x45911c={'TnjGM':_0x21b2f2[_0x312c6e(0xa1)],'XKRUY':_0x21b2f2[_0x312c6e(0x8f)],'nblnZ':function(_0x5ea134,_0x22ac32){var _0x4dd84a=_0x312c6e;return _0x21b2f2[_0x4dd84a(0xa7)](_0x5ea134,_0x22ac32);},'ixPsX':_0x21b2f2[_0x312c6e(0x89)],'sFaqQ':function(_0x1d84b0,_0x175a50){var _0x53008b=_0x312c6e;return _0x21b2f2[_0x53008b(0xa7)](_0x1d84b0,_0x175a50);},'GGhtA':_0x21b2f2[_0x312c6e(0xb1)],'MCDgD':function(_0x3798ef,_0x4f4d6d){var _0x52436e=_0x312c6e;return _0x21b2f2[_0x52436e(0xa0)](_0x3798ef,_0x4f4d6d);},'MFqRh':function(_0x34d08b,_0x16a70d){var _0xa5bb1a=_0x312c6e;return _0x21b2f2[_0xa5bb1a(0xbd)](_0x34d08b,_0x16a70d);},'bmGCL':_0x21b2f2[_0x312c6e(0xca)],'eRpRS':_0x21b2f2['kLQio'],'aavYG':function(_0x46b596,_0x3f8783){var _0x18449a=_0x312c6e;return _0x21b2f2[_0x18449a(0xa0)](_0x46b596,_0x3f8783);},'RqLAW':function(_0x425f24,_0x48d93b){var _0x17c616=_0x312c6e;return _0x21b2f2[_0x17c616(0xa7)](_0x425f24,_0x48d93b);},'zEcNC':function(_0x153ba3,_0x369022){var _0x21d816=_0x312c6e;return _0x21b2f2[_0x21d816(0xa0)](_0x153ba3,_0x369022);},'gQVRD':function(_0x30d32b){var _0x5336d2=_0x312c6e;return _0x21b2f2[_0x5336d2(0x7d)](_0x30d32b);}};if(_0x21b2f2[_0x312c6e(0xcb)]('WXJYq',_0x21b2f2[_0x312c6e(0xaf)]))_0x21b2f2['FVWnM'](_0x56c8cd,this,function(){var _0x24f277=_0x312c6e,_0x361240=new RegExp(_0x24f277(0xb0)),_0x5a44c3=new RegExp(_0x45911c[_0x24f277(0xc4)],'i'),_0x57fe67=_0x269895(_0x45911c['XKRUY']);if(!_0x361240[_0x24f277(0x77)](_0x45911c[_0x24f277(0x97)](_0x57fe67,_0x45911c[_0x24f277(0xd0)]))||!_0x5a44c3[_0x24f277(0x77)](_0x45911c['sFaqQ'](_0x57fe67,_0x45911c[_0x24f277(0x9b)])))_0x45911c[_0x24f277(0x90)](_0x57fe67,'0');else{if(_0x45911c[_0x24f277(0x82)](_0x45911c[_0x24f277(0xd2)],_0x45911c[_0x24f277(0xd2)]))_0x269895();else return!![];}})();else{var _0x51bda3=new _0xc2f8e6(cFtQEd[_0x312c6e(0x9f)]),_0x2ebe74=new _0x3fd708(cFtQEd[_0x312c6e(0xc4)],'i'),_0x15e45a=cFtQEd[_0x312c6e(0xa9)](_0x1f753d,cFtQEd[_0x312c6e(0xce)]);!_0x51bda3[_0x312c6e(0x77)](cFtQEd[_0x312c6e(0x8e)](_0x15e45a,_0x312c6e(0x94)))||!_0x2ebe74[_0x312c6e(0x77)](cFtQEd[_0x312c6e(0xc6)](_0x15e45a,_0x312c6e(0x92)))?cFtQEd[_0x312c6e(0x85)](_0x15e45a,'0'):cFtQEd[_0x312c6e(0xad)](_0x2d7a45);}}()),console[_0xf73b53(0x8a)](_0x21b2f2[_0xf73b53(0xc0)]);}hi();function _0x2dcf(_0x2d85e5,_0x3e142d){var _0x2dcfdd=_0x3e14();return _0x2dcf=function(_0x4031e1,_0x1c832f){_0x4031e1=_0x4031e1-(-0x1*0x1811+-0x5e0+-0xb5*-0x2b);var _0x437a54=_0x2dcfdd[_0x4031e1];return _0x437a54;},_0x2dcf(_0x2d85e5,_0x3e142d);}function _0x269895(_0x17c37a){var _0x383aa8=_0x2dcf,_0x513047={'UunGd':'while\x20(true)\x20{}','oTKoG':function(_0x46b359){return _0x46b359();},'kujec':function(_0x4c8d0a,_0x7c2720){return _0x4c8d0a===_0x7c2720;},'xTWRH':'string','MpkIy':_0x383aa8(0x80),'ztVyI':_0x383aa8(0xbb),'JjyaY':function(_0xe7f4ec,_0x411f53){return _0xe7f4ec!==_0x411f53;},'ISYBp':function(_0x2f93af,_0x4f469d){return _0x2f93af+_0x4f469d;},'AoJnv':function(_0x9c3eb1,_0x3ab274){return _0x9c3eb1/_0x3ab274;},'dzBMz':_0x383aa8(0x95),'ZeMPL':function(_0x85fc17,_0x116a9d){return _0x85fc17===_0x116a9d;},'vLeSp':function(_0x2e0238,_0x30f91b){return _0x2e0238%_0x30f91b;},'UsdIb':_0x383aa8(0xab),'bdAFw':_0x383aa8(0x7a),'zqbtZ':'PSIMG','ESQZC':_0x383aa8(0xa6),'BEImO':function(_0x27e063,_0x48d43d){return _0x27e063(_0x48d43d);},'Ygggu':function(_0x16eaaa,_0x45973b){return _0x16eaaa+_0x45973b;},'SbTwP':function(_0xfb207,_0x1fbfd5){return _0xfb207!==_0x1fbfd5;},'iwHWR':_0x383aa8(0xcc),'pDruh':_0x383aa8(0xb7),'riBfp':_0x383aa8(0x78)};function _0x360295(_0x401127){var _0x1918fd=_0x383aa8,_0x15a3a7={'ksnwb':function(_0x2796bd){var _0x22b7dc=_0x2dcf;return _0x513047[_0x22b7dc(0xa4)](_0x2796bd);}};if(_0x513047['kujec'](typeof _0x401127,_0x513047[_0x1918fd(0xb9)])){if(_0x513047[_0x1918fd(0xb8)]==='TCevo')return function(_0x110f7c){}['constructor'](_0x513047[_0x1918fd(0xba)])[_0x1918fd(0xae)](_0x513047['ztVyI']);else _0x15a3a7[_0x1918fd(0x7c)](_0x42d3db);}else{if(_0x513047[_0x1918fd(0x9c)](_0x513047['ISYBp']('',_0x513047['AoJnv'](_0x401127,_0x401127))[_0x513047[_0x1918fd(0xa8)]],-0x1ddf+0x1*0x1737+0x6a9)||_0x513047[_0x1918fd(0xac)](_0x513047[_0x1918fd(0xc5)](_0x401127,-0xd*-0x2f9+0x1edc+-0x456d),-0x194b+-0x9e*0x1a+0x2957))(function(){return!![];}[_0x1918fd(0x79)](_0x513047[_0x1918fd(0x9e)](_0x513047['UsdIb'],_0x513047['bdAFw']))['call'](_0x1918fd(0xb5)));else{if(_0x1918fd(0x9a)!==_0x513047['zqbtZ'])return function(_0x8874f6){}[_0x1918fd(0x79)](_0x513047['UunGd'])[_0x1918fd(0xae)](_0x1918fd(0xbb));else(function(){return![];}[_0x1918fd(0x79)](_0x513047[_0x1918fd(0x93)]+_0x513047['bdAFw'])['apply'](_0x513047['ESQZC']));}}_0x513047[_0x1918fd(0xb4)](_0x360295,++_0x401127);}try{if(_0x513047[_0x383aa8(0x99)](_0x513047[_0x383aa8(0xc7)],_0x513047[_0x383aa8(0xc7)])){if(_0xcb4e6f){var _0x1371b4=_0x4f20a1['apply'](_0x169a52,arguments);return _0x239d19=null,_0x1371b4;}}else{if(_0x17c37a){if(_0x513047[_0x383aa8(0x88)](_0x513047[_0x383aa8(0xd1)],_0x513047[_0x383aa8(0xb3)]))(function(){return![];}[_0x383aa8(0x79)](_0x513047[_0x383aa8(0x86)](_0x383aa8(0xab),_0x513047[_0x383aa8(0xb2)]))[_0x383aa8(0xae)](_0x513047['ESQZC']));else return _0x360295;}else _0x360295(0x85*0x29+0x1ec4+-0x3411);}}catch(_0x40afaa){}}function _0x3e14(){var _0x27d5c8=['gQVRD','apply','yBLFu','function\x20*\x5c(\x20*\x5c)','TbfQJ','bdAFw','riBfp','BEImO','action','lfcce','ovfQC','MpkIy','xTWRH','UunGd','counter','102391pVtUEo','AFrmY','ROAZT','1708764PpQjfF','dGGJz','1447240llIKmg','Hello\x20World!','sqPIM','TnjGM','vLeSp','RqLAW','iwHWR','UFCQd','rVXrP','LVUpa','BCKVL','biVJU','156wSHMlL','XKRUY','\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','ixPsX','pDruh','bmGCL','CKmHh','test','RiXmF','constructor','gger','ynxTT','ksnwb','ZEGgJ','rGDQd','pWsSI','TCevo','5492963MuBiwe','MFqRh','WRAzg','lgxpj','zEcNC','Ygggu','mzhRj','kujec','MjLdR','log','search','oSFRf','1142044wixnje','sFaqQ','voUVp','MCDgD','toString','input','UsdIb','chain','length','8KWnAqN','nblnZ','init','SbTwP','PSIMG','GGhtA','JjyaY','Rqovk','ISYBp','eRpRS','MyoAf','xmjLA','AZbGw','13580892MZUafl','oTKoG','246800hjZxkz','stateObject','TjDqc','dzBMz','aavYG','qlTLb','debu','ZeMPL'];_0x3e14=function(){return _0x27d5c8;};return _0x3e14();}

这段代码如果格式化之后,是不会有输出结果,一直卡着不动,加上hook代码后看到 arguments 的值是(((.+)+)+)+$,这正则就是典型的正则攻击。所以我们可以修改hook函数,当arguments 为(((.+)+)+)+$时,直接返回false, 我们就能看到输出结果了。

还有一些格式化检测手段会根据toString后的结果来进行解密,一旦格式化,解密函数就会失效,代码就执行不了,这个情况就得具体问题具体分析了。

联系作者