进程线程007 进程挂靠与跨进程读写内存

文章目录

    • 进程挂靠
      • 进程与线程的关系
      • 线程与进程如何关联
      • 为什么需要ApcState.Process
      • CR3的值可以随便改吗
      • 分析NtReadVirtualMemory函数
      • 总结
    • 跨进程读写内存
      • 跨进程操作
      • NtReadVirtualMemory流程解析

进程挂靠

进程与线程的关系

一个进程可以包含多个线程

一个进程至少要有一个线程

进程为线程提供资源,也就是提供CR3的值,CR3中存储的是页目录表的基址,CR3确定了线程能访问的内存也就确定了

mov eax,dowrd ptr ds:[0x12345678]

CPU如何解析0x12345678这个地址呢?

  1. CPU解析线性地址时要通过页目录表来找到对应的物理页,页目录表基址存在CR3寄存器中
  2. 当前的CR3的值来源于当前的进程(_KPROCESS.DirectoryTableBase(+0x018))

线程与进程如何关联

ETHREAD结构体:
	+0x034 ApcState
		+0x000 ApcListHead 
   		+0x010 Process 
   		+0x014 KernelApcInProgress
   		+0x015 KernelApcPending
   		+0x016 UserApcPending
	+0x220 ThreadsProcess

ETHREAD结构体+0x220的位置存储的就是当前线程所属的进程。

另外在KTHREAD结构体0x34的位置是子结构体ApcState,ApcState也有一个成员Process指向了当前线程所属的进程。

这就存在一个问题,同一个线程结构体里存了两份指针,这两份指针代表什么?

为什么需要ApcState.Process

下面分析SwapContext函数:

进程线程007 进程挂靠与跨进程读写内存_第1张图片

这里首先取出目标线程的ApcState.Process存到eax里,然后比较当前线程的ApcState.Process和目标线程的这个成员是否相同,如果不相同就说明不属于同一个进程

进程线程007 进程挂靠与跨进程读写内存_第2张图片

代码继续往下走,就会切换CR3的值

线程切换的时候,会比较KTHREAD结构体0x044处指定的EPROCESS是否为同一个,如果不是同一个,会将eax的值取出,赋给CR3。eax此时存储的是目标线程的ApcState.Process。这个时候就发生了进程切换

所以,线程需要的CR3的值来源于0x44处偏移指定的EPROCESS

总结:

0x220亲生父母:这个线程谁创建的

0x44 养父母:谁在为这个线程提供资源,也就是提供CR3

一般情况下,0x220与0x44指向的是同一个进程

CR3的值可以随便改吗

正常情况下,CR3的值是由养父母提供的,但CR3的值也可以改成和当前线程毫不相关的其他进程的DirectoryTableBase

线程代码:

mov cr3,A.DirectoryTableBase
mov eax,dword ptr ds:[0x12345678]		//A进程的0x12345678内存
mov cr3,B.DirectoryTableBase
mov eax,dword ptr ds:[0x12345678]		//B进程的0x12345678内存
mov cr3,C.DirectoryTableBase
mov eax,dword ptr ds:[0x12345678]		//C进程的0x12345678内存

将当前cr3的值改为其他进程,称为进程挂靠

分析NtReadVirtualMemory函数

接下来就通过分析NtReadVirtualMemory函数,来看看是怎么读取其他进程的内存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hprSWzoq-1581511027410)(assets/1581507589446.png)]

首先找到NtReadVirtualMemory函数

进程线程007 进程挂靠与跨进程读写内存_第3张图片

这个函数在内部调用了MmCopyVirtualMemory,继续跟进

进程线程007 进程挂靠与跨进程读写内存_第4张图片

真正COPY的函数是MiDoMappedCopy,继续跟进

进程线程007 进程挂靠与跨进程读写内存_第5张图片

在开始读取之前先调用了KeStackAttachProcess,也就是之前说过的进程挂靠,继续

进程线程007 进程挂靠与跨进程读写内存_第6张图片

这个函数又继续调用了KiAttachProcess,继续跟进

进程线程007 进程挂靠与跨进程读写内存_第7张图片

这里先将该线程的+0x44位置的ApcState.Process修改为要读取的进程的KPROCESS

进程线程007 进程挂靠与跨进程读写内存_第8张图片

然后又调用KiSwapProcess,真正的挂靠是通过这个函数实现的,继续跟进

进程线程007 进程挂靠与跨进程读写内存_第9张图片

真正关键的代码是上面两行,首先取出要读取进程的CR3,+0x18的位置是DirectoryTableBase页目录表基址,然后修改CR3为要读取进程的CR3

NtReadVirtualMemory流程总结

  1. NtReadVirtualMemory
  2. MmCopyVirtualMemory
  3. MiDoMappedCopy
  4. KeStackAttachProcess
  5. KiAttachProcess 修改当前线程的ApcState.Process为要挂靠的进程
  6. KiSwapProcess修改CR3为要挂靠的进程的CR3

既然修改CR3就可以读取目标进程的内存,那么NtReadVirtualMemory可不可以只修改CR3,不修改当前线程的ApcState.Process为要挂靠的进程。

答案是不可以。

回顾一下之前的为什么需要ApcState.Process的问题就会发现,当调用SwapContext进行线程切换的时候,给CR3赋值的时候赋的是ApcState.Process的值。

如果没有修改ApcState.Process,那就意味着ApcState.Process指向的不是挂靠的进程,而是自己的父进程,一旦这个时候发生线程切换并且在线程切换回来的时候,NtReadVirtualMemory读取的就是自己进程的内存了。

如果我们自己来写这个代码,在切换CR3后关闭中断,并且不调用会导致线程切换的API,就可以不用修改养父母的值

总结

正常情况下,当前线程使用的CR3是由其所属进程提供的(ETHREAD 0x44偏移处指定的EPROCESS),正因为如此,A进程中的线程只能访问A进程的内存

如果要让A进程中的内容能够访问B进程的内存,就必须修改CR3的值为B进程的页目录表基址(DirectoryTableBase),这就是所谓的进程挂靠

跨进程读写内存

跨进程的本质就是进程挂靠,也就是修改CR3的值为目标进程的页目录表基址

跨进程操作

A进程中的线程代码

mov cr3,B.DirectoryTableBase			//切换Cr3的值为B进程
mov eax,dword ptr ds:[0x12345678]		//将进程B 0x12345678的值存的eax中
mov dword ptr ds:[0x00401234],eax		//将数据存储到0x00401234中
mov cr3,A.DirectoryTableBase			//切换回Cr3的值	 

这段代码的问题在于,当我切换CR3为B进程的页目录基址时,读取的是B进程的内存,那么读取的这段数据该如何传递给A进程呢?不管将这段数据放在哪个位置,始终都是B进程的内存空间。

这里需要回顾一下进程的地址空间管理,低2GB是每个进程私有的,而高2GB的操作系统共享的。如果在B进程将读取的数据放到高2GB共享的内核空间,然后在切回CR3的时候,从高2GB取数据,就解决了这个问题

NtReadVirtualMemory流程解析

我们来看一下NtReadVirtualMemory是如何解决这个问题的:

进程线程007 进程挂靠与跨进程读写内存_第10张图片

  1. 首先切换CR3为目标进程的页目录基址
  2. 接着将数据复制到高2GB的某一个位置
  3. 然后切回CR3
  4. 最后从高2GB复制到目标位置

NtWriteVirtualMemory和NtReadVirtualMemory的执行流程类似

你可能感兴趣的:(Windows内核)