3.1.2 进程间通信及管道

“再重述一遍我们Shellcode的功能:在目标机器开一个Telnet 服务器,监听某个端口,然后等待攻击机来连接。当攻击机连接之后,为它开创一个cmd.exe,把攻击机的输入输出和cmd.exe的输入输出联系起来。这样,远程攻击者就像Telnet一样,有了一个远程Shell。”

“刚才我们学习了绑定某个端口,接受连接和发送数据的编程实现。接下来我们要:一、开创cmd.exe进程;二、把CMD进程和客户的输入连起来。”

“先看第一个,为客户开创一个cmd.exe。可以用CreateProcess来创建这个子进程,其原型是:

BOOL CreateProcess(
  LPCTSTR lpApplicationName,
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCTSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

“哇!好多参数啊!”台下的眼睛都看大了。

“很多参数都可以不用,直接用NULL(即空)来代替即可。我们把它的第二个参数设为 cmd.exe /k ,就可以直接创建一个控制台窗口,而且不消失,程序如下:”

#include<windows.h>
int main()
{
     PROCESS_INFORMATION ProcessInformation;
     STARTUPINFO si;
     ZeroMemory(&si,sizeof(si));
     CreateProcess(NULL, "cmd.exe /k",NULL, NULL,1,0,NULL, NULL, &si, &ProcessInformation);
     return 0;
}

“主要使用了三个参数,执行效果如图3-13,开创了一个CMD窗口,参数‘/k’使控制台执行并保留下来。”

“哦!好像又是一种开本地控制台窗口的好方法啊!”台下有人说道。

“是啊!你们能意识到就很好!知识就是这样,前后可以融会贯通。”老师说,“现在就剩下把远程攻击机的输入输出和cmd.exe的输入输出联系起来了,这就涉及到进程间的通信了。”

小知识:进程间通信(IPC)机制

进程间通信(IPC)机制是指同一台计算机的不同进程之间或网络上不同计算机进程之间的通信。Windows下的方法包括邮槽(Mailslot)、管道(Pipes)、事件(Events)、文件映射(FileMapping)等。

“在这里,我们使用匿名管道(Anonymous Pipe)来完成这个联系过程。”

“管道?匿名管道?”大家更晕了。

“管道(Pipe)是一种简单的进程间通信(IPC)机制。实际是一段共享内存,在Windows NT/Win2000/ Win 98/ Win 95下都可以使用。一个进程向管道写入数据后,另一个进程就可从管道的另一端将其读取出来。”

“管道分有名和匿名两种。命名管道可以在同台机器的不同进程间以及不同机器上的不同进程之间进行双向通信。而匿名管道就要简单多了,只是在父子进程之间或者一个进程的两个子进程之间进行通信,它是单向的。”

“匿名管道实际上是内存中的一个独立的临时存储区,它对数据采用先进先出的方式管理,并严格按顺序操作,不能被搜索。”

“有了管道,我们向其他进程传输数据时就可像对普通文件读写那样简单。管道操作标示符是HANDLE,也就是说,我们可以直接使用readFile、WriteFile来读写,根本不必了解网络间/进程间通信的具体细节。”

老师说了这么多,台下似懂非懂。

“不要紧,等会看看具体的程序就能清楚流程和具体的实现。”老师轻松的说道,“这里先介绍一下相关函数,匿名管道由CreatePipe()函数创建。”

CreatePipe()函数相关知识

CreatePipe()的函数原型为:

BOOL CreatePipe(   PHANDLE hReadPipe,
PHANDLE hWritePipe,
LPSECURITY_ATTRIBUTES lpPipeAttributes,
DWORD nSize );

功能是创建匿名管道,并返回管道的读句柄和写句柄。

参数:

“hReadPipe”指向返回的读句柄的指针;

“hWritePipe”指向返回的写句柄的指针;

“lpPipeAttributes”指向安全属性的指针;

“nSize”表示管道的大小。

“创建了匿名管道和相应的读写句柄后,我们把写句柄放入一个进程中,读句柄放入另一个进程中,注意这两个进程必须要是父子继承关系,通过设置CreateProcess()的bInheritHandles为True来实现。”

“第一个进程要把数据写入Pipe,针对写句柄调用WriteFile函数即可。WriteFile函数将数据写入一个文件,成功返回非0,失败返回0;”

“另一个进程要读取Pipe里的数据时,针对读句柄先调用PeekNamedPipe函数,用来确定Pipe中是否有数据,然后再调用ReadFile函数,将Pipe中的数据读出。”

“管道通信的整体流程示意图如图3-14。”老师在黑板上画了出来。

“哦!”大家一口气听完,感觉还是有点过瘾。

“进程间的通信就是这样的。现在预备知识都有了,大家休息一下,下节课我们把它们结合起来,构造出一个完整的Telnet后门程序。”

小知识:Pipe的共用、建立、写入和读取过程

( 1 ) Pipe 的共用。 Windows中的Pipe并不是共用资源,2个进程如果没有“父子“关系,而且子进程又没有继承父进程资源,那么这2个进程将无法使用Pipe来传递数据。如何让2个进程产生父子及继承关系呢?条件是子进程由父进程启动,且在启动子进程时必须设置好继承参数。

上面的条件,通过调用API函数CreateProcess就可以实现。其中CreateProcess函数用来创建新进程,返回值非0表示成功,为0表示失败。为了让2个进程产生父子及继承关系,参数“bInheritHandles”应设置为True。

( 2 ) Pipe 的建立。 设置好Pipe的共用后,父进程通过调用API函数CreatePipe来创建Pipe,之后再将Pipe设置成可继承的。其中,CreatePipe函数用来创建一个匿名管道,返回值为Long,非0表示成功,0表示失败。

(3)Pipe的写入和读取  

Pipe的写入:要将数据写入Pipe,调用WriteFile函数即可;其中,WriteFile函数将数据写入一个文件。返回值为Long,TRUE(非0)表示成功,否则返回0。

Pipe的读取:必须分两步:先调用PeekNamedPipe函数,用来确定Pipe中是否有数据,以避免数据接收方长时间等待或处于永远等待状态;再调用ReadFile函数将Pipe中的数据读出。其中,PeekNamedPipe函数不会把Pipe中的数据读走,若Pipe中没有数据,它会正常返回,不会长时间等待,但ReadFile函数会长时间等待。

通过以上步骤,就可以利用Pipe技术来传送数据了。