天天看點

通過暴力搜尋PID周遊程序并擷取程序資訊

背景

通常我們在核心中使用 ZwQuerySystemInformation 函數來周遊程序子產品并擷取程序資訊,這種是通過正常的程序周遊方式,是以,有很多 Rootkit 程式會 HOOK 這個 ZwQuerySystemInformation 函數,過濾指定程序,進而實作程序的隐藏。

本文實作的程序周遊和擷取程序資訊并不打算使用 ZwQuerySystemInformation 這種方式,而是直接暴力搜尋程序的 PID,根據有效的 PID 擷取相應程序的資訊,進而實作程序的周遊。現在,我就來講解具體的實作過程和原理,這個程式支援 32 位和 64 位 Win 7 至 Win10 全平台。

函數介紹

PsLookupProcessByProcessId

PsLookupProcessByProcessId(
        _In_  HANDLE    ProcessId,        // 指定程序的程序ID。
        _Out_ PEPROCESS *Process        // 傳回指向ProcessId指定的程序的EPROCESS結構的引用指針。
    );

// 傳回指向ProcessId指定的程序的EPROCESS結構的引用指針。      

如果對 PsLookupProcessByProcessId 的調用成功,PsLookupProcessByProcessId 會增加Process參數中傳回的對象的引用計數。是以,當驅動程式完成使用 Process 參數時,驅動程式必須調用 ObDereferenceObject 來取消引用從 PsLookupProcessByProcessId 例程接收的Process參數。

IoQueryFileDosDeviceName

IoQueryFileDosDeviceName(
        _In_  PFILE_OBJECT             FileObject,                    // 指向檔案的檔案對象
        _Out_ POBJECT_NAME_INFORMATION *ObjectNameInformation        // 傳回指向新配置設定的OBJECT_NAME_INFORMATION結構的指針。 這個結構用MS-DOS裝置名稱資訊成功傳回填寫
    );
    // 成功,則傳回STATUS_SUCCESS;否則錯誤。

/*
    typedef struct _OBJECT_NAME_INFORMATION {
        UNICODE_STRING Name;
    } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
*/      

實作原理

暴力搜尋 PID 這個不難了解,即 PID 從 4 開始,步長為 4,一直周遊到 0x1000,因為程序的 PID 總是為 4 的倍數,是以,我們設定周遊的步長為 4。

然後,我們根據 PID,調用 PsLookupProcessByProcessId 函數擷取 EPROCESS 程序結構體,因為 EPROCESS 這個結構體裡存儲着程序所有的資訊,我們可以從這個 EPROCESS 結構體中擷取我們想要的資訊,例如程序的父程序、程序名、路徑、程序雙連結清單鍊等等。調用完 PsLookupProcessByProcessId 擷取 EPROCESS 之後,記得調用 ObDereferenceObject 來釋放對象。

在不同的系統版本中,EPROCESS 這個結構體定義也不相同,是以不建議使用固定的偏移位址來擷取程序資訊,而應該使用提供的 API 函數從 EPROCESS 結構體中擷取。例如:

擷取程序 PID,可以使用 PsGetProcessId 函數。

擷取程序的父程序 PID,可以使用PsGetProcessInheritedFromUniqueProcessId 函數。

擷取程序名稱,可以使用 PsGetProcessImageFileName 函數。

擷取程序路徑,可以先試用 PsReferenceProcessFilePointer 函數擷取檔案指針對象,然後再調用 IoQueryFileDosDeviceName 擷取 Dos 路徑,最後還要記得使用 ObDereferenceObject 函數來釋放對象。

編碼實作

程序周遊:

// 周遊程序
    NTSTATUS EnumProcess()
    {
        NTSTATUS status = STATUS_SUCCESS;
        ULONG i = 0;
        PEPROCESS pEProcess = NULL;
        // 開始周遊
        for (i = 4; i < 0x10000; i = i + 4)
        {
            status = PsLookupProcessByProcessId((HANDLE)i, &pEProcess);
            if (NT_SUCCESS(status))
            {
                // 從程序結構中擷取程序資訊
                GetProcessInfo(pEProcess);
                ObDereferenceObject(pEProcess);
            }
        }
        return status;
    }      
// 從程序結構中擷取程序資訊
    VOID GetProcessInfo(PEPROCESS pEProcess)
    {
        NTSTATUS status = STATUS_SUCCESS;
        HANDLE hParentProcessId = NULL, hProcessId = NULL;
        PCHAR pszProcessName = NULL;
        PVOID pFilePoint = NULL;
        POBJECT_NAME_INFORMATION pObjectNameInfo = NULL;
        // 擷取父程序 PID
        hParentProcessId = PsGetProcessInheritedFromUniqueProcessId(pEProcess);
        // 擷取程序 PID
        hProcessId = PsGetProcessId(pEProcess);
        // 擷取程序名稱
        pszProcessName = PsGetProcessImageFileName(pEProcess);
        // 擷取程序程式路徑
        status = PsReferenceProcessFilePointer(pEProcess, &pFilePoint);
        if (NT_SUCCESS(status))
        {
            status = IoQueryFileDosDeviceName(pFilePoint, &pObjectNameInfo);
            if (NT_SUCCESS(status))
            {
                // 顯示
                DbgPrint("PEPROCESS=0x%p, PPID=%d, PID=%d, NAME=%s, PATH=%ws\n",
                    pEProcess, hParentProcessId, hProcessId, pszProcessName, pObjectNameInfo->Name.Buffer);
            }
            ObDereferenceObject(pFilePoint);
        }    
    }      

繼續閱讀