听闻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
})

联系作者

Node服务设置–max-old-space-size参数

可以先问问GPT这个参数是做什么的

–max-old-space-size 是 Node.js 运行时的一个参数,它用来设置 V8 引擎的老生代内存限制。
V8 的内存空间主要分为新生代空间(Young Generation)和老生代空间(Old Generation),这两种空间的大小决定了应用程序可以使用的最大内存。新生代空间通常较小,并且垃圾回收频率较高;而老生代空间较大,并且垃圾回收频率较低。
当你设置了 –max-old-space-size 的值,你就在 V8 引擎运行时告诉它,它在老生代空间可以使用的最大内存量。如果你的应用程序尝试超过这个限制,V8 引擎会抛出一个 FATAL ERROR: Reached heap limit Allocation failed 的错误,表示内存已用尽,无法分配新的内存。
请注意,–max-old-space-size 只是 V8 引擎可以使用的总内存的一个部分,还有其他部分是由 V8 内部使用的,例如它的执行代码和堆栈空间等。所以,如果你的系统内存较少,你可能会需要减小 –max-old-space-size 的值,以确保其他重要的系统进程也能正常运行。

在PM2中我们就可以如此启动服务 pm2 start server.js -i 2 –node-args=”–max-old-space-size=800” –no-daemon

–max-old-space-size 这个参数比 –max-memory-restart 好用多了,它会尝试进行内存回收,能回收了,就不重启进程,不能回收就才会重启进程,这样可以减少 502 服务器错误。

有了这个参数,也不需要再使用强制内存回收插件了,因为本身自带内存回收。唯一的问题是,PM2守护的进程内存都是从0开始,然后进程一起到达设置的老生代内存大小,此时CPU占比会很高,可能会影响服务执行。

联系作者

未雨绸缪,虽然有了补环境通用方案,但为了应对可能更高的QPS需求,一直在想办法储备更快的cookies生成方案。简单计算下,假设需要400 QPS,而一般的补环境耗时250ms, 那么一个CPU一秒可以处理4个请求,400 QPS就需要100核CPU, 这对资源的消耗就很可观了。如果有个方案能50ms生成一个cookies, 那么就只需要20核CPU就能满足需求。

寻寻觅觅,在如画佬的星球里找到一个补环境方案,环境代码极少。示例代码是在Python中使用execjs来执行生成cookies, 改成Node服务后执行速度非常理想,能做到80ms生成一次。

美中不足的是需要在Node中使用eval执行js代码,会导致内存泄漏,这种内存泄漏使用强制内存回收也回收不了,最后程序会奔溃,用PM2限制内存使用和自动重启可以解决。但不可避免会出现502服务器错误,这在我们公司的生成环境中就不合适,因为运维一旦发现502超过一定数量就得让解决,得寻求其它方案。

要想又快又稳,想来只有算法方案了。

联系作者

之前在群里就看到有道友在讨论这个问题,但因为没有遇到过这个问题,就没细看,这次自己也遇到了,就认真看了下。

在寻找高效的某数 cookies 生成方案时,看到如画佬星球的补环境方案后,深有感触。最近两年,虽然把补环境框架搞的越来越完善,但同时执行速度也越来越慢,想想似乎有点背离补环境的初衷。上一次yidun的加密生成方案尤其明显,补环境方案把整个CPU都打满了,后面搞了个补环境和扣代码结合的方案后跑的就稳稳的,两者之间性能整整差了10倍。

于是就尝试这种最开始学习的补环境方案,缺啥补啥,争取补的环境每一行代码都是有用的,然后就遇到这个 ActiveXObject 问题了。在 proxy 输出日志中,看到有 ActiveXObject 调用日志,于是在浏览器里看,window.ActiveXObject 就是 undefined, 在Node里,window=global 后,window.ActiveXObject 也是 undefined,于是就没管 ActiveXObject 。但生成的 cookies 就是过不去,只好和如画佬的代码做对比,发现就是因为他的代码有 window.ActiveXObject=undefined,所以能过。

于是去了解 ActiveXObject,发现它是IE才有的对象,在IE中,’ActiveXObject’ in window 是true, 而在 Chrome 中 ‘ActiveXObject’ in window 是 false。如果在检测代码中有 ‘ActiveXObject’ in window 这种检测,那么补环境代码中有没有 window.ActiveXObject=undefined 就会影响检测代码执行路径了。在Node中加上 window.ActiveXObject=undefined 后,’ActiveXObject’ in window 也就成了 true 了,检测代码就会认为是 IE浏览器,就会按照IE浏览器来检测。手头上方便调试的只有Chome浏览器,就再继续探究后面如何检测的。神奇的是,我是在Mac上用Chrome调试的代码,最后还得伪装成IE才能通过检测,就离谱。

回过头来看代理日志,有针对in操作符的代理has=> [window] 有无属性名 => [ActiveXObject], 结果 => [false] 这行,就是 代理has输出日志,说明检测代码里有 ‘ActiveXObject’ in window 的检测,只是没注意到。

联系作者

本来是想问ChatGPT在Node.js里如何读取Redis的,问着问着,就拿如何解决document.all问题去问ChatGPT, 顺便测试下其它几个厂商给出的回答,其它几个都是挺常规的回答,只有Copilot给出的回答出乎意料,因为它的回答是基于我的当补环境遇到document.all文章给的。

Copilot 给的回答是

在解决 jsdom 被针对的逆向过程中,遇到了 document.all 的问题。尽管 document.all 的类型是 undefined,但却能够获取值,这让人感到非常困惑。不过,我们有一些解决办法:

  1. 不补环境:有时候网站的校验可能不严格,直接绕过补环境步骤也许就能成功。
  2. 扣算法:这是一种绕过各种检测的方法,直接将值写死。不过,这需要一定的技巧。
  3. 使用 V8 中修改后的不可检测对象:这是风佬提出的方法,可以完美解决问题。1
  4. 请大佬们出山,共同探讨解决方案。
    总之,解决这个问题的途径有很多,我们可以根据具体情况选择合适的方法。

我的文章里说到:

目前看来,解决这个问题的办法有如下几种

  1. 直接不补了,有可能网站校验不严格,直接可以过
  2. 扣算法,这样绕过各种检测,直接把值写死,就是扣算法难搞。
  3. 用风佬修改后的Node
  4. V8里的不可检测对象
  5. 请大佬出山帮你解决

惊不惊喜,意不意外。阿龙的学习笔记作为个人学习笔记从13年就开始写,有一年还因为域名续费失败的原因,导致域名被抢注而换到现在的域名,但还是坚持独立博客写下来,不管有没有人看。最近一份工作的主要工作内容是Web逆向,写了很多补环境相关的技术文章后收到一些道友的关注,并加联系方式讨论相关问题,还收到一位不知名字道友的20块钱巨赏, 甚是鼓舞。今天看到自己的文章被AI引用,甚是开心。

还是那句话,兼济天下则达,独善其身则穷。

联系作者