进程空间的地址划分
线性地址有4G 但未必都能访问,所以需要记录,哪些地方分配了
比如你申请了一块内存在0x12345678的位置大小为4字节,这些都要被记录总不能在这申请了下次还在这申请
在_EPROCESS.+ 0x11c VadRoot 这是一颗搜索二叉树,它里面的每一个节点都记录了一块被占用的线性地址空间。
VadRoot的类型为_MMVAD
kd> dt _MMVAD
nt!_MMVAD
+0x000 StartingVpn //当前节点线性地址起始位置(以页为单位)
+0x004 EndingVpn //当前节点线性地址结束位置(以页为单位)
+0x008 Parent //父节点
+0x00c LeftChild //左子树
+0x010 RightChild //右子树
+0x014 u //用于标识内存属性之类的
+0x018 ControlArea //控制区
+0x01c FirstPrototypePte //指向原型PTE第一个 239
+0x020 LastContiguousPte //指向所映射视图PTE最后一个
+0x024 u2
+0x018 ControlArea 控制区:
+0x000 Segment //指向段
+0x004 DereferenceList
+0x00c NumberOfSectionReferences
+0x010 NumberOfPfnReferences
+0x014 NumberOfMappedViews //反映关联的内存区对象被映射多少次
+0x018 NumberOfSubsections //
+0x01a FlushInProgressCount
+0x01c NumberOfUserReferences
+0x020 u
+0x024 FilePointer //文件指针
+0x028 WaitingForDeletion
+0x02c ModifiedWriteCount
+0x02e NumberOfSystemCacheViews
+0x024 FilePointer 文件指针:
如果这个值为null,当前MMVAD的这块内存就是一块物理页(文件映射来的),
如果不为null就是我们自己分配的内存
nt!_FILE_OBJECT
+0x000 Type
+0x002 Size
+0x004 DeviceObject
+0x008 Vpb
+0x00c FsContext
+0x010 FsContext2
+0x014 SectionObjectPointer
+0x018 PrivateCacheMap
+0x01c FinalStatus
+0x020 RelatedFileObject
+0x024 LockOperation
+0x025 DeletePending
+0x026 ReadAccess
+0x027 WriteAccess
+0x028 DeleteAccess
+0x029 SharedRead
+0x02a SharedWrite
+0x02b SharedDelete
+0x02c Flags
+0x030 FileName //这块内存对应的模块名称
+0x038 CurrentByteOffset
+0x040 Waiters
+0x044 Busy
+0x048 LastLock
+0x04c Lock
+0x05c Event
+0x06c CompletionContext
所有的内存都可以分成两类,一类为我们用VirtualAllocEx()申请的内存,
还有一类为文件映射,这个文件可能是.dll/.exe也可能是一个数据的文件
上面的都是自己一层一层的找,windbg也提供了一个指令可以帮你类出来所有的节点,
!vad 0xxxxx
+0x014 u 用于标识内存属性之类的
union
{
ULONG_PTR LongFlags;
MMVAD_FLAGS VadFlags; 一般只用这个成员
} u;
nt!_MMVAD_FLAGS
+0x000 CommitCharge //最大可提供物理页的数目
+0x000 PhysicalMapping
+0x000 ImageMap //1:镜像文件 0:其他
+0x000 UserPhysicalPages
+0x000 NoChange
+0x000 WriteWatch
+0x000 Protection //当前内存属性(读?写?执行)
//1:READONLY 2:EXECUTE 3:EXECUTE _READ 4:READWITER
//5:WRITECOPY 6:EXECUTE_READWITER 7:EXECUTE_WRITECOPY
+0x000 LargePages
+0x000 MemCommit
+0x000 PrivateMemory 1:内存是Private 0:这块内存是Mapped
《Windows内核原理与实现》- 243页
Private Memory(私有内存)
通过VirtualAlloc() / VirtualAllocEx()申请的: Private Memory
void main()
{
printf("程序运行了...按下回车开始申请内存");
getchar();
//参数1:要申请的地址
//参数2:大小 (以页为单位)
//参数3:MEM_COMMIT(创建节点也分配物理页) MEM_RESERVE(只创建节点不分配物理页)
//参数4:访问权限
LPVOID address = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_READWRITE);
printf("内存地址:%X\n", address);
getchar();
}
申请前
申请后
堆内存与栈内存
int x = 0x1111;
void main()
{
printf("按下回车开始申请内存");
getchar();
int y = 0x2222;
int* z = (int*)malloc(sizeof(int)*128);
printf("全局变量:%X\n", &x);
printf("栈:%X\n", &y);
printf("堆:%X\n", z);
getchar();
}
堆就是系统提前替我们申请的一块很大的内存,相当于批发
当我们用的时候调用malloc(),就是从这些已经分配好的内存中拿出一块
无论是堆还是栈都是系统提前就用VirtualAlloc()分配好的内存,
全局变量是程序编译完了它就会有一块属于自己的地址空间,属于映射得到的
Mapped Memory(映射内存)
通过CreateFileMapping() 映射的: Mapped Memory
映射内存分两类
- 多个进程共享一个物理页面
- 多个进程共享一个文件
共享内存
A进程
void main()
{
//创建物理页
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUFSIZ, L"共享内存");
//将物理页与线性地址进行映射
LPTSTR lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
*(PDWORD)lpBuff = 0x12345678;
printf("A进程写入地址 内容:%p - %x ", g_lpBuff,*(PDWORD)g_lpBuff);
getchar();
}
B进程
void main()
{
HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,L"共享内存");
LPTSTR buffer = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);
printf("B进程读取:%x", *(PDWORD)buffer);
getchar();
}
共享文件
void main()
{
HANDLE hFile = CreateFile(L"C:\\XueTr.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, BUFSIZ, NULL);
LPTSTR lpBuff = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
printf("%x", lpBuff);
getchar();
}
共享文件如果A进程改了该映射的文件,所有使用该文件的进程都会被影响。
镜像文件
HMODULE hModule = ::LoadLibrary("C:\\XueTr.exe");
- LoadLibrary 就是通过内存映射的方式实现的
- 为了避免影响到别人,属性为:写拷贝
写拷贝:
修改该模块里面的数据:系统是不让你修改这个物理页的,会映射一份新的物理页。
你修改的数据会放到一个新的物理页上,你的线性地址会指向这个新的物理页。
不修改该模块里面的数据:多个进程都是指向同一个物理页