Yo 歡迎回來,我是 CX330。昨天介紹了 Shellcode injection 的技術,其中我們的第一個步驟就是要枚舉所有的進程。而我們目前到現在,使用的方式都是用 CreateToolHelp32Snapshot
,而現在,我們要另外介紹兩種不同的方式,會分成上下兩篇來介紹。
那我們開始囉!
完整程式碼可於此處找到:https://black-hat-zig.cx330.tw/Advanced-Malware-Techniques/Process-Enumeration/enum_processes/
中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」
本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!
本系列文章中使用的 Zig 版本號為 0.14.1。
歡迎,又有一個新的 Windows API 來和大家見面了。我們先來看一下微軟的文檔吧!
BOOL EnumProcesses(
[out] DWORD *lpidProcess,
[in] DWORD cb,
[out] LPDWORD lpcbNeeded
);
注意,這邊函數會回傳 PIDs 的陣列回來,但是它並不會包含與之對應的進程名稱。這對我們來說就會有個麻煩,就是比如我們選定了目標進程為 notepad.exe
,但現在只有 PID,我們便無法確認哪個才是我們的目標。
要解決這樣的問題,我們需要再多用幾個不同的 Windows API,分別是 OpenProcess
、EnumProcessModules
、GetModuleBaseNameW
。這邊來介紹一下它們。
OpenProcess
PROCESS_QUERY_INFORMATION
和 PROCESS_VM_READ
存取權限的 PID 句柄EnumProcessModules
GetModuleBaseNameW
那使用 EnumProcess
來沒舉進程究竟有什麼優點呢?舉例而言,如果我們是使用 CreateToolhelp32Snapshot
的方法,會建立一個快照並進行字串比對,以確定進程名稱是否與目標進程匹配。但問題是,當有多個相同進程的實例(Instance)在不同的權限等級(Privilege levels)上運行時,我們無法透過字串的比較來區分它們。例如,有些 svchost.exe
進程以普通使用者權限運行,而其他則以較高的權限運行,但在字串比較中無法確定 svchost.exe
的權限等級。因此,唯一能確定該進程是否運行於高權限的方法是去判斷 OpenProcess
呼叫是否會失敗(如果我們的程式是跑在普通使用者權限的話)。如果失敗,則代表該進程是運行在高權限中的。
但如果我們是使用 EnumProcess
,它在過程中就會提供我們 PID 和句柄,我們再透過 PID 去獲取進程名稱,這樣就不會有無法判斷進程的權限的問題了,因為如果打不開,就代表是高權限的。
在這個函數中,我們會把所有被列舉的進程的名稱和 PID 都打印出來。當然,只有和我們的程式相同或更低權限的進程的資訊才會被獲取,如果是更高權限的進程則無法獲取到資訊,這會導致 ERROR_ACCESS_DENIED
錯誤。
我們的判斷標準是去看 OpenProcess
是否可以正確被呼叫,可以開啟的則可以作為目標,反之則不然。
一開始,我們會先去把系統中所有進程的 PID 都寫到我們的變數裡。
// Get the array of PIDs in the system
if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) {
std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()});
return;
}
注意,這邊的 processes
是 var processes: [1024 * 2]DWORD = undefined;
。接著,我們可以去計算我們獲取到的進程數量。
// Calculate the number of elements in the array returned
const number_of_pids = return_len / @sizeOf(DWORD);
std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids});
最後,就是針對我們的進程數量做迭代,去把每個進程的 PID 都用 OpenProcess
來嘗試獲取句柄,如果成功,再使用 EnumProcessModules
去獲取該進程中的模組,然後再用 GetModuleBaseNameW
從模組獲取進程的名稱。
for (0..number_of_pids) |i| {
if (processes[i] != 0) {
// Open a process handle with limited access
if (OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, processes[i])) |h_process| {
var h_module: HMODULE = undefined;
// Get a handle of a module in the process
if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) {
var proc_name_buffer: [MAX_PATH]u16 = undefined;
// Get the name of the process
if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) {
// Find the null terminator
var name_len: usize = 0;
for (proc_name_buffer) |char| {
if (char == 0) break;
name_len += 1;
}
// Convert UTF-16 to UTF-8 for printing
var utf8_name: [MAX_PATH * 2]u8 = undefined;
if (std.unicode.utf16LeToUtf8(&utf8_name, proc_name_buffer[0..name_len])) |utf8_len| {
std.debug.print("[{:0>3}] Process \"{s}\" - Of Pid: {}\n", .{ i, utf8_name[0..utf8_len], processes[i] });
} else |_| {
std.debug.print("[{:0>3}] Process [encoding error] - Of Pid: {}\n", .{ i, processes[i] });
}
} else {
std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
} else {
std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() });
}
_ = CloseHandle(h_process);
}
}
}
剛剛的 printProcesses
函數只是把所有的進程都打印出來,但這個函數則是剛剛的函數的再包裝,會回傳指定進程的句柄。它需要傳入一個目標的進程名稱,getRemoteProcessHandle
則會回傳該遠端進程的句柄。
它的大部分邏輯都和 printProcesses
差不多,不過在 OpenProcess
的權限設置中,我們會把它從 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
改為 PROCESS_ALL_ACCESS
,這樣我們拿到的句柄就會有更多的權限,讓我們再後續操作的時候可以使用更為強大的進程句柄。
然後裡面會使用字串的比對,去比較是否為我們需要的目標進程。
// Compare process names
if (std.mem.eql(u16, proc_name, proc_name_buffer[0..name_len])) {
return ProcessInfo{
.pid = processes[i],
.handle = h_process,
};
}
我們先來看一下使用 printProcesses
的執行結果會長怎樣吧。
測試完成後,我們會把 printProcesses
給註解掉,因為我們只需要獲取目標進程的句柄而已,我們來測試一下 getRemoteProcessHandle
吧。我們在這邊把目標進程設定為 svchost.exe
試試。
成功獲取到了我們目標的進程了!
結束囉!今天跟大家介紹了使用 EnumProcess
來進行進程枚舉的方法,明天會再來介紹另外一種方法~。那就先這樣囉!
如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!