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

frida_hook_libart(简单解释)

一:直接取代码


//frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log
//frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.logconst STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {constructor() {this.handle = Memory.alloc(STD_STRING_SIZE);}dispose() {const [data, isTiny] = this._getData();if (!isTiny) {Java.api.$delete(data);}}disposeToString() {const result = this.toString();this.dispose();return result;}toString() {const [data] = this._getData();return data.readUtf8String();}_getData() {const str = this.handle;const isTiny = (str.readU8() & 1) === 0;const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();return [data, isTiny];}
}function prettyMethod(method_id, withSignature) {const result = new StdString();Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);return result.disposeToString();
}/*
GetFieldID is at  0xe39b87c5 _ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetMethodID is at  0xe39a1a19 _ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_
NewStringUTF is at  0xe39cff25 _ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc
RegisterNatives is at  0xe39e08fd _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
GetStaticFieldID is at  0xe39c9635 _ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetStaticMethodID is at  0xe39be0ed _ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_
GetStringUTFChars is at  0xe39d06e5 _ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh
FindClass is at  0xe399ae5d _ZN3art3JNI9FindClassEP7_JNIEnvPKc
*/function hook_libart() {var symbols = Module.enumerateSymbolsSync("libart.so");var addrGetStringUTFChars = null;var addrNewStringUTF = null;var addrFindClass = null;var addrGetMethodID = null;var addrGetStaticMethodID = null;var addrGetFieldID = null;var addrGetStaticFieldID = null;var addrRegisterNatives = null;var so_name = "lib";      //TODO 这里写需要过滤的sofor (var i = 0; i < symbols.length; i++) {var symbol = symbols[i];if (symbol.name.indexOf("art") >= 0 &&symbol.name.indexOf("JNI") >= 0 &&symbol.name.indexOf("CheckJNI") < 0 &&symbol.name.indexOf("_ZN3art3JNIILb0") >= 0) {if (symbol.name.indexOf("GetStringUTFChars") >= 0) {addrGetStringUTFChars = symbol.address;console.log("GetStringUTFChars is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("NewStringUTF") >= 0) {addrNewStringUTF = symbol.address;console.log("NewStringUTF is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("FindClass") >= 0) {addrFindClass = symbol.address;console.log("FindClass is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("GetMethodID") >= 0) {addrGetMethodID = symbol.address;console.log("GetMethodID is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("GetStaticMethodID") >= 0) {addrGetStaticMethodID = symbol.address;console.log("GetStaticMethodID is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("GetFieldID") >= 0) {addrGetFieldID = symbol.address;console.log("GetFieldID is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("GetStaticFieldID") >= 0) {addrGetStaticFieldID = symbol.address;console.log("GetStaticFieldID is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("RegisterNatives") >= 0) {addrRegisterNatives = symbol.address;console.log("RegisterNatives is at ", symbol.address, symbol.name);} else if (symbol.name.indexOf("CallStatic") >= 0) {console.log("CallStatic is at ", symbol.address, symbol.name);Interceptor.attach(symbol.address, {onEnter: function (args) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var java_class = args[1];var mid = args[2];var class_name = Java.vm.tryGetEnv().getClassName(java_class);if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {var method_name = prettyMethod(mid, 1);console.log("<>CallStatic:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);}}},onLeave: function (retval) { }});} else if (symbol.name.indexOf("CallNonvirtual") >= 0) {console.log("CallNonvirtual is at ", symbol.address, symbol.name);Interceptor.attach(symbol.address, {onEnter: function (args) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var jobject = args[1];var jclass = args[2];var jmethodID = args[3];var obj_class_name = Java.vm.tryGetEnv().getObjectClassName(jobject);var class_name = Java.vm.tryGetEnv().getClassName(jclass);if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {var method_name = prettyMethod(jmethodID, 1);console.log("<>CallNonvirtual:", DebugSymbol.fromAddress(this.returnAddress), class_name, obj_class_name, method_name);}}},onLeave: function (retval) { }});} else if (symbol.name.indexOf("Call") >= 0 && symbol.name.indexOf("Method") >= 0) {console.log("Call<>Method is at ", symbol.address, symbol.name);Interceptor.attach(symbol.address, {onEnter: function (args) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var java_class = args[1];var mid = args[2];var class_name = Java.vm.tryGetEnv().getObjectClassName(java_class);if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {var method_name = prettyMethod(mid, 1);console.log("<>Call<>Method:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);}}},onLeave: function (retval) { }});}}}if (addrGetStringUTFChars != null) {Interceptor.attach(addrGetStringUTFChars, {onEnter: function (args) {},onLeave: function (retval) {if (retval != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var bytes = Memory.readCString(retval);console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress));}}}});}if (addrNewStringUTF != null) {Interceptor.attach(addrNewStringUTF, {onEnter: function (args) {if (args[1] != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var string = Memory.readCString(args[1]);console.log("[NewStringUTF] bytes:" + string, DebugSymbol.fromAddress(this.returnAddress));}}},onLeave: function (retval) { }});}if (addrFindClass != null) {Interceptor.attach(addrFindClass, {onEnter: function (args) {if (args[1] != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var name = Memory.readCString(args[1]);console.log("[FindClass] name:" + name, DebugSymbol.fromAddress(this.returnAddress));}}},onLeave: function (retval) { }});}if (addrGetMethodID != null) {Interceptor.attach(addrGetMethodID, {onEnter: function (args) {if (args[2] != null) {var clazz = args[1];var class_name = Java.vm.tryGetEnv().getClassName(clazz);var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var name = Memory.readCString(args[2]);if (args[3] != null) {var sig = Memory.readCString(args[3]);console.log("[GetMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));} else {console.log("[GetMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));}}}},onLeave: function (retval) { }});}if (addrGetStaticMethodID != null) {Interceptor.attach(addrGetStaticMethodID, {onEnter: function (args) {if (args[2] != null) {var clazz = args[1];var class_name = Java.vm.tryGetEnv().getClassName(clazz);var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var name = Memory.readCString(args[2]);if (args[3] != null) {var sig = Memory.readCString(args[3]);console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));} else {console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));}}}},onLeave: function (retval) { }});}if (addrGetFieldID != null) {Interceptor.attach(addrGetFieldID, {onEnter: function (args) {if (args[2] != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var name = Memory.readCString(args[2]);if (args[3] != null) {var sig = Memory.readCString(args[3]);console.log("[GetFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));} else {console.log("[GetFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));}}}},onLeave: function (retval) { }});}if (addrGetStaticFieldID != null) {Interceptor.attach(addrGetStaticFieldID, {onEnter: function (args) {if (args[2] != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var name = Memory.readCString(args[2]);if (args[3] != null) {var sig = Memory.readCString(args[3]);console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));} else {console.log("[GetStaticFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));}}}},onLeave: function (retval) { }});}if (addrRegisterNatives != null) {Interceptor.attach(addrRegisterNatives, {onEnter: function (args) {console.log("[RegisterNatives] method_count:", args[3], DebugSymbol.fromAddress(this.returnAddress));var env = args[0];var java_class = args[1];var class_name = Java.vm.tryGetEnv().getClassName(java_class);var methods_ptr = ptr(args[2]);var method_count = parseInt(args[3]);for (var i = 0; i < method_count; i++) {var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));var name = Memory.readCString(name_ptr);var sig = Memory.readCString(sig_ptr);var find_module = Process.findModuleByAddress(fnPtr_ptr);console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));}},onLeave: function (retval) { }});}
}setImmediate(hook_libart);

二:代码解释

Frida 脚本旨在监控 Android 应用程序中 libart.so 库的 Java Native Interface (JNI) 调用。这个脚本通过拦截特定的 JNI 方法,捕获和记录它们的调用信息,帮助我们分析应用程序的行为。

1. 脚本结构

该脚本主要分为几个部分:

  • 常量和类定义: 定义了一些常量和 StdString 类,用于处理 C++ 字符串。
  • 辅助函数: prettyMethod 函数用于获取方法的可读名称。
  • hook_libart 函数: 该函数负责查找和拦截 libart.so 中的 JNI 方法。
  • 设置和执行: 最后调用 setImmediate 执行 hook_libart 函数。

2. 关键功能

2.1 StdString
class StdString {constructor() {this.handle = Memory.alloc(STD_STRING_SIZE);}dispose() {const [data, isTiny] = this._getData();if (!isTiny) {Java.api.$delete(data);}}disposeToString() {const result = this.toString();this.dispose();return result;}toString() {const [data] = this._getData();return data.readUtf8String();}_getData() {const str = this.handle;const isTiny = (str.readU8() & 1) === 0;const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();return [data, isTiny];}
}
  • 功能: 这个类用于处理 C++ 中的 std::string 对象,提供了字符串的读取和释放方法。
  • 内存管理: dispose 方法负责释放非小字符串的内存。
2.2 prettyMethod 函数
function prettyMethod(method_id, withSignature) {const result = new StdString();Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);return result.disposeToString();
}
  • 功能: 该函数调用 art::ArtMethod::PrettyMethod 获取方法的可读名称,便于后续的日志记录。
2.3 hook_libart 函数

这是脚本的核心部分,负责查找和拦截多个 JNI 方法:

  • 符号查找: 使用 Module.enumerateSymbolsSync 获取 libart.so 中的符号,并根据名称过滤出相关的 JNI 方法。

  • 拦截 JNI 方法: 对于每个找到的 JNI 方法,通过 Interceptor.attach 进行拦截,记录方法调用的详细信息。

例如,拦截 GetStringUTFChars 方法:

if (addrGetStringUTFChars != null) {Interceptor.attach(addrGetStringUTFChars, {onEnter: function (args) {},onLeave: function (retval) {if (retval != null) {var module = Process.findModuleByAddress(this.returnAddress);if (module != null && module.name.indexOf(so_name) == 0) {var bytes = Memory.readCString(retval);console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress));}}}});
}
  • 参数和返回值处理: 在 onEnteronLeave 中处理方法的参数和返回值,记录相关信息。

3. 监控的 JNI 方法

该脚本监控了多个 JNI 方法,包括:

  • GetFieldID
  • GetMethodID
  • GetStaticFieldID
  • GetStaticMethodID
  • RegisterNatives
  • NewStringUTF
  • GetStringUTFChars
  • FindClass
  • CallStatic
  • CallNonvirtual

这些方法的调用通常涉及 Java 和 C/C++ 之间的交互,监控这些方法可以帮助分析应用程序的行为,尤其是在涉及 JNI 的部分。

4. 使用方法

要使用这个脚本,需要:

  1. 安装 Frida: 确保您已经在设备上安装了 Frida Server,并且在我们的计算机上安装了 Frida 工具。

  2. 运行命令: 使用如下命令运行脚本:

    frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log
    frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.log
    

    这里 -U 表示连接到 USB 设备,-f 指定要启动的应用程序,-l 指定要加载的脚本,-o 指定输出日志文件。

5. 总结

这个 Frida 脚本是一个强大的工具,用于监控 Android 应用程序中的 JNI 调用。通过拦截 libart.so 中的关键方法,可以深入了解 Java 和本地代码之间的交互,帮助进行调试、分析和安全研究。脚本的灵活性和可扩展性使其能够适应多种分析需求。

相关文章:

frida_hook_libart(简单解释)

一&#xff1a;直接取代码 //frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log //frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.logconst STD_STRING_SIZE 3 * Process.pointerSize; class StdString {constructor() {this.handle Memory.alloc(STD_S…...

计算机网络八股整理(二)

计算机网络八股整理&#xff08;二&#xff09; 应用层 1&#xff1a;dns的全称了解过吗&#xff1f; dns全称domain-name-system&#xff0c;翻译过来就是域名系统&#xff0c;是在计算机网络中将域名转换成ip地址的分布式数据库系统&#xff1b; 域名服务器的层级类似一个树…...

强化学习off-policy进化之路(PPO->DPO->KTO->ODPO->ORPO->simPO)

需要LLM在训练过程中做生成的方法是 On Policy&#xff0c;其余的为Off Policy。 On Policy是包含了反馈机制&#xff0c;Off Policy不包含反馈机制。 若进行环境交互的模型与被更新的模型是相同的模型&#xff0c;通常这种更新策略被称为on-policy的策略。on-policy的方法会有…...

Linux 如何创建逻辑卷并使用

一、逻辑卷的介绍 生成环境中逻辑卷使用率很高 逻辑卷的诞生&#xff1a;如果对磁盘直接使用fdisk分区&#xff0c;那么这中分区&#xff0c;我们叫做Linux的标准分区&#xff0c;Linux的标准分区格式化成文件系统之后&#xff0c;挂载使用&#xff0c;那么一旦文件系统的空间…...

java实现将图片插入word文档

插入图片所用依赖 private static void insertImage(XWPFDocument document, String path) {List<XWPFParagraph> paragraphs document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {CTP ctp paragraph.getCTP();for (int dwI 0; dwI < ctp.sizeO…...

初识java(3)

大家好&#xff0c;今天我们来讲讲我们的老伙计-变量&#xff0c;在哪一门编程语言中&#xff0c;变量的作用都是不可或缺的&#xff0c;那么下面我们就来详细了解一下java中的变量。 一.变量概念 在程序中&#xff0c;除了有始终不变的常量外&#xff0c;有些内容可能会经常…...

coqui-ai TTS 初步使用

项目地址&#xff1a;https://github.com/coqui-ai/TTS 1. 创建一个新的conda环境&#xff0c;如果自己会管理python环境也可以用其他方法 克隆项目下来 pip install -r requirements.txt # 安装依赖 pip install coqui-tts # 只要命令行工具的话 下载自己想要的模型 …...

matlab代码--卷积神经网络的手写数字识别

1.cnn介绍 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;是一种深度学习的算法&#xff0c;在图像和视频识别、图像分类、自然语言处理等领域有着广泛的应用。CNN的基本结构包括输入层、卷积层、池化层&#xff08;Pooling Layer&#xff09;、全连…...

Scala—Map用法详解

Scala—Map用法详解 在 Scala 中&#xff0c;Map 是一种键值对的集合&#xff0c;其中每个键都是唯一的。Scala 提供了两种类型的 Map&#xff1a;不可变 Map 和可变 Map。 1. 不可变集合&#xff08;Map&#xff09; 不可变 Map 是默认的 Map 实现&#xff0c;位于 scala.co…...

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【六】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

ES6 、ESNext 规范、编译工具babel

ES6 、ESNext 规范、编译工具简介 ES6ES&#xff08;ECMAScript&#xff09; vs JS常量进一步探讨 obj对象的扩展面试&#xff1a;使对象属性也不能更改——Object.freeze(obj) 解构deconstruction变量的解构赋值&#xff1a;数组解构赋值&#xff1a;对象解构赋值&#xff1a;…...

DeepSpeed 配置文件(DeepSpeed Configuration Files)详解:中英文解释

中文版 本文详细介绍 DeepSpeed 配置文件&#xff0c;结合 4 卡 3090 的实际使用场景&#xff0c;重点解释各个参数的含义&#xff0c;并提供应对爆显存的方案。 DeepSpeed 配置文件详解&#xff1a;从基础到实战 DeepSpeed 是用于加速大规模分布式训练的重要工具&#xff0c…...

前端JavaScript(一)---基本介绍

Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的客户端脚本语言&#xff0c;主要目的是为了解决服务器端语言&#xff0c;比如Perl&#xff0c;遗留的速度问题&#xff0c;为客户提供更流畅的浏览效果。当时服务端需要对…...

文本处理之sed

1、概述 sed是文本编辑器&#xff0c;作用是对文本的内容进行增删改查。 和vim不一样&#xff0c;sed是按行进行处理。 sed一次处理一行内容&#xff0c;处理完一行之后紧接着处理下一行&#xff0c;一直到文件的末尾 模式空间&#xff1a;临时储存&#xff0c;修改的结果临…...

uniapp在App端定义全局弹窗,当打开关闭弹窗会触发onShow、onHide生命周期怎么解决?

在uniapp(App端)中实现自定义弹框&#xff0c;可以通过创建一个透明页面来实现。点击进入当前页面时&#xff0c;页面背景会变透明&#xff0c;用户可以根据自己的需求进行自定义&#xff0c;最终效果类似于弹框。 遇到问题&#xff1a;当打开弹窗(进入弹窗页面)就会触发当前页…...

计算机网络 实验七 NAT配置实验

一、实验目的 通过本实验理解网络地址转换的原理和技术&#xff0c;掌握扩展NAT/NAPT设计、配置和测试。 二、实验原理 NAT配置实验的原理主要基于网络地址转换&#xff08;NAT&#xff09;技术&#xff0c;该技术用于将内部私有网络地址转换为外部公有网络地址&#xff0c;从…...

数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!

文章目录 前言一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本 快排1.2.2 挖坑法 快排1.2.3 lomuto前后指针 快排 二、归并排序总结 前言 继上篇学习了排序的前面两个部分:直接插入排序和选择排序 今天我们来学习排序中常用的交换排序以及非常稳定的归并排序 快排可是有多…...

【kafka04】消息队列与微服务之Kafka 图形工具

Kafka 在 ZooKeeper 里面的存储结构 topic 结构 /brokers/topics/[topic] partition结构 /brokers/topics/[topic]/partitions/[partitionId]/state broker信息 /brokers/ids/[o...N] 控制器 /controller 存储center controller中央控制器所在kafka broker的信息 消费者 /c…...

剖析前后端 API 接口参数设计:JSON 数据结构化全攻略

在当今软件开发领域&#xff0c;前后端分离架构已成为主流趋势。而 API 接口作为前后端之间数据交互的桥梁&#xff0c;其设计的合理性对系统的可维护性和扩展性起着至关重要的作用。JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&…...

vue3 多种方式接受props,定义ref,reactive

定义props 1 第一种 interface AddType { dialogStudyVisible: boolean; } const props defineProps<AddType>(); 第二种 // const props defineProps({ // dialogStudyVisible:{ // type:Boolean, // default:false // } // }) 第三种 // const …...

从MWC 2016看5G与物联网:技术演进、产业博弈与生态构建

1. 从巴塞罗那看2016年移动通信的十字路口 时间回到2016年初&#xff0c;如果你身处通信行业&#xff0c;那么2月底的日程表上&#xff0c;巴塞罗那的“移动世界大会”绝对是一个绕不开的焦点。那不是一个普通的展会&#xff0c;更像是一个行业在技术迭代、市场转型和地缘政治多…...

告别臃肿!Dell G15笔记本散热控制的轻量级开源替代方案

告别臃肿&#xff01;Dell G15笔记本散热控制的轻量级开源替代方案 【免费下载链接】tcc-g15 Thermal Control Center for Dell G15 - open source alternative to AWCC 项目地址: https://gitcode.com/gh_mirrors/tc/tcc-g15 你是否厌倦了Dell原厂AWCC软件的缓慢响应和…...

从行业会议议程到个人技能地图:嵌入式工程师系统化成长指南

1. 从行业盛会到个人技能地图&#xff1a;如何将MASTERs会议的精髓转化为你的嵌入式成长引擎又到了一年一度技术人“充电”的季节。如果你在工业自动化、电机控制或者机器人领域深耕&#xff0c;那么对Microchip Technology这家公司及其产品线一定不会陌生。每年夏天&#xff0…...

Awesome-Robotics-3D:机器人3D视觉资源精选与高效利用指南

1. 项目概述&#xff1a;一个机器人学3D视觉的“藏宝图” 如果你正在机器人、自动驾驶或者三维感知领域摸爬滚打&#xff0c;并且时常为了找一个靠谱的开源实现、一篇奠基性的论文&#xff0c;或者一个高质量的数据集而翻遍GitHub、arXiv和各大实验室主页&#xff0c;那么你很可…...

开关电源传导共模噪声抑制:Y电容原理、安规限制与EMI滤波器设计

1. 项目概述&#xff1a;理解隔离式开关电源中的传导共模噪声在开发离线式开关电源&#xff0c;比如我们常见的手机充电器、笔记本电脑适配器或者工业电源模块时&#xff0c;工程师们常常会遇到一个既棘手又必须解决的难题&#xff1a;传导电磁干扰&#xff08;Conducted EMI&a…...

GTA5线上小助手:终极免费工具完整使用指南,快速提升游戏体验

GTA5线上小助手&#xff1a;终极免费工具完整使用指南&#xff0c;快速提升游戏体验 【免费下载链接】GTA5OnlineTools GTA5线上小助手 项目地址: https://gitcode.com/gh_mirrors/gt/GTA5OnlineTools 想要在《侠盗猎车手5》线上模式中摆脱繁琐操作&#xff0c;享受更流…...

别再默认用E1000了!VMware虚拟机网卡选VMXNET3还是E1000E?实测数据告诉你答案

VMware虚拟机网卡性能实战&#xff1a;从理论到选型决策树 在虚拟化环境中&#xff0c;网络性能往往是决定整体系统效率的关键瓶颈之一。作为一名长期奋战在VMware运维一线的技术专家&#xff0c;我见过太多因为网卡选型不当导致的性能问题——从莫名其妙的网络延迟到令人抓狂的…...

3步完成微信聊天记录永久备份:开源工具WeChatExporter终极指南

3步完成微信聊天记录永久备份&#xff1a;开源工具WeChatExporter终极指南 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter WeChatExporter是一款专为Mac用户设计的开源工…...

AI驱动BI分析:MCP协议与Metabase助手实战指南

1. 项目概述&#xff1a;当AI助手成为你的BI分析师如果你和我一样&#xff0c;每天都要和Metabase打交道&#xff0c;那你肯定经历过这样的场景&#xff1a;业务同事跑过来问&#xff0c;“能不能帮我拉一下上个月每个渠道的转化率&#xff1f;”&#xff0c;或者产品经理说&am…...

WinDirStat插件开发终极指南:构建自定义磁盘管理功能

WinDirStat插件开发终极指南&#xff1a;构建自定义磁盘管理功能 【免费下载链接】windirstat WinDirStat is a disk usage statistics viewer and cleanup tool for Microsoft Windows 项目地址: https://gitcode.com/gh_mirrors/wi/windirstat 作为Windows平台最知名的…...