图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,从functionsimports中看到程序中使用的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进行了解析,并给出了大概的代码。其实整个过程并不复杂,清晰的思路才是关键,这一点对于我们搞入侵研究的人尤为重要,希望能给朋友们一点启示