剪贴板的话只能实现本机上进程之间的通信,而邮槽的话虽然是可以实现跨网络之间的进程的通信,但麻烦的是邮槽的服务端只能接收数据,邮槽的客户端只能发送数据,太悲剧了,而对于匿名管道的话,其也只能实现本机上进程之间的通信,你要是能够实现本机进程间的通信也就算了,关键是它还只用来实现本地的父子进程之间的通信,也太局限了吧?而这里介绍的这个命名管道的话,就和他们有些不同了,在功能上也就显得强大很多了,至少其可以实现跨网络之间的进程的通信,同时其客户端既可以接收数据也可以发送数据,服务端也是既可以接收数据,又可以发送数据的。
管道(pipe)是用于进程间通信的共享内存区域。创建管道的进程称为管道服务器,而连接到这个管道的进程称为管道客户端。一个进程向管道写入信息,而另外一个进程从管道读取信息。
管道是一种进程间,确切的说应该是线程间的通信方法。顾名思义。管道是一个有两端的对象。进程可以从这个对象的一个端口写数据,从另一个端口读数据。管道其实也是一种共享内存,只是更加规范。
管道有两种类型:匿名管道和命名管道。匿名管道是单向的,也就是说数据只能从写句柄写入,从读句柄读出。
异步管道是基于字符和半双工的(即单向),一般用于程序输入输出的重定向;命名管道则强大地多,它们是面向消息和全双工的,同时还允许网络通信,用于创建客户端/服务器系统。
(1)异步管道
前面已经说到过,异步管道的缺陷:只能够在父子进程之间通信,而且是半双工的,server与client不能同时发送数据。
创建匿名管道的函数式CreatePipe,创建一个匿名管道,并从中得到读写管道的句柄。
函数的原型为:
说明:匿名管道不允许异步操作,所以如在一个管道中写入数据,且缓冲区已满,那么除非另一个进程从管道中读出数据,从而腾出了缓冲区的空间,否则写入函数不会返回
关于匿名管道的一个应用:重定向。
这个例子是这样的:后门程序需要调用目标机器上的CDM,后门程序发送命令给cmd.exe程序,由cmd.exe程序把结果返回给我们的后门程序。这里就有两个问题了:
(1)cmd程序的默认输出在cmd窗口,那么如何把输出传给后门程序呢?
(2)后门程序的输入如何传给cmd程序呢?
解决方法:后门程序创建cmd进程,即cmd程序是后门程序的子进程,这样的话我们可以方便把需要的命令直接传给子进程,让其执行即可,这样第二个问题就解决了。cmd执行后的结果如何传给其父进程(后门程序)?这里我们需要使用一个重定向来处理,把cmd程序的输出重定位到后门程序。
又是子进程与父进程的关系,又是重定向,那么我们顺其自然使用管道了,这里使用匿名管道。
从图中可以看到,创建了两个管道,因为匿名管道是单向的,所以必须使用两个管道,要不然没法通信。
cmd程序把执行结果写入到管道1中,并从管道2中读出后门发过来的命令;后门程序从管道1中读出cmd程序的执行结果,把命令写入到管道2中。
每一个管道都有一个读句柄和一个写句柄,就是通过这两个句柄来进行数据读写从而来实现通信的。
创建子进程我们知道,可以使用CreateProcess函数,重定位我们需要使用进程创建过程中的一个参数STARTUPINFO,这个参数就是实现重定向的关键。
其原型为:
这个结构中只有三个参数跟我们的重定向有关,就是:hStdOutup,hStdInput,hStdError。通过设置这个结构中的参数为管道的输入输出句柄的值,可以设置创建的进程的输入输出重定向到管道。最终创建的进程的输入输出会与管道的句柄联系,达到了重定向的效果。
设置如下:
STARTUPINFO s ;
s.hStdError = hWritePipe ; // 错误输出到管道1写句柄
s.hStdOutput = hWritePipe ; // 执行结构从管道写句柄输入
s.hStdInput = hReadPipe ; // 从管道2读句柄输入,读取命令
当然了我们要在目标主机上创建一个cmd.exe,要具有一定的隐藏性至少不能出现窗口,所以创建的进程窗口的属性也是必须要设置的,因为默认情况下是可见的,所以需要把进程窗口特性wShowWindow改变为SW_HIDE属性。
实际上在不使用管道的情况下也可以实现cmd与后门的直接通信。那么我们就需要抛弃在unix,linux以及windows下都可以使用的socket,而选择MS为我们提供的WSASocket了,它与Socket最大的不同就是WSASocket支持异步I/O操作,而Socket编写的通信进程都会发生阻塞,除非使用多线程来处理。在上面使用管道的方法中我们就使用了两个管道来处理Socket的阻塞情况。
在我们编程过程中,为了方面我将使用WSASocket来处理cmd与后门之间的通信。那么我们就需要直接把cmd的输入输出与后门WASSocket关联起来,具体关联的方法就是:
s.hStdInput = s.hStdOutput = s.StdError = (void*)sock ;
也就是说,cmd处理命令后的结果直接发送给后门套接字,而后门套接字的命令也直接发送给cmd进行处理。这样就只需创建好后门套接字进行等待就行。
WriteFile函数
将数据写入一个文件。该函数比fwrite函数要灵活的多。也可将这个函数应用于对通信设备、管道、套接字以及邮槽的处理
BOOLWriteFile(
HANDLEhFile,
//文件句柄
LPCVOIDlpBuffer,
//数据缓存区指针
DWORDnNumberOfBytesToWrite,
//你要写的字节数
LPDWORDlpNumberOfBytesWritten,
//用于保存实际写入字节数的存储区域的指针
LPOVERLAPPEDlpOverlapped
//OVERLAPPED结构体指针
);
ReadFile 函数
BOOLReadFile(
HANDLEhFile,
//文件的句柄
LPVOIDlpBuffer,
//用于保存读入数据的一个缓冲区
DWORDnNumberOfBytesToRead,
//要读入的字节数
LPDWORDlpNumberOfBytesRead,
//指向实际读取字节数的指针
LPOVERLAPPEDlpOverlapped
//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
(2)命名管道
命名管道具有以下几个特征:
(1)命名管道是双向的,所以两个进程可以通过同一管道进行交互。
(2)命名管道不但可以面向字节流,还可以面向消息,所以读取进程可以读取写进程发送的不同长度的消息。
(3)多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。
(4)命名管道可以用于网络间两个进程的通信,而其实现的过程与本地进程通信完全一致。
服务端:
服务端进程调用 CreateNamedPipe 函数来创建一个有名称的命名管道,
在创建命名管道的时候必须指定一个本地的命名管道名称(不然就不叫命名管道了),
Windows 允许同一个本地的命名管道名称有多个命名管道实例,
所以,服务器进程在调用 CreateNamedPipe 函数时必须指定最大允许的实例数(0 -255),
如果 CreateNamedPipe 函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄,
然后,服务器进程就可以调用 ConnectNamedPipe 来等待客户的连接请求,
这个 ConnectNamedPipe 既支持同步形式,又支持异步形式,
若服务器进程以同步形式调用 ConnectNamedPipe 函数,
(同步方式也就是如果没有得到客户端的连接请求,则会一直等到)
那么,当该函数返回时,客户端与服务器之间的命名管道连接也就已经建立起来了。
在已经建立了连接的命名管道实例中,
服务端进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务端句柄。
同时,服务端进程可以调用 DisconnectNamedPipe 函数,
将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户进程。
当然在服务端也是可以调用 CloseHandle 来关闭一个已经建立连接的命名管道实例。
客户端
客户端进程调用 CreateFile 函数连接到一个正在等待连接的命名管道上,
在这里客户端需要指定将要连接的命名管道的名称,
当 CreateFile 成功返回后,客户进程就得到了一个指向已经建立连接的命名管道实例的句柄,
到这里,服务器进程的 ConnectNamedPipe 也就完成了其建立连接的任务。
客户端进程除了调用 CreateFile 函数来建立管道连接以外,
还可以调用 WaitNamedPipe 函数来测试指定名称的管道实例是否可用。
在已经建立了连接的命名管道实例中,客户端进程就会得到一个指向该管道实例的句柄,
这个句柄称之为客户端句柄。
在客户端可以调用 CloseHandle 来关闭一个已经建立连接的命名管道实例。