远程主机流程图:
客户机流程图:
CGh0stApp theApp; 唯一的实例在初始化中调用了主框架的 Activate 函数:BOOL CGh0stApp::InitInstance(){ ((CMainFrame*) m_pMainWnd)->Activate(nPort, nMaxConnection);}
Activate 函数构造了一个 CIOCPServer 对象,然后调用 Initialize 函数初始化:void CMainFrame::Activate(UINT nPort, UINT nMaxConnections){ m_iocpServer = new CIOCPServer; m_iocpServer->Initialize(NotifyProc, this, 100000, nPort)}
Initialize 注册了一个回调函数 m_pNotifyProc ,创建了一个监听套接字,一个监听线程 ListenThreadProc ,然后初始化 IOCP 服务端bool CIOCPServer::Initialize(NOTIFYPROC pNotifyProc, CMainFrame* pFrame, int nMaxConnections, int nPort){ m_pNotifyProc = pNotifyProc; m_socListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); nRet = bind(m_socListen, (LPSOCKADDR)&saServer, sizeof(struct sockaddr)); nRet = listen(m_socListen, SOMAXCONN); m_hThread =(HANDLE)_beginthreadex(NULL,0,ListenThreadProc,(void*) this,0,&dwThreadId); InitializeIOCP();}
IOCP 注册的回调函数 NotifyProc ,收到 NC_CLIENT_DISCONNECT 就从客户列表视图移除,收到 NC_RECEIVE 调用 ProcessReceive 函数,收到 NC_RECEIVE_COMPLETE 调用 ProcessReceiveComplete 函数void CALLBACK CMainFrame::NotifyProc(LPVOID lpParam, ClientContext *pContext, UINT nCode){ 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); break; case NC_RECEIVE_COMPLETE: ProcessReceiveComplete(pContext); break; }}
void CMainFrame::ProcessReceive(ClientContext *pContext){ if (pContext == NULL) return; // 如果管理对话框打开,交给相应的对话框处理 CDialog *dlg = (CDialog *)pContext->m_Dialog[1]; // 交给窗口处理 if (pContext->m_Dialog[0] > 0) { switch (pContext->m_Dialog[0]) { case SCREENSPY_DLG: ((CScreenSpyDlg *)dlg)->OnReceive(); break; default: break; } return; }}
void CMainFrame::ProcessReceiveComplete(ClientContext *pContext){ if (pContext == NULL) return;
// 如果管理对话框打开,交给相应的对话框处理 CDialog *dlg = (CDialog *)pContext->m_Dialog[1]; // 交给窗口处理 if (pContext->m_Dialog[0] > 0) { switch (pContext->m_Dialog[0]) { case SCREENSPY_DLG: ((CScreenSpyDlg *)dlg)->OnReceiveComplete(); break; default: break; } return; }
switch (pContext->m_DeCompressionBuffer.GetBuffer(0)[0]) { case TOKEN_AUTH: // 要求验证 m_iocpServer->Send(pContext, (PBYTE)m_PassWord.GetBuffer(0), m_PassWord.GetLength() + 1); break; case TOKEN_HEARTBEAT: // 回复心跳包 { BYTE bToken = COMMAND_REPLAY_HEARTBEAT; m_iocpServer->Send(pContext, (LPBYTE)&bToken, sizeof(bToken)); }
break; case TOKEN_LOGIN: // 上线包
{ if (m_iocpServer->m_nMaxConnections <= g_pConnectView->GetListCtrl().GetItemCount()) { closesocket(pContext->m_Socket); } else { pContext->m_bIsMainSocket = true; g_pConnectView->PostMessage(WM_ADDTOLIST, 0, (LPARAM)pContext); } // 激活 BYTE bToken = COMMAND_ACTIVED; m_iocpServer->Send(pContext, (LPBYTE)&bToken, sizeof(bToken)); }
break; case TOKEN_BITMAPINFO: // // 指接调用public函数非模态对话框会失去反应, 不知道怎么回事 g_pConnectView->PostMessage(WM_OPENSCREENSPYDIALOG, 0, (LPARAM)pContext); break; // 命令停止当前操作 default: closesocket(pContext->m_Socket); break; } }
现在从客户连接列表右键弹出菜单,选择“屏幕控制”选项开始。
ON_COMMAND(IDM_SCREENSPY, OnScreenspy)
void CGh0stView::OnScreenspy() { BYTE bToken = COMMAND_SCREEN_SPY; SendSelectCommand(&bToken, sizeof(BYTE));}
void CGh0stView::SendSelectCommand(PBYTE pData, UINT nSize){ ClientContext* pContext = (ClientContext*)m_pListCtrl->GetItemData(nItem); m_iocpServer->Send(pContext, pData, nSize);}
void CIOCPServer::Send(ClientContext* pContext, LPBYTE lpData, UINT nSize){ //使用zlib压缩数据 //构造包头 'G' 'h' '0' 's' 't' | PacketLen | UnZipLen //填充压缩后的数据 OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite); PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) pContext, &pOverlap->m_ol);}
IOCP 的线程函数收到发送过来的消息,将这个消息映射到函数 IO_MESSAGE_HANDLER(IOWrite, OnClientWriting)unsigned CIOCPServer::ThreadPoolFunc (LPVOID thisContext){ pThis->ProcessIOMessage(pOverlapPlus->m_ioType, lpClientContext, dwIoSize);}
bool CIOCPServer::OnClientWriting(ClientContext* pContext, DWORD dwIoSize){ OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite); m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_TRANSMIT); //表示正在传输,无实际意义 int nRetVal = WSASend(pContext->m_Socket, //发送命令到客户机 &pContext->m_wsaOutBuffer, 1, &pContext->m_wsaOutBuffer.len, ulFlags, &pOverlap->m_ol, NULL);}
客户机收到命令后回复主机已经准备好,ThreadPoolFunc 收到消息做好读操作
IO_MESSAGE_HANDLER(IORead, OnClientReading)
bool CIOCPServer::OnClientReading(ClientContext* pContext, DWORD dwIoSize){ //验证数据包格式 //zlib解压缩数据 //通知主框架接收完成m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE_COMPLETE);}
void CALLBACK CMainFrame::NotifyProc 函数响应操作,调用 ProcessReceiveComplete 函数,case TOKEN_BITMAPINFO: 响应操作g_pConnectView->PostMessage(WM_OPENSCREENSPYDIALOG, 0, (LPARAM)pContext);ON_MESSAGE(WM_OPENSCREENSPYDIALOG, OnOpenScreenSpyDialog)gh0stView 收到消息,转到 OnOpenScreenSpyDialog 函数处理
LRESULT CGh0stView::OnOpenScreenSpyDialog(WPARAM wParam, LPARAM lParam){ //创建了一个 CScreenSpyDlg 的非模式对话框 CScreenSpyDlg *dlg = new CScreenSpyDlg(this, m_iocpServer, pContext);}
服务端不停地收到客户机发过来的数据,IOCP 接收线程产生 NC_RECEIVE 消息,CMainFrame::NotifyProc 处理,调用 ProcessReceive显示帧数和进度 //192.168.1.101 800 * 600 第1532帧 100%
NC_RECEIVE_COMPLETE 消息调用 ProcessReceiveComplete 函数处理((CScreenSpyDlg *)dlg)->OnReceiveComplete();
void CScreenSpyDlg::OnReceiveComplete(){ //画图像}
CScreenSpyDlg 重载 CDialog 的虚函数 PreTranslateMessage(MSG* pMsg) ,处理鼠标键盘事件BOOL CScreenSpyDlg::PreTranslateMessage(MSG* pMsg) {
#define MAKEDWORD(h,l) (((unsigned long)h << 16) | l)
CRect rect; GetClientRect(&rect);
switch (pMsg->message) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MOUSEMOVE: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MOUSEWHEEL: { MSG msg; memcpy(&msg, pMsg, sizeof(MSG)); msg.lParam = MAKEDWORD(HIWORD(pMsg->lParam) + m_VScrollPos, LOWORD(pMsg->lParam) + m_HScrollPos); msg.pt.x += m_HScrollPos; msg.pt.y += m_VScrollPos; SendCommand(&msg); } break; case WM_KEYDOWN: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_SYSKEYUP: if (pMsg->wParam != VK_LWIN && pMsg->wParam != VK_RWIN) { MSG msg; memcpy(&msg, pMsg, sizeof(MSG)); msg.lParam = MAKEDWORD(HIWORD(pMsg->lParam) + m_VScrollPos, LOWORD(pMsg->lParam) + m_HScrollPos); msg.pt.x += m_HScrollPos; msg.pt.y += m_VScrollPos; SendCommand(&msg); } if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) return true; break; default: break; }
return CDialog::PreTranslateMessage(pMsg);}
SendCommand 发送控制命令 COMMAND_SCREEN_CONTROL ,带上实际的消息数据到客户机void CScreenSpyDlg::SendCommand(MSG* pMsg){ if (!m_bIsCtrl) return;
LPBYTE lpData = new BYTE[sizeof(MSG) + 1]; lpData[0] = COMMAND_SCREEN_CONTROL; memcpy(lpData + 1, pMsg, sizeof(MSG)); m_iocpServer->Send(m_pContext, lpData, sizeof(MSG) + 1);
delete[] lpData;}
-----------------------------------------------------------------------------
客户端主函数:
创建一个 CClientSocket 对象,一个 CKernelManager 对象,关联 socketClient 到 manager
int main(int argc, char **argv){ CClientSocket socketClient; socketClient.Connect(lpszHost, dwPort); CKernelManager manager(&socketClient, strServiceName, g_dwServiceType, strKillEvent, lpszHost, dwPort); socketClient.setManagerCallBack(&manager);}
class CKernelManager : public CManager { public: virtual void OnReceive(LPBYTE lpBuffer, UINT nSize);}
OnReceive 函数是个虚函数
Connect 函数连接到服务器,同时创建了一个工作线程 WorkThread,bool CClientSocket::Connect(LPCTSTR lpszHost, UINT nPort){ connect(m_Socket, (SOCKADDR *)&ClientAddr, sizeof(ClientAddr)) m_hWorkerThread = (HANDLE)MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkThread, (LPVOID)this, 0, NULL, true);}
WorkThread 接受数据,调用 OnRead 成员函数处理数据,
DWORD WINAPI CClientSocket::WorkThread(LPVOID lparam) { int nSize = recv(pThis->m_Socket, buff, sizeof(buff), 0); if (nSize > 0) pThis->OnRead((LPBYTE)buff, nSize);}
OnRead 函数核查数据,转交给成员对象 m_pManager 处理,void CClientSocket::OnRead( LPBYTE lpBuffer, DWORD dwIoSize ){ m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());}
class CClientSocket{ private: CManager *m_pManager;}
m_pManager 是CClientSocket 的一个私有成员函数指针,指向 CManager, socketClient.setManagerCallBack(&manager); 这句代码设置 m_pManager 指向 CKernelManager 的对象,由于 CKernelManager 重新定义了 OnReceive 虚函数,所以 m_pManager->OnReceive 实际上调用了 CKernelManager 的 OnReceive
void CKernelManager::OnReceive(LPBYTE lpBuffer, UINT nSize){ switch (lpBuffer[0]) { case COMMAND_ACTIVED: InterlockedExchange((LONG *)&m_bIsActived, true); 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_REPLAY_HEARTBEAT: // 回复心跳包 break; }}
Loop_ScreenManager 创建了一个 CClientSocket 对象,连接到远程主机,然后创建了一个 CScreenManager 对象,关联到 socketClient
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 构造函数创建了 2 个 工作线程
CScreenManager::CScreenManager(CClientSocket *pClient):CManager(pClient){ 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);}
WorkThread 这个工作线程做的工作就是首先发送一个 TOKEN_BITMAPINFO 类型的数据包,远程主机收到这个包后,CMainFrame::NotifyProc 函数响应,调用 ProcessReceiveComplete 函数,case TOKEN_BITMAPINFO: 响应操作g_pConnectView->PostMessage(WM_OPENSCREENSPYDIALOG, 0, (LPARAM)pContext);自定义窗口消息被传到 gh0stView,CGh0stView::OnOpenScreenSpyDialog 函数被调用,此时一个 CScreenSpyDlg 对象被创建。然后做的工作就是不停地发送桌面位图到远程主机,
DWORD WINAPI CScreenManager::WorkThread(LPVOID lparam){ CScreenManager *pThis = (CScreenManager *)lparam;
pThis->sendBITMAPINFO(); // 等控制端对话框打开
pThis->WaitForDialogOpen();
pThis->sendFirstScreen(); try // 控制端强制关闭时会出错 { while (pThis->m_bIsWorking) pThis->sendNextScreen(); }catch(...){};
return 0;}
ControlThread 函数的主要目的是让客户机一直让显示器省电,黑屏
DWORD WINAPI CScreenManager::ControlThread(LPVOID lparam){ if (pThis->m_bIsBlankScreen) { SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 1, NULL, 0); SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)2); bIsScreenBlanked = true; }}
下面看客户端和服务器端是怎么进行屏幕控制的
回到 Loop_ScreenManager 函数,有一句代码 CScreenManager manager(&socketClient);
class CScreenManager : public CManager
CScreenManager 首先调用基类的构造函数 CManager(pClient)CManager 和 CClientSocket 是友元类,CManager 构造函数设置成员函数指针 m_pClient指向传进来的 pClient,同时设置 CClientSocket的成员函数指针 m_pManager 指向自己 this
CManager::CManager(CClientSocket *pClient){ m_pClient = pClient; m_pClient->setManagerCallBack(this);}void CClientSocket::setManagerCallBack( CManager *pManager ){ m_pManager = pManager;}
CScreenManager 重新定义了 CManager 的虚函数 OnReceive,当 CClientSocket 对象的线程函数收到数据包时候,DWORD WINAPI CClientSocket::WorkThread(LPVOID lparam) 会调用pThis->OnRead((LPBYTE)buff, nSize); OnRead 会调用m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());
void CScreenManager::OnReceive(LPBYTE lpBuffer, UINT nSize){ try { switch (lpBuffer[0]) { case COMMAND_NEXT: // 通知内核远程控制端对话框已打开,WaitForDialogOpen可以返回 NotifyDialogIsOpen(); break; case COMMAND_SCREEN_RESET: ResetScreen(*(LPBYTE)&lpBuffer[1]); break; case COMMAND_ALGORITHM_RESET: m_bAlgorithm = *(LPBYTE)&lpBuffer[1]; m_pScreenSpy->setAlgorithm(m_bAlgorithm); break; case COMMAND_SCREEN_CTRL_ALT_DEL: ::SimulateCtrlAltDel(); break; case COMMAND_SCREEN_CONTROL: { // 远程仍然可以操作 BlockInput(false); ProcessCommand(lpBuffer + 1, nSize - 1); BlockInput(m_bIsBlockInput); } break; case COMMAND_SCREEN_BLOCK_INPUT: //ControlThread里锁定 m_bIsBlockInput = *(LPBYTE)&lpBuffer[1]; break; case COMMAND_SCREEN_BLANK: m_bIsBlankScreen = *(LPBYTE)&lpBuffer[1]; break; case COMMAND_SCREEN_CAPTURE_LAYER: m_bIsCaptureLayer = *(LPBYTE)&lpBuffer[1]; m_pScreenSpy->setCaptureLayer(m_bIsCaptureLayer); break; case COMMAND_SCREEN_GET_CLIPBOARD: SendLocalClipboard(); break; case COMMAND_SCREEN_SET_CLIPBOARD: UpdateLocalClipboard((char *)lpBuffer + 1, nSize - 1); break; default: break; } }catch(...){}}
服务端发送的鼠标键盘事件由 ProcessCommand 函数处理,此函数的功能就是切换到当前桌面,模拟鼠标键盘输入SwitchInputDesktop()是自定义函数void CScreenManager::ProcessCommand( LPBYTE lpBuffer, UINT nSize ){ // 数据包不合法 if (nSize % sizeof(MSG) != 0) return;
SwitchInputDesktop();
// 命令个数 int nCount = nSize / sizeof(MSG);
// 处理多个命令 for (int i = 0; i < nCount; i++) { MSG *pMsg = (MSG *)(lpBuffer + i * sizeof(MSG)); switch (pMsg->message) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MOUSEMOVE: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: { POINT point; point.x = LOWORD(pMsg->lParam); point.y = HIWORD(pMsg->lParam); SetCursorPos(point.x, point.y); SetCapture(WindowFromPoint(point)); } break; default: break; }
switch(pMsg->message) { case WM_LBUTTONDOWN: mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); break; case WM_LBUTTONUP: mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); break; case WM_RBUTTONDOWN: mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0); break; case WM_RBUTTONUP: mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0); break; case WM_LBUTTONDBLCLK: mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); break; case WM_RBUTTONDBLCLK: mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0); mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0); break; case WM_MBUTTONDOWN: mouse_event(MOUSEEVENTF_MIDDLEDOWN, 0, 0, 0, 0); break; case WM_MBUTTONUP: mouse_event(MOUSEEVENTF_MIDDLEUP, 0, 0, 0, 0); break; case WM_MOUSEWHEEL: mouse_event(MOUSEEVENTF_WHEEL, 0, 0, GET_WHEEL_DELTA_WPARAM(pMsg->wParam), 0); break; case WM_KEYDOWN: case WM_SYSKEYDOWN: keybd_event(pMsg->wParam, MapVirtualKey(pMsg->wParam, 0), 0, 0); break; case WM_KEYUP: case WM_SYSKEYUP: keybd_event(pMsg->wParam, MapVirtualKey(pMsg->wParam, 0), KEYEVENTF_KEYUP, 0); break; default: break; } }}
