|
![]() | 作者: tabris17 [tabris17]
![]() |
登录 |
一、进程的内存分配 对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。对于 windows 操作系统而言,动态数据区有堆(HEAP)内存区域和栈(STACK)内存区域,这里的栈内存区域就相当于我们平时说的“堆栈”,每个线程都有自己的栈,而“堆”在这里是一个新的概念,它是由操作系统管理的,堆的分配和回收可以由操作系统自动实现。每个进程都有自己的默认堆,可以通过 GetProcessHeaps 来得到堆句柄,也可以通过 Heap 函数族可以实现对堆的操作。进程的堆信息可以通过查询进程 PEB(1) 结构得到。对于 Visual C++ 编译器,C++ 语言中的 new,delete 操作符就是通过堆来实现内存动态分配的。Windows 操作系统有它自己的内存动态分配函数 VirtualAlloc,相当于 C 语言中的 malloc 函数,堆的动态内存分配最终就是通过它实现的,这些会在后文讲到。 ├―――――――┤ │ …… │ ├―――――――┤ │ 动态数据区 │ ├―――――――┤ │ …… │ ├―――――――┤ │ 代码区 │ ├―――――――┤ │ 静态数据区 │ ├―――――――┤ │ …… │ ├―――――――┤ 首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。其中全局变量和静态变量是存放在静态数据区的,存放他们的内存地址是绝对的,进程中任何代码都可以访问他们;本地变量是在堆、栈中分配的;寄存器变量存放在 CPU 的寄存器中,实现高速存取,但是否真的这样分配,还要视硬件环境和编译器而异,所以并不太用到。例如: void func() { char szText[100]="hello,China!"; char *lpText="hello,world!"; MessageBox(NULL,"text","title",MB_OK); } 在上面那段代码中,字符串 "hello,China!" 将被分配(请注意这里的用词,事实上在 szText 这个字符串被初始化前,"hello,China!"这个字符串数据本身是在静态内存空间中的)在栈内存中的,而 "hello,world!"、"text"、"title" 都在静态内存区里的。还有一点要提一下,如果是用 VC 编译的话,那么变量在内存中的分配并不是连续的,而是按四字节(DOUBLE WORD)对齐的,即使是一个 char,它也要占用4各字节。而结构体则默认按八字节(QUAD WORD)分段,内部的成员是紧密排列的,但结构体的大小必须是 8 字节的整数倍,这就是用 sizeof() 得到的大小和实际占用空间大小不一致的原因。 说到这里,就不得不提到“缓冲区溢出”了。在 windows 下,缓中溢出分为堆溢出(heap overflow)和栈溢出(stack overflow),造成溢出的原因都是由于分配的内存空间小于实际使用的空间,导致输入的数据覆盖了相邻的内存空间。栈溢出导致执行任意代码的过程是这样的:栈溢出是发生在函数调用过程中的,调用函数时 CPU 执行 call 指令,它先把函数的返回地址压入堆栈,再把 EBP(2) 址压入堆栈。函数的返回地址是函数执行结束后调用的第一个指令的地址,即是说,函数返回后,CPU 就将执行返回地址指向的代码。而堆栈的顺序和内存顺序是相反的,内存中数据是由低端开始,堆栈中的数据由高端开始,栈顶的地址随着数据的压入而变小,这就导致了函数中的动态数据可能覆盖函数的返回指针,导致函数返回后没有执行原来压入的返回地址指向的代码,而是执行了可能是恶意构造的返回地址指向的代码,这个返回地址指针就指向了一段 Shellcode。 二、可执行文件到内存的映射 windows 下主要的可执行文件格式就是 PE 格式。它的结构如下图: �q―――――――�r │ DOS MZ header│ ├―――――――┤ │ DOS stub │ ├―――――――┤ │ PE header │ ├―――――――┤ │ Section table│ ├―――――――┤ │ Section 1 │ ├―――――――┤ │ Section 2 │ ├―――――――┤ │ …… │ ├―――――――┤ │ Section n │ �t―――――――�s 在 PE header 中存放了一些包含运行方式,文件属性等等信息,在这里就不做详解。当操作系统运行一个 PE 文件时,会将 PE 文件映射到改进程的内存空间里,当然,并不是原封不动地映射,而是会根据 PE header 中的信息做一些处理,比如函数导入表,偏移量等等。可执行文件的代码、数据会根据不同的逻辑属性分段存放在各个 Section 中,每个 Section 都有自己的名字,每个 Section 都有自己的属性,这些属性会被对应到内存的属性,如:可读、可写、可执行、写入复制。一般来说,名为“.text”的节存放可执行代码,名为“.data”的节存放数据,名为“.rdata”的节存放只读数据,名为“.rsrc”的节存放资源(菜单、对话框、字符串等)。PE header 有2个重要的信息,一个是“ImageBase”,一个是“AddressOfEntryPoint”。“ImageBase”指定了该 PE 文件的映射的基地址,“AddressOfEntryPoint”指定了可执行代码的入口地址。DLL 也是 PE 格式文件,它能被动态地加载到进程的内存空间中,DLL 文件也有“ImageBase”,但问题是 DLL 并不一定能加载到这个基地址,它可能只能加载到另外一个内存空间中。那么,DLL 中的一些代码就可能不能正确执行了,如以下代码: #include <windows.h> char szText[]="hey,Juliey!"; BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { MessageBox(NULL,szText,"",MB_OK); return TRUE; } 其中 szText 这个指针的内容在代码被编译时就决定了,这个 DLL 也就必须加载到它的基地址,MessaeBox 函数才能得到正确执行。为了应对 DLL 可能不被加载到基地址的情况,szText 必须被重定位,PE 文件的“.reloc”节就是存放重定位信息的。 (先写到这里,待续) 三、进程内存空间的布局 四、内存的分页机制 五、进程间通信 (1)PEB 进程环境块,存放进程信息的。 (1)EBP 堆栈基地址。 |
地主 发表时间: 04-01-14 20:42 |
![]() | 回复: newmyth21 [newmyth21] ![]() |
登录 |
很好啊,顶了。![]() |
B1层 发表时间: 04-01-15 00:11 |
![]() | 回复: hannyu [hannyu] ![]() |
登录 |
能再讲得通俗的么,不是说你讲的不好,我感觉没头没尾的。 |
B2层 发表时间: 04-01-17 14:30 |
![]() | 回复: mooncry [mooncry] ![]() |
登录 |
原来是教程呀, 让我白高兴一场 ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
B3层 发表时间: 04-06-16 10:28 |
![]() | 回复: tianya2003 [tianya2003] ![]() |
登录 |
楼上的怎么拉,不喜欢教程吗? |
B4层 发表时间: 04-06-16 14:34 |
![]() | 回复: lijingxi [lijingxi] ![]() |
登录 |
我也不是很喜欢这样的教程 枯燥!乏味!看着就想睡觉! |
B5层 发表时间: 04-06-18 13:13 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号