某保网逆向
目标地址:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL2RydWctZGlyZWN0b3J5
一、明确目标
通过翻页,我们可以看见这个包,它的响应如下,对数据做了个加密。

我们来看看请求头和表单。


很明显,这里有很多参数,但是强校验的只有signData,encData。但是处于学习目的,我们要把所有参数都整出来。
二、请求头参数逆向
采用直接搜索的方式,定位到如下几个位置。

在这里,我们能拿到大部分需要的参数,在node里复现一下。
function get_headers() { return { "x-tif-nonce": get_x_nonce(), "x-tif-paasid": undefined, "x-tif-signature": get_signature(), "x-tif-timestamp": x_timestamp(), "x-tingyun": get_tingyun(), } }signature是一个sha256算法,传入的是时间戳和get_x_nonce返回值。
function x_timestamp() { return Math.ceil((new Date).getTime() / 1e3) }
function get_x_nonce() { var e, t, n, i = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", r = "0123456789"; return e = o(6, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), t = o(1, i), n = o(1, r), t + n + e;
function o(e, t) { e = e || 32; for (var n = "", i = 0; i < e; i++) n += t.charAt(Math.ceil(1e3 * Math.random()) % t.length); return n } }
function get_signature() { s = x_timestamp() h = get_x_nonce() f = s + h + s; return CryptoJS.SHA256(f).toString(); }function get_tingyun() { function t(t) { return t < 0 ? NaN : t <= 30 ? 0 | Math.random() * (1 << t) : t <= 53 ? (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << t - 30)) * (1 << 30) : NaN }
function e(t, e) { for (var n = t.toString(16), r = e - n.length, i = "0"; r > 0; r >>>= 1, i += i) 1 & r && (n = i + n); return n }
r = 'c=B|4Nl_NnGbjwY'
function G(n) { var r = "-"; return n && (r = ""), e(t(32), 8) + r + e(t(16), 4) + r + e(16384 | t(12), 4) + r + e(32768 | t(14), 4) + r + e(t(48), 12) }
n = G(!0).substring(0, 16); return r += ";x=" + n }第38行的r是个前缀,网页上定位如下。

把缺失的函数补一下,请求头基本就这样能够拿到了。
三、表单参数逆向
表单是个大对象,里面重要的是时间戳和encdata。
网站上定位到,把他扣下来。

function get_encdata(t) { return function (e) { for (var t = e.data.data && JSON.stringify(e.data.data), n = "", i = 0; i < t.length; i++) { var r = t.charAt(i) , o = t.charCodeAt(i); n += o > 127 ? "\\u" + o.toString(16).padStart(4, "0") : r } var a = k(n);
e.data.appCode && e.data.appCode !== p && (p = e.data.appCode);
var s = x(p, m) , l = S(s, a); return l.toUpperCase()
}(t) }接着补缺少的函数。
其中e这个对象我们可以在网站上寻找一下,不然from函数要再扣很多东西。
e来自于这个自执行函数的传参。

function S(t, n) { var i = 16 - parseInt(n.length % 16); n = n.concat(new Array(i).fill(i)); var r = m.encrypt(n, t); return e.from(r).toString("hex") }
function k(e) { var t, n, i = new Array; t = e.length; for (var r = 0; r < t; r++) (n = e.charCodeAt(r)) >= 65536 && n <= 1114111 ? (i.push(n >> 18 & 7 | 240), i.push(n >> 12 & 63 | 128), i.push(n >> 6 & 63 | 128), i.push(63 & n | 128)) : n >= 2048 && n <= 65535 ? (i.push(n >> 12 & 15 | 224), i.push(n >> 6 & 63 | 128), i.push(63 & n | 128)) : n >= 128 && n <= 2047 ? (i.push(n >> 6 & 31 | 192), i.push(63 & n | 128)) : i.push(255 & n); return i }
function x(e, t) { return k(S(k(e.substr(0, 16)), k(t)).toUpperCase().substr(0, 16)) }
m = 'NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P' p = 'T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ'm和p都是硬编码,这个自己多试几次就能发现。
接着我们处理这两个
var r = m.encrypt(n, t); return e.from(r).toString("hex")s往上找,可以发现是个大模块。

这里其实差不多能发现是webpack,我们直接把整个文件拿下来,放到mywebpack.js然后做一些手脚。

放一个loader加载器,劫持一下它的。
webpack整整70000多行,有大量vue等环境,我们可以补些环境,让它能顺利执行。如果你不想不环境,也可以把需要的模块手动抠出来,大概100多个模块。
环境代码如下
window = globalThis;delete globalThis.navigator
createConstructor('HTMLCollection', true, [], {},);
createConstructor('EventTarget', true, [], { addEventListener:function(){}, removeEventListener:function(){}});createConstructor('Node', true, [], {}, 'EventTarget');createConstructor('WindowProperties', true, [], {}, 'EventTarget')createConstructor('Window', true, [], {}, 'WindowProperties');createConstructor('Navigator', true, [], {});createConstructor('Screen', true, [], {}, 'EventTarget');createConstructor('History', true, [], {});createConstructor('Location', true, [], {});createConstructor('Element', true, [], { getAttribute: function(name) { return name === 'href' ? _href : null; }, setAttribute:function(a){ console.log("setAttribute创建",a) }},'Node');createConstructor('HTMLElement', true, [], {},"Element");createConstructor('HTMLHtmlElement', true, [], {},"HTMLElement");createConstructor('HTMLAnchorElement', true, [], {},"HTMLElement");createConstructor('Document', true, [], { createEvent:function(event){ return { timestamp:0 } }, getElementsByTagName:function(name){ console.log("getElementsByTagName————",name)
if (name === 'script') { var arr = []; for (var i = 0; i < 10; i++) { arr.push({ tagName: 'SCRIPT', src: '', innerHTML: '', readyState: 'complete', type: 'text/javascript', getAttribute: function(attr) { return null; }, setAttribute: function() {} }); } arr.length = 10; arr.item = function(i) { return this[i] || null; };
return arr;} }, createElement:function(name){ console.log("createElement————",name) if(name==='a'){ return new HTMLAnchorElement({ tagName: 'A', href: '', protocol: '', host: '', hostname: '', port: '', pathname: '/', search: '', hash: '', setAttribute: function(name, value) { console.log("a.setAttribute:", name, '=', value);
if (name === 'href') { _href = value; // ✅ 用 URL 解析 try { var url = new URL(value, window.location.href || 'https://fuwu.nhsa.gov.cn'); this.href = url.href; this.protocol = url.protocol.replace(/:$/, ''); this.host = url.host; this.hostname = url.hostname; this.port = url.port; this.pathname = url.pathname.charAt(0) === '/' ? url.pathname : '/' + url.pathname; this.search = url.search ? url.search.replace(/^\?/, '') : ''; this.hash = url.hash ? url.hash.replace(/^#/, '') : ''; } catch(e) { this.href = value; } } else { this['_' + name] = value; } },
getAttribute: function(name) { return name === 'href' ? _href : (this['_' + name] || null); } },null,"fatdog") } }}, 'Node');createConstructor('HTMLDocument', true, [], {}, 'Document');createConstructor('Storage', true, [], { getItem: function(key) { console.log('localStorage.getItem获取', key); return this[key] || null; }, setItem: function(key, value) { console.log('localStorage.setItem设置', key, value); this[key] = String(value); }, removeItem: function(key) { console.log('localStorage.removeItem删除', key); if (this[key]) delete this[key]; }, clear: function() { this.__storage__ = {}; }});
window.window = window.self =window.top = window
Object.setPrototypeOf(window, Window.prototype);
location =new Location({ "ancestorOrigins": {}, "href": "https://fuwu.nhsa.gov.cn/nationalHallSt/#/search/drug-directory?code=174000&message=serverUrl%20is%20null&gbFlag=true", "origin": "https://fuwu.nhsa.gov.cn", "protocol": "https:", "host": "fuwu.nhsa.gov.cn", "hostname": "fuwu.nhsa.gov.cn", "port": "", "pathname": "/nationalHallSt/", "search": "", "hash": "#/search/drug-directory?code=174000&message=serverUrl%20is%20null&gbFlag=true"},null,'fatdog')document = new HTMLDocument({ documentElement:new HTMLHtmlElement({},null,'fatdog'), querySelector:function(a){ if(a==='base'){ return null; } }, cookie:'amap_local=320500', "location":location, implementation:{}},null,"fatdog")navigator = new Navigator({ userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36', product:'Gecko', appName:'Netscape',},null,"fatdog")screen=new Screen({
},null,"fatdog")history=new History({ pushState:function(n, r, i) { var o; o = e.apply(this, arguments); try { Wa(t) } catch (a) {} return o }},null,'fatdog')localStorage = new Storage({},null,'fatdog')
// window.location = watch(location,"window.location")// window.navigator = watch(navigator,'window.navigator')// window.screen = watch(screen,'window.screen')// window.history = watch(history,'window.history')// window.document = watch(document,'window.document')// window.localStorage = watch(localStorage,"window.localStorage")
// window = watch(window,"window")_log = console.logconsole.log = function(){}补到这里就能出值了,但是还会报错,我们可以用process.exit(0)截断。
四、解密函数
function dec_data(t) { var n = e.from(t.data.data.encData, "hex") , i = function (t, n) { var i = sm4.decrypt(n, t) , r = i[i.length - 1]; return i = i.slice(0, i.length - r), e.from(i).toString("utf-8") }(x(p, m), n); return JSON.parse(i) }这个直接拿就行,定位就在encdata的下面,里面的函数基本在前面补齐了,用的SM4解密。
main.js做如下处理
try { const loader = require('./mywebpack.js'); ······} catch (e) { console.log = _log; // 恢复日志 console.log(JSON.stringify({error: e.message})); process.exit(0);}// main.js 最后加上:process.on('exit', function () { process.exitCode = 0; // 强制退出码为 0});这样可以让vue环境静默,不需要多补环境或者手动去拿模块。
最后那个退出码是为了阻止execjs报错。
部分信息可能已经过时









