当前位置: 首页 > news >正文

JavaScript逆向Hook技术及常用Hook脚本

JavaScript逆向Hook技术及常用Hook脚本

  • 什么是Hook技术
  • Hook技术的原理是什么
    • 函数是一等公民 (Functions are First-Class Citizens)
    • 对象属性的可变性 (Mutability of Object Properties)
    • 原型链 (The Prototype Chain)
    • 元编程 API (Metaprogramming APIs)
  • 常见的 Hook 技术实现
    • 直接覆盖函数 (最简单)
    • 使用 Object.defineProperty (更强大)
  • 常用的Hook脚本
    • 监听 键盘 与 鼠标 事件
    • webpack 半自动扣
    • document下的createElement()方法
    • 当header中包含Authorization时,则插入断点
    • 当请求的url里包含MmEwMD时,则插入断点
    • docuemnt.getElementById 以及value属性
    • 过debugger
    • JSON
    • 对象属性及属性自定义
    • cookie
    • window attr
    • eval/Function
    • eval 取返回值
    • eval proxy代理
    • websocket
    • 正则
    • canvas (定位图片生成的地方)
    • setInterval 定时器
    • setInterval 循环清除定时器
    • console.log 检测例子 (不让你输出调试)
    • 检测函数是否被Hook例子
    • 模拟sleep函数,实现Date的时间增加
    • Array.concat
    • 控制台检测

什么是Hook技术

JavaScript 逆向中的 Hook(中文常译为“挂钩”或“钩子”),是一种动态分析技术。它的核心思想是“劫持”或“挂钩”——在程序正常运行的过程中,拦截一个已有的、目标函数或属性的调用,然后替换成我们自己编写的一段代码。
通过这段我们自定义的代码,我们可以实现:

  1. 监控 (Monitor):监视目标函数何时被调用、传入了什么参数、返回了什么结果。
  2. 修改 (Modify):在不改变原始函数逻辑的情况下,修改传入的参数或传出的结果。
  3. 绕过 (Bypass):完全阻止原始函数的执行,直接返回一个我们伪造的结果。

Hook技术的原理是什么

Hook 技术的核心原理是利用了 JavaScript 作为一门动态语言的特性,特别是其在运行时修改对象行为的能力。JavaScript 的以下几个语言特性,使得该技术得以被实现:

函数是一等公民 (Functions are First-Class Citizens)

这是最重要的基石。在 JavaScript 中,函数和其他变量(如数字、字符串、对象)的地位是一样的。这意味着:

  • 函数可以被赋值给变量: 这使得我们可以轻松地“保存”原始函数。
     // 将 window.alert 的功能保存到我们自己的变量里
    const originalAlert = window.alert;
    
  • 函数可以作为参数传递和返回: 允许我们进行更复杂的函数式操作。

对象属性的可变性 (Mutability of Object Properties)

// 用我们自己的函数,覆盖掉 window.alert 的原始功能
window.alert = function(message) {console.log(`Alert was hooked! Message: ${message}`);// 我们甚至可以选择不调用原始的 alert// originalAlert(message); 
};

当我们执行 alert('hello') 时,实际上执行的是我们刚刚定义的新函数。

原型链 (The Prototype Chain)

JavaScript 的继承是基于原型链的,并且这个原型链在运行时也是可以修改的。通过修改一个类的 prototype,我们可以 Hook 所有该类的实例的方法。

// 保存原始的 Array.prototype.push 方法
const originalPush = Array.prototype.push;// 重写它
Array.prototype.push = function(...args) {console.log(`Hooked! Pushing ${args.length} items to an array.`);// 调用原始的 push 方法,并返回其结果return originalPush.apply(this, args);
}const myArray = [];
myArray.push(1, 2, 3); // 控制台会打印出 "Hooked! Pushing 3 items to an array."

元编程 API (Metaprogramming APIs)

JavaScript 提供了一些底层的 API,让我们能够更精细地控制对象的行为,这为实现更隐蔽、更强大的 Hook 提供了工具。

  • Object.defineProperty:允许我们重写对象的属性访问器(get 和 set)。这意味着我们不仅能 Hook 函数调用,还能 Hook 属性的读取和写入操作。这比直接覆盖属性要强大得多,因为它可以作用于那些不可写的属性。
  • Proxy:ES6 引入的 Proxy 是 Hook 技术的“终极形态”。它可以为一个对象创建一个虚拟的“代理层”,能够拦截对该对象的几乎所有操作(get, set, has, apply, construct 等),而无需对原始对象做任何修改

常见的 Hook 技术实现

直接覆盖函数 (最简单)

这是最基础的 Hook 方式,适用于挂载在全局对象(如 window)或其他可访问对象上的函数。
如以下接口,返回的数据是加密的字符串,我们需要定位解密的位置。

在这里插入图片描述
由于解密后一定是json字符串,需要调用JSON.parse去转换成JavaScript对象来渲染页面,此时我们可以使用Hook技术来断点到JSON.parse的调用位置。这里我们详细介绍下实现步骤。

  1. 准备Hook代码
    (function () {var parse_ = JSON.parse;JSON.parse = function (params) {//这里可以添加其他逻辑比如 debuggerdebugger;console.log("json_parse params:", params);return parse_(params);};
    })();
    
    这里使用自执行匿名函数包裹,是防止变量污染环境,让我们自定义的变量全部限制为局部变量。
  2. 在该接口的启动器最底层处打上断点,并再次触发接口调用执行至断点处
    在这里插入图片描述
    在这里插入图片描述
  3. 在控制台执行我们准备好的Hook代码
    在这里插入图片描述
  4. 重新触发接口,程序可以在我们Hook代码中的debugger处断住,通过调用堆栈,我们就能定位到JSON.parse的调用位置,也就很容易找到返回参数的解密位置了
    在这里插入图片描述
    在这里插入图片描述

使用 Object.defineProperty (更强大)

对于一些通过 const 定义或者被设置为不可写的属性,直接覆盖可能会失败。这时,Object.defineProperty 就派上了用场。我们之前在讨论反调试时用它来监控 document.cookie,原理完全一样。这种方法可以用来 Hook 对象的属性读取 (getter)属性设置 (setter),这是直接覆盖无法做到的。
Object.defineProperty 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。具体用法参考文档Object.defineProperty。
这里是一个使用Object.defineProperty来控制属性读取 (getter) 和**属性设置 (setter)**简单的例子

user = {age: "123",
};
aa = user.age;
Object.defineProperty(user, "age", {get: function () {return aa;},set: function (newVal) {console.log("这个人来设置值了!!");aa = newVal;},
});
console.log(user.age);
user.age = "23342";
console.log(user.age);

我们使用Object.defineProperty定位cookie的赋值来找到cookie加密的位置。这里直接给出Hook代码。

// 1. 获取并保存浏览器原始的 cookie 设置功能
const originalCookieSetter = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie').set;// 2. 重写 document.cookie 的 "set" 功能
Object.defineProperty(document, 'cookie', {// 当有代码执行 document.cookie = "..." 时,这个 set 函数就会被触发set: function(value) {console.log('拦截到 Cookie 设置:', value);// 例如,当设置的 cookie 包含 'secret' 字符串时,就暂停if (value.includes('secret')) {debugger; }// 3. 调用原始的设置功能,保证 cookie 能被正常设置//    必须用 .call(document, ...) 的方式来调用,以保证 this 指向正确originalCookieSetter.call(document, value);},// 为了不破坏读取功能(get),我们必须将原始的 get 方法也一并保留get: Object.getOwnPropertyDescriptor(Document.prototype, 'cookie').get,// 保证该属性可以被再次配置configurable: true
});

注意,大多数网站和视频教程给出的cookie的Hook代码都是错误的,会影响cookie的赋值,这里是AI给出的正确版本。

常用的Hook脚本

转载自https://www.cnblogs.com/xiaoweigege/p/14954648.html

监听 键盘 与 鼠标 事件

// 判断是否按下F12  onkeydown事件
/*
提示: 与 onkeydown 事件相关联的事件触发次序:
onkeydown
onkeypress
onkeyup
*/// F12的键码为 123,可以直接全局搜索 keyCode == 123, == 123 ,keyCode
document.onkeydown = function() {if (window.event && window.event.keyCode == 123) {// 改变键码event.keyCode = 0;event.returnValue = false;// 监听到F12被按下直接关闭窗口window.close();window.location = "about:blank";}
}
;
// 监听鼠标右键是否被按下方法 1, oncontextmenu事件
document.oncontextmenu = function () { return false; };// 监听鼠标右键是否被按下方法 2,onmousedown事件
document.onmousedown = function(evt){// button属性是2 就代表是鼠标右键 if(evt.button == 2){alert('监听到鼠标右键被按下')evt.preventDefault() // 该方法将通知 Web 浏览器不要执行与事件关联的默认动作return false;}
}// 监听用户工具栏调起开发者工具,判断浏览器的可视高度和宽度是否有改变,有改变则处理,
// 判断是否开了开发者工具不太合理。
var h = window.innerHeight, w = window.innerWidth;
window.onresize = function(){alert('改变了窗口高度')
}// hook代码
(function() {//严谨模式 检查所有错误'use strict';// hook 鼠标选择Object.defineProperty(document, 'onselectstart', {set: function(val) {console.log('Hook捕获到选中设置->', val);return val;}});// hook 鼠标右键Object.defineProperty(document,'oncontextmenu',{set:function(evt){console.log("检测到右键点击");return evt}});
})();

webpack 半自动扣

//在加载器后面下断点  执行下面代码
// 这里的f 替换成需要导出的函数名
window.zhiyuan = f;
window.wbpk_ = "";
window.isz = false;
f = function(r){if(window.isz){// e[r]里的e 是加载器里的call那里window.wbpk_ = window.wbpk_ + r.toString()+":"+(e[r]+"")+ ",";}return window.zhiyuan(r);
}//在你要的方法加载前下断点 执行 window.isz=true
//在你要的方法运行后代码处下断点  执行 window.wbpk_  拿到所有代码  注意后面有个逗号function o(t) {if (n[t])return n[t].exports;var i = n[t] = {i: t,l: !1,exports: {}};console.log("被调用的 >>> ", e[t].toString());//  这里进行拼接,bb变量需要在全局定义一下 // t 是模块名, e[t] 是模块对应的函数, 也就是key:value形式bb += `"${t}":${e[t].toString()},`return e[t].call(i.exports, i, i.exports, o),i.l = !0,i.exports
}
bz = o;

document下的createElement()方法

(function() {'use strict'var _createElement = document.createElement.bind(document);document.createElement = function(elm){// 这里做判断 是否创建了script这个元素    if(elm == 'body'){debugger;}return _createElement(elm);
}
})();

当header中包含Authorization时,则插入断点

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);

当请求的url里包含MmEwMD时,则插入断点

var code = function(){
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async){if (url.indexOf("MmEwMD")>-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);

docuemnt.getElementById 以及value属性

// docuemnt.getElementById 以及value属性的hook,可以参考完成innerHTML的hook
document.getElementById = function(id) {var value = document.querySelector('#' + id).value;console.log('DOM操作 id: ', id)try {Object.defineProperty(document.querySelector('#'+ id), 'value', {get: function() {console.log('getting -', id, 'value -', value);return value;},set: function(val) {console.log('setting -', id, 'value -', val)value = val;}})} catch (e) {console.log('---------华丽的分割线--------')}return document.querySelector('#' + id);
}

过debugger

function Closure(injectFunction) {return function() {if (!arguments.length)return injectFunction.apply(this, arguments)arguments[arguments.length - 1] = arguments[arguments.length - 1].replace(/debugger/g, "");return injectFunction.apply(this, arguments)}
}var oldFunctionConstructor = window.Function.prototype.constructor;
window.Function.prototype.constructor = Closure(oldFunctionConstructor)
//fix native function
window.Function.prototype.constructor.toString = oldFunctionConstructor.toString.bind(oldFunctionConstructor);var oldFunction = Function;
window.Function = Closure(oldFunction)
//fix native function
window.Function.toString = oldFunction.toString.bind(oldFunction);var oldEval = eval;
window.eval = Closure(oldEval)
//fix native function
window.eval.toString = oldEval.toString.bind(oldEval);// hook GeneratorFunction
var oldGeneratorFunctionConstructor = Object.getPrototypeOf(function*() {}).constructor
var newGeneratorFunctionConstructor = Closure(oldGeneratorFunctionConstructor)
newGeneratorFunctionConstructor.toString = oldGeneratorFunctionConstructor.toString.bind(oldGeneratorFunctionConstructor);
Object.defineProperty(oldGeneratorFunctionConstructor.prototype, "constructor", {value: newGeneratorFunctionConstructor,writable: false,configurable: true
})// hook Async Function
var oldAsyncFunctionConstructor = Object.getPrototypeOf(async function() {}).constructor
var newAsyncFunctionConstructor = Closure(oldAsyncFunctionConstructor)
newAsyncFunctionConstructor.toString = oldAsyncFunctionConstructor.toString.bind(oldAsyncFunctionConstructor);
Object.defineProperty(oldAsyncFunctionConstructor.prototype, "constructor", {value: newAsyncFunctionConstructor,writable: false,configurable: true
})// hook dom
var oldSetAttribute = window.Element.prototype.setAttribute;
window.Element.prototype.setAttribute = function(name, value) {if (typeof value == "string")value = value.replace(/debugger/g, "")// 向上调用oldSetAttribute.call(this, name, value)
}
;
var oldContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "contentWindow").get
Object.defineProperty(window.HTMLIFrameElement.prototype, "contentWindow", {get() {var newV = oldContentWindow.call(this)if (!newV.inject) {newV.inject = true;core.call(newV, globalConfig, newV);}return newV}
})// hook constructor
var _constructor = constructor;
Function.prototype.constructor = function(s) {if (s == "debugger") {console.log(s);return null;}return _constructor(s);
}// hook eval
(function() {'use strict';var eval_ = window.eval;window.eval = function(x) {eval_(x.replace("debugger;", "  ; "));};window.eval.toString = eval_.toString;
}
)();

JSON

var my_stringify = JSON.stringify;
JSON.stringify = function (params) {//这里可以添加其他逻辑比如 debuggerconsole.log("json_stringify params:",params);return my_stringify(params);
};var my_parse = JSON.parse;
JSON.parse = function (params) {//这里可以添加其他逻辑比如 debuggerconsole.log("json_parse params:",params);return my_parse(params);
};

对象属性及属性自定义

(function(){// 严格模式,检查所有错误'use strict'// document 为要hook的对象 ,属性是cookieObject.defineProperty(document,'cookie',{// hook set方法也就是赋值的方法,get就是获取的方法set: function(val){// 这样就可以快速给下面这个代码行下断点,从而快速定位设置cookie的代码debugger;  // 在此处自动断下console.log('Hook捕获到set-cookie ->',val);return val;}})
})();

cookie

// 方法1
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie', {get: function() {console.log('Getting cookie');return cookie_cache;},set: function(val) {console.log("Seting 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;})}
})// 方法2
var code = function(){var org = document.cookie.__lookupSetter__('cookie');document.__defineSetter__("cookie",function(cookie){if(cookie.indexOf('TSdc75a61a')>-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);// 当cookie中匹配到了 TSdc75a61a, 则插入断点。

window attr

// 定义hook属性
var window_flag_1 = "_t";
var window_flag_2 = "ccc";var key_value_map = {};
var window_value = window[window_flag_1];// hook
Object.defineProperty(window, window_flag_1, {get: function(){console.log("Getting",window,window_flag_1,"=",window_value);//debuggerreturn window_value},set: function(val) {console.log("Setting",window, window_flag_1, "=",val);//debuggerwindow_value = val;key_value_map[window[window_flag_1]] = window_flag_1;set_obj_attr(window[window_flag_1],window_flag_2);},});function set_obj_attr(obj,attr){var obj_attr_value = obj[attr];Object.defineProperty(obj,attr, {get: function() {console.log("Getting", key_value_map[obj],attr, "=", obj_attr_value);//debuggerreturn obj_attr_value;},set: function(val){console.log("Setting", key_value_map[obj], attr, "=", val);//debuggerobj_attr_value = val;},});
}

eval/Function

window.__cr_eval = window.eval;
var myeval = function(src) {// src就是eval运行后 最终返回的值console.log(src);console.log("========= eval end ===========");return window.__cr_eval;
}var _myeval = myeval.bind(null);
_myeval.toString = window.__cr_eval.toString;
Object.defineProperty(window, 'eval',{value: _myeval});window._cr_fun = window.Function
var myfun = function(){var args = Array.prototype.slice.call(arguments, 0, -1).join(","), src = arguments[arguments.lenght -1];console.log(src);console.log("======== Function end =============");return window._cr_fun.apply(this, arguments)
}myfun.toString = function() {return window._cr_fun + ""} //小花招,这里防止代码里检测原生函数
Object.defineProperty(window, "Function",{value: myfun})

eval 取返回值

_eval = eval;
eval = (res)=>{res1 = res // 返回值return _eval(res)
}eval(xxxxxxxxx)

eval proxy代理

// 代理eval   https://segmentfault.com/a/1190000025154230
eval = new Proxy(eval,{// 如果代理的是函数 查看调用 就用apply属性// 第二个参数是prop 这里用不上 因为是属性,eval只是个函数 所以prop为undefind 这里设置了下划线 ——apply: (target,_,arg)=>{// target 是被代理的函数或对象名称,当前是[Function: eval]// arg是传进来的参数,返回的是个列表console.log(arg[0])}
})//  eval执行的时候就会被代理拦截
// 传入的如果是字符串 那么只会返回字符串,这里是匿名函数 直接执行 return了内容
eval((function(){return "我是包子 自己执行了"})()
)// 结果 : 我是包子 自己执行了

websocket

 // 1、webcoket 一般都是json数据格式传输,那么发生之前需要JSON.stringify  
var my_stringify = JSON.stringify;
JSON.stringify = function (params) {//这里可以添加其他逻辑比如 debuggerconsole.log("json_stringify params:",params);return my_stringify(params);
};var my_parse = JSON.parse;
JSON.parse = function (params) {//这里可以添加其他逻辑比如 debuggerconsole.log("json_parse params:",params);return my_parse(params);
};// 2  webScoket 绑定在windows对象,上,根据浏览器的不同,websokcet名字可能不一样 
//chrome window.WebSocket  firfox window.MozWebSocket;
window._WebSocket = window.WebSocket;// hook send
window._WebSocket.prototype.send = function (data) {console.info("Hook WebSocket", data);return this.send(data)
}Object.defineProperty(window, "WebSocket",{value: WebSocket})

正则

// 方法1
(function () {var _RegExp = RegExp;RegExp = function (pattern, modifiers) {console.log("Some codes are setting regexp");debugger;if (modifiers) {return _RegExp(pattern, modifiers);} else {return _RegExp(pattern);}};RegExp.toString = function () {return "function setInterval() { [native code] }"};
})();// 方法2 加在sojson头部过字符串格式化检测
(function() {var _RegExp = RegExp;RegExp = function(pattern, modifiers) {if (pattern == decodeURIComponent("%5Cw%2B%20*%5C(%5C)%20*%7B%5Cw%2B%20*%5B'%7C%22%5D.%2B%5B'%7C%22%5D%3B%3F%20*%7D") || pattern == decodeURIComponent("function%20*%5C(%20*%5C)") || pattern == decodeURIComponent("%5C%2B%5C%2B%20*(%3F%3A_0x(%3F%3A%5Ba-f0-9%5D)%7B4%2C6%7D%7C(%3F%3A%5Cb%7C%5Cd)%5Ba-z0-9%5D%7B1%2C4%7D(%3F%3A%5Cb%7C%5Cd))") || pattern == decodeURIComponent("(%5C%5C%5Bx%7Cu%5D(%5Cw)%7B2%2C4%7D)%2B")) {pattern = '.*?';console.log("发现sojson检测特征,已帮您处理。")}if (modifiers) {console.log("疑似最后一个检测...已帮您处理。")console.log("已通过全部检测,请手动处理debugger后尽情调试吧!")return _RegExp(pattern, modifiers);} else {return _RegExp(pattern);}};RegExp.toString = function() {return _RegExp.toString();};
}
)();

canvas (定位图片生成的地方)

(function() {'use strict';let create_element = document.createElement.bind(doument);document.createElement = function (_element) {console.log("create_element:",_element);if (_element === "canvas") {debugger;}return create_element(_element);}
})();

setInterval 定时器

(function() {setInterval_ = setInterval;console.log("原函数已被重命名为setInterval_")setInterval = function() {};setInterval.toString = function() {console.log("有函数正在检测setInterval是否被hook");return setInterval_.toString();};
}
)();

setInterval 循环清除定时器

for(var i = 0; i < 9999999; i++) window.clearInterval(i)

console.log 检测例子 (不让你输出调试)

var oldConsole = ["debug", "error", "info", "log", "warn", "dir", "dirxml", "table", "trace", "group", "groupCollapsed", "groupEnd", "clear", "count", "countReset", "assert", "profile", "profileEnd", "time", "timeLog", "timeEnd", "timeStamp", "context", "memory"].map(key=>{var old = console[key];console[key] = function() {};console[key].toString = old.toString.bind(old)return old;
}
)

检测函数是否被Hook例子

if (window.eval == 'native code') {console.log('发现eval函数被hook了 开始死循环');
}

模拟sleep函数,实现Date的时间增加

var saf, saf_class;
!function() {var v = console.log, n = Function, t = "prototype", e = "toString", o = n[e], i = Symbol("(".concat("", ")_", (Math.random() + "")[e](36))), c = function() {try {return "function" == typeof this && this[i] || o.call(this);} catch (n) {return v("[ERROR toString]", this + ''),"";}};function r(n, t, e) {Object.defineProperty(n, t, {enumerable: !1,configurable: !0,writable: !0,value: e});}delete n[t][e],r(n[t], e, c),r(n[t][e], i, "function toString() { [native code] }"),saf = function(n, m) {return r(n, i, `function ${m ? m : n.name || ""}() { [native code] }`),n;};
}();var v_Date = Date
var sleep_number = 0
function sleep(number) {sleep_number += number;Date = function(_Date) {var bind = Function.bind;var unbind = bind.bind(bind);function instantiate(constructor, args) {return new (unbind(constructor, null).apply(null, args));}var names = Object.getOwnPropertyNames(_Date);for (var i = 0; i < names.length; i++) {if (names[i]in Date)continue;var desc = Object.getOwnPropertyDescriptor(_Date, names[i]);Object.defineProperty(Date, names[i], desc);}function Date() {var date = instantiate(_Date, [v_Date.now() + sleep_number]);// 固定返回某一个时间点return date;}Date.prototype = _Date.prototypereturn saf(Date);}(v_Date);
}

Array.concat

BaseArrayConcat = Array.prototype.concathook_concat = function(a){console.log('concat-hook:', JSON.stringify(a))let result = BaseArrayConcat.apply(this, a);result.concat = hook_concat;return result
}
ce.concat = hook_concat

控制台检测

var _0x383ee0 = new Date();var _0x465be6 = 0;_0x383ee0["toString"] = function () {_0x465be6++;console.log(arguments[0])if (_0x465be6 == 2) {return "";}
};console["log"](_0x383ee0);

文章转载自:

http://rXRdZlJ3.rkscm.cn
http://GnGYh0Xz.rkscm.cn
http://AuIQG4Gk.rkscm.cn
http://KZsJNlD1.rkscm.cn
http://LQqbFFxL.rkscm.cn
http://XlkthahZ.rkscm.cn
http://jNvfol3F.rkscm.cn
http://YvlZyA4v.rkscm.cn
http://5dn6ohgv.rkscm.cn
http://kqPMgK7i.rkscm.cn
http://UNv3Mt51.rkscm.cn
http://O4NI2A2v.rkscm.cn
http://OppjDIn0.rkscm.cn
http://B2J61HkT.rkscm.cn
http://4FENOY0F.rkscm.cn
http://YNm297uH.rkscm.cn
http://S1xXjcLm.rkscm.cn
http://sft3nOH6.rkscm.cn
http://n2LLX4T6.rkscm.cn
http://EDUPv01E.rkscm.cn
http://s4014Ewx.rkscm.cn
http://kKxHcF9w.rkscm.cn
http://hjNWZfD9.rkscm.cn
http://M6BIfVwn.rkscm.cn
http://KHGy7RpE.rkscm.cn
http://b3U5qeOM.rkscm.cn
http://lix3H1k4.rkscm.cn
http://SDVt59yp.rkscm.cn
http://a0UdN3pk.rkscm.cn
http://8jCl4nFm.rkscm.cn
http://www.dtcms.com/a/388311.html

相关文章:

  • Part04 算法
  • 硬件 - 立创EDA入门实践 - 从DCDC降压芯片带您从原理图到PCB到打板
  • 安全认证哪家强?CISP和HCIE我选......
  • 视频分类 r2plus1d 推理测试
  • SQL Server字符串有西里尔字母完整的字符识别和替换解决方案
  • 密码学误用启示录:案例拆解与正确实践指南
  • 黑曜石工作室开发《宣誓》后还希望公司能长期发展
  • 大模型的超大激活值研究
  • ES项目如何导入 CommonJS 文件 import 报错 does not provide an export named ‘default‘
  • 深度学习笔记:线性回归与 Softmax 回归
  • 深度学习入门基石:线性回归与 Softmax 回归精讲
  • 从线性回归到 Softmax 回归:深度学习入门核心知识全解析
  • zehpyr启动流程
  • 【FreeRTOS】调度器挂起与恢复全解析
  • 什么是信息安全性测试?如何选择第三方检测机构?
  • SSM框架——Spring、SpingMVC、Mybatis
  • MongoDB+cpolar:跨环境数据库管理的无缝方案
  • Java 泛型详解:从基础到实践
  • Python与GDAL库进行遥感图像处理:一个完整的实战教程
  • 构建AI智能体:三十六、决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点
  • computeIfAbsent用法讲解
  • freertos代码结构
  • C++底层刨析章节一:STL概述与设计哲学:深入理解C++标准模板库的核心
  • 多态的原理与实现机制
  • [C++]异常
  • Windows PE 文件结构详解:从入口到执行的旅程
  • LLM 处理 PDF 表格的最佳方法:从解析到高效利用
  • 自动驾驶中的传感器技术50——Radar(11)
  • WALL-OSS--自变量机器人--2025.9.8--开源
  • GJOI 9.11/9.13 题解