命名管道和匿名管道的区别(超详解析2者不同处)

很多时候,在一些文章中,工具利用中,都会提到管道(pipe)。那么,什么是管道呢?管道能做什么呢?本文以 windows 管道为主,边学习边整理,希望可以给其他感兴趣的人提供帮助。如有不到之处,或是描述错误的地方请大家多多包涵,多多指点。

一、管道简述

管道并不是什么新鲜事物,它是一项古老的技术,可以在很多操作系统(Unix、Linux、Windows 等)中找到,其本质是是用于进程间通信的共享内存区域,确切的的说应该是线程间的通信方法(IPC)。

顾名思义,管道是一个有两端的对象。一个进程向管道写入信息,而另外一个进程从管道读取信息。进程可以从这个对象的一个端口写数据,从另一个端口读数据。创建管道的进程称为管道服务器(Pipe Server),而连接到这个管道的进程称为管道客户端(Pipe Client)。

在 Windows 系统中,存在两种类型的管道: “匿名管道”(Anonymous pipes)和“命名管道”(Named pipes)。匿名管道是基于字符和半双工的(即单向);命名管道则强大的多,它是面向消息和全双工的,同时还允许网络通信,用于创建客户端/服务器系统。

这两种管道的主要区别:

命名管道:可用于网络通信;可通过名称引用;支持多客户端连接;支持双向通信;支持异步重叠 I/O 。

匿名管道:单向通信,只能本地使用。

由于匿名管道单向通信,且只能在本地使用的特性,一般用于程序输入输出的重定向,如一些后门程序获取 cmd 内容等等,在实际攻击过程中利用不过,因此就不过多展开讨论,有兴趣可以自行检索相关信息。

二、命名管道1、定义与特点

命名管道是一个具有名称,可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信管道。

命名管道的所有实例拥有相同的名称,但是每个实例都有其自己的缓冲区和句柄,用来为不同客户端提供独立的管道。

任何进程都可以访问命名管道,并接受安全权限的检查,通过命名管道使相关的或不相关的进程之间的通讯变得异常简单。

用命名管道来设计跨计算机应用程序实际非常简单,并不需要事先深入掌握底层网络传送协议(如 TCP、UDP、IP、IPX)的知识。这是由于命名管道利用了微软网络提供者(MSNP)重定向器通过同一个网络在各进程间建立通信,这样一来,应用程序便不必关心网络协议的细节。

任何进程都可以成为服务端和客户端双重角色,这使得点对点双向通讯成为可能。在这里,管道服务端进程指的是创建命名管道的一端,而管道客户端指的是连接到命名管道某个实例的一端。

总结一下:

1、命名管道的名称在本系统中是唯一的。

2、命名管道可以被任意符合权限要求的进程访问。

3、命名管道只能在本地创建。

4、命名管道是双向的,所以两个进程可以通过同一管道进行交互。

5、多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。

6、命名管道的客户端可以是本地进程(本地访问:\.\pipe\PipeName)或者是远程进程(访问远程:\ServerName\pipe\PipeName)。

7、命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯。

2、查看管道列表

在 windows 系统中,列出管道列表的方法有很多。这里列举几种常见的查看方式。

a、powershell

使用 powershell 列出管道列表需要区分版本,V3 以下版本的 powershell 只能使用:

[System.IO.Directory]::GetFiles(“\.\pipe\”)

命名管道和匿名管道的区别(超详解析2者不同处)

V3 及以上版本的 powershell 还可以使用:

Get-ChildItem\.\pipe\

b、chrome

注:部分系统可能不支持 chrome 查看管道列表

c、其他工具

可以使用Process Explorer的Find-Find Handle or DLL功能查找名为\Device\NamedPipe

或者还可以使用 Sysinternals 工具包中的pipelist.exe等工具。

d、远程查看

需要注意的是,这些方法仅支持本地查看,无法远程查看。

A pipe server can provide the pipe name to its pipe clients, so they can connect to the pipe. The pipe client discovers the pipe name from some persistent source, such as a registry entry, a file, or another application. Otherwise, the clients must know the pipe name at compile time.

从微软官方提供的说明文档,我们可以看出,想要知道管道名就需要具有一定的本地操作权限,而在正常情况下,管道客户端是无法直接获取管道列表的,如果想要访问就必须提前获得管道名称。

当然,由于固定管道名称的存在,还是可以通过其他方式获取一部分管道名称。

例如使用 Metasploit 中的pipe_auditor和pipe_dcerpc_auditor两个模块,就可以通过碰撞确认哪些命名管道可通过 SMB 使用。

常见的可能会被利用到的管道名有以下这些:

netlogonlsarpcsamrbrowseratsvcDAV RPC SERVICEepmappereventlogInitShutdownkeysvclsassLSM_API_servicentsvcsplugplayprotected_storagerouterSapiServerPipeS-1-5-5-0-70123scerpcsrvsvctapsrvtrkwksW32TIME_ALTwkssvcPIPE_EVENTROOT\CIMV2SCM EVENT PROVIDERdb2remotecmd3、创建与访问

在 windows 中命名管道的通信方式是:

1.创建命名管道 --> 2.连接命名管道 --> 3.读写命名管道

a、创建

管道服务器无法在另一台计算机上创建管道,因此CreateNamedPipe必须使用句点.作为服务器名称,如以下示例所示。

\\.\pipe\PipeName

管道名称字符串可以包含反斜杠以外的任何字符,包括数字和特殊字符。整个管道名称字符串最多可以包含 256 个字符。管道名称不区分大小写。

服务端的整个创建过程如下:

(一)服务端进程调用 CreateNamedPipe 函数来创建一个有名称的命名管道,在创建命名管道的时候必须指定一个命名管道名称(pipe name)。

因为 Windows 允许同一个本地的命名管道名称有多个命名管道实例,所以,服务器进程在调用 CreateNamedPipe 函数时必须指定最大允许的实例数(0 -255),如果 CreateNamedPipe 函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄。

(二)然后,服务器进程就可以调用 ConnectNamedPipe 来等待客户的连接请求,这个 ConnectNamedPipe 既支持同步形式,又支持异步形式,若服务器进程以同步形式调用 ConnectNamedPipe 函数,(同步方式也就是如果没有得到客户端的连接请求,则会一直等到有客户端的连接请求)那么,当该函数返回时,客户端与服务器之间的命名管道连接也就已经建立起来了。

(三)在已经建立了连接的命名管道实例中,服务端进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务端句柄。

管道的访问方式相当于指定管道服务端句柄的读写访问,下表列出了可以使用CreateNamedPipe指定的每种访问方式的等效常规访问权限:

访问方式访问权限PIPE_ACCESS_INBOUNDGENERIC_READPIPE_ACCESS_OUTBOUNDGENERIC_WRITEPIPE_ACCESS_DUPLEXGENERIC_READ|GENERIC_WRITE

如果管道服务器使用 PIPE_ACCESS_INBOUND 创建管道,则该管道对于管道服务器是只读的,对于管道客户端是只写的。

如果管道服务器使用 PIPE_ACCESS_OUTBOUND 创建管道,则该管道对于管道服务器是只写的,对于管道客户端是只读的。

用 PIPE_ACCESS_DUPLEX 创建的管道对于管道服务器和管道客户端都是可以读/写的。

同时,管道客户端使用 CreateFile 函数连接到命名管道时必须在 dwDesiredAccess 参数中指定一个和管道服务端(创建管道时指定的访问模式)相兼容的访问模式。

例如,当管道服务端创建管道时指定了 PIPE_ACCESS_OUTBOUND 访问模式,那么,管道客户端就必须指定 GENERIC_READ 访问模式。

更多内容内容可以参考,微软官方说明:

https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-open-modes

在不过分追求底层逻辑的情况下,创建管道可以使用多种语言实现,包括 C、C 、C# 以及 powershell 等。

Powershell

$PipeSecurity = New-Object System.IO.Pipes.PipeSecurity$AccessRule = New-Object System.IO.Pipes.PipeAccessRule( “Everyone”, “ReadWrite”, “Allow” )$PipeSecurity.AddAccessRule($AccessRule) //设置权限$pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipename,”InOut”,10, “Byte”, “None”, 1024, 1024, $PipeSecurity)//创建命名管道$pipe.WaitForConnection()$pipeReader = new-object System.IO.StreamReader($pipe)$Null = $pipereader.ReadToEnd() //读取数据

以下为其他语言的创建方法:

C

SECURITY_ATTRIBUTES sa ={0}; SECURITY_DESCRIPTOR sd={0}; InitializeSecurityDescriptor( &sd,SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(&sd,TRUE,NULL,FALSE); sa.bInheritHandle =false; sa.lpSecurityDescriptor =&sd; sa.nLength =sizeof(sa); //设置安全描述符为任意用户均可访问 hPipe = CreateNamedPipe(TEXT(“\\\\.\\pipe\\Pipe”), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, // FILE_FLAG_FIRST_PIPE_INSTANCE is not needed but forces CreateNamedPipe(..) to fail if the pipe already exists… 1, 1024 * 16, 1024 * 16, NMPWAIT_USE_DEFAULT_WAIT, &sa); while (hPipe != INVALID_HANDLE_VALUE) { if (ConnectNamedPipe(hPipe, NULL) != FALSE) { …. } DisconnectNamedPipe(hPipe); }

C#

var server = new NamedPipeServerStream(“PipesOfPiece”);server.WaitForConnection();while (true){ …}

以参考代码 pipeserverimpersonate 为例,在创建命名管道的时候可以通过不同参数具体指定所需要的权限与功能

https://github.com/decoder-it/pipeserverimpersonate)

执行参考代码,管道已经被创建。

b、访问

客户端访问(连接)服务端的过程如下:

客户端进程调用 CreateFile 函数连接到一个正在等待连接的命名管道上,在这里客户端需要指定将要连接的命名管道的名称,当 CreateFile 成功返回后,客户进程就得到了一个指向已经建立连接的命名管道实例的句柄,到这里,服务器进程的 ConnectNamedPipe 也就完成了其建立连接的任务。

简单一点,可以通过命令行利用重定向符号直接把内容写入到命名管道中echo “test” > \\.\pipe\dummypipe

通过 C# 类 NamedPipeClientStream 实现访问命名管道

NamedPipeClientStream pipeClient =new NamedPipeClientStream(“.”, “testpipe”, PipeDirection.In))//System.Security.Principal.TokenImpersonationLevel.Delegation添加此参数可以允许服务端模拟客户端powershell同样调用NamedPipeClientStream实现访问命名管道 C 访问命名管道 HANDLE hPipe = CreateFile(TEXT(“\\\\.\\pipe\\test”), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hPipe != INVALID_HANDLE_VALUE) { …. CloseHandle(hPipe); }

但是需要注意的是,虽然命名管道支持跨计算机跨网的访问连接,但是会受到访问控制列表(ACL)或者说本地策略限制。

在 windows server 2003 及以下的版本中,默认开启了匿名管道通信,但是之上的系统版本中(包括 windows 7)默认禁止匿名管道通信。

windows server 2003 的默认本地策略,默认允许部分管道匿名访问。

windows server 2008 的默认本地策略,完全禁止匿名访问管道。

也就是说,在高版本中,或者说禁止匿名访问的系统中,如果想要实现远程管道访问,与管道进行通信,需要一个有效的身份进行验证。比如建立 smb 连接,或者 IPC 连接等。

举一个例子:

首先,我们在远程计算机开启一个管道

使用本地计算机尝试往管道内写入内容,此时会提示用户名密码不正确

但是,如果使用 smb 进行身份验证后,建立了有效的连接,此时就可以访问指定管道进行数据交互。这里回显找不到指定文件是由于远程计算机脚本的原因,虽然报错,但是在远程端确实检测到有数据输入。

这也正是 Metasploit 中某些工具无法正常使用的原因。

以 ms17_010_command 为例,对 server 2008 尝试攻击

返回的信息中就会提示不能找到可访问的管道Unable to find accessible named pipe!。而直接攻击 server 2003 就可以。

那么,在设置 2008 匿名访问,或提供有效的身份验证后,就可以执行成功。

c、连接测试工具

这类工具有很多,一下是通过网上随意找的一个小测试工具,有兴趣的话可以自己试验一下。

Server:

#include “iostream”#include “windows.h”using namespace std;#define PIPE_NAME L”\\\\.\\Pipe\\test”void main(){ char buffer[1024]; DWORD ReadNum; HANDLE hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); if (hPipe == INVALID_HANDLE_VALUE) { cout << “创建命名管道失败!” << endl; CloseHandle(hPipe); return; } if (ConnectNamedPipe(hPipe, NULL) == FALSE) { cout << “与客户机连接失败!” << endl; CloseHandle(hPipe); return; } cout << “与客户机连接成功!” << endl; while (1) { if (ReadFile(hPipe, buffer, 1024, &ReadNum, NULL) == FALSE) { cout << “读取数据失败!” << endl; break; } buffer[ReadNum] = 0; cout << “读取数据:” << buffer << endl; } cout << “关闭管道!” << endl; CloseHandle(hPipe); system(“pause”);}

Client:

#include “iostream”#include “windows.h”#include “stdio.h”using namespace std;#define PIPE_NAME L”\\\\.\\Pipe\\test”void main(){ char buffer[1024]; DWORD WriteNum; if (WaitNamedPipe(PIPE_NAME, NMPWAIT_WAIT_FOREVER) == FALSE) { cout << “等待命名管道实例失败!” << endl; return; } HANDLE hPipe = CreateFile(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPipe == INVALID_HANDLE_VALUE) { cout << “创建命名管道失败!” << endl; CloseHandle(hPipe); return; } cout << “与服务器连接成功!” << endl; while (1) { gets(buffer);//等待数据输入 if (WriteFile(hPipe, buffer, strlen(buffer), &WriteNum, NULL) == FALSE) { cout << “数据写入管道失败!” << endl; break; } } cout << “关闭管道!” << endl; CloseHandle(hPipe); system(“pause”);}

实现效果:

4、利用分析

在了解了命名管道的这些特性之后,就可以来看看命名管道有哪些具体利用了,常见的命名管道利用有以下三种,当然,如上文所说,在很多漏洞利用脚本当中也会有所涉及。

a、绕过防火墙

当尝试使用Bind()绑定一个 TCP Socket 时,Defender 就会自动弹窗提示是否允许此程序进行网络连接,在高权限下,通过修改防火墙规则,可以轻松的绕过这一限制,但是,当权限不足时,就需要另外想办法了。

这个时候我们还有另外的办法就是利用命名管道,命名管道网络通信使用了未加密的 SMB 协议(端口 445)或 DCE\RPC(端口 135)。在 Windows 中,通常默认允许 SMB 协议出入站(如果当年没有因为 WannaCry 主动做策略限制的情况下),因此,如果有什么功能或机制可以用于与外部机器进行通信的,SMB 协议无疑是一种很好的选择。所以我们可以基于命名管道与外部机器进行通信,从而建立控制通道。

b、模拟令牌(system 权限)

这也是命名管道中常见的一种方法,一般可以用来提权操作,Metasploit 中的 getsystem 也就是这个原理,官方给出的内容为:

Technique 1creates a named pipe from Meterpreter. It also creates and runs a service that runs cmd.exe /c echo “some data” >\.\pipe[random pipe here]. When the spawned cmd.exe connects to Meterpreter’s named pipe, Meterpreter has the opportunity to impersonate that security context. Impersonation of clients is a named pipes feature. The context of the service is SYSTEM, so when you impersonate it, you become SYSTEM.

大体意思也就是说,msf 会创建一个命名管道,然后创建一个服务去运行cmd.exe /c echo “some data” >\\.\pipe\[random pipe here],当 cmd 连接到 Meterpreter 的明明管道的时候,因为服务是 system 权限,msf 也就得到了一个 system 的 shell。

使用之前的 ps 脚本也可以完成此操作。

具体过程可以参考以下两篇文章:

https://www.anquanke.com/post/id/190207#h3-7

Windows Named Pipes & Impersonation

但是需要强调的是,该功能只能作为本地使用,模拟令牌产生的用户进程无法用于任何远程认证。模拟客户端产生进程,是通过提取当前进程 token 产生的,而 token 中只存在 sid 和 acl 等信息,其中不包含认证所需要的密码、hash,所以只能用于本地权限认证。如果用于远程认证就会出现权限鉴定出错的情况 失败的情况。

如果客户端采用 SECURITYDELEGATION 权限连接服务端,则允许服务端任意模拟客户端权限,包括本地和远程认证,但是根据官网文档,服务端要接受 SECURITYDELEGATION 权限的委派。

开启委派后也可以实现委派级别的模拟。具体就不在这里展开了。

c、C2 信道

命名管道还常被用作 C2 信道,通讯执行命令。

如图所示,每个终端将为每个直接连接的子终端提供一个命名管道服务器和一个命名管道客户端。服务器监听管道名称,并等待客户端的连接。客户端连接到特定主机名和管道名称的服务器,从而创建命名管道。管道的每一个终端都有从另一个终端读取和写入的能力,即,将Payload运行(注入)后,创建了自定义命名管道(作服务端),等待连接即可,这一过程被称为 ” 绑定 “(Bind)连接。

这种连接方式很常见,如 Metasploit 和 Cobalt Strike 都有类似功能。

三、参考资料

Windows Named Pipes & Impersonation

https://docs.microsoft.com/en-us/windows/win32/ipc/pipes

http://www.blakewatts.com/namedpipepaper.html

发表评论

登录后才能评论