突破Session 0隔离:远线程注入的令牌路由方案与避坑指南

1. 从需求讲起:服务进程为什么要跨过SESSION 0

先说一个我早年踩过的真实场景。当时我在做一个终端管理工具的后台服务,功能很简单:捕获当前登录用户在某个业务系统里的操作行为,做合规审计。服务跑在Windows服务里(Session 0),但是用户进程跑在Session 1甚至Session 2。最初方案是直接在服务里对用户进程做远线程注入,把抓取DLL塞进目标进程。结果就是一连串的"拒绝访问",当时我还以为是权限不够,狂调OpenProcess的参数,后来才发现问题压根不在权限上,而在会话隔离上。

这个问题的本质要从Windows Vista说起。在XP时代,服务进程和用户进程共享同一个Session 0,所以服务可以创建窗口、弹消息框,甚至直接给用户桌面的窗口发消息。好处是方便,坏处是:恶意软件也方便。比如一个以SYSTEM权限运行的服务,可以直接操控用户桌面上的所有程序,弹窗、截屏、注入什么都行。微软从Vista开始引入SESSION 0隔离:所有服务统统治进Session 0,第一个登录的用户进Session 1,后续用户进Session 2、3……服务默认接触不到用户会话的任何UI元素。

但业务需求不会因为隔离就消失。远程支持、屏幕共享、输入法注入、终端审计、EDR的进程行为监控,全都需要服务与用户会话交互。于是"突破SESSION 0隔离"成了一个绕不开的话题。这篇文章我就详细拆一下远线程注入在跨会话场景下的原理、实现路线和实际坑位,适合做Windows系统编程、安全研究或者终端软件开发的读者参考。

2. 先搞清楚远线程注入的标准玩法

2.1 注入的本质:让目标进程执行我们的代码

远线程注入的核心思路非常简单:在目标进程的地址空间里分配一块内存,把我们要加载的DLL路径写进去,然后让目标进程执行LoadLibrary,让系统在目标进程里加载这个DLL。原理上走的是这么四步:

  1. OpenProcess:拿到目标进程句柄,要求有PROCESS_CREATE_THREAD、PROCESS_VM_OPERATION、PROCESS_VM_WRITE、PROCESS_VM_READ权限。
  2. VirtualAllocEx:在目标进程里分配内存,大小至少够存DLL的全路径。
  3. WriteProcessMemory:把DLL路径字符串写进刚刚分配的内存。
  4. CreateRemoteThread:在目标进程里创建一个线程,线程入口指向LoadLibraryA,参数指向刚写入的路径字符串。

如果拿C/C++写的经典模板,大概长这样:

HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwPid); LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)dllPath, strlen(dllPath) + 1, NULL); LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pLoadLibrary, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread, INFINITE);

这套模板在任何一本Windows核心编程的书里都有,执行起来也没什么玄学。但请注意:这套模板默认了调用方和目标进程在同一个会话里。一旦跨会话,事情就不对了。

2.2 为什么标准模板在跨会话时会翻车

我第一次跨会话跑这个模板,OpenProcess用SYSTEM权限加上SeDebugPrivilege,句柄拿到了,VirtualAllocEx也成功了,结果CreateRemoteThread直接返回失败,错误码是ERROR_ACCESS_DENIED。后来反复测试,总结出三个层面的问题:

第一,句柄权限问题。跨会话打开对方进程,仅仅有SYSTEM令牌是不够的。有时候进程的DACL会显式拒绝或允许某些SID访问,特别是保护进程(Protected Process Light)级别以上的,SYSTEM也打不开。这不是会话隔离本身,但跨会话场景下很容易触发。

第二,线程的Session归属问题。CreateRemoteThread创建的线程是从哪来的?它的Session归属并不取决于目标进程,而是继承自调用线程所属的Session和Window Station。也就是说,你在Session 0的服务里调用CreateRemoteThread,即使目标进程是Session 1的,新线程起来后它的Win32k内核对象归属仍然是Session 0的。如果这个线程想创建窗口、访问desktop,它仍然会受Session隔离约束。

第三,DLL加载的上下文问题。虽然你WriteProcessMemory写进去了路径,但LoadLibrary的DLL搜索路径在目标进程里是基于目标进程的环境变量。如果路径写的是相对路径,或者依赖目标会话里的某个环境变量,结果就是LoadLibrary返回NULL,DLL根本没进去。

所以说,如果是服务想注入用户进程,标准四步曲已经不够用了。你得先解决"让代码在目标会话上下文中运行"的问题,再考虑注入本身。

3. SESSION 0隔离真正的三个层次

3.1 会话、窗口站、桌面:从大到小的三重边界

做Windows编程的,尤其是碰UI或者注入的,必须把Session、Window Station、Desktop这三层关系理顺。Windows的会话隔离不是一个单点机制,而是层层嵌套的三层结构:

层级作用隔离内容跨边界访问方式
Session独立的内核对象命名空间、独立的会话空间窗口消息、剪贴板、全局原子、内核对象子集需要Token或句柄复制
Window Station包含剪贴板、原子的容器剪贴板、原子表、部分窗口句柄无法直接访问其他Station
Desktop包含窗口、钩子窗口消息、鼠标键盘输入SetThreadDesktop可切换

服务进程默认在Session 0的Service-0x0-3e7$窗口站,非交互桌面NonInteractive Desktop。用户进程在Session 1的WinSta0窗口站,Default桌面。大部分人只注意到Session这层,忽略了Window Station和Desktop,这导致跨会话注入即使把DLL加载进去了,DLL内部如果碰了UI、钩子、剪贴板,照样是瞎忙活。

3.2 服务令牌与用户令牌的差距

Session 0里的服务,令牌类型通常是LocalSystem或者LocalService/NetworkService。LocalSystem令牌权限极高,但它没有加载用户配置文件的用户令牌特征;用户进程的令牌是用户登录后由Winlogon创建的,包含用户SID、组成员身份以及该用户特有的安全上下文。

所以跨会话执行时,最干净的做法不是试图从服务侧"够"到用户侧,而是拿到用户的令牌,然后用这个令牌在目标会话里启动一个新的进程来完成注入动作。这有点像你想进一个门禁卡控制的大楼,与其在外面想办法撬锁,不如找到门口保安(Winlogon的机制)给你一张合法的门禁卡。

3.3 SESSION 0这道墙到底挡了什么

Vista之后的隔离,挡的具体是这么几类事:

  • 服务创建的窗口无法显示在用户桌面上。
  • 服务无法给用户桌面的窗口发送消息(SendMessage、PostMessage)。
  • 服务无法枚举或操作其他Session的进程窗口。
  • 服务直接远线程注入用户进程后,新线程的UI操作默认落在Session 0的非交互桌面,导致失败或静默无效。
  • 服务无法直接访问用户会话里创建的全局命名对象(需要正确的SD)。

但需要注意的是,这套隔离通过"获取目标会话用户令牌 → 以该令牌在目标会话创建进程 → 注入"这条路是可以绕开的——这正是很多远程支持工具和辅助软件的做法,也是恶意软件盯上的路径。

4. 突破隔离的主流路线:三条路线的取舍

4.1 路线A:用户令牌路由(最推荐)

这是我在实际项目里最终采用的方案,核心逻辑是:不强行跨Session注入,而是让注入动作发生在目标会话内部。具体流程:

  1. 服务调用WTSEnumerateSessions枚举所有会话,找到目标用户的活跃会话(State == WTSActive)。
  2. 调用WTSQueryUserToken拿到该会话的用户令牌。
  3. DuplicateTokenEx把令牌改成主令牌(Primary Token),并指定TokenPrimary。
  4. 用CreateProcessAsUser以该令牌在目标会话中启动一个轻量helper进程。
  5. helper进程自身位于目标会话中,再执行标准远线程注入。

这个方案表面上多了一步,但安全性、稳定性都很好。因为它从根上解决了上下文归属问题:helper进程创建出来的线程天然属于目标Session,后续DLL里的任何UI操作、钩子安装、消息发送都不会被隔离拦截。

4.2 路线B:服务内直接跨会话注入

这种是我一开始考虑过、后来放弃的方案。流程上就是前面说的标准四步曲,但需要额外处理几件事:

  • 启用SeDebugPrivilege提权。
  • 手动处理目标进程的ACL(有些进程需要调整DACL才能打开)。
  • 注入成功后,DLL内部要自己切换Window Station和Desktop,调用SetProcessWindowStation和SetThreadDesktop,切到目标会话的WinSta0\Default。
  • 如果DLL要访问用户配置相关的资源,还得用WTSQueryUserToken获取用户令牌来模拟(Impersonate)。

路B最大的坑是"上下文割裂":代码虽然跑在目标进程里,但内核层面的Session归属还是从调用线程继承来的,涉及窗口的操作都需要手动切换。如果只是做非UI的数据采集,这条路够用;一旦牵扯UI,就等着踩坑吧。

4.3 路线C:替代注入载体(APC注入、线程劫持)

还有一些变体方案:QueueUserAPC注入、SetThreadContext劫持、手动映射(Manual Map)。APC注入需要目标线程处于可告警状态,跨会话场景下还得解决同样的Token和权限问题;手动映射不依赖LoadLibrary,能绕过DLL加载的路径和依赖问题,但工程量大,还要自己搞定重定位和导入表。

我做技术选型时,给这几个方案排了个优先级:

需求推荐方案原因
服务需要稳定注入用户进程令牌路由+helper注入上下文正确,成功率高
DLL不需要UI操作服务内直接注入少一套进程管理逻辑
目标进程不可创建远程线程APC注入更隐蔽,不需要CreateRemoteThread权限
极少数EDR查得严的场景手动映射不依赖LoadLibrary

但要注意,方案C不是银弹,APC注入对线程状态有要求,手动映射对64位下的重定位、异常处理、TLS都要自己处理,代码量直接翻倍。

5. 令牌路由方案的关键代码拆解

5.1 服务端枚举会话并获取用户令牌

这是整个方案的地基。这里我用WTS系列API,注意需要链接Wtsapi32.lib,并包含wtsapi32.h:

#include <wtsapi32.h> #include <userenv.h> BOOL GetActiveUserToken(HANDLE* phToken) { DWORD dwSessionId = 0; PWTS_SESSION_INFO pSessionInfo = NULL; DWORD dwCount = 0; // 枚举所有会话 if (!WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount)) { return FALSE; } // 找到处于Active状态的会话 BOOL bFound = FALSE; for (DWORD i = 0; i < dwCount; i++) { if (pSessionInfo[i].State == WTSActive) { dwSessionId = pSessionInfo[i].SessionId; bFound = TRUE; break; } } if (!bFound) { WTSFreeMemory(pSessionInfo); return FALSE; } WTSFreeMemory(pSessionInfo); // 获取该会话的用户令牌 HANDLE hUserToken = NULL; if (!WTSQueryUserToken(dwSessionId, &hUserToken)) { DWORD dwErr = GetLastError(); // 常见错误:5(拒绝访问,令牌权限不足)、7025(会话未登录) SetLastError(dwErr); return FALSE; } *phToken = hUserToken; return TRUE; }

这里有个细节我说一下:WTSQueryUserToken要求调用者至少拥有SYSTEM权限,或者进程中启用了该Token所需的特权。服务以LocalSystem运行就满足条件;如果调试时用普通管理员身份跑,经常返回错误码5。

第二个细节:枚举的时候最好记录那个State==WTSActive的SessionId。有时候会出现多个会话都Active(比如用户同时有控制台会话和远程桌面会话),需要根据业务逻辑决定选哪个。我当时是选了SessionId最小的非0会话,处理物理控制台优先的场景。

5.2 用用户令牌在目标会话中启动进程

拿到了用户Token,还不能直接用CreateProcessAsUser搞,有几个坑要躲:DuplicateTokenEx必须先做,把SecurityImpersonation令牌改成Primary令牌;环境块要用CreateEnvironmentBlock生成,否则目标进程缺少用户环境变量,比如%APPDATA%找不到。

看代码:

BOOL LaunchHelperInUserSession(HANDLE hUserToken, LPCWSTR lpszHelperPath) { HANDLE hPrimaryToken = NULL; // 提升Token为主令牌 if (!DuplicateTokenEx(hUserToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken)) { return FALSE; } // 创建用户环境块 LPVOID pEnv = NULL; if (!CreateEnvironmentBlock(&pEnv, hPrimaryToken, FALSE)) { // 环境块创建失败不是致命错误,可以用默认NULL pEnv = NULL; } STARTUPINFOW si = { 0 }; si.cb = sizeof(si); si.lpDesktop = L"winsta0\\default"; // 指定到目标会话默认桌面 PROCESS_INFORMATION pi = { 0 }; BOOL bOk = CreateProcessAsUserW( hPrimaryToken, lpszHelperPath, NULL, // 不要拼接命令行,防止注入参数 NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, pEnv, NULL, // 当前目录设为NULL,避免盘符映射问题 &si, &pi ); if (pEnv) DestroyEnvironmentBlock(pEnv); CloseHandle(hPrimaryToken); if (!bOk) { return FALSE; } CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return TRUE; }

这一段的坑我当时踩得比较痛的是机上三个:

  1. si.lpDesktop不能省。如果留NULL,CreateProcessAsUser默认创建的进程窗口站是目标会话的默认Window Station。某些情况下(特别是服务处于Session 0),进程还是会落到服务自己的WinSta0。显式指定"winsta0\default"最稳。
  2. CreateEnvironmentBlock如果失败,一定不要整个流程失败。有的远程登录场景下用户配置没加载,环境块确实拉不到,pEnv=NULL还能跑,最多环境变量不全。
  3. 当前目录lpCurrentDirectory最好显式设为某个明确的目录。如果不设置,helper继承的是服务进程的当前目录,而服务的工作目录往往是C:\Windows\System32,有时候helper要读取同目录下DLL就会出幺蛾子。

5.3 helper进程注入DLL的标准流程

helper在目标会话里跑起来了,后面的注入动作其实就回到了第2节的经典模板。你可以让helper内部直接把自己所在的模块、或者某个DLL LoadLibrary进目标进程。

还有一个小技巧:如果helper注入的DLL是和helper.exe放在同一个目录的,helper拿到自己的模块路径后用GetModuleFileNameW提取目录,再拼上DLL文件名。这样路径天然正确,不会踩DLL搜索顺序的坑。

void InjectDllToProcess(DWORD dwTargetPid, const wchar_t* dllPath) { HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwTargetPid); if (!hProcess) return; size_t pathLen = (wcslen(dllPath) + 1) * sizeof(wchar_t); LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, pathLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf, dllPath, pathLen, NULL); LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress( GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pLoadLibrary, pRemoteBuf, 0, NULL); if (hThread) { WaitForSingleObject(hThread, 10000); CloseHandle(hThread); } VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hProcess); }

注意这里我用的是LoadLibraryW而不是LoadLibraryA。主要原因是路径里如果有中文字符,ANSI版本会挂。用宽字符版本,配合wcslen计算大小,路径越长越稳。

6. 实测中踩过的坑和排查过程

6.1 坑一:WTSQueryUserToken返回错误5

现象:服务是LocalSystem运行的,调用WTSEnumerateSessionsW成功,循环也找到了Session 1是Active,结果WTSQueryUserToken返回FALSE,GetLastError是5。

排查过程:一开始怀疑是权限不够,于是把服务调成LocalSystem跑,还是5。又怀疑是SessionState判断问题,打印出来确实是WTSActive。后来翻文档发现,WTSQueryUserToken在Vista之后要求进程必须运行在Session 0里。我用调试器以普通管理员身份附加进程跑的,调试器进程本身在Session 1,自然拿不到。把服务正常部署后在Session 0跑,问题就消失了。这个坑做开发时特别容易踩,因为调试模式下的Session归属和服务真实运行环境完全不同。

6.2 坑二:CreateRemoteThread成功了,但DLL没加载成功

现象:线程创建成功,WaitForSingleObject也等了,但目标进程没有加载出DLL,GetExitCodeThread返回的不是0。

排查过程:我把DLL路径从相对路径改成了绝对路径,结果还是失败。最后发现问题是helper进程和目标进程的位数不匹配。服务是64位的,helper是64位的,但目标进程是32位的——64位的LoadLibraryW地址传给32位进程的CreateRemoteThread,线程入口直接错乱,LoadLibrary根本没有正常执行。

解决思路:要么按目标进程位数编译两套helper和DLL,要么加载前先判断目标进程位数。判断方法就是用IsWow64Process2,如果目标是32位,就注入32位helper,用32位的LoadLibrary地址。这个坑纯属架构设计时没把位数兼容想清楚,属于低级但致命的问题。

6.3 坑三:注入成功、DLL也加载了,但UI完全没有反应

现象:DLL加载成功了,里面调用了MessageBoxW,结果什么也没弹。

排查过程:这就要回到第3节说的Session、Window Station和Desktop三层结构。DLL虽然加载在Session 1的进程里,但创建远程线程的动作是从Session 0发起的,新线程的Window Station必须是调用方所属Window Station的子树或者兄弟。事实上,线程创建时没有自动切换到目标Session的WinSta0\Default,MessageBox往Session 0的非交互桌面上弹,用户当然看不见。

解决:在DLL里,入口函数第一时间调用SetProcessWindowStation和SetThreadDesktop。但这不是万能的,SetProcessWindowStation只能设置当前进程的window station,而且需要传入正确的HWINSTA。更省事的方案是:DLL入口判断自己所在进程是否在目标Session的Interactive Window Station,如果不是,就调用WTSQueryUserToken去拿用户Token,然后ImpersonateLoggedOnUser,再SetThreadDesktop。这一套动作非常容易出错,这也是我为什么强烈推荐路线A的原因——让helper进程在目标会话里创建,就不会存在这个问题。

让我说得更直白一点:如果你在服务里直接CreateRemoteThread,就算你DLL加载成功、就算你代码全对,你依然面临一个很难绕过的"线程上下文归属"问题。与其和它搏斗,不如让注入动作发生在目标会话内。这是我这几个项目下来最深的体会。

6.4 坑四:CreateProcessAsUser启动的helper一闪而逝

现象:helper进程起来了,但立即退出,看不到任何弹窗,日志也没写。

排查过程:最初怀疑是权限问题,后来在helper启动路径上加cmd窗口重定向输出,才看到错误是"0xC0000142"(DLL初始化失败)。原因是CreateEnvironmentBlock失败时我用了pEnv=NULL,但CreateProcessAsUser要求CRF_UNICODE_ENVIRONMENT标志时,环境块必须是有效的。NULL在某些Windows版本里会导致进程初始化时找不到关键环境变量,直接初始化失败。

解决:要么创建环境块,要么干脆不用CREATE_UNICODE_ENVIRONMENT,改用继承父进程环境。但继承父进程环境又会导致helper拿到的是服务进程的环境变量,对某些依赖用户配置的功能不友好。最后我选择了:CreateEnvironmentBlock失败时,用GetEnvironmentStrings手动复制一份最小环境块,虽然丑,但能跑。

7. 从防御视角看这类注入:如何发现它

研究突破SESSION 0隔离的远线程注入,不应该只站在攻击方的角度。实际上,我之所以把这些问题摸得这么透,很大程度是因为在做EDR类产品的检测规则时,需要从反方向理解攻击路径。

Windows自己留了不少观测点,最常用的是Sysmon的Event ID 8(CreateRemoteThread检测)。它记录的是调用进程对目标进程创建远程线程的核心参数,包括SourceProcessId、TargetProcessId、StartModule等。如果StartModule指向的不是已知的系统模块,而是某个临时目录的DLL,那基本就是注入行为。

另一个观测点是ETW(Event Tracing for Windows)的Microsoft-Windows-Kernel-Process,可以记录VirtualAllocEx、WriteProcessMemory、CreateRemoteThread的内核级调用。ETW的优点在于很难被用户态代码篡改,缺点是配置复杂,对系统性能有一定影响。

如果是检测"跨会话注入"这种特定行为,核心特征是:调用进程的Session和TargetProcess的Session不一致,但CreateRemoteThread事件发生了。正常业务几乎没有理由跨会话创建远程线程,一旦出现就很可疑。我们可以把这条作为高置信度告警规则。

防御侧的建议是:

  • 不要轻易给服务进程授予SeDebugPrivilege,这是多数跨会话访问的关键前提。
  • 对SESSION 0里的常驻服务进程做行为监控,特别注意WTSQueryUserToken和CreateProcessAsUser的调用链。
  • 关键进程开启Protected Process Light保护,能拦住大多数非特权注入。
  • 如果业务确实需要跨会话支持,优先选官方支持的机制,比如CreateProcessAsUser启动助手进程,而不是直接注入。

8. 这项技术的边界与我的最终建议

写到这里,我得把话说透:DLL注入本身是Windows系统编程里很常规的技术,SDK文档里就有对CreateRemoteThread的详细描述。而SESSION 0隔离,本质上是系统为了安全和稳定性引进的一道防线。"突破隔离"的技术,从正面用途来说,是合法的远程协助、屏幕共享、无障碍工具等产品的必备能力;从反面用途来说,也是恶意软件常用的手段。这篇分析的目的,是帮助读者理解背后的机制,进而无论是做正常的产品功能,还是做攻击检测,都能有清晰的思路。

我的建议是:如果你的业务真需要跨会话交互,优先选令牌路由方案,让辅助进程在目标会话里做注入。它虽然多了一套进程管理的复杂度,但收益是后面的麻烦不超过20%。而如果你做的是安全产品,我强烈建议去实测一遍WTSQueryUserToken、CreateProcessAsUser、CreateRemoteThread这套链路,理解每一种异常表现,才能在写检测规则时做出精准判断。

我个人在实际开发中还有一个习惯:所有注入实验先丢到虚拟机里跑,目标进程选notepad或者自己写的一个测试进程,绝对不在真实生产系统上直接验证。注入的技术难度不在于把代码写出来,而在于你计算过每一种失败模式下系统的反应。

相关新闻

大语言模型编码增强建筑语义分割的实践与思考

大语言模型编码增强建筑语义分割的实践与思考

最近我在做一个建筑实景分割项目时&#xff0c;被一个看似很基础的问题卡了两周&#xff1a;模型在验证集上mIoU能到0.71&#xff0c;但一旦换到新区块&#xff0c;预测结果里"外墙""幕墙""遮阳板"就开始来回打架&#xff0c;错得非常有规律。后…

2026/10/20 7:17:57 阅读更多 →
MCP开源项目REA:让编码智能体像读源码一样分析二进制

MCP开源项目REA:让编码智能体像读源码一样分析二进制

编码智能体读源码、读日志、读文档都挺顺手&#xff0c;但一碰二进制文件就哑火。这不是模型不够聪明&#xff0c;而是我们没给它们一个可靠的结构化入口。你让一个以对话为核心的智能体直接去读编译产物&#xff0c;它拿到的基本就是一堆十六进制转储&#xff0c;既没有调用关…

2026/10/20 7:17:57 阅读更多 →
E5080A网分实战:从S参数校准到滤波器与天线测试

E5080A网分实战:从S参数校准到滤波器与天线测试

射频工程师的工位上&#xff0c;网络分析仪永远是C位。做天线的人离不开它&#xff0c;调滤波器的人离不开它&#xff0c;测放大器、测混频器、测射频线缆的人统统离不开它。E5080A就是这一类设备里我这两年用得最多的一台。它对得起“主力”这两个字&#xff0c;四平八稳&…

2026/10/20 7:16:57 阅读更多 →

最新新闻

RAG面试高频考点全解析:从检索到生成的优化链路

RAG面试高频考点全解析:从检索到生成的优化链路

1. 为什么面试官揪着RAG不放&#xff1a;先把这个动机吃透我最近在系统梳理大模型方向的面试题&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;几乎是每家必问的模块。尤其是做应用层项目的岗位&#xff0c;RAG的提问密度比微…

2026/10/20 8:14:30 阅读更多 →
机票电商平台重构实战:领域拆分与异步订单链路设计

机票电商平台重构实战:领域拆分与异步订单链路设计

简介&#xff1a;eyoo旅游机票电商平台&#xff08;重构版&#xff09;是一份基于Ruby与Rails技术构建的在线机票销售系统项目资源&#xff0c;面向Ruby后端开发者、电商平台学习者以及关注系统重构优化的人群。项目覆盖业务逻辑处理、ActiveRecord数据库操作、RESTful API接口…

2026/10/20 8:14:30 阅读更多 →
Docker容器化实战指南:从DockerFile到Compose编排全解析

Docker容器化实战指南:从DockerFile到Compose编排全解析

1. 先从一张架构图说起&#xff1a;为什么我们绕不开Docker我的个人项目X&#xff0c;从零开始做全栈实践。最初只开了一个云服务器&#xff0c;上面跑着数据库、缓存、定时任务&#xff0c;后来要加前后端服务&#xff0c;每个环境都折腾一遍依赖。结果不到三个月&#xff0c;…

2026/10/20 8:14:30 阅读更多 →
PHP借贷源码拆解与本地部署:从业务闭环到避坑实践

PHP借贷源码拆解与本地部署:从业务闭环到避坑实践

简介&#xff1a;一套基于 PHP 实现的借贷公司/网贷平台整站源码&#xff0c;面向需要快速搭建在线借贷系统的技术团队、中小型网贷项目开发者和 PHP 学习者。源码在得得系统基础上完成改编&#xff0c;并经过实际测试可用&#xff0c;覆盖用户注册、信用评估、借款申请、还款计…

2026/10/20 8:14:30 阅读更多 →
实战推演:用 esp32-iphone-agent 的思路,给自己 iPhone 做个“自动化开关“,可行吗

实战推演:用 esp32-iphone-agent 的思路,给自己 iPhone 做个“自动化开关“,可行吗

实战推演&#xff1a;用 esp32-iphone-agent 的思路&#xff0c;给自己 iPhone 做个"自动化开关"&#xff0c;可行吗 【免费下载链接】esp32-iphone-agent Let an AI model use a real iPhone with a $7 ESP32: AirPlay eyes, USB HID hands, any OpenAI-compatible …

2026/10/20 8:14:30 阅读更多 →
Agent上线就崩?从状态管理到可观测性,拆解工程化落地的五大核心难题

Agent上线就崩?从状态管理到可观测性,拆解工程化落地的五大核心难题

1. 演示与上线之间的鸿沟到底在哪做 Agent 项目的人大概都经历过这种场面&#xff1a;内部演示的时候&#xff0c;Agent 流畅地理解需求、调用工具、多步推理、最后给出一个漂亮的答案&#xff0c;会议室里一片掌声。然后老板说“下周上线吧”&#xff0c;结果一上线&#xff0…

2026/10/20 8:13:30 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →