通常我们写好一个接口后,需要做接口性能测试,知道接口的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后的结果来进行解密,一旦格式化,解密函数就会失效,代码就执行不了,这个情况就得具体问题具体分析了。

联系作者

24年下半年算是比较密集搞APP的一段时间,不得不说,Android逆向要学的东西比Web真是多多了。Web拿着浏览器就开始调试了,Android得学习一堆工具。Android逆向要解决的问题也比Web多多了,连抓包都是个问题。每次都是直接上强度,20年刚来做爬虫时直接就开搞rs, 24年Android逆向时直接就是企业级, 像libsgmain.so, libmpaas_crypto.so这种。好的一点是遇到的难点都是加密,没有风控,要不然就更头疼了。

这里列举下Andorid逆向遇到的问题,root检测,Frida反调试(因为我主要是用Frida分析,用Xposed的话也会遇到Xposed反调试), SSL pinning机制,加固壳, so加密。真是一步一个坑,举步维艰。还好有好多开源的工具, 有大佬们的星球可以学习,要不然真的累麻了。下面记录下每一步都是如何解决的,以供参考。

root检测

最开始是用Magisk里的Shamiko模块,但发现这个过不去一些检测,后面了解到还有kernelSU和APatch,因为手上都是一些便宜手机,带不动kernelSU,就用APatch, 能解决问题。

Frida反调试

大都是通过hook pthread_create解决,搞不定就请大佬出山。这里重点推荐霜哥的星球。

SSL pinning机制

之前APP抓包进阶里写过,用DroidSSLUnpinning完事。

加固壳

开源的Fart,Fartext,MikRom试试,搞不定的话只好请小黄鱼大神解决了

so加密

用Unidbg一把梭,Unidbg搞不定的话,只能慢慢分析so了,我也不会了。这里推荐白龙的星球,写的真的细。

根据https://www.52pojie.cn/thread-1244902-1-1.html 这篇文章写的,现在停留在基础技能的阶段,后续有时间再学习了。遇到问题多去Github, 看雪,吾爱破解找找。

参考资料

联系作者

2025年了,卷逆向越来越没意思了,但终归要吃饭,得有备用方案在手上才不慌。万一某数今年更新了,上一些类似window[“Object”]“getOwnPropertyNames”等奇奇怪怪的检测,一时半会搞不定咋办。要知道document.all刚出来的时候,卡了好久, 要不是大佬出手,不知道还得卡多少天。于是测试了下别人家的方案,多备几套方案在手上。

sdenv

项目地址https://github.com/pysunday/sdenv,根据example/use-local/index.js示例,给它做成服务,跑的挺稳,速度也挺好,在m1上50毫秒,普通服务器上估计300毫秒,用在生产环境中需解决内存泄漏问题。

qxvm

项目地址https://github.com/ylw00/qxVm,根据z_working/rs4Vm.js示例,给它做成服务, 能跑起来。但换成vmp版本的话,跑不起来,因为dom操作不全, 没法用。

node-sandbox

项目地址https://github.com/bnmgh1/node-sandbox,根据demo例子main.js, 给它做成接口服务能跑起来,打印的调用记录也很详细。但换了个网站就卡住了,不知道错在哪里,也不知道怎么排查,因为是魔改的node, 不知道怎么使用node-inspect功能。

cynode

卷卷大陆知识星球里的一个补环境框架,按照demo例子修改后能跑起来,打印的调用记录也很详细,但生成速度很慢,生成一个cookies要3秒,不知道问题出在哪里,得慢慢排查。

浏览器渲染方案

用playwright搞了个浏览器渲染服务,生成cookies速度2.5秒,将就能用

结论

目前来看,sdenv最舒服,又快又稳。浏览器渲染方案将就能用,能再优化下速度就更好了。cynode将就能用,解决了速度慢问题的话那就更舒服了。 node-sandbox如果能知道怎么调试,找到问题,应该能用。qxvm是没法用了,dom树操作不全。

联系作者

这几天一直在处理APP,遇到了libsgmain.so, 问了大佬和找资料后,基于Unidbg的demo跑通了,于是做个简单记录。

  1. 代码里找不到核心加密接口和类,Frida里也hook不到(如ISecureSignatureComponent接口的实现类,JNICLibrary类),是因为这些代码是动态注册的。如何解决看参考资料1
  2. libsgmain.so是假so, 本质上是jar, 在jadx里打开就能看到核心加密类,真正的so文件也在这假so里,形如libsgmainso-*.so
  3. doCommandNative是真正加密的方法,这个方法也用于初始化,至于初始化多少次,看so文件难度了, 我要处理的so相对简单,只有一次初始化,缺啥补啥完事。可以看参考资料2。
  4. Unidbg真是大杀器,so加密小白福音。

参考资料

  1. 奋飞佬的某A系电商App x-sign签名分析
  2. 意识存在感的阿里系某电影票务APP加密参数还原-Unidbg篇

联系作者

remove debugger

有些时候想移除debugger

1
2
3
4
5
6
7
8
_Function = Function;
Function.prototype.constructor = function(){
if (arguments[0].indexOf('debugger') != -1){
//debugger;
return _Function('');
}
return _Function(arguments[0]);
};

hook cookie

我们需要快速定位cookie在哪里生成的,需要hook cookie

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
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function() {
debugger;
console.log('Getting cookie');
return cookie_cache;
},
set: function(val) {
debugger;
console.log('Setting cookie', val);
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 + "; ";
}
this._value = val;
return cookie_cache;
},
});

hook eval

有时候我们需要快速定位vm.js在哪里生成的,需要hook eval

1
2
3
4
5
6
7
8
9
window.__eval = window.eval
var myeval = function(src) {
debugger ;
console.log('=======eval====')
return window.__eval(src)
}
Object.defineProperty(window.prototype, 'eval', {
value: myeval
})

hook random

有时候我们需要固定随机参数,需要hook random

1
2
3
4
old_random = Math.random
window.Math.random = Math.random = function () {
return 0.3
};

hook regex

有时候我们需要知道代码如何进行格式化检测,需要hook 正则

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

let my_regex = function(arguments) {
debugger ;
console.log(this, arguments)
return this.my_test(arguments)
}

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

联系作者