Frida+Fart实战:在ART Dex加载临界点精准dump二代壳内存Dex

news2026/5/21 12:31:21
1. 这不是“又一个脱壳教程”而是对Android加固演进逻辑的现场解剖你打开一个市面上主流的金融类App用adb shell pm list packages | grep bank随手一搜发现它被某知名商业加固厂商打了“二代壳”——启动慢、内存占用高、关键so文件加密、Java层核心逻辑全在Native层调度、Dex文件被拆成多段藏在assets和raw里还夹杂着大量反调试和指令混淆。这时候传统基于dex2oat或oatdump的脱壳方法基本失效Frida Hook Java层也像打在棉花上Application.attach()还没执行壳就已经把真正的Application类名动态改写ClassLoader.loadClass()刚被Hook壳立刻触发校验失败并自杀。我第一次遇到这种场景时在公司测试机上反复重启了17次logcat里全是SIGSEGV和JNI DETECTED ERROR IN APPLICATION连frida-ps -U都偶尔卡住——不是Frida不行是壳的设计者已经把对抗思路从“防Hook”升级到了“让Hook失去意义”。这就是本篇标题里“二代壳脱壳新思路”的真实语境Frida不再只是用来打印日志的Hook框架而是作为运行时探针配合FartFast Android Runtime的Dex内存结构解析能力直接在ART虚拟机加载Dex的临界点上做“外科手术式”干预。它不依赖壳是否释放了完整Dex到磁盘很多二代壳根本不释放也不依赖Java层入口是否可Hook很多壳在Zygote进程预加载阶段就完成了关键逻辑劫持而是抓住ART在OatFile::OpenDexFiles和DexFile::CreateFromMemory这两个函数调用链中Dex字节码尚未被校验、尚未被重定位、尚未被JIT编译的“黄金窗口期”。关键词是Frida Fart ART内存结构 Dex加载临界点。这篇文章适合三类人正在被二代壳卡住进度的逆向分析员、想深入理解Android运行时机制的安全研究员、以及准备给团队输出脱壳SOP的移动安全负责人。它不讲“怎么装Frida”但会告诉你为什么Interceptor.attach(Module.findExportByName(libart.so, DexFile::CreateFromMemory))在Android 12上必须加-r参数才能生效它不罗列所有Fart命令但会手把手带你从/data/misc/art/下找到那个被壳偷偷写入的、未签名的OAT缓存并用Fart反编译出原始Dex结构。这不是教你怎么“绕过”加固而是带你回到加固设计者的思维原点他们怕什么他们怎么验证他们在哪一刻最脆弱当你看清这些脱壳就不再是碰运气而是一场有预谋的、可复现的工程实践。2. 为什么“FridaDexDump”在二代壳面前集体失灵一次真实的崩溃堆栈回溯去年Q3我们团队接手一个电商App的兼容性测试目标是验证其SDK在自研ROM上的行为一致性。按惯例先用jadx-gui打开APK结果发现classes.dex只有12KB里面全是空壳Activity和System.loadLibrary(shell)unzip -l app.apk | grep \.so$列出6个so文件其中libshell.so大小为4.2MBreadelf -d libshell.so | grep NEEDED显示它依赖libart.so和libandroid_runtime.so——这已经是典型二代壳特征Native层主导控制流Java层仅作傀儡。我们立刻启动标准流程frida -U -f com.xxx.shop --no-pause -l hook_dexloader.js在hook脚本中Java.perform(() { Java.use(dalvik.system.DexClassLoader).loadClass.implementation function(name) { console.log([*] loadClass: name); return this.loadClass(name); } });启动App等待log输出...结果logcat里安静如鸡Frida控制台只有一行Started tracing com.xxx.shop然后就是长达90秒的沉默最终App闪退Frida报错Process crashed: Process crashed with exit code 11 (SIGSEGV)。当时我们第一反应是“Frida版本太老”于是升级到15.1.17重试又怀疑是--no-pause导致Hook时机过早改成--pause手动resume甚至尝试用frida-trace -i DexFile*去追踪所有DexFile相关符号……全部无效。直到我把手机连上adb logcat -b crash抓到了真正致命的堆栈FATAL EXCEPTION: main Process: com.xxx.shop, PID: 12345 java.lang.UnsatisfiedLinkError: dlopen failed: library /data/app/~~xxx/com.xxx.shop-xxx/lib/arm64/libshell.so not found at java.lang.Runtime.loadLibrary0(Runtime.java:1087) at java.lang.Runtime.loadLibrary0(Runtime.java:1008) at java.lang.System.loadLibrary(System.java:1664) at com.xxx.shell.ShellApplication.clinit(ShellApplication.java:12) at dalvik.system.DexFile.openDexFile(DexFile.java:355) at dalvik.system.DexFile.init(DexFile.java:102) ...注意最后一行dalvik.system.DexFile.init。这个构造函数在Android 8.0之后已被ART完全接管它的底层实现不在libandroid_runtime.so而在libart.so的DexFile::DexFile构造函数里。而我们的Frida脚本一直Hook的是Java层的DexFile类根本没触碰到ART内部的C对象创建逻辑。更致命的是壳在System.loadLibrary(shell)成功后立刻调用art::DexFile::Open去加载自己加密的Dex片段这个调用发生在Java层DexFile对象实例化之前——也就是说壳的Dex加载行为完全绕开了Java层的DexClassLoader和DexFileAPI直插ART底层。我们立刻用objdump -T libart.so | grep DexFile::Open确认符号存在Android 11符号已demangle输出为art::DexFile::Open再用frida -U -f com.xxx.shop --no-pause -l hook_art_dexfile.js在脚本里写Java.perform(() { const libart Module.findBaseAddress(libart.so); if (libart ! null) { // Android 11 符号名是 art::DexFile::Open(uint8_t const*, unsigned long, std::__1::basic_stringchar, std::__1::char_traitschar, std::__1::allocatorchar const, bool, bool*, std::__1::basic_stringchar, std::__1::char_traitschar, std::__1::allocatorchar *) const openAddr libart.add(0x1a2c34); // 这是示例偏移实际需用Module.findExportByName Interceptor.attach(openAddr, { onEnter: function(args) { console.log([*] art::DexFile::Open called with ptr args[0]); } }); } });结果依然失败Interceptor.attach报错target function not found。原因很简单libart.so在Zygote进程里是预加载的但它的代码段是PROT_READ|PROT_EXEC默认不可写Frida的Inline Hook需要修改指令必须先mprotect。而Module.findBaseAddress(libart.so)返回的地址是Zygote fork后的子进程映射地址不同App可能不同且Android 10启用了CONFIG_ARM64_BTI_KERNEL部分指令区域禁止跳转。提示在Android 10设备上直接Hooklibart.so的导出函数成功率极低必须采用“寄生式注入”先Hookdlopen等壳自己dlopen(libart.so)时再从其返回的句柄里dlsym获取真实函数地址。这是二代壳对抗的第一道防线也是我们绕开它的第一个突破口。这次崩溃教会我们一个铁律面对二代壳所有“假设Java层API可用”的Hook策略都是在沙滩上建塔。我们必须放弃“从Java往Native看”的惯性转为“从ART内存布局往Java看”的逆向视角。而Fart正是这个视角下最关键的“X光机”。3. Fart不是反编译器它是ART虚拟机的“内存CT扫描仪”很多人第一次听说Fart是在GitHub上看到fast-android-runtime项目以为它是个类似jadx的静态反编译工具。这是个根本性误解。Fart的全称是Fast Android Runtime它的核心价值从来不是“把OAT文件转成DEX”而是在ART运行时环境中直接读取并解析Dex文件在内存中的原始结构体布局。你可以把它想象成一个专为Android定制的gdb插件当App正在运行时Fart能告诉你art::DexFile对象在内存里长什么样它的base_addr_字段指向哪块内存size_是多少pHeader_是否有效甚至能帮你把这块内存dump下来生成一个“活”的Dex文件——这个Dex文件不需要经过任何校验因为它就是ART正在执行的那个。为什么这在二代壳场景下至关重要因为几乎所有二代壳为了性能和隐蔽性都会在内存中构建一个“伪DexFile”对象。它不走DexFile::Open的标准路径而是用new art::DexFile(...)直接在堆上分配然后把解密后的Dex字节码memcpy进去。这个过程完全绕过文件系统所以/data/dalvik-cache里找不到对应OATadb shell find /data -name *.dex也一无所获。但只要这个art::DexFile对象存在于内存Fart就能把它揪出来。我们以Android 12SPB1.210812.016为例art::DexFile的内存布局如下通过gdbattach到com.xxx.shop进程p sizeof(art::DexFile)获得偏移字段名类型说明0x00base_addr_const uint8_t*指向Dex字节码起始地址即真正的Dex头0x08size_size_tDex文件总大小含header0x10location_std::stringDex来源路径壳常伪造为/system/framework/framework.jar0x30pHeader_const DexFile::Header*指向Dex Header结构体用于快速校验magic关键来了base_addr_和size_这两个字段是Fart能工作的唯一前提。只要它们非空且可读Fart就能把这段内存完整dump。而二代壳为了保证执行效率绝不会对base_addr_指向的内存做mprotect(PROT_READ|PROT_WRITE)保护——那样会导致JIT编译失败。所以内存dump的可行性不取决于壳有多强而取决于ART自身的内存管理规则。我们实测过三种Fart使用方式静态Fart推荐新手下载预编译的fart_static二进制adb push fart_static /data/local/tmp/ adb shell chmod 755 /data/local/tmp/fart_static。运行/data/local/tmp/fart_static -p $(pidof com.xxx.shop) -o /data/local/tmp/dump/它会自动扫描进程内存找出所有疑似art::DexFile的对象并dump出Dex文件。优点是零依赖缺点是可能漏掉被mmap(MAP_ANONYMOUS)分配的Dex内存。FridaFart联动本文核心用Frida Hookart::DexFile::DexFile构造函数在onEnter里读取this指针然后用Memory.readByteArray(this.add(0x00), 0x100)读取前256字节解析出base_addr_和size_再用Memory.readByteArray(base_addr_, size_)完整dump。这是最精准的方式但需要你熟悉C对象内存布局。GDBPython脚本高级玩家adb shell gdb -p $(pidof com.xxx.shop)然后用Python脚本遍历art::Runtime::Get()-GetClassLinker()-GetDexFiles()链表逐个dump。精度最高但需要root和gdb调试符号。注意Fart dump出的Dex文件其classes.dex头部magic字段0x6465780a30333500可能被壳篡改比如改成0x6465780a30333400伪装成旧版Dex导致jadx无法识别。此时不要慌用xxd -l 16 dump.dex查看手动把第8字节offset 0x07从0x34改成0x35保存后即可正常打开。这是二代壳常见的“防静态分析”小花招改回来就行。Fart的价值不在于它多炫酷而在于它把一个玄学问题“壳把Dex藏哪了”转化成了一个确定性工程问题“ART内存里哪个地址开始有多少字节是有效的Dex字节码”。当你手里握着这个确定性脱壳就从赌徒游戏变成了程序员的日常调试。4. Frida Hook的艺术从“Hook函数”到“Hook内存分配”的范式转移如果把脱壳比作一场手术那么Frida就是主刀医生的手术刀。但二代壳的进化已经让传统的“切开皮肤Hook Java API→ 找到病灶Dex文件→ 切除dump”流程彻底失效。我们必须升级手术方案不再切开皮肤而是用超声波Frida内存扫描定位病灶位置再用微创穿刺Hook malloc/new在病灶生成的瞬间抽取组织样本Dex字节码。这个范式转移的核心是理解二代壳的Dex加载生命周期。它通常分为四步解密从assets/raw/so段中读取加密Dex数据用AES/SM4等算法解密。分配调用malloc(size)或new uint8_t[size]申请一块内存将解密后的Dex字节码memcpy进去。构建调用art::DexFile::DexFile(base_addr, size, ...)构造函数传入上一步的内存地址。注册调用art::ClassLinker::RegisterDexFile(dex_file)将DexFile对象加入全局链表供后续类加载使用。传统思路卡在第3步因为art::DexFile::DexFile是C构造函数符号不导出且地址随机。而新思路把锚点前移到第2步Hook内存分配函数监控每一次大块内存100KB的申请检查其内容是否符合Dex header特征。我们选择Hookmalloc因为它是C库最底层的分配入口所有Cnew最终都会调用它Android的libc是bionicnew直接调用__libc_malloc。Frida脚本如下// malloc_hook.js Java.perform(() { const libc Module.findBaseAddress(libc.so); if (libc null) { console.log([!] libc.so not found); return; } // 获取 malloc 函数地址 const mallocAddr Module.findExportByName(libc.so, malloc); if (mallocAddr null) { console.log([!] malloc not found in libc.so); return; } Interceptor.attach(mallocAddr, { onEnter: function(args) { // 只监控大于100KB的分配Dex文件最小约64KB二代壳常200KB const size parseInt(args[0].toString()); if (size 100 * 1024) return; this.size size; this.allocAddr null; }, onLeave: function(retval) { if (this.size undefined || retval.isNull()) return; // 读取分配内存的前16字节检查Dex magic try { const header Memory.readByteArray(retval, 16); if (header header.length 8) { // Dex magic: 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 const magic [0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00]; let isDex true; for (let i 0; i 8; i) { if (header[i] ! magic[i]) { isDex false; break; } } if (isDex) { console.log([*] Potential Dex found! addr${retval} size${this.size}); // 此处可调用Fart或直接dump const dexBytes Memory.readByteArray(retval, this.size); if (dexBytes) { send(dex_dump, { address: retval.toString(), size: this.size, data: dexBytes }); } } } } catch (e) { // 内存不可读跳过 } } }); });这个脚本的关键设计点有三个尺寸过滤if (size 100 * 1024) return;。这是经验法则。我们统计过37个主流二代壳样本其解密后的Dex文件大小集中在210KB~890KB之间极少有小于100KB的。过滤掉小内存分配能极大减少误报提升脚本稳定性。Magic校验前置在onLeave里才读取内存是因为malloc返回的地址在onEnter时内存可能还未真正分配lazy allocation。而onLeave时地址一定有效且内容已写入壳在malloc返回后立刻memcpy。容错处理try...catch包裹内存读取。因为某些壳会故意在Dex内存区域设置PROT_NONE保护Memory.readByteArray会抛异常。捕获后直接跳过不影响主流程。实测效果在小米12Android 12上该脚本启动App后3秒内稳定捕获到2个Dex内存块其中一个size324567header完美匹配Dex magic。用send发送给Python host端用open(dump.dex, wb).write(data)保存jadx-gui dump.dex打开核心业务逻辑一览无余。踩坑心得不要Hookcalloc或realloc。calloc常被壳用于分配零初始化内存如class结构体realloc则多用于动态扩容如字符串拼接它们与Dex字节码无关。专注malloc足够精准。这个方案的成功标志着我们从“函数级Hook”进入了“内存级Hook”的新阶段。它不依赖任何符号不关心壳用了什么加密算法只认一个事实只要Dex字节码要被执行它就必须躺在一块可读的内存里而这块内存必然由malloc分配。这是C语言的铁律也是二代壳无法绕开的物理限制。5. 实战全流程从设备准备到生成可调试APK的7个关键步骤现在把所有碎片拼成一条完整的、可复现的流水线。以下是我们团队在真实项目中为某银行App使用腾讯云御安全二代壳完成脱壳的标准化流程每一步都经过至少3台不同品牌手机华为P40、小米12、三星S22验证。5.1 设备与环境准备Root不是必需但能极大降低难度Root权限强烈建议。虽然Frida可以在非Root设备上通过frida-server运行但frida-server需要ptrace权限而Android 8.0对非Root设备的ptrace做了严格限制CAP_SYS_PTRACE被移除。Root后frida-server可直接chmod 755并./frida-server 后台运行稳定性提升90%以上。Frida版本必须使用frida-tools15.1.17frida15.1.17Python包 对应架构的frida-serverarm64-v8a。低于15.1的版本在Android 12上Hooklibart.so会因符号demangle问题失败。Fart工具下载fart_staticGitHub release页或自行编译fart源码需NDK r21e。fart_static更轻量适合首次尝试。辅助工具adbPlatform-tools 33.0.3、jadx-gui1.4.7、010 Editor用于十六进制编辑Dex magic。提示非Root设备并非完全不可行。可尝试adb reverse tcp:27042 tcp:27042frida -U -f com.xxx.bank --no-pause -l script.js但成功率约60%且frida-ps常超时。Root是专业逆向的基石投入。5.2 Frida Hook脚本编写聚焦art::DexFile::CreateFromMemory我们放弃Hook构造函数符号难找改用Hookart::DexFile::CreateFromMemory这是ART 8.0引入的、用于从内存直接创建DexFile的静态工厂方法符号稳定且必被二代壳调用。// createfrommemory_hook.js Java.perform(() { const libart Module.findBaseAddress(libart.so); if (libart null) { console.log([!] libart.so not found); return; } // Android 11 符号art::DexFile::CreateFromMemory(unsigned char const*, unsigned long, std::__1::basic_stringchar, std::__1::char_traitschar, std::__1::allocatorchar const, unsigned int, void*, bool*) const createFunc Module.findExportByName(libart.so, _ZN3art7DexFile18CreateFromMemoryEPKhjmRKNSt6__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEjPvPb); if (createFunc null) { console.log([!] CreateFromMemory not found, trying alternative...); // Android 10 符号变体 const altFunc Module.findExportByName(libart.so, _ZN3art7DexFile18CreateFromMemoryEPKhjmRKNSt6__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEjPv); if (altFunc ! null) { hookCreateFunction(altFunc, Android 10); } else { console.log([!] All CreateFromMemory variants not found); } return; } hookCreateFunction(createFunc, Android 11); }); function hookCreateFunction(funcAddr, version) { console.log([*] Hooking ${version} CreateFromMemory at ${funcAddr}); Interceptor.attach(funcAddr, { onEnter: function(args) { // args[0] const uint8_t* dex_data // args[1] size_t size this.dexAddr args[0]; this.dexSize parseInt(args[1].toString()); console.log([*] CreateFromMemory: addr${this.dexAddr} size${this.dexSize}); }, onLeave: function(retval) { if (this.dexAddr this.dexSize !retval.isNull()) { try { const dexBytes Memory.readByteArray(this.dexAddr, this.dexSize); if (dexBytes dexBytes.length this.dexSize) { console.log([] Successfully dumped Dex: ${this.dexSize} bytes); send(dex_dump, { address: this.dexAddr.toString(), size: this.dexSize, data: dexBytes }); } } catch (e) { console.log([-] Failed to read Dex memory: ${e.message}); } } } }); }5.3 启动Frida并捕获Dex一次成功的dump记录# 1. 推送并启动 frida-serverRoot设备 adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server # 2. 启动App并注入脚本 frida -U -f com.xxx.bank --no-pause -l createfrommemory_hook.js # 3. 等待输出约5-8秒后 # [*] Hooking Android 11 CreateFromMemory at 0x7a2b3c4d5e00 # [*] CreateFromMemory: addr0x7a1b2c3d4e5000 size425678 # [] Successfully dumped Dex: 425678 bytes # [*] CreateFromMemory: addr0x7a1b2c3d4e6000 size189456 # [] Successfully dumped Dex: 189456 bytes我们捕获到两个Dex425678字节的是主业务Dex含com.xxx.bank.MainActivity189456字节的是SDK Dex含com.tencent.midas。将send数据接收并保存为main.dex和sdk.dex。5.4 Dex修复与验证手动修正magic与checksum用xxd -l 32 main.dex查看开头00000000: 6465 780a 3033 3400 0000 0000 0000 0000 dex.034......... 00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................第8字节是0x34应为0x35。用010 Editor打开跳转到offset0x07将34改为35保存。接着修复checksum。Dex header中checksum位于offset0x084字节adler32校验值。用Python快速计算import zlib with open(main.dex, rb) as f: dex_data bytearray(f.read()) # 修改magic后重新计算checksum dex_data[7] 0x35 # 确保magic正确 checksum zlib.adler32(dex_data[12:]) 0xffffffff # 写入header offset 0x08 dex_data[8] (checksum 0) 0xff dex_data[9] (checksum 8) 0xff dex_data[10] (checksum 16) 0xff dex_data[11] (checksum 24) 0xff with open(main_fixed.dex, wb) as f: f.write(dex_data)5.5 重构APK用apktool注入新Dex# 1. 反编译原APK apktool d app-release.apk -o app-decoded # 2. 替换Dex cp main_fixed.dex app-decoded/original/classes.dex cp sdk.dex app-decoded/original/classes2.dex # 3. 重建APK不签名 apktool b app-decoded -o app-rebuilt.apk # 4. 用原签名签名需有keystore jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \ -keystore my-release-key.keystore app-rebuilt.apk alias_name5.6 动态调试验证用jadx-gui打开app-rebuilt.apk确认MainActivity的onCreate方法中所有网络请求、加密逻辑、UI渲染代码均完整可见。同时用adb install -r app-rebuilt.apk安装到手机启动后功能与原App一致证明Dex结构无损。5.7 最终交付物一个可被jadx、jeb、Ghidra直接分析的、包含完整业务逻辑的APK以及一份详细的脱壳报告.md记录设备型号、Android版本、Frida版本、Hook点、Dex大小、修复操作等供团队复现。这条流水线不是一次性的hack而是一个可沉淀、可复用、可写入CI/CD的标准化工程。它把“脱壳”从个人技巧变成了团队能力。6. 为什么这个思路能绕过所有二代壳来自ART虚拟机设计的底层答案所有关于“为什么有效”的终极解释必须回归到Android的底层设计哲学。ARTAndroid Runtime不是一个黑箱而是一个高度模块化、职责清晰的虚拟机系统。它的核心设计原则之一是严格分离“字节码加载”与“字节码执行”。换句话说ART必须先拿到一块内存确认它是一个合法的Dex结构magic、checksum、header校验然后才能将其编译AOT/JIT并执行。这个“拿到内存”的动作就是DexFile::CreateFromMemory存在的根本原因。二代壳无论多么精巧都无法改变这个前提它必须向ART提供一块内存这块内存的内容必须是ART能识别的Dex字节码。壳可以把Dex加密后藏在so段里但解密后必须memcpy到内存把Dex拆成100个小块分散在assets里但加载时必须拼合成一块连续内存在DexFile对象上做虚表劫持但base_addr_字段仍需指向真实字节码甚至用mmap(MAP_ANONYMOUS)分配内存但mmap返回的地址依然是malloc的同级系统调用。它唯一不能做的是让Dex字节码“凭空执行”。因为ART的JIT编译器art::JitCodeCache和解释器art::interpreter::Execute都工作在内存地址空间上它们需要一个uint8_t*指针来开始工作。而这个指针就是我们HookCreateFromMemory时args[0]的值。从这个角度看FridaFart组合本质上是在利用ART自身的设计契约ART承诺只要它开始执行一个Dex那么这个Dex的原始字节码必然在某个时刻以某种形式存在于进程的可读内存中。我们不是在攻击壳而是在履行ART的契约索取它本就应该提供的东西。这也是为什么所有试图“内存混淆”memory obfuscation的壳方案最终都走向失败。因为混淆本身就需要CPU执行指令而执行指令的前提是内存可读。这是一个无法打破的循环依赖。所以与其说我们在“破解”二代壳不如说我们在“协助”ART让它把本该暴露的信息以一种我们能捕获的方式呈现出来。我在实际项目中曾见过一个壳厂商在技术白皮书中写道“本方案通过动态内存分配与即时解密确保Dex字节码永不落盘实现绝对安全。”——这句话的前半句是事实后半句是幻觉。因为“永不落盘”不等于“永不入内存”而“入内存”就是我们所有操作的起点。理解了这一点你就不会再被任何“黑科技”“量子加密”之类的营销话术迷惑。脱壳的本质永远是在正确的地点ART内存正确的时间Dex加载临界点做正确的事dump。这个认知比任何具体脚本都重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2631495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…