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

进程挂靠

进程与线程的关系

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

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

进程为线程提供资源,也就是提供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函数:

在这里插入图片描述

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

在这里插入图片描述

代码继续往下走,就会切换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函数

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

真正关键的代码是上面两行,首先取出要读取进程的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是如何解决这个问题的:

在这里插入图片描述

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

NtWriteVirtualMemory和NtReadVirtualMemory的执行流程类似

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼手56

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值