windows:重寫openprocess函數跨進程讀數據


   外掛、木馬、病毒等可能需要讀取其他進程的數據,windows提供了OpenProcess、ReadProcessMemory等函數。但越是大型的軟件,防護做的越好,大概率會做驅動保護,比如hook SSDT表等,這些系統調用都會先被過濾一次,導致返回的數據不是想要的;為了確保能讀到目標進程數據,最好重寫ReadProcessMemory;要想讀取其他進程的內容,思路大概有一下幾種:

  •  注冊PsSetLoadImageNotifyRoutine函數,其他進程加載模塊會時調用我們注冊的函數,這個時候已經進入目標進程的空間,可用memcpy復制數據
  •     KeStackAttachProcess可以切換到目標進程,然后用memcpy復制數據,最后調用KeUnstackDetachProcess切換回來
  •     利用進程ID查找EPROCESS,根據名稱得到目標EPROCESS后再讀取CR3,最用利用目標進程的CR3讀取其內存數據;

  前兩種方式要調用大家熟知的函數,目的性比較明顯,這些函數肯定會被大廠家重點關注,返回的結果可能在邏輯上有誤;而第三種方式僅僅根據ID查詢EPROCESS,相對前兩種更加“人畜無害”,被攔截的概率要小很多,今天詳細介紹第三種方式。

  1、為了驗證讀取數據是否正確,先在notepad隨便寫一些內容,然后用CE查看地址,如下,后續就選這個地址來測試了;

            

       這里多說幾句:CE為了防止被針對,自己重寫了關鍵的內存掃描/讀寫、進程打開函數,並未使用windows原生自帶的系統調用函數;

           

 

   2、遍歷進程,根據ID查找EPROCESS,進而得到進程名、CR3等關鍵信息,這塊也比較簡單,如下:

PEPROCESS LookupProcess(HANDLE Pid)
{
    PEPROCESS eprocess = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    Status = PsLookupProcessByProcessId(Pid, &eprocess);
    if (NT_SUCCESS(Status))
        return eprocess;
    return NULL;
}

    因不同版本EPROCESS結構體可能有細微區別,保險起見最好在windbg下通過dt _EPROCESS查看一下CR3的偏移,我這里是0x28,那么CR3的獲取代碼:

ULONG64 target_CR3 = *(PULONG64)((ULONG64)eproc + 0x28);

  3、目標CR3都拿到了,接下來就可以讀進程數據了,這里的環境是win10 x64,默認開啟了PAE;最初的想法很簡單,系統用的是9-9-9-9-12分頁,那么先把虛擬地址拆分,再用CR3一步一步跟蹤不就得到物理地址了嗎? 物理地址都有了,再讀取數據豈不是探囊取物般簡單了么?

     所以剛開始的代碼是這樣的:(1)先拆分虛擬地址,得到各個層級的偏移  (2)再仿造windbg種逐步計算的方式一步一步得到物理地址;  (3)這里有點要注意:& 和移位的優先級較低,注意使用括號,避免計算的邏輯錯誤;

    ULONG64 PML4E_index = (virtualAddress >> 39) & 0x1ff;
    ULONG64 PDPE_index = (virtualAddress >> 30) & 0x1ff;
    ULONG64 PDE_index = (virtualAddress >> 21) & 0x1ff;
    ULONG64 PTE_index = (virtualAddress >> 12) & 0x1ff;
    ULONG64 physical_offset = virtualAddress & 0xfff;
    ULONG64 PML4E = CR3 + (PML4E_index << 3);
    ULONG64 PDPE = (*(PULONG64)PML4E & 0x00000007fffff000) + (PDPE_index << 3);//上一級table entry的12~35位提供下一級table物理基地址的高24位,此時36~51是保留位,必須置0,低12位補零
    ULONG64 PDE = (*(PULONG64)PDPE & 0x00000007fffff000) + (PDE_index << 3);
    ULONG64 PTE = (*(PULONG64)PDE & 0x00000007fffff000) + (PTE_index << 3);
    ULONG64 physicalAddress = (*(PULONG64)PTE & 0x00000007fffff000) + physical_offset;

   然而一運行就藍屏,通過下斷點逐步跟蹤,發現罪魁禍首在這行:ULONG64 PDPE = (*(PULONG64)PML4E & 0x00000007fffff000) + (PDPE_index << 3)

   這樣代碼做的運算有好幾個,為了徹查到底是哪個運算導致的藍屏,繼續把代碼拆分地更細,最終發現導致藍屏的真凶:*(PULONG64)PML4E;

     這行代碼本身很簡單,就是把PML4E轉換成指針,再讀取其指向的內容,這么簡單的操作,為啥會導致藍屏了?這就牽扯到win10 x64下用戶態CR3和內核態CR3的區別了;本案例用的是驅動,在0環內核態執行,自然用的是內核態CR3。但代碼讀取數據的虛擬地址明顯是3環的,屬於用戶態。不同的CR3會映射到不同的物理地址,最終出錯。問題找到了,怎么解決了?既然是CR3不對,那么讀取內存之前先切換一下不就行了? 

          由於vs2019對於x64的程序不允許內聯匯編,這里單獨新建一個asm文件來切換CR3,如下:

_swapCR3 PROC
        mov cr3,rcx;
        ret
_swapCR3 Endp

  然后繼續單步調試,切換CR3的函數執行完后,CR3成功更改:

       

   但是在執行*(PULONG64)PML4E前查看時CR3又變回了內核態CR3:

        

   繼續執行不出意外又藍屏:

        

    為什么函數 _swapCR3 執行完后,CR3又變回了內核態?經過多次反復嘗試,發現都是斷點惹的禍:代碼里面設置了斷點,執行到斷點時會被系統進程接管,然后CR3自然就切換;

  既然ret后CR3又被改回,那么只能在匯編代碼里面讀取虛擬地址的內容了,這次重新寫一個_ReadVirtualMemory代碼,如下(這里直接使用mov rax,[rdx],還省去了繁瑣的地址轉換,這部分直接讓cpu自動做了):

_ReadVirtualMemory PROC
        push rax;
        push rbx;
        mov rbx,cr3;保存舊的CR3
        mov cr3,rcx;
        mov rax,[rdx];第二個參數,是VirtualMemory
        mov data,rax
        mov cr3,rbx;還原舊的CR3
        pop rbx;
        pop rax;
        ret
_ReadVirtualMemory Endp

 

  在這里讀的數據終於對了,效果如下:0x7ffd350ca309 地址前2個字節的內容確實是CC,和通過CE查找到的一致;

       

 

   完整代碼如下:主文件

#include "ReadProcessMemory.h"

ULONG64 data;

// 根據進程ID返回進程EPROCESS結構體,失敗返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
    PEPROCESS eprocess = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    Status = PsLookupProcessByProcessId(Pid, &eprocess);
    if (NT_SUCCESS(Status))
        return eprocess;
    return NULL;
}

VOID EnumProcess()
{
    PEPROCESS eproc = NULL;
    for (int temp = 0; temp < 100000; temp += 4)
    {
        eproc = LookupProcess((HANDLE)temp);
        if (eproc != NULL )
        {
            if (!strcmp(PsGetProcessImageFileName(eproc),"notepad.exe"))
            {
                DbgPrint("進程名: %s --> 進程PID = %d --> 父進程PPID = %d -->EPROCESS地址:%p \r\n", PsGetProcessImageFileName(eproc), PsGetProcessId(eproc),
                    PsGetProcessInheritedFromUniqueProcessId(eproc), eproc);
                ULONG64 target_CR3 = *(PULONG64)((ULONG64)eproc + 0x28);
                KdBreakPoint();
                _ReadVirtualMemory(target_CR3, 0x7ffd350ca309);
                DbgPrint("存儲內容:%c \r\n", data);
                ObDereferenceObject(eproc);
                return;
            }
            
        }
    }
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    EnumProcess();
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

  頭文件:

#ifndef READPROCESSMEMORY_
#define READPROCESSMEMORY_

#include <ntifs.h>

NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); //未公開的進行導出即可
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);//未公開進行導出

extern void Dbg_Break();
extern void _swapCR3(ULONG64 target_CR3);
extern void _ReadVirtualMemory(ULONG64 target_CR3, ULONG64 VirtualMemory);

extern ULONG64 data;

#endif

     asm文件:這里切換CR3的時候為了保險起見,建議先保存舊CR3,用完了再切回去;

EXTERN data:qword

.code
 
Dbg_Break Proc
        int 3
        ret
Dbg_Break Endp

_swapCR3 PROC
        mov cr3,rcx;
        ret
_swapCR3 Endp

_ReadVirtualMemory PROC
        push rax;
        push rbx;
        mov rbx,cr3;保存舊的CR3
        mov cr3,rcx;
        mov rax,[rdx];第二個參數,是VirtualMemory
        mov data,rax
        mov cr3,rbx;還原舊的CR3
        pop rbx;
        pop rax;
        ret
_ReadVirtualMemory Endp

END

   最后注意: VS2019在x64的默認調用約定是_stdcall,不是_fastcall,建議改成_fastcall;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM