分享
 
 
 

JIURL玩玩Win2k内存篇 内存共享(二) CopyOnWrite

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

JIURL玩玩Win2k内存篇 内存共享(二) CopyOnWrite

作者: JIURL

主页: http://jiurl.yeah.net

日期: 2003-7-30

CopyOnWrite

对于同一个可执行文件运行的两个进程,或者被多个进程共享的动态链接库,只读共享代码部分的物理页是没有问题的。而对于向代码页中写(调试器就可能向代码页中写),这将会影响其他共享这个代码页的进程的正确执行。或者数据页比如说初始化了的全局变量所在的页,每个进程可能会写入不同的数据,也将影响其他共享这个页的进程的正常运行。为了使程序正常运行,应该使每个进程的可能被写的页映射到自己的物理页上,这样就不会影响别的进程了。如果为每个进程每个可能会被写入的页都直接分配新的物理页的话,很有可能会造成不必要的浪费,如果这个一个进程自始至终都没有向某页写入数据的话,那么分配该页所花的执行时间,该页所占的物理内存,都是没有用的。Win2k 为了避免这种浪费,提高效率,使用叫做 Copy On Write 的机制来处理这种情况。这是叫做 Lazy Evaluation 技术的一部分。

Copy On Write 就是对于一页,多个使用进程共享,直到一个进程要向该页写入数据的时候,系统会给该进程一个新的物理页,并把原来的页的数据复制过来,更新该进程的页表项,使该进程映射新的物理页,数据会写入该进程新的自己的物理页中。这样就不会影响原来的数据页。而其他共享该页的进程仍然可以继续共享,直到他们也试图写入数据。

Win2k 中 Copy On Write 机制的主要应用是,可以方便调试器向一个进程的代码页写入东西,而不会影响别的共享这个代码页的进程。某些数据页。

Copy On Write 机制的实现。Win2k 把需要 Copy On Write 的页的页表项(PTE)的标志位中的读写位设为只读,并设置 CopyOnWrite 标志位。这样当一个进程向该页写入数据的时候,因为页表项设为只读,所以会引起 Page-Fault 异常(Exception)。从而使 CPU 转去执行异常处理程序,异常处理程序检查页表项发现设置了 CopyOnWrite 标志,就会完成分配新物理页,更新进程的页表项,把新的页表项的读写标志设为读写等工作。最后 CPU 重新执行引起异常的指令,这时该指令所写的虚拟地址已经是新的物理页了,并且该虚拟地址的页表项的标志也设为了可写,于是就可以顺利执行。下面我们针对 x86 CPU 做更详细的说明。x86 CPU 的页表项定义如下

struct _HARDWARE_PTE_X86 (sizeof=4)

bits0-0 Valid

bits1-1 Write

bits2-2 Owner

bits3-3 WriteThrough

bits4-4 CacheDisable

bits5-5 Accessed

bits6-6 Dirty

bits7-7 LargePage

bits8-8 Global

bits9-11 reserved

bits12-31 PageFrameNumber

首先要说明的是,这个格式是由 CPU 定义的,CPU 将按照这个定义,对每一位做出解释,然后决定处理方式。其中要注意的是 bits1-1 Write 这将决定该页是否只读,为 0 表示只读,为 1 表示可读可写。bits9-11 reserved 这3位,CPU 没有定义,留给操作系统使用。Win2k 用 bits9-9 CopyOnWrite 来表示是否使用 CopyOnWrite,该位为0表示不使用,为1表示使用。

当某页的页表项中标志位设置了只读,和 CopyOnWrite 之后,当某条指令向该页中写入的时候,比如指令 MOV AddressInCopyOnWritePage,1 ,执行这条指令时,CPU 会自动通过页目录和页表把 虚拟地址AddressInCopyOnWritePage 转换成物理地址,在地址转换过程中,CPU 在从页表项得到物理页地址的同时,会进行页保护检查,比如看该页表项是否有效,是否是只读等等。在这里我们指令中地址的页表项设置了只读标志,于是就会引发异常。异常也是由 CPU 实现的。这里引起的是一个 Page Fault 异常,它的中断号是 0xe (十进制14),需要注意的是 Page Fault 的中断号是 0xe 这是由 CPU 定义的( x86 CPU 的 从 0 - 31 这32个中断是由 CPU 定义的,CPU 将根据这个定义做相应工作)。在发生异常时,CPU 自动把一些寄存器压入堆栈,然后根据中断号,(通过IDTR找到中断描述符表)在中断描述符表中找到相应的中断描述符,根据中断描述符中的地址,转到异常处理程序。中断描述符是由Win2k设置,异常处理程序也是由Win2k决定。对于 Win2k Build 2195 来说,中断 0xe 的处理程序是 ntoskrnl!KiTrap0E 地址在 804648a4 。当转到 KiTrap0E 时,CPU 已经在堆栈中压入了下面的内容

|-------------|

| EFLAGS |

|-------------|

| CS |

|-------------|

| EIP |

|-------------|

| Error Code |

|-------------|<---- [ ESP ]

page-fault 异常 (#PF) 的 Error Code 定义如下( CPU 定义 )

| 3 | 2 | 1 | 0 |

+---------------------------------------------------+

| Reserved |RSVD|U/S|R/W| P |

+---------------------------------------------------+

P 0 错误由无效页引起

1 错误由违反页保护引起

W/R 0 引起错误的内存访问是读

1 引起错误的内存访问是写

U/S 0 访问错误时处理器处在管理模式

1 访问错误时处理器处在用户模式

需要说明的是堆栈中压入的 EIP 就是引发异常的指令地址,将来将根据这个地址重新执行该指令。而寄存器 cr2 中是引发异常时访问的地址。

#PF异常处理程序 KiTrap0E(由Win2k提供)将会调用 ntoskrnl!MmAccessFault ,MmAccessFault 根据是写操作引起的异常,以及CR2中的访问地址,计算出相应的 PDE,PTE地址,检查 PTE 发现设置了CopyOnWrite 标志(这个标志是由 Win2k 定义的,也是由它来处理),最终会调用 ntoskrnl!MiCopyOnWrite 来完成 Copy On Write 的相关工作。比如分配新的物理页,改变页表项中的物理地址,改变页表项的标志,把只读改为可读可写。

执行完异常处理程序之后,CPU 重新执行引起异常的指令,这时指令可以正常执行了。至此 Copy On Write 已经被实现了。

下面我们通过一个例子来观察 Copy On Write 的实际情况

//----------------------------------------------------------------------

#include <windows.h>

#include <stdio.h>

#include <conio.h>

#pragma data_seg(".jiurl")

char JiurlSegData[32]="aaaaaaaaaaaaaaaaaaaaaaaa";

#pragma data_seg()

void main()

{

printf("JiurlSegData Address: 0x%08x\n",JiurlSegData);

printf("JiurlSegData Pte Address: 0x%08x\n",

((ULONG)(JiurlSegData)>>12)*4+0xC0000000);

printf("JiurlSegData: %s\n",JiurlSegData);

printf("\n");

printf("Input New String to JiurlSegData\n:");

scanf("%s",JiurlSegData);

printf("JiurlSegData: %s\n",JiurlSegData);

getch();

}

//----------------------------------------------------------------------

这个程序一个叫 ".jiurl" 的节中,有一个初始化了的全局变量(把这个全局变量放在单独的一个节中,是为了避免受其他变量的影响)。分析编译生成的 PE 可执行文件,就可以看到这个节,以及这个节的属性,读,写,初始化数据。程序运行时,将输出这个全局变量的地址,以及根据这个地址计算出的该地址的PTE的地址,并 printf 全局变量中的内容。然后等待输入一个新的字符串写入全局变量中。我们将运行这个程序的两个实例,使用 SoftICE 来观察 Copy On Write 。需要强调一点,运行程序之后,Ctrl+D 动作快点,因为时间长了系统很有可能把该页换出物理内存。

这个程序的名字叫做 CopyOnWrite-Er

1. 运行一个 CopyOnWrite-Er ,再运行一个 CopyOnWrite-Er

首先用 SoftICE 的 addr 命令列出当前运行的进程

:addr

CR3 LDT Base:Limit KPEB Addr PID Name

00030000 8141E020 0008 System

04E2B000 810F75C0 008C smss

06562000 810E8C40 00A8 csrss

07547000 810CC0C0 00BC winlogon

078E9000 810C14E0 00D8 services

078FA000 810BFD60 00E4 lsass

00ABD000 8109F200 0170 svchost

00324000 810924C0 0190 svchost

00564000 81054880 0204 Explorer

024CF000 8108B960 0250 internat

07FEC000 82F873C0 01F4 conime

036C1000 8331C180 034C NOTEPAD

0328F000 84130860 01F8 NOTEPAD

018EF000 8323F180 0100 CopyOnWrite-Er

06AB8000 83091020 02BC CopyOnWrite-Er

*00030000 8046BB60 0000 Idle

看到了2个 CopyOnWrite-Er ,进程ID 分别是 0100 和 02BC

这2个 CopyOnWrite-Er 的输出都是

_________________________________________

JiurlSegData Address: 0x0040a000

JiurlSegData Pte Address: 0xc0001028

JiurlSegData: aaaaaaaaaaaaaaaaaaaaaaaa

Input New String to JiurlSegData

_________________________________________

CopyOnWrite-Er 100

使用 addr 命令转换到 CopyOnWrite-Er 100 的地址空间

:addr 100

再看一下 CopyOnWrite-Er 100 的段的情况,看到了 ".jiurl" 段,

地址范围在 0040A000 大小为 00000020

和程序的输出 JiurlSegData Address: 0x0040a000 相符

:map32 -u

Owner Obj Name Obj# Address Size Type

CopyOnWrit.text 0001 001B:00401000 00004D73 CODE RO

CopyOnWrit.rdata 0002 0023:00406000 00000890 IDATA RO

CopyOnWrit.data 0003 0023:00407000 000021C8 IDATA RW

CopyOnWrit.jiurl 0004 0023:0040A000 00000020 IDATA RW

kernel32 .text 0001 001B:77E61000 0005D1AE CODE RO

kernel32 .data 0002 0023:77EBF000 00001A30 IDATA RW

kernel32 .rsrc 0003 0023:77EC1000 00070000 IDATA RO

kernel32 .reloc 0004 0023:77F31000 0000359C IDATA RO

ntdll .text 0001 001B:77F81000 00042492 CODE RO

ntdll ECODE 0002 001B:77FC4000 00004371 CODE RO

ntdll PAGE 0003 001B:77FC9000 00003983 CODE RO

ntdll .data 0004 0023:77FCD000 00002350 IDATA RW

ntdll .rsrc 0005 0023:77FD0000 00026D08 IDATA RO

ntdll .reloc 0006 0023:77FF7000 00001DA8 IDATA RO

根据我们计算出的 JiurlSegData Pte Address: 0xc0001028 ,显示该页表项

:dd c0001028 l 10

0010:C0001028 06AC7225 00000000 00000000 00000000 %r..............

可以看到

CopyOnWrite-Er 100 的 JiurlSegData 地址范围的 页表项值为 06AC7225

注意物理页为 06AC7 ,标志为 225(hex)= 0010 0010 0101 (bin)

bits1-1 Write 标志为0,表示只读。bits9-9 CopyOnWrite 标志为1,表示使用 Copy On Write 。

CopyOnWrite-Er 2bc

转换到 CopyOnWrite-Er 2bc 的地址空间

:addr 2bc

根据我们计算出的 JiurlSegData Pte Address: 0xc0001028 ,显示该页表项

:dd c0001028 l 10

0010:C0001028 06AC7225 00000000 00000000 00000000 %r..............

可以看到

CopyOnWrite-Er 2bc 的 JiurlSegData 地址范围的 页表项值也为 06AC7225

首先就是它和 CopyOnWrite-Er 100 映射了同样的物理页 06AC7 ,页中的内容当然是一样的。他们共享了这个数据页。标志位同样也说明了使用 Copy On Write。

2. 向 CopyOnWrite-Er 2bc 的全局变量写入

向 CopyOnWrite-Er 2bc 的全局变量写入 bbbbbbbbb

程序输出如下

_________________________________________

Input New String to JiurlSegData

:bbbbbbbbb

JiurlSegData: bbbbbbbbb

_________________________________________

可以看到正确读出了输入的 bbbbbbbbb

转换到 CopyOnWrite-Er 2bc 的地址空间

:addr 2bc

显示 CopyOnWrite-Er 2bc 的 0xc0001028 处的页表项

:dd c0001028 l 10

0010:C0001028 04427067 00000000 00000000 00000000 gpB.............

该页表项值为 04427067 ,可以看到映射的物理页变成了 04427 ,这说明分配并使用了新的物理页。

标志为 067(hex)= 0000 0110 0111 (bin) 可以看到

bits1-1 Write 标志为1,表示可写了。bits9-9 CopyOnWrite 标志为0。

对于 CopyOnWrite-Er 2bc ,Copy On Write 机制得到了验证

对于 CopyOnWrite-Er 100 的情况

转换到 CopyOnWrite-Er 100 的地址空间

:addr 100

显示 CopyOnWrite-Er 100 的 0xc0001028 处的页表项

:dd c0001028 l 10

0010:C0001028 06AC7225 00000000 00000000 00000000 %r..............

可以看到该页表项的值仍然是 06AC7225 ,说明

CopyOnWrite-Er 100 继续使用原来的物理页

这也符合前面描述的 Copy On Write 机制

3. 向 CopyOnWrite-Er 100 的全局变量写入

向 CopyOnWrite-Er 2bc 的全局变量写入 cccccccccc

程序输出如下

_________________________________________

Input New String to JiurlSegData

:cccccccccc

JiurlSegData: cccccccccc

_________________________________________

可以看到正确读出了输入的 cccccccccc

转换到 CopyOnWrite-Er 100 的地址空间

:addr 100

显示 CopyOnWrite-Er 100 的 0xc0001028 处的页表项

:dd c0001028 l 10

0010:C0001028 00D07067 00000000 00000000 00000000 gp..............

可以看到,写入导致了使用新的物理页,并且改变了标志

转换到 CopyOnWrite-Er 2bc 的地址空间

:addr 2bc

显示 CopyOnWrite-Er 2bc 的 0xc0001028 处的页表项

:dd c0001028 l 10

0010:C0001028 04427067 00000000 00000000 00000000 gpB.............

还是刚才的

通过这个例子,我们看到了 Win2k 使用 Copy On Write 的效果

4. PfnDataBaseEntry 和 Copy On Write

我们再来观察一下被共享的物理页的 PfnDataBaseEntry 中的情况。

运行一个 CopyOnWrite-Er 我们观察它的物理页的页帧号

:addr CopyOnWrite-Er

:dd c0001028 l 10

0010:C0001028 06AC7225 00000000 00000000 00000000 %r..............

页帧号为 06AC7

计算 06AC7 的页帧号数据库项的地址,页帧号数据库的首地址在全局变量 MmPfnDatabase 中,

在我机子上它的值为 81456000 ,每项大小 0x18 个字节

我们可以看到 物理页 6ac7

/*08*/ uint32 blink / share count = 00000001

/*0D*/ byte page state = 06

:dd 81456000+18*6ac7 l 18

0010:814F62A8 0000009B E301F2A8 00000001 00010608 ................

0010:814F62B8 907B64B8 00004727 00000000 C03B37D8 .d{.'G.......7;.

说明该物理页处在 Active 状态,并且共享数为1

我们又运行了两个 CopyOnWrite-Er

我们可以看到 物理页 6ac7

/*08*/ uint32 blink / share count = 00000003

/*0D*/ byte page state = 06

:dd 81456000+18*6ac7 l 18

0010:814F62A8 0000009B E301F2A8 00000003 00010608 ................

0010:814F62B8 907B64B8 00004727 00000000 C03B37D8 .d{.'G.......7;.

说明该物理页处在 Active 状态,并且共享数增为3

对一个 CopyOnWrite-Er 写入数据

我们可以看到 物理页 6ac7

/*08*/ uint32 blink / share count = 00000002

/*0D*/ byte page state = 06

:dd 81456000+18*6ac7 l 18

0010:814F62A8 0000009B E301F2A8 00000002 00010608 ................

0010:814F62B8 907B64B8 00004727 00000000 C03B37D8 .d{.'G.......7;.

并且共享减为2,写数据的进程由于 Copy On Write 而不再使用本物理页

对剩下所有的 CopyOnWrite-Er 写入数据

我们可以看到 物理页 6ac7

/*08*/ uint32 blink / share count = 000068B9

/*0D*/ byte page state = 02

:dd 81456000+18*6ac7 l 18

0010:814F62A8 FFFFFFFF E301F2A8 000068B9 00000208 .........h......

0010:814F62B8 907B64B8 00004727 00000000 C03B37D8 .d{.'G.......7;.

没有进程再使用这个物理页,所以状态变为了 Standby ,而且 /*08*/ 由于状态的变化含义也变成了 blink

广泛存在的 Copy On Write

观察一个进程的页表,你会发现除了我们设计的例子之外,很难找到其他的设置了 CopyOnWrite 标志的页表项(还是可以找到的,我用一个小工具就在记事本进程中找到了几项,标志位为 205),难道 Copy On Write 只有如此少量的应用?实际上 Win2k 中大量使用了 Copy On Write 我们使用 SoftICE 就可以观察到。

当向 Copy On Write 页写时,会引发 Page-Fault 异常,CPU 会执行异常处理程序 ntoskrnl!KiTrap0E ntoskrnl!KiTrap0E 会调用 ntoskrnl!MmAccessFault ,分析 ntoskrnl!MmAccessFault 的汇编代码,可以知道 CopyOnWrite 引起的异常将由 ntoskrnl!MiCopyOnWrite 处理。分析汇编代码我们还可以看出 MiCopyOnWrite 函数有两个传入参数,第一个参数是引发异常的指令访问的虚拟地址,我们叫做 BadAddress 。第二个参数是该虚拟地址的页表项地址,我们叫做 PteAddress 。MiCopyOnWrite 使用 fastcall 调用协议,第一个参数 BadAddress 通过 ecx 寄存器传入,第二个参数 PteAddress 通过 edx 寄存器传入。只要向 CopyOnWrite 页写入数据,我们就应该可以断到 MiCopyOnWrite ,并且从 ecx 和 edx 当中看到 引发异常的指令访问的虚拟地址 和 该地址的 PTE 地址。

我们在 SoftICE 中下这样的断点

BPX 8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx l 4 ; dd edx l 4"

其中,8044BE22 是 MiCopyOnWrite 的入口地址( Win2k Build 2195 ),通过使用 kd , u MiCopyOnWrite 获得。断到之后,让 SoftICE 执行 db ((ffdff124->0)+44)->0+1fc l 10 ,这将显示当前运行的进程名。然后再显示出 ecx 和 edx 中的内容。

当我们运行一个程序时,这里我运行了一记事本程序,SoftICE 窗口立刻弹出,说明有 Copy On Write 发生。

// 这就是下的断点。

:bl

00) * BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx l 4 ; d

:be 0

// 退出 SoftICE 窗口

// 打开一个文本文件,运行了记事本程序,SoftICE 窗口立刻弹出

NTICE: Load32 START=1000000 SIZE=10000 KPEB=80D5AD40 MOD=NOTEPAD

NTICE: Load32 START=77F80000 SIZE=79000 KPEB=80D5AD40 MOD=ntdll

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=4.09 seconds)

// 可以看到当前进程是 NOTEPAD.EXE

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

// ecx 也就是 BadAddress 为 77FCD34C

0023:77FCD34C FFFFFFFF 00000000 00000000 00000000 ................

// edx 也就是 PteAddress 为 C01DFF34 ,可以看到页表项中标志位的 CopyOnWrite位 被设置,Write位 没// 有被设置

0023:C01DFF34 006AA225 00000000 00000000 00000000 %.j.............

// 只有 NOTEPAD 和 ntdll ,BadAddress= 77FCD34C 在 ntdll .data 范围内。

// 只有 NOTEPAD 和 ntdll 说明了其他的 dll 还没有被载入,也就是说在 ntdll 被载入,执行 DllMain 中// 对 ntdll 初始化时,就向使用了 CopyOnWrite 的数据页中写入数据,这将导致数据页的页表项中的物理页// 为新分配的物理页,并且标志将从 ReadOnly CopyOnWrite 变成 ReadWrite。

:map32 -u

Owner Obj Name Obj# Address Size Type

NOTEPAD .text 0001 001B:01001000 000065CA CODE RO

NOTEPAD .data 0002 0023:01008000 00001944 IDATA RW

NOTEPAD .rsrc 0003 0023:0100A000 00005238 IDATA RO

ntdll .text 0001 001B:77F81000 00042492 CODE RO

ntdll ECODE 0002 001B:77FC4000 00004371 CODE RO

ntdll PAGE 0003 001B:77FC9000 00003983 CODE RO

ntdll .data 0004 0023:77FCD000 00002350 IDATA RW

ntdll .rsrc 0005 0023:77FD0000 00026D08 IDATA RO

ntdll .reloc 0006 0023:77FF7000 00001DA8 IDATA RO

// Ctrl+D , SoftICE 立刻(322ms后)又被中断,ET=322.23 microseconds

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=322.23 microseconds)

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:77FCE340 00000000 00000000 00000000 00000000 ................

0023:C01DFF38 0200B225 00000000 00000000 00000000 %...............

NTICE: Load32 START=76AF0000 SIZE=3E000 KPEB=80D5AD40 MOD=comdlg32

NTICE: Load32 START=77C50000 SIZE=4A000 KPEB=80D5AD40 MOD=SHLWAPI

NTICE: Load32 START=77F40000 SIZE=3C000 KPEB=80D5AD40 MOD=gdi32

NTICE: Load32 START=77E60000 SIZE=D5000 KPEB=80D5AD40 MOD=kernel32

NTICE: Load32 START=77DF0000 SIZE=64000 KPEB=80D5AD40 MOD=user32

NTICE: Load32 START=77D90000 SIZE=5A000 KPEB=80D5AD40 MOD=advapi32

NTICE: Load32 START=77D20000 SIZE=6F000 KPEB=80D5AD40 MOD=rpcrt4

NTICE: Load32 START=77B30000 SIZE=8A000 KPEB=80D5AD40 MOD=COMCTL32

NTICE: Load32 START=77560000 SIZE=240000 KPEB=80D5AD40 MOD=shell32

NTICE: Load32 START=78000000 SIZE=46000 KPEB=80D5AD40 MOD=MSVCRT

// ET=4.43 milliseconds

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=4.43 milliseconds)

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:78033000 0003A09A 00039BC6 00039BD2 00039BE2 ................

0023:C01E00CC 00714225 00000000 00000000 00000000 %Bq.............

NTICE: Load32 START=777C0000 SIZE=1D000 KPEB=80D5AD40 MOD=WINSPOOL

// ET=2.41 milliseconds

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=2.41 milliseconds)

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:77EBF060 00000000 00000000 00000000 00000000 ................

0023:C01DFAFC 04B56225 00000000 00000000 00000000 %b..............

// ET=250.33 microseconds

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=250.33 microseconds)

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:77EC0800 00000000 00000000 00000000 00000000 ................

0023:C01DFB00 04737225 00000000 00000000 00000000 %rs.............

// 几个中断之后,可以看到每个中断的 PteAddress 中的页表项的标志 ReadOnly CopyOnWrite 都被设置

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=2.07 milliseconds)

0023:80D5AF3C 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:77E484D0 00000000 00000000 00000000 00000000 ................

0023:C01DF920 03B1A225 00000000 00000000 00000000 %...............

// 我们看这时载入的 dll,已经载入了比刚才多的多的 dll ,这说明了刚才的判断是对的,在程序载入 dll // 时,dll 初始化时就引发了 Copy On Write

:map32 -u

Owner Obj Name Obj# Address Size Type

NOTEPAD .text 0001 001B:01001000 000065CA CODE RO

NOTEPAD .data 0002 0023:01008000 00001944 IDATA RW

NOTEPAD .rsrc 0003 0023:0100A000 00005238 IDATA RO

comdlg32 .text 0001 001B:76AF1000 00029D3A CODE RO

comdlg32 .data 0002 0023:76B1B000 00003668 IDATA RW

comdlg32 .rsrc 0003 0023:76B1F000 0000B2A8 IDATA RO

comdlg32 .reloc 0004 0023:76B2B000 000022B0 IDATA RO

shell32 .text 0001 001B:77561000 0011A686 CODE RO

shell32 .data 0002 0023:7767C000 00003AE8 IDATA RW

shell32 .rsrc 0003 0023:77680000 00110ED8 IDATA RO

shell32 .reloc 0004 0023:77791000 0000EA90 IDATA RO

WINSPOOL .text 0001 001B:777C1000 00016B56 CODE RO

WINSPOOL .data 0002 0023:777D8000 00002AF0 IDATA RW

WINSPOOL .rsrc 0003 0023:777DB000 000009A8 IDATA RO

WINSPOOL .reloc 0004 0023:777DC000 00000FC8 IDATA RO

COMCTL32 .text 0001 001B:77B31000 000643C2 CODE RO

COMCTL32 .data 0002 0023:77B96000 000004A0 IDATA RW

COMCTL32 .rsrc 0003 0023:77B97000 0001E948 IDATA RO

COMCTL32 .reloc 0004 0023:77BB6000 00003784 IDATA RO

SHLWAPI .text 0001 001B:77C51000 0004243C CODE RO

SHLWAPI .data 0002 0023:77C94000 00000C28 IDATA RW

SHLWAPI .rsrc 0003 0023:77C95000 000010E8 IDATA RO

SHLWAPI .reloc 0004 0023:77C97000 0000256C IDATA RO

rpcrt4 .text 0001 001B:77D21000 0005FABA CODE RO

rpcrt4 .orpc 0002 001B:77D81000 00007CFC CODE RO

rpcrt4 .data 0003 0023:77D89000 00000F5C IDATA RW

rpcrt4 .rsrc 0004 0023:77D8A000 000003D0 IDATA RO

rpcrt4 .reloc 0005 0023:77D8B000 00003800 IDATA RO

advapi32 .text 0001 001B:77D91000 0004F330 CODE RO

advapi32 .data 0002 0023:77DE1000 00002E4C IDATA RW

advapi32 .rsrc 0003 0023:77DE4000 00001250 IDATA RO

advapi32 .reloc 0004 0023:77DE6000 0000381C IDATA RO

user32 .text 0001 001B:77DF1000 0005692A CODE RO

user32 .data 0002 0023:77E48000 00000E60 IDATA RW

user32 .rsrc 0003 0023:77E49000 0000742C IDATA RO

user32 .reloc 0004 0023:77E51000 00002ACC IDATA RO

kernel32 .text 0001 001B:77E61000 0005D1AE CODE RO

kernel32 .data 0002 0023:77EBF000 00001A30 IDATA RW

kernel32 .rsrc 0003 0023:77EC1000 00070000 IDATA RO

kernel32 .reloc 0004 0023:77F31000 0000359C IDATA RO

gdi32 .text 0001 001B:77F41000 0003649E CODE RO

gdi32 .data 0002 0023:77F78000 00000CFC IDATA RW

gdi32 .rsrc 0003 0023:77F79000 00000398 IDATA RO

gdi32 .reloc 0004 0023:77F7A000 0000151C IDATA RO

ntdll .text 0001 001B:77F81000 00042492 CODE RO

ntdll ECODE 0002 001B:77FC4000 00004371 CODE RO

ntdll PAGE 0003 001B:77FC9000 00003983 CODE RO

ntdll .data 0004 0023:77FCD000 00002350 IDATA RW

ntdll .rsrc 0005 0023:77FD0000 00026D08 IDATA RO

ntdll .reloc 0006 0023:77FF7000 00001DA8 IDATA RO

MSVCRT .text 0001 001B:78001000 0003174D CODE RO

MSVCRT .rdata 0002 0023:78033000 000075B4 IDATA RO

MSVCRT .data 0003 0023:7803B000 00006D84 IDATA RW

MSVCRT .rsrc 0004 0023:78042000 000003A8 IDATA RO

MSVCRT .reloc 0005 0023:78043000 00002600 IDATA RO

:bd *

// 还有更多的中断,不过这些对我们已经够了,所以关中断。

我们看到了这些 dll 的数据页初始都是设置了 CopyOnWrite 标志,只是由于在载入时,DllMain 对 dll 初始化时就向数据页中写数据,使得页表项的标志从 255 变成了 67。这就解释了为什么我们在一个进程的页表中几乎看不到设置 CopyOnWrite 标志的页。

下面我们用 SoftICE 观察对页表项的改变,以及找到引起异常的指令

:bl

00) BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx l 4 ; d

// 仍然在 MiCopyOnWrite 的入口地址下断点

// 运行一个记事本程序,断到了

NTICE: Load32 START=1000000 SIZE=10000 KPEB=8409E700 MOD=NOTEPAD

NTICE: Load32 START=77F80000 SIZE=79000 KPEB=8409E700 MOD=ntdll

Break due to BPX #0008:8044BE22 DO "db ((ffdff124->0)+44)->0+1fc l 10 ; dd ecx

l 4 ; dd edx l 4" (ET=2.08 seconds)

0023:8409E8FC 4E 4F 54 45 50 41 44 2E-45 58 45 00 00 00 00 00 NOTEPAD.EXE.....

0023:77FCD34C FFFFFFFF 00000000 00000000 00000000 ................

0023:C01DFF34 006AA225 00000000 00000000 00000000 %.j.............

// 可以看到 BadAddress= 77FCD34C ,PteAddress=C01DFF34 ,Pte= 006AA225

// 我们在 PteAddress 的 4个字节内存上 下一个断点,这样,如果改变 Pte 就会被我们断到

:bpmd c01dff34 w

// 被断到了

Break due to BPMD #0023:C01DFF34 W DR3 (ET=280.80 microseconds)

MSR LastBranchFromIp=80001ECA

MSR LastBranchToIp=8044BF69

:bd 1

// 关闭断点1

// 显示 Pte

:dd c01dff34 l 10

0023:C01DFF34 00FEA067 00000000 00000000 00000000 g...............

// 可以看到 Pte 的改变,物理页从 006AA 变成了 新的物理页 00FEA

// 标志 从 225 变成了 067

// 反汇编向 Pte 写入新内容的指令

// 使用 kd, u 8044bf6f 可以看到 MiCopyOnWrite+15c ,也就是说 MiCopyOnWrite 改变了 Pte 中的内容

:u 8044bf6f l 10

0008:8044BF6F MOV [EAX],ESI

0008:8044BF71 MOV EAX,[EBP-24]

0008:8044BF74 INVLPG [EAX]

0008:8044BF77 MOV ECX,EBX

0008:8044BF79 CALL 80449BCE

0008:8044BF7E MOV EAX,FS:[00000124]

// F12 (F12=^p ret;)

// F12

// 反汇编调用 MiCopyOnWrite 的部分

// 使用 kd, u 80447672 可以看到 ntoskrnl!MmAccessFault+834

:u 80447672 l 10

0008:80447672 CALL 8044BE21

0008:80447677 MOV EBX,00000112

0008:8044767C MOV EAX,[EBP-1C]

0008:8044767F MOV ECX,[EBP-20]

// F12

// 反汇编调用 MmAccessFault 的部分

// 使用 kd, u 80464961 可以看到 ntoskrnl!KiTrap0E+be

:u 80464961 l 10

0008:80464961 CALL 80446DD4

0008:80464966 CMP BYTE PTR [80471A38],00

0008:8046496D JZ 8046497A

0008:8046496F MOV EBX,[EBP+68]

// 在 KiTrap0E 中我们可以找到 CPU 压入的引起异常的指令,在 EBP+68 处

:dd ebp+68 l 10

0010:EBA0CDCC 77F89798 0000001B 00000246 0006FCA4 ...w....F.......

// 引起异常的指令地址是 77F89798

// 我们向前一点反汇编就可以看到是 ntdll!RtlTryEnterCriticalSection 中的指令

// 0008:77F89798 CMPXCHG [ECX+04],EDX

// 写入 CopyOnWrite 页时引起的异常

:u 77f89784 l 20

0008:77F89784 JMP 77F8350F

ntdll!RtlTryEnterCriticalSection

0008:77F89789 MOV ECX,[ESP+04]

0008:77F8978D MOV EAX,FFFFFFFF

0008:77F89792 MOV EDX,00000000

0008:77F89797 NOP

0008:77F89798 CMPXCHG [ECX+04],EDX

0008:77F8979C JNZ 77F8F47B

0008:77F897A2 MOV EAX,FS:[00000024]

:bd *

欢迎交流,欢迎交朋友,

欢迎访问 http://jiurl.yeah.net http://jiurl.cosoft.org.cn/forum

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有