news 2026/10/23 2:01:23

Windows DLL劫持检测:路径判定、签名校验与告警降噪

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Windows DLL劫持检测:路径判定、签名校验与告警降噪

简介:dll_hijack_detect 是一份面向 Windows 安全研究、渗透测试与 C++ 开发学习者的轻量级 DLL 劫持检测源码包,主要用于排查正在运行进程中可能被利用的 DLL 加载风险。压缩包共 5 个文件,含 3 个 cpp 源文件、1 份 LICENSE 和 1 份 README 说明文档,整体约 20KB,体积虽小却覆盖检测逻辑实现、授权信息与使用说明等关键内容。README 给出 dll_hijack_detect.exe 的命令行调用方式,并解释 /unsigned、/verbose 两个可选参数:前者只标记至少包含一个未签名 DLL 的情况,后者展示多个搜索顺序位置中发现的全部 DLL,组合使用可减少噪音并辅助判断异常加载。文中还说明了 CWD 被排除在搜索顺序扫描之外的原因,提醒读者在远程式 CWD 场景中结合冗余输出观察实际加载情况。已有 285 人学习,适合需要研究 Windows DLL 加载机制、复现检测流程、排查可疑模块或二次开发安全工具的中高级读者参考。

1. 检测 DLL 劫持:先认路径,再认签名

线上跑过一轮全量扫描之后,我盯着输出结果看了很久:一个正常开发机上抛出来的「可疑模块」有几十条,全是各种运行时自带的 DLL,真正需要看的可能只有零星一两条。这件事让我调整了整个检测思路——Windows 上做 DLL 劫持检测,难点从来不是「抓不到」,而是「怎么让抓到的那条值得看」。所以这篇不聊虚的,就围绕一份 C++ 实现的 DLL 劫持检测工具,讲清楚运行中的进程里那些已加载模块该按什么顺序判定、路径和签名哪个才是真锚点、以及规则定到什么粒度才不会把自己淹掉。如果你正在做主机侧模块完整性核查,或者手头有一批机器需要定期体检,这份东西的判定逻辑可以直接抄。

2. DLL 劫持的四种形态:从搜索顺序到幽灵模块

先把概念对齐。所谓「DLL 劫持」,落到 Windows 运行中的进程上,本质上是一件事:某个进程加载了它本不该加载的模块,而这个模块的加载位置、加载时机或者模块本身来源出了问题。不同的落地方式,检测时看的字段完全不一样。我一般会把它们拆成下面几类,因为拆开之后每一类对应的判定条件都很具体,写进代码就是几个 if。

2.1 搜索顺序劫持:靠目录优先级吃进去的模块

这是最常见也最好理解的一类。Windows 加载器在解析一个只有文件名、没有完整路径的依赖时,会按固定顺序去各个目录里找。安全搜索模式开启时,顺序大致是:可执行文件所在目录、系统目录、16 位系统目录、Windows 目录、当前工作目录、PATH 里的各个目录。问题就出在「可执行文件所在目录」排在最前面——如果某个程序装在了一个普通用户有写权限的目录里(比如某些绿色版软件、解压即用的工具、用户自己下载目录里跑的 exe),那么只要往它旁边扔一个同名的 DLL,下次启动时加载的就是这个冒牌货。

这类劫持的检测特征非常明确:模块的完整路径落在 exe 所在目录,但这个目录不在受信任位置清单里,而且这个目录的 ACL 对普通用户可写。两个条件同时满足,基本可以确认问题。我见过不少工具只检查「模块是否在系统目录」就下结论,结果把所有正常程序自带的依赖全报了一遍,误报率直接爆掉。

还有一种更隐蔽的变体:进程的当前工作目录被改过。有些程序启动时会去设置工作目录,加载器随后会在那个目录里找 DLL。检测上表现为模块路径是一个跟 exe 完全无关的目录,甚至可能是某个盘符根目录或者用户文档目录。看到模块路径和 exe 路径在两个完全不相干的树上,就该警惕。

2.2 缺失 DLL 与幽灵加载:等不到真身就被人顶替

第二类值得单独拿出来说,因为它的检测思路和前一类正好相反。某些程序会去加载一个「可选」的依赖,也就是这个 DLL 在正常安装环境下压根不存在,程序自己也做了容错——加载失败就走降级分支。攻击面就在这里:攻击者不需要覆盖任何已有文件,只需要在那个会被搜索的目录里放一个同名 DLL,加载器找到它、加载成功,代码就跑起来了。因为原本就不该有这个文件,所以「文件存在性」检查完全失效。

检测上这类要靠基线。做法是给一批干净环境里的进程模块列表做快照,把每个 exe 的已加载模块集合固化成基线,之后扫描时发现某个进程多出一个基线里从来没有的模块,且这个模块来自非标准目录,就标出来。基线不用做得很复杂,按 exe 的完整路径 + 模块名做键就够了。工程上麻烦的是版本更新会让基线漂移,所以要留一个「基线过期重新采样」的开关。

另一类幽灵模块是进程退出后 DLL 文件被删除,或者模块被手动卸载但文件句柄还挂着。这类在枚举时表现为路径指向的文件不存在,或者路径里带着明显的临时目录特征。检测器里加一条「模块路径对应文件不可访问」的检查成本很低,收益不小。

2.3 侧加载与相对路径:跟正经软件绑在一起的那种

第三类常出现在大型软件的安装目录里。有些程序会刻意从一个相对路径加载插件式 DLL,路径拼接逻辑写得不严谨,或者允许通过配置文件、环境变量影响加载位置。这种场景下,模块路径本身是「合法目录下的合法文件名」,光看路径判不出来,必须结合签名。

我一般这么处理:对每一个已加载模块做签名校验,同时记录签发主体。如果一个模块位于某个第三方软件的安装目录,但它的签名主体和该软件主程序完全不一致,甚至压根没有有效签名,那就要打问号。注意这里不能简单地说「无签名就是恶意」——大量内部工具、开源软件、自编译产物都没有签名,所以这条只能作为加权项,不能作为唯一判据。

侧加载还有一个变种是利用系统自带的、带清单文件的可执行文件去加载同目录下的 DLL。检测这类目标的思路是:进程名属于系统组件,但它的模块列表里出现了非系统目录的模块。这个组合非常刺眼,命中率很高,我在实际扫描里看到的绝大多数真问题都落在这个组合上。

2.4 检测的锚点:为什么路径比文件名可靠

把上面四类收一收,会发现一个共同点:文件名几乎不能作为判据。系统目录里有一个 DLL,攻击者完全可以造一个同名文件放到别处,文件名一模一样,路径天差地别。所以检测器的第一判据必须是完整路径,第二判据才是签名,文件名只用来做「同名不同路径」的比对。

具体比对逻辑是这样:维护一份系统关键模块的名单(可以从注册表的 KnownDLLs 项里读,也可以从系统目录里的文件清单里生成)。扫描时如果发现某个模块的文件名命中这份名单,但完整路径不在系统目录下,直接高危。这条规则误报极低,因为正常情况下这类模块只会从系统目录加载。我在实际使用中把这条设为最高优先级,命中即告警,不做任何降级。

再往下才是「路径是否在受信任位置」「目录 ACL 是否可写」「签名是否有效」这几层加权。顺序不能乱,乱了就会出现「一个签名齐全但放在临时目录里的模块被放过」这种尴尬情况。

还有一个容易忽略的点:模块的加载基址和大小。有些检测场景需要判断模块是不是被手动映射进来的(反射式加载、手动 PE 映射这类),这类模块在正常的模块枚举里可能根本看不到。要覆盖这种情况,光靠快照枚举不够,得配合加载通知回调或者内存扫描,成本和复杂度都上去了。对于一份以「可定期跑、结果可读」为目标的检测工具来说,我倾向于先不做,把已知的枚举路径做扎实,覆盖面反而更实用。

3. C++ 采集链路:把进程和模块信息取全的三个环节

判定逻辑再漂亮,数据采集不全也是白搭。这一段讲具体的 C++ 实现路径,从拿到进程列表、到取到每个进程的模块清单、再到补齐路径和签名信息。代码里我只保留关键调用,参数含义和失败时的处理会在下面说清楚。

3.1 进程枚举与权限准备

第一步是拿到所有正在运行的进程。可以用CreateToolhelp32Snapshot配TH32CS_SNAPPROCESS,也可以用EnumProcesses一次拿到 PID 数组。前者能顺带拿到父进程 ID 和线程数,后者更轻。我习惯用EnumProcesses,因为它返回的是一个 PID 数组,后续可以并行处理,节奏更好控制。

真正的坑在打开进程这一步。要枚举模块,需要PROCESS_QUERY_INFORMATION和PROCESS_VM_READ权限。普通权限下,系统进程、其他用户会话的进程、受保护进程都会打不开,返回错误码 5。这不是程序写错了,是权限模型决定的。所以采集层必须把「打不开的进程」单独记下来,而不是静默跳过——否则你会以为扫了一百多个进程,实际上只看了三十个。

// 枚举全部 PID,逐个尝试打开,打不开的记录原因而不是丢弃 #include <windows.h> #include <psapi.h> #include <vector> #include <string> struct ProcHandle { DWORD pid; HANDLE h; // 打开失败时为 nullptr DWORD lastError; // 记录失败原因,便于统计覆盖率 }; std::vector<ProcHandle> OpenAllProcesses() { std::vector<DWORD> pids(1024); DWORD needed = 0; // 第一次调用拿实际需要的数量,缓冲区不够时扩容重试 if (!EnumProcesses(pids.data(), (DWORD)(pids.size() * sizeof(DWORD)), &needed)) { return {}; } size_t count = needed / sizeof(DWORD); std::vector<ProcHandle> out; out.reserve(count); for (size_t i = 0; i < count; ++i) { if (pids[i] == 0) continue; HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pids[i]); out.push_back({ pids[i], h, h ? 0 : GetLastError() }); } return out; }

这段的关键在最后那个lastError。跑完全量扫描后,我会统计一下打开失败的进程占比,如果超过三成,说明当前跑工具的权限不够,结果要打折扣看。提升到管理员权限能解决大部分问题,但受保护进程依然打不开,这是设计如此,不用死磕。

另外注意 32 位和 64 位的交叉问题。用 32 位编译出来的程序去枚举 64 位进程的模块,会失败或者只能拿到部分结果。这件事必须用 64 位编译,别在这儿省事。

3.2 模块枚举的两条路:快照与 PSAPI

拿到进程句柄之后,取模块清单有两条主路。第一条是CreateToolhelp32Snapshot配TH32CS_SNAPMODULE,然后Module32FirstW/Module32NextW循环。每个MODULEENTRY32W里有模块名、完整路径、基址、大小,信息比较全。第二条是EnumProcessModulesEx配GetModuleFileNameExW,先用LIST_MODULES_ALL拿到句柄数组,再逐个查路径。

两条路我都用过,差别在于:快照方式对 WOW64 进程更友好(可以通过TH32CS_SNAPMODULE32标志专门处理 32 位模块),但快照本身在某些频繁加载卸载模块的进程上会失败,返回ERROR_BAD_LENGTH,需要重试。PSAPI 方式更稳,但拿模块路径要靠额外调用,而且对被保护进程同样无能为力。

我一般用快照为主、PSAPI 兜底,快照失败就换一条路再试一次。代码上封装成一个返回vector<ModuleInfo>的函数,内部做重试,上层不关心用哪条路。

// 快照方式取模块清单,失败时重试两次,仍失败返回空由上层切换策略 #include <tlhelp32.h> struct ModuleInfo { std::wstring name; // 模块文件名 std::wstring path; // 完整路径,判定主要靠它 uintptr_t base; // 加载基址 DWORD size; // 映像大小 }; bool SnapshotModules(DWORD pid, std::vector<ModuleInfo>& out) { for (int retry = 0; retry < 3; ++retry) { // 同时请求 32/64 位模块,兼容 WOW64 进程 HANDLE snap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); if (snap == INVALID_HANDLE_VALUE) { // ERROR_BAD_LENGTH 表示进程正在加载模块,稍等重试即可 if (GetLastError() == ERROR_BAD_LENGTH) { Sleep(20); continue; } return false; } MODULEENTRY32W me{}; me.dwSize = sizeof(me); if (Module32FirstW(snap, &me)) { do { out.push_back({ me.szModule, me.szExePath, (uintptr_t)me.modBaseAddr, me.modBaseSize }); } while (Module32NextW(snap, &me)); } CloseHandle(snap); return !out.empty(); } return false; }

szExePath是这里最重要的字段,后面的所有判定都基于它。modBaseAddr和modBaseSize在需要判断模块是否被篡改、或者做内存比对时用得上。重试逻辑别省,尤其在做全量扫描的时候,几十个进程里总有一两个正在启动或者正在退出,不重试就会漏。

3.3 路径合法性判定的字段拆解

路径拿到手之后,怎么判?我把判断拆成几个独立的布尔条件,每个条件单独算,最后汇总打分。这样调试的时候能一眼看出是哪条命中的,也方便后面调阈值。

第一个条件是「是否在系统目录」。比较时要注意大小写和短路径问题,Windows 的路径比较不能直接字符串相等。稳妥做法是先用GetFullPathNameW规范化,再用GetLongPathNameW把 8.3 短名展开,最后用CompareStringOrdinal配TRUE做忽略大小写比较。我见过因为C:\WINDOWS\system32和C:\Windows\System32大小写不同导致整条规则失效的情况,纯属自己给自己挖坑。

第二个条件是「目录是否对普通用户可写」。这个不能靠路径字符串猜,得真的去查 ACL。用GetNamedSecurityInfoW拿目录的 DACL,然后构造一个「普通用户」的令牌去做AccessCheck,看有没有写权限。简化一点的替代方案是检查目录是否在几个已知的可写位置下面:临时目录、用户配置目录、公共程序数据目录、下载目录、回收站。这种基于路径前缀的检查会漏,但实现成本低,作为第一层过滤够用。

第三个条件是「文件是否存在且可读」。模块已经加载了但文件消失,这本身就是个信号。用GetFileAttributesW判断,返回INVALID_FILE_ATTRIBUTES就记一条。

第四个条件是「同名模块是否出现在系统名单里」。这个前面说过,从 KnownDLLs 读,或者从系统目录扫一遍文件名建集合。

四个条件组合起来,规则就出来了。比如「同名命中系统名单 + 路径不在系统目录」= 高危;「路径在可写目录 + 文件存在」= 中危;「文件不存在」= 可疑,需要人工看。这种组合式的判定比单一阈值靠谱得多。

3.4 签名校验与结果输出

签名这块用WinVerifyTrust,配WINTRUST_ACTION_GENERIC_VERIFY_V2。调用前要填WINTRUST_FILE_INFO指定文件路径,WINTRUST_DATA里把dwUIChoice设成WTD_UI_NONE、fdwRevocationChecks设成WTD_REVOKE_NONE。最后这个设置很关键——默认会去联网做吊销检查,全量扫描几千个模块的时候,光等网络超时就能把程序卡死。离线环境下更是直接报错。

返回值要分情况处理:ERROR_SUCCESS是签名有效;TRUST_E_NOSIGNATURE是压根没签名;TRUST_E_EXPLORER_FROM_DIFFERENT_DOWNLOAD之类的属于有其他问题但签名存在;CERT_E_UNTRUSTEDROOT是根证书不受信。别把非零返回一律当「签名无效」,这会把自签名证书的内部工具全打成可疑对象。

结果输出我倾向用 CSV,理由很朴素:扫描结果最终是要给人看的,人要拿去排序、筛选、贴表格。CSV 用 Excel 一开就能按风险等级排,比 JSON 友好。字段至少要有:进程 ID、进程名、进程完整路径、模块名、模块完整路径、命中规则编号、签名状态、签发主体、风险等级。签发主体这个字段在做溯源分析时特别有用,能快速看出某个目录下的模块是不是同一个来源。

还有一点:输出里要保留「扫描时打不开的进程」清单。这份清单本身就是个信息,说明当前权限覆盖不到哪些地方。我一般把它单独写一个文件,方便下次提权后对比差异。

4. 规则分层:把「能报」调成「报得准」的四道闸

工具跑起来之后,第二件麻烦事就是调规则。第一版一定是满屏告警,这很正常。问题在于怎么收敛。我摸索下来是四道闸门,一道一道往下筛,每一道都有明确的判定依据,而不是拍脑袋定阈值。

4.1 第一道闸:同名异路径,直接高危

这道闸前面提过,是最硬的规则。实现上需要一份「系统模块名单」。名单来源有两个:一是注册表里 KnownDLLs 那一项,里面列的是加载器直接映射的系统模块;二是把系统目录下的所有 DLL 文件名扫一遍建集合。前者更精确,后者覆盖更广,两个取并集。

判定时只看两件事:模块文件名是否在名单里,模块完整路径的父目录是否是系统目录。两个条件同时成立就告警。这条规则的误报几乎为零,因为正常情况下这类模块只会从系统目录加载,出现在别处只有两种可能:一是人为复制过去的(有些软件安装包会干这事,属于不规范但无害),二是被替换了。前者可以通过签名校验进一步区分。

我一般把这条的结果单独出一份清单,先看它。实际经验里,这份清单通常很短,但含金量最高。

4.2 第二道闸:可写目录 + 无有效签名

第二道闸处理那些路径合法但目录不可信的情况。判断逻辑是:模块所在目录属于普通用户可写位置,同时模块没有有效签名,或者签名主体与该进程主程序的签名主体不一致。

这里要强调「主体不一致」这个条件的价值。很多第三方软件会把一堆 DLL 装在自己目录下,这些 DLL 都有同一个签发主体,彼此一致,属于正常。但如果某个 DLL 的签发主体和同目录下其他模块完全不同,甚至是个陌生主体,那就要注意了。做这个比对需要先算出进程主程序的签名主体,再逐个模块比。

实现上要注意性能。签名校验是文件 IO 操作,全量扫描几千个模块会慢。我的做法是加缓存:以「文件路径 + 文件大小 + 最后修改时间」做键,同一个文件在一次扫描里只校验一次。跨次扫描也能复用,把缓存落盘,下次启动直接读。这招能把全量扫描时间压下来一大截。

第三道和第四道闸门其实是同一个思路的延伸:一层是「进程名属于系统组件但模块来自非系统目录」,另一层是「模块路径指向的文件已不存在或不可访问」。前者命中率不错,后者更多是提示性质。四道闸门走完,剩下的告警数量通常能压到个位数,这时候人看起来就不累了。

4.3 白名单怎么建才不失控

说到规则就绕不开白名单。白名单是双刃剑,建得好能把噪音清干净,建不好就把真问题一起埋了。我的原则是:白名单只按「路径前缀 + 模块名」建,不按「模块名」单独建。

按模块名建白名单是最容易犯的错。理由很直白:攻击者完全可以把自己的 DLL 命名成白名单里的名字,只要路径不同,就是两回事。按路径前缀建,至少保证了位置固定,攻击者要利用就得往那个目录里写文件,门槛高得多。

白名单条目的来源应该是实际观察,而不是想象。做法是:先不加白名单跑一轮,把结果里确认无害的条目按路径前缀聚合,人工过一遍之后再加进去。加的时候留个备注字段写清楚为什么加,不然过半年自己都不记得。我见过白名单条目堆到几百条、没人敢删的情况,那基本等于把检测关了一半。

定期复审白名单是必须的。每次软件大版本更新、每次新装一批机器,都要重新看一遍白名单里有没有已经失效的条目。失效条目不清,就是给自己留后门。

4.4 告警分级与输出格式的取舍

最后说分级。三级够用:高危、可疑、提示。高危是「同名异路径」和「系统进程加载非系统模块」这两类,必须处理。可疑是「可写目录 + 无签名」,需要人看一眼判断。提示是「文件不存在」「签名校验失败但路径正常」这类,留着做趋势观察。

分级的意义在于让人知道该先看哪个。一份几千行的 CSV,如果没有等级列,看的人会直接放弃。加了等级列,按等级排一下序,高危那几行先处理完,剩下的慢慢看,心态完全不一样。

输出格式上我踩过一个坑:早期版本把每个模块一行全部输出,结果一个进程几十个模块,一份报告上万行。后来改成只输出「命中规则的模块」,正常模块只统计数量不列明细,报告体积立刻降下来。这个改动看着小,实际用起来的体验差别很大。

5. 避坑与排查:五类翻车现场的处理

工具写完只是开始,真正花时间的是跟各种异常结果打交道。下面这几条都是我实际遇到过、并且反复出现的,按「现象 → 原因 → 解决」写清楚,照着排查能省不少时间。

现象一:扫描结果里出现大量来自同一个目录的模块,全是某个软件的安装目录,全被标成可疑。原因是这个软件把运行时依赖全放在自己目录下,且这些 DLL 没有数字签名。解决方式是把该目录整个加进路径前缀白名单,同时保留一条「该目录下若出现新增模块则告警」的规则,这样既不误报也不失守。

现象二:某些进程的模块列表永远是空的,怎么都取不到。原因是这些进程以更高权限运行,或者属于受保护进程,当前进程句柄打不开。解决方式是先确认工具本身的权限,普通用户跑就只能看到普通进程;提升到管理员能覆盖大部分;剩下的受保护进程无法枚举,这是权限模型决定的,接受它,并在报告里标注覆盖率。

现象三:同一台机器连续跑两次,结果不一致,第二次少了几条告警。原因是第一次扫描时某些进程正在启动,模块还没加载完;或者某些进程已经退出但快照还没释放。解决方式是在采集层加重试和延迟,扫描前先稳定几百毫秒;另外对同一 PID 的模块列表做两次采样取并集,能显著降低漏报。

现象四:签名校验把一批内部工具全标成「签名无效」。原因是这些工具用自签名证书,根证书没装进受信存储。解决方式是区分「无签名」和「签名存在但根不受信」两种状态,前者按可疑处理,后者降级为提示,同时把企业内部的证书主体加进信任清单。不要把这两种情况混在一起。

现象五:模块路径里出现短路径形式(带波浪号的那种),导致路径比较全部失败。原因是某些老程序加载模块时用的是 8.3 格式路径。解决方式是在比较前统一做一次GetLongPathNameW展开,展开失败再用GetFullPathNameW规范化,两步都失败才放弃比较。这个坑很隐蔽,因为它在大部分机器上不出现,只在特定系统配置下才冒出来。

还有一条不算坑但值得提:扫描耗时。全量扫描在普通办公机上跑一遍,如果每个模块都做签名校验,可能要几分钟。加缓存之后能压到十几秒。如果还是慢,可以考虑把签名校验做成异步,先出路径判定结果,签名结果后补。这个取舍要看使用场景,定期巡检可以等,交互式排查就不行。

6. 落地验证:单进程核对与全量扫描的收尾动作

规则调完、白名单建好,最后一步是验证这套东西到底管不管用。我一般分两步走:先做单进程核对,再做全量扫描对比。

单进程核对的做法是挑一个自己写的测试程序,把它放在一个普通用户可写的目录里,然后用工具单独扫这个 PID,看输出是否符合预期。为了验证「同名异路径」这条规则,我会在测试目录里放一个和系统模块同名的空壳 DLL,观察工具是否把它标成高危。这里必须强调:只在完全属于自己控制的测试环境里做,用自己编译的空壳文件,不要拿任何真实系统的模块文件做实验。验证完把测试文件清理干净。

全量扫描对比更实用。做法是同一批机器,改动规则前后各跑一次,把两次结果做差集。新出现的告警逐条看,确认是规则收紧带来的真实发现,还是误报扩大。这个动作我建议每次调规则都做一遍,成本不高但能防止规则越调越偏。下面这张表是我常用的记录格式,几个字段固定下来,前后对比时一眼能看出变化:

字段含义对比时的用法
规则编号命中的是哪条判定规则看新增告警集中在哪条规则上
进程路径宿主程序的完整路径判断是否为同一批软件
模块路径被加载模块的完整路径聚合后看目录分布
签名状态有效 / 无签名 / 根不受信区分真假可疑
风险等级高危 / 可疑 / 提示决定处理优先级

跑完这一轮,工具基本就能进日常流程了。我的习惯是把它挂到定期巡检里,每天跑一次,只推高危和新增可疑,其余归档。归档的数据攒上几周之后,会浮现出一些之前没注意的模式——比如某台机器上某个目录反复出现新模块,那台机器就值得单独看一眼。

回过头看,这个工具真正值钱的地方不是代码本身,而是那套判定顺序:先看路径归属,再看同名比对,然后看目录权限,最后看签名。顺序错了,结果就没法看。我从那以后每次做类似的模块核查,都会先把这四层顺序在纸上写一遍再动手,宁可前期多想十分钟,也不想后期对着一屏告警发愣。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 2:00:38

3 步完成 Android 证书迁移,让 HTTPS 抓包在手机上真正生效

3 步完成 Android 证书迁移&#xff0c;让 HTTPS 抓包在手机上真正生效 【免费下载链接】MoveCertificate 支持Android7-16移动证书&#xff0c;兼容magiskv20.4/kernelsu/APatch, Support Android7-16, compatible with magiskv20.4/kernelsu/APatch 项目地址: https://gitc…

作者头像 李华
网站建设 2026/10/23 2:00:20

Path of Building PoE2:算清PoE2构建规划里的DPS与血量

Path of Building PoE2&#xff1a;算清PoE2构建规划里的DPS与血量 【免费下载链接】PathOfBuilding-PoE2 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding-PoE2 Path of Building PoE2&#xff08;玩家常叫它 PoB2&#xff09;是一款面向《流放之路…

作者头像 李华
网站建设 2026/10/23 1:58:31

XAutoDaily QQ自动签到完整指南:三步装好,15 项签到任务一次托管

XAutoDaily QQ自动签到完整指南&#xff1a;三步装好&#xff0c;15 项签到任务一次托管 【免费下载链接】XAutoDaily 一个基于QQ的全自动签到模块 项目地址: https://gitcode.com/GitHub_Trending/xa/XAutoDaily XAutoDaily 是一款基于 Xposed 框架的开源 QQ 自动签到模…

作者头像 李华