函数NtReadVirtualMemory:该函数从指定进程的指定地址空间读取指定大小的内存 到 当前进程。(This function copies the specified address range from the specified process into the specified address range of the current process.)
1 2 3 4 5 6 7
NTSTATUS __stdcall NtReadVirtualMemory( IN HANDLE ProcessHandle, //从该进程读取内存 IN PVOID BaseAddress, //指向指定进程中要读取的基本地址的指针 OUT PVOID Buffer, //接收从指定进程的地址空间读取的内容 IN SIZE_T BufferSize, //从指定进程中读取的字节数 OUT PSIZE_T NumberOfBytesRead OPTIONAL //指向一个接收实际读取字节数的变量 )
NTSTATUS NtReadVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID Buffer, IN SIZE_T BufferSize, OUT PSIZE_T NumberOfBytesRead OPTIONAL ) /*++ Routine Description: This function copies the specified address range from the specified process into the specified address range of the current process. Arguments: ProcessHandle - Supplies an open handle to a process object. BaseAddress - Supplies the base address in the specified process to be read. Buffer - Supplies the address of a buffer which receives the contents from the specified process address space. BufferSize - Supplies the requested number of bytes to read from the specified process. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. Return Value: NTSTATUS. --*/ { SIZE_T BytesCopied; KPROCESSOR_MODE PreviousMode; PEPROCESS Process; NTSTATUS Status; PETHREAD CurrentThread;
PAGED_CODE();
// Get the previous mode and probe output argument if necessary. CurrentThread = PsGetCurrentThread (); PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb); if (PreviousMode != KernelMode) {
// If the buffer size is not zero, then attempt to read data from the // specified process address space into the current process address // space. BytesCopied = 0; Status = STATUS_SUCCESS; if (BufferSize != 0) {
// Reference the target process. Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_READ, PsProcessType, PreviousMode, (PVOID *)&Process, NULL); // If the process was successfully referenced, then attempt to // read the specified memory either by direct mapping or copying // through nonpaged pool. if (Status == STATUS_SUCCESS) {
Status = MmCopyVirtualMemory (Process, BaseAddress, PsGetCurrentProcessByThread(CurrentThread), Buffer, BufferSize, PreviousMode, &BytesCopied); // Dereference the target process. ObDereferenceObject(Process); } }
// If requested, return the number of bytes read. if (ARGUMENT_PRESENT(NumberOfBytesRead)) { try { *NumberOfBytesRead = BytesCopied;
NTSTATUS __stdcall MmCopyVirtualMemory( IN PEPROCESS FromProcess, //从该进程复制数据到ToProcess进程 IN CONST VOID *FromAddress, //从该地址开始复制数据 IN PEPROCESS ToProcess, //从FromProcess进程复制数据到该进程 OUT PVOID ToAddress, //将数据复制到该地址 IN SIZE_T BufferSize, //要复制的数据大小字节 IN KPROCESSOR_MODE PreviousMode, //函数调用的先前模式 OUT PSIZE_T NumberOfBytesCopied //指向一个变量,该变量保存实际复制的数据大小 )
通过逆向分析,将函数NtReadVirtualMemory压栈的顺序为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
NTSTATUS __stdcall NtReadVirtualMemory( IN HANDLE ProcessHandle, //从该进程读取内存 IN PVOID BaseAddress, //指向指定进程中要读取的基本地址的指针 OUT PVOID Buffer, //接收从指定进程的地址空间读取的内容 IN SIZE_T BufferSize, //从指定进程中读取的字节数 OUT PSIZE_T NumberOfBytesRead OPTIONAL //指向一个接收实际读取字节数的变量 ) push NumberOfBytesRead; //NumberOfBytesCopied push PreviousMode; //PreviousMode push BufferSize; //BufferSize push Buffer; //ToAddress push KTHREAD.ApcState.Process;//ToProcess push BaseAddress; //FromAddress push ProcessHandle->Object; //FromProcess
NTSTATUS __stdcall MiDoMappedCopy( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead )
NTSTATUS MmCopyVirtualMemory( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesCopied ) { NTSTATUS Status; PEPROCESS ProcessToLock;
// 断言 BufferSize != 0 if (BufferSize == 0) { ASSERT (FALSE); // No one should call with a zero size. return STATUS_SUCCESS; }
// Make sure the process still has an address space. // 确保进程还活着 if (ExAcquireRundownProtection (&ProcessToLock->RundownProtect) == FALSE) { return STATUS_PROCESS_IS_TERMINATING; }
// If the buffer size is greater than the pool move threshold, // then attempt to write the memory via direct mapping.
// #define POOL_MOVE_THRESHOLD 511 // 如果要复制的字节数大于511,采用内存映射方式 if (BufferSize > POOL_MOVE_THRESHOLD) { Status = MiDoMappedCopy(FromProcess, FromAddress, ToProcess, ToAddress, BufferSize, PreviousMode, NumberOfBytesCopied);
// If the completion status is not a working quota problem, // then finish the service. Otherwise, attempt to write the memory through nonpaged pool. if (Status != STATUS_WORKING_SET_QUOTA) { goto CompleteService; }
*NumberOfBytesCopied = 0; }
// There was not enough working set quota to write the memory via // direct mapping or the size of the write was below the pool move // threshold. Attempt to write the specified memory through nonpaged pool. Status = MiDoPoolCopy(FromProcess, FromAddress, ToProcess, ToAddress, BufferSize, PreviousMode, NumberOfBytesCopied);
// Dereference the target process. CompleteService:
// Indicate that the vm operation is complete. ExReleaseRundownProtection (&ProcessToLock->RundownProtect);
NTSTATUS __stdcall MiDoMappedCopy( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead )
NTSTATUS MiDoMappedCopy( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead ) /*++ Routine Description: This function copies the specified address range from the specified process into the specified address range of the current process. Arguments: FromProcess - Supplies an open handle to a process object. FromAddress - Supplies the base address in the specified process to be read. ToProcess - Supplies an open handle to a process object. ToAddress - Supplies the address of a buffer which receives the contents from the specified process address space. BufferSize - Supplies the requested number of bytes to read from the specified process. PreviousMode - Supplies the previous processor mode. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. Return Value: NTSTATUS. --*/ { KAPC_STATE ApcState; SIZE_T AmountToMove; ULONG_PTR BadVa; LOGICAL Moving; LOGICAL Probing; LOGICAL LockedMdlPages; CONST VOID *InVa; SIZE_T LeftToMove; PSIZE_T MappedAddress; SIZE_T MaximumMoved; PMDL Mdl; PFN_NUMBER MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1]; PVOID OutVa; LOGICAL MappingFailed; LOGICAL ExceptionAddressConfirmed;
// Map the data into the system part of the address space, then copy it. LeftToMove = BufferSize; AmountToMove = MaximumMoved;
Probing = FALSE;
// Initializing BadVa & ExceptionAddressConfirmed is not needed for // correctness but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables.
BadVa = 0; ExceptionAddressConfirmed = FALSE;
#if 0
// It is unfortunate that Windows 2000 and all the releases of NT always // inadvertently returned from this routine detached, as we must maintain this behavior even now. KeDetachProcess();
#endif
while (LeftToMove > 0) { if (LeftToMove < AmountToMove) { // Set to move the remaining bytes. AmountToMove = LeftToMove; }
// We may be touching a user's memory which could be invalid, declare an exception handler. // 读3环内存,确保可读,设置异常处理 try {
// Probe to make sure that the specified buffer is accessible in the target process. if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; }
// Initialize MDL for request. MmInitializeMdl (Mdl, (PVOID)InVa, AmountToMove);
if (MappedAddress == NULL) { MappingFailed = TRUE; ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES); }
// Deattach from the FromProcess and attach to the ToProcess. // deattach 即分离,已经读好了 KeUnstackDetachProcess (&ApcState); KeStackAttachProcess (&ToProcess->Pcb, &ApcState);
// Now operating in the context of the ToProcess. if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; }
// If the failure occurred during the move operation, determine // which move failed, and calculate the number of bytes // actually moved. *NumberOfBytesRead = BufferSize - LeftToMove;
if (Moving == TRUE) { if (ExceptionAddressConfirmed == TRUE) { *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)BadVa - (ULONG_PTR)FromAddress); } }
NTSTATUS __stdcall MiDoPoolCopy( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead )
NTSTATUS MiDoPoolCopy( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead ) /*++ Routine Description: This function copies the specified address range from the specified process into the specified address range of the current process. Arguments: ProcessHandle - Supplies an open handle to a process object. BaseAddress - Supplies the base address in the specified process to be read. Buffer - Supplies the address of a buffer which receives the contents from the specified process address space. BufferSize - Supplies the requested number of bytes to read from the specified process. PreviousMode - Supplies the previous processor mode. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. Return Value: NTSTATUS. --*/ { KAPC_STATE ApcState; SIZE_T AmountToMove; LOGICAL ExceptionAddressConfirmed; ULONG_PTR BadVa; PEPROCESS CurrentProcess; LOGICAL Moving; LOGICAL Probing; CONST VOID *InVa; SIZE_T LeftToMove; SIZE_T MaximumMoved; PVOID OutVa; PVOID PoolArea; LONGLONG StackArray[COPY_STACK_SIZE]; ULONG FreePool;
PAGED_CODE();
ASSERT (BufferSize != 0);
// Get the address of the current process object and initialize copy // parameters. CurrentProcess = PsGetCurrentProcess();
InVa = FromAddress; OutVa = ToAddress;
// Allocate non-paged memory to copy in and out of. MaximumMoved = MAX_MOVE_SIZE; if (BufferSize <= MAX_MOVE_SIZE) { MaximumMoved = BufferSize; }
MaximumMoved = MaximumMoved >> 1; if (MaximumMoved <= sizeof(StackArray)) { PoolArea = (PVOID)&StackArray[0]; break; } } while (TRUE); }
// Initializing BadVa & ExceptionAddressConfirmed is not needed for // correctness but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables. BadVa = 0; ExceptionAddressConfirmed = FALSE;
// Copy the data into pool, then copy back into the ToProcess. LeftToMove = BufferSize; AmountToMove = MaximumMoved; Probing = FALSE;
#if 0
// It is unfortunate that Windows 2000 and all the releases of NT always // inadvertently returned from this routine detached, as we must maintain // this behavior even now. KeDetachProcess();
#endif
while (LeftToMove > 0) {
if (LeftToMove < AmountToMove) {
// Set to move the remaining bytes. AmountToMove = LeftToMove; }
// We may be touching a user's memory which could be invalid, // declare an exception handler. try {
// Probe to make sure that the specified buffer is accessible in // the target process. if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; }
// Now operating in the context of the ToProcess. if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; }
// If an exception occurs during the move operation or probe, // return the exception code as the status value. KeUnstackDetachProcess (&ApcState);
if (FreePool) { ExFreePool (PoolArea); } if (Probing == TRUE) { return GetExceptionCode();
}
// If the failure occurred during the move operation, determine // which move failed, and calculate the number of bytes // actually moved. *NumberOfBytesRead = BufferSize - LeftToMove;
if (Moving == TRUE) {
// The failure occurred writing the data. if (ExceptionAddressConfirmed == TRUE) { *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)(BadVa - (ULONG_PTR)FromAddress)); }
KeStackAttachProcess函数:将当前线程附加到目标进程的地址空间。(This function attaches a thread to a target process’ address space and returns information about a previous attached process.)
函数原型如下:
1 2 3 4
VOID __stdcall KeStackAttachProcess( IN PRKPROCESS Process, //指向目标进程对象的指针 OUT PRKAPC_STATE ApcState //指向 KAPC_STATE 结构的不透明指针。调用方必须从非分页池或从调用方的线程堆栈中为此结构分配存储空间。 )
VOID KeStackAttachProcess( IN PRKPROCESS Process, OUT PRKAPC_STATE ApcState ) /*++ Routine Description: This function attaches a thread to a target process' address space and returns information about a previous attached process. Arguments: Process - Supplies a pointer to a dispatcher object of type process. Return Value: None. --*/ {
// // If the target process is not the current process, then attach the // target process. Otherwise, return a distinguished process value to // indicate that an attach was not performed. // 如果尝试 attach 自己,那么设置ApcState->Process 等于1 // 否则就正常 attach
if (Thread->ApcState.Process == Process) { ApcState->Process = (PRKPROCESS)1;
} else {
// // Raise IRQL to dispatcher level and lock dispatcher database. // 提升 IRQL 等级到 dispatcher level 并锁定 dispatcher database。 // If the current thread is attached to a process, then save the // current APC state in the callers APC state structure. Otherwise, // save the current APC state in the saved APC state structure, and // return a NULL process pointer. // 如果当前线程已经 attach 了别的进程,那么保存当前 APC state 到调用者的 APC state 结构 // 否则,保存当前 APC state 到 SavedApcState 结构,并设置 ApcState->Process = NULL // // N.B. The dispatcher lock is released ay the attach routine. // dispatcher lock 在 attach 函数中释放
函数KiAttachProcess:This function attaches a thread to a target process’ address space. The dispatcher database lock must be held when this routine is called.(调用该函数时必须保持调度程序数据库锁起来)
函数原型如下:
1 2 3 4 5 6
VOID __stdcall KiAttachProcess( IN PRKTHREAD Thread, //指向当前线程的指针 IN PKPROCESS Process, //指向要附加的进程 IN KIRQL OldIrql, //先前的IRQL OUT PRKAPC_STATE SavedApcState //输出值,保存备用APC状态 )
VOID KiAttachProcess( IN PRKTHREAD Thread, IN PKPROCESS Process, IN KIRQL OldIrql, OUT PRKAPC_STATE SavedApcState ) /*++ Routine Description: This function attaches a thread to a target process' address space. N.B. The dispatcher database lock must be held when this routine is called. Arguments: Thread - Supplies a pointer to a dispatcher object of type thread. Process - Supplies a pointer to a dispatcher object of type process. OldIrql - Supplies the previous IRQL. SavedApcState - Supplies a pointer to the APC state structure that receives the saved APC state. Return Value: None. --*/ {
ASSERT(Process != Thread->ApcState.Process); // Bias the stack count of the target process to signify that a // thread exists in that process with a stack that is resident. Process->StackCount += 1;
// Save current APC state and initialize a new APC state. // KiMoveApcState 的功能是将参数1的APC队列复制一份到参数2 // 这里这么做的原因是 attach 前要把父进程的 APC 队列保存起来,保存到 SavedApcState KiMoveApcState(&Thread->ApcState, SavedApcState); // InitializeListHead 的功能是让链表头指向自己 InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]); // ApcListHead[0] InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]); // ApcListHead[1] Thread->ApcState.Process = Process; Thread->ApcState.KernelApcInProgress = FALSE; Thread->ApcState.KernelApcPending = FALSE; Thread->ApcState.UserApcPending = FALSE; if (SavedApcState == &Thread->SavedApcState) { Thread->ApcStatePointer[0] = &Thread->SavedApcState; // 原进程的 APC state Thread->ApcStatePointer[1] = &Thread->ApcState; // attach 进程的 APC state Thread->ApcStateIndex = 1; // 表示现在已经 attach }
// If the target process is in memory, then immediately enter the // new address space by loading a new Directory Table Base. Otherwise, // insert the current thread in the target process ready list, inswap // the target process if necessary, select a new thread to run on the // the current processor and context switch to the new thread. if (Process->State == ProcessInMemory) { // It is possible that the process is in memory, but there exist // threads in the process ready list. This can happen when memory // management forces a process attach. NextEntry = Process->ReadyListHead.Flink; while (NextEntry != &Process->ReadyListHead) { OutThread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry); RemoveEntryList(NextEntry); OutThread->ProcessReadyQueue = FALSE; KiReadyThread(OutThread); NextEntry = Process->ReadyListHead.Flink; }
VOID KiMoveApcState( IN PKAPC_STATE Source, OUT PKAPC_STATE Destination ) /*++ Routine Description: This function moves the APC state from the source structure to the destination structure and reinitializes list headers as appropriate. Arguments: Source - Supplies a pointer to the source APC state structure. Destination - Supplies a pointer to the destination APC state structure. Return Value: None. --*/ {
PLIST_ENTRY First; PLIST_ENTRY Last;
// Copy the APC state from the source to the destination. *Destination = *Source; if (IsListEmpty(&Source->ApcListHead[KernelMode]) != FALSE) { InitializeListHead(&Destination->ApcListHead[KernelMode]);