介紹
? 病毒木馬植入模塊成功植入用戶計算機之后,便會啟動攻擊模塊來對用戶計算機數(shù)據(jù)實施竊取和回傳等動作。通常植入和攻擊是分開在不同模塊之中,有些病毒木馬具有模擬PE加載器的功能,它們把DLL 和exe 從內(nèi)存中直接加載到病毒木馬的內(nèi)存中執(zhí)行,不需要LoadLibrary 等現(xiàn)成的API 函數(shù)去操作,以此躲過殺毒軟件的攔截檢測。
? 幾乎所有的病毒木馬程序都會用到自啟動技術(shù),當程序感染之后的第一件事往往不是破壞,而是隱藏自己和如何執(zhí)行,即使馬上啟動注入模塊發(fā)動攻擊依然解決不了永久駐留的問題,在Windows系統(tǒng)下解決永久駐留的第一步就是伴隨系統(tǒng)啟動而啟動,即開機自啟動。設(shè)置為開機自啟動之后即使關(guān)閉計算機病毒程序仍然可以在下次開機的時候隨著系統(tǒng)啟動,由系統(tǒng)加載到內(nèi)存中運行,從而竊取用戶數(shù)據(jù)和隱私。開機自啟動技術(shù)是病毒木馬至關(guān)重要的技術(shù)。
直接加載內(nèi)存運行
? 很多病毒木馬具有PE加載器的功能,他們把DLL 或者EXE 等文件直接從內(nèi)存加載到病毒木馬的內(nèi)存中執(zhí)行,不需要通過LoadLibrary 等現(xiàn)成的API 中去操作,以此躲過殺毒軟件的攔截檢測。
? 在程序需要動態(tài)的調(diào)用DLL 文件,內(nèi)存加載運行技術(shù)可以把這些DLL 作為資源直接插入到自己的程序中,直接在內(nèi)存中加載運行即可,不需要將DLL 釋放到本地。內(nèi)存直接加載運行技術(shù)需要對PE文件結(jié)構(gòu)有較深的認識,明白PE格式中的導入、導出和重定位表的具體操作過程。PE加載器模擬PE文件的加載過程的核心就是對導入、導出和重定位表的操作過程。
-
實現(xiàn)流程
-
在DLL 文件中,根據(jù)PE結(jié)構(gòu)獲取其加載映像的大小SizeofImage ,并根據(jù)SizeOfImage 在自己的程序中申請可讀、可寫、可執(zhí)行的內(nèi)存,那么這塊內(nèi)存的首地址就是DLL 的加載基址
-
根據(jù)DLL 中的PE 結(jié)構(gòu)獲取其映像對齊大小SectionAlignment ,然后把DLL 文件數(shù)據(jù)按照SectionAlignment 復制到上述申請的可讀、可寫、可執(zhí)行的內(nèi)存中
-
根據(jù)PE結(jié)構(gòu)的重定位表,重新對重定位進行修正
-
根據(jù)PE結(jié)構(gòu)的導入表,加載所需的DLL ,并獲取導入函數(shù)的地址并寫入導入表中,修改DLL 加載基址ImageBase
-
根據(jù)PE結(jié)構(gòu)獲取DLL 的入口地址,然后構(gòu)造并調(diào)用DllMain 函數(shù),實現(xiàn)DLL 加載。
在直接加載EXE 文件時,不需要構(gòu)造DllMain 函數(shù),而是根據(jù)PE結(jié)構(gòu)獲取EXE 的入口地址偏移AddressOfEntryPoint 并計算出入口地址,然后直接跳轉(zhuǎn)到入口地址執(zhí)行即可,而且對于EXE 文件來說重定位表并不是必須的,即使沒有重定位表,EXE 也可以正常運行。因為對于EXE 進程來說,進程最早加載的模塊是EXE 模塊,所以它可以按照默認的加載基址加載到內(nèi)存。對于沒有重定位表的程序,只能把它加載到默認的加載基址上,如果默認加載基址已被占用,則直接內(nèi)存加載運行會失敗。
-
關(guān)鍵實現(xiàn)
// 模擬GetProcAddress獲取內(nèi)存DLL的導出函數(shù)
LPVOID MyGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName)
{
LPVOID lpFunc = NULL;
// 獲取導出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// 獲取導出表的數(shù)據(jù)
PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpFuncName = NULL;
PWORD lpAddressOfNameOrdinalsArray = (PWORD)((DWORD)pDosHeader + pExportTable->AddressOfNameOrdinals);
WORD wHint = 0;
PDWORD lpAddressOfFunctionsArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfFunctions);
DWORD dwNumberOfNames = pExportTable->NumberOfNames;
DWORD i = 0;
// 遍歷導出表的導出函數(shù)的名稱, 并進行匹配
for (i = 0; i < dwNumberOfNames; i++)
{
lpFuncName = (PCHAR)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);
if (0 == ::lstrcmpi(lpFuncName, lpszFuncName))
{
// 獲取導出函數(shù)地址
wHint = lpAddressOfNameOrdinalsArray[i];
lpFunc = (LPVOID)((DWORD)pDosHeader + lpAddressOfFunctionsArray[wHint]);
break;
}
}
return lpFunc;
}
// 修改PE文件重定位表信息
BOOL DoRelocationTable(LPVOID lpBaseAddress)
{
// 重定位表的結(jié)構(gòu):
// DWORD sectionAddress, DWORD size (包括本節(jié)需要重定位的數(shù)據(jù))
//注意重定位表的位置可能和硬盤文件中的偏移地址不同,應(yīng)該使用加載后的地址
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
// 判斷是否有 重定位表
if ((PVOID)pLoc == (PVOID)pDosHeader)
{
// 重定位表 為空
return TRUE;
}
while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //開始掃描重定位表
{
WORD *pLocData = (WORD *)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
//計算本節(jié)需要修正的重定位地址的數(shù)目
int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (int i = 0; i < nNumberOfReloc; i++)
{
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //這是一個需要修正的地址
{
// 32位dll重定位,IMAGE_REL_BASED_HIGHLOW
// 對于x86的可執(zhí)行文件,所有的基址重定位都是IMAGE_REL_BASED_HIGHLOW類型的。
DWORD* pAddress = (DWORD *)((PBYTE)pDosHeader + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
DWORD dwDelta = (DWORD)pDosHeader - pNtHeaders->OptionalHeader.ImageBase;
*pAddress += dwDelta;
}
}
//轉(zhuǎn)移到下一個節(jié)進行處理
pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}
return TRUE;
}
設(shè)置自啟動
注冊表
? 注冊表相當于操作系統(tǒng)的數(shù)據(jù)庫,記錄著系統(tǒng)中方方面面的數(shù)據(jù),其中也不乏直接或者間接導致開機自啟動的數(shù)據(jù)。下面是Run注冊表中添加程序路徑的方式,實現(xiàn)開機自啟。
WINADVAPI LSTATUS
APIENTRY
RegOpenKeyExW(
_In_ HKEY hKey, // 當前打開或者預定義的鍵
_In_opt_ LPCWSTR lpSubKey, // 指向一個非中斷字符串將要打開鍵的名稱
_In_opt_ DWORD ulOptions, // 保留,必須設(shè)置為 0
_In_ REGSAM samDesired, // 對指定鍵希望得到的訪問權(quán)限進行的訪問標記
_Out_ PHKEY phkResult // 指向一個變量的指針,該變量保存打開注冊表的句柄
);
WINADVAPI LSTATUS
APIENTRY
RegSetValueExW(
_In_ HKEY hKey, // 指定一個已打開項的句柄,或者一個標準項名
_In_opt_ LPCWSTR lpValueName, // 指向一個字符串的指針,該字符串包含了欲設(shè)置值的名稱
_Reserved_ DWORD Reserved, // 保留值,必須強制為 0
_In_ DWORD dwType, // 指定將存儲的數(shù)據(jù)類型
_In_reads_bytes_opt_(cbData) CONST BYTE* lpData,
// 指向一個緩沖區(qū),該緩沖區(qū)包含了為指定名稱存儲的數(shù)據(jù)
_In_ DWORD cbData // 指定由lpData參數(shù)所指向的數(shù)據(jù)大小,單位是字節(jié)
);
? 在Windows中提供了專門的開機自啟動注冊表,在每次開機完成后,他都會在這個注冊表鍵下遍歷鍵值,以獲取鍵值中的程序路徑,并創(chuàng)建進程自啟動程序,所以,要想修改注冊表實現(xiàn)自啟動,只需要在這個注冊表鍵下添加想要設(shè)置自啟動程序的程序路徑就可以了,這兩個路徑分別為:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
另外在修改注冊表的權(quán)限問題上,在編程實現(xiàn)上,要修改HKEY_LOCAL_MACHINE 主鍵的注冊表要求程序具有管理員的權(quán)限。而修改HKEY_CURRENT_USER 主鍵的注冊表,只需要用戶默認權(quán)限就可以實現(xiàn)。
BOOL RegCurrentUser(char *lpFileName, char *lpValueName)
{
// 默認權(quán)限
HKEY hKey;
// 打開注冊表鍵
RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)
// 修改注冊表值,實現(xiàn)開機自啟
RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName)))
// 關(guān)閉注冊表鍵
RegCloseKey(hKey);
return TRUE;
}
語言 |
方法 |
3416 |
I9rJq0D7sn |
v65bl |
游戲賺錢 |
4461 |
2007/07/21 01:48:00 |
// 需管理員權(quán)限的路徑
BOOL RegLocalMachine(char *lpFileName, char *lpValueName)
{
// 管理員權(quán)限
HKEY hKey;
// 打開注冊表鍵
RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)
// 修改注冊表值,實現(xiàn)開機自啟
RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName)))
// 關(guān)閉注冊表鍵
RegCloseKey(hKey);
return TRUE;
}
快速啟動目錄
快速啟動目錄是一種實現(xiàn)起來最為簡單的自啟動方法,不用修改任何系統(tǒng)數(shù)據(jù)。
BOOL SHGetSpecialFolderPathA(
HWND hwnd, // 窗口所有者的句柄
LPSTR pszPath, // 返回路徑的緩沖區(qū)
int csidl, // 系統(tǒng)路徑的CSIDL標識
BOOL fCreate // 指示文件夾是否要創(chuàng)建,false不創(chuàng)建 true創(chuàng)建
);
? Windows 系統(tǒng)有自帶的快速啟動的文件夾,他是最為簡單的自啟動方式,只要把程序放入到這個快速啟動文件夾中,系統(tǒng)在啟動時就會自動地加載并運行相應(yīng)的程序,實現(xiàn)開機自啟動功能。
? 快速啟動目錄并不是一個固定地目錄,每臺計算機地目錄都不相同,但是可以使用SHGetSpecialFolderPath 函數(shù)獲取Windows系統(tǒng)中快速啟動目錄地路徑,快速啟動目錄的CSIDL 標識值為CSIDL_STARTUP 。
BOOL AutoRun_Startup(char *lpszSrcFilePath, char *lpszDestFileName)
{
BOOL bRet = FALSE;
char szStartupPath[MAX_PATH] = {0};
char szDestFilePath[MAX_PATH] = {0};
// 獲取 快速啟動目錄 路徑
bRet = SHGetSpecialFolderPath(NULL, szStartupPath, CSIDL_STARTUP, TRUE);
printf("szStartupPath=%s\n", szStartupPath);
if (FALSE == bRet)
{
return FALSE;
}
// 構(gòu)造拷貝的 目的文件路徑
wsprintf(szDestFilePath, "%s\\%s", szStartupPath, lpszDestFileName);
// 拷貝文件到快速啟動目錄下
bRet = CopyFile(lpszSrcFilePath, szDestFilePath, FALSE);
if (FALSE == bRet)
{
return FALSE;
}
return TRUE;
}
系統(tǒng)服務(wù)
? 大多數(shù)在后臺運行的系統(tǒng)服務(wù)是隨著系統(tǒng)啟動而啟動的,系統(tǒng)進程自啟動是通過創(chuàng)建系統(tǒng)服務(wù)并設(shè)置服務(wù)啟動類型為自啟動來實現(xiàn)的,下面來看一下創(chuàng)建系統(tǒng)服務(wù)進程的原理和實現(xiàn)。
WINADVAPI SC_HANDLE
// 建立一個到服務(wù)控制管理器的連接,并打開指定的數(shù)據(jù)庫
WINAPI OpenSCManagerW(
_In_opt_ LPCWSTR lpMachineName,
//指向零終止字符串,指定目標計算機的名稱
_In_opt_ LPCWSTR lpDatabaseName,
//指向零終止字符串,指定將要打開的服務(wù)控制管理數(shù)據(jù)庫的名稱 SERVICES_ACTIVE_DATABASE
_In_ DWORD dwDesiredAccess
// 指向服務(wù)訪問控制管理器的權(quán)限
);
WINADVAPI SC_HANDLE
// 創(chuàng)建一個服務(wù)對象,并將其添加到指定的服務(wù)控制管理器數(shù)據(jù)庫中
WINAPI CreateServiceW(
_In_ SC_HANDLE hSCManager, // 指向服務(wù)控制管理器數(shù)據(jù)庫的句柄
_In_ LPCWSTR lpServiceName, // 要安裝服務(wù)的名稱
_In_opt_ LPCWSTR lpDisplayName, // 用戶界面用來識別服務(wù)的顯示名稱
_In_ DWORD dwDesiredAccess, // 對服務(wù)的訪問
_In_ DWORD dwServiceType, // 服務(wù)類型
_In_ DWORD dwStartType, // 服務(wù)啟動項
_In_ DWORD dwErrorControl,
// 當該服務(wù)啟動失敗時,指定產(chǎn)生錯誤嚴重程度以及應(yīng)采取的保護措施
_In_opt_ LPCWSTR lpBinaryPathName,
// 服務(wù)程序的二進制文件,它完全限定路徑。如果路徑中包含空格,則必須引用它,以便能正確地解析
_In_opt_ LPCWSTR lpLoadOrderGroup, // 指向加載排序組的名稱
_Out_opt_ LPDWORD lpdwTagId, // 指定的組中唯一的標記值變量
_In_opt_ LPCWSTR lpDependencies,
// 空分隔名稱的服務(wù)或加載順序組系統(tǒng)在這個服務(wù)開始之前的雙空終止數(shù)組的指針
_In_opt_ LPCWSTR lpServiceStartName, // 該服務(wù)應(yīng)運行的賬戶名稱
_In_opt_ LPCWSTR lpPassword // 由lpServiceStartName參數(shù)指定的賬戶名的密碼
);
WINADVAPI SC_HANDLE
// 打開一個已經(jīng)存在的服務(wù)
WINAPI OpenServiceW(
_In_ SC_HANDLE hSCManager, // 指向SCM數(shù)據(jù)庫句柄
_In_ LPCWSTR lpServiceName, // 要打開服務(wù)的名字
_In_ DWORD dwDesiredAccess // 指定服務(wù)權(quán)限
);
WINADVAPI BOOL
// 啟動服務(wù)
WINAPI StartServiceW(
_In_ SC_HANDLE hService,
// OpenService或者CreateService函數(shù)返回的服務(wù)句柄,需要有SERVICE_START
_In_ DWORD dwNumServiceArgs,
// 下一個形參的字符串個數(shù)
_In_reads_opt_(dwNumServiceArgs)
LPCWSTR *lpServiceArgVectors
// 傳遞給服務(wù)ServiceMain的參數(shù),如果沒有可以為NULL
);
WINADVAPI BOOL
// 將服務(wù)進程的主線程連接到服務(wù)控制管理器,該線程將作為調(diào)用過程的服務(wù)控制分派器線程
WINAPI StartServiceCtrlDispatcherW(
_In_ CONST SERVICE_TABLE_ENTRYW *lpServiceStartTable
// 指向SERVICE_TABLE_ENTRY結(jié)構(gòu)的指針,其中包含可在調(diào)用進程中執(zhí)行的每個服務(wù)的條目
);
- 通過
OpenSCManager 函數(shù)打開服務(wù)控制管理器數(shù)據(jù)庫并獲取數(shù)據(jù)庫的句柄
BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType)
{
BOOL bRet = TRUE;
char szName[MAX_PATH] = { 0 };
lstrcpy(szName, lpszDriverPath);
// 過濾掉文件目錄,獲取文件名
PathStripPath(szName);
SC_HANDLE shOSCM = NULL, shCS = NULL;
SERVICE_STATUS ss;
DWORD dwErrorCode = 0;
BOOL bSuccess = FALSE;
// 打開服務(wù)控制管理器數(shù)據(jù)庫
shOSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!shOSCM)
{
ShowError("OpenSCManager");
return FALSE;
}
if (0 != iOperateType)
{
// 打開一個已經(jīng)存在的服務(wù)
shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS);
if (!shCS)
{
ShowError("OpenService");
CloseServiceHandle(shOSCM);
shOSCM = NULL;
return FALSE;
}
}
switch (iOperateType)
{
case 0:
{
// 創(chuàng)建服務(wù)
// SERVICE_AUTO_START 隨系統(tǒng)自動啟動
// SERVICE_DEMAND_START 手動啟動
shCS = CreateService(shOSCM, szName, szName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
lpszDriverPath, NULL, NULL, NULL, NULL, NULL);
if (!shCS)
{
ShowError("CreateService");
bRet = FALSE;
}
break;
}
case 1:
{
// 啟動服務(wù)
if (!StartService(shCS, 0, NULL))
{
ShowError("StartService");
bRet = FALSE;
}
break;
}
case 2:
{
// 停止服務(wù)
if (!ControlService(shCS, SERVICE_CONTROL_STOP, &ss))
{
ShowError("ControlService");
bRet = FALSE;
}
break;
}
case 3:
{
// 刪除服務(wù)
if (!DeleteService(shCS))
{
ShowError("DeleteService");
bRet = FALSE;
}
break;
}
default:
break;
}
// 關(guān)閉句柄
if (shCS)
{
CloseServiceHandle(shCS);
shCS = NULL;
}
if (shOSCM)
{
CloseServiceHandle(shOSCM);
shOSCM = NULL;
}
return bRet;
}
- 系統(tǒng)服務(wù)程序的編寫
自啟動服務(wù)程序并不是普通的程序,而是要求程序創(chuàng)建服務(wù)入口點函數(shù),否則,不能創(chuàng)建系統(tǒng)服務(wù)。
調(diào)用系統(tǒng)函數(shù)StartServiceCtrlDispatcher 將程序的主線程連接到服務(wù)控制管理程序,服務(wù)控制管理程序啟動服務(wù)后,等待服務(wù)控制主函數(shù)調(diào)用StartServiceCtrlDispatcher 函數(shù),如果沒有調(diào)用該函數(shù)時設(shè)置服務(wù)入口點,則會報錯。
服務(wù)程序ServiceMain 入口函數(shù)的代碼
void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv)
{
g_ServiceStatusHandle = RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);
TellSCM(SERVICE_START_PENDING, 0, 1);
TellSCM(SERVICE_RUNNING, 0, 0);
while (TRUE)
{
Sleep(5000);
DoTask();
}
}
總結(jié)
? 關(guān)于自啟動技術(shù)最常見的是第一種注冊表,下面的相比于第一種有的適用行不是很強,或者編寫比較麻煩。這種技術(shù)也是殺軟重點監(jiān)測的技術(shù),對于殺軟來說,只要守住“入口”,就可以將病毒木馬擋在門外了,下一篇會繼續(xù)探索一下隱藏技術(shù),學習一點簡單的免殺。
|