Windows内核初窥
作者:admin 日期:2009-11-08
每天我们都在使用Windows系统学习、编程、听音乐、玩游戏,Windows的操作想来是很熟练了,可是你又对Windows到底了解多少呢?本系列的目的,就是让你对Windows系统有个更直观、更清楚、更彻底的认识。虽然我们大多数人看不到Windows的源码,对其内存调度算法这种最深层次的技术内幕不能明窥,但是我们可以做到比现在知道的更多,了解这些之后你会发现在Windows上面开发会轻车熟路,任何木马病毒到了你机器上不过只会成为你的试验品。
鉴于Windows 9X内核早已淘汰,技术过时,在此不予讨论。主要是针对Windows2000(Windows 5.0)以后版本,尤以2000为主。要知道xp是Windows 5.1版本,2003也才是5.2版本。那么,对于本系列提到Windows OS技术,统指Windows 5.X技术。
一、直观认识Windows
鉴于Windows 9X内核早已淘汰,技术过时,在此不予讨论。主要是针对Windows2000(Windows 5.0)以后版本,尤以2000为主。要知道xp是Windows 5.1版本,2003也才是5.2版本。那么,对于本系列提到Windows OS技术,统指Windows 5.X技术。
一、直观认识Windows
黑客如何获取passwd密码档
作者:admin 日期:2009-11-08
Windows NT下的内核黑客后门
作者:admin 日期:2009-11-08
突破windows nt 内核进程监视限制
作者:admin 日期:2009-11-08
Windows NT内核分析
作者:admin 日期:2009-11-08
在安全性上的五件你应该知道的事(windows seven)
作者:admin 日期:2009-11-08
来源:阿风 编译 51CTO.com
【51CTO.com 独家翻译】微软表示,Windows 7是Windows操作系统有史以来开发出的最安全的版本。这没有什么大不了的,对不对?我相当确定,在过去的十五年里,微软对每个Windows版本都这么声称过,当然这也是一个有效的声明。
你还会期望别的什么?微软会推出一个新的操作系统,并且安全性低于其前身吗?我认为不会。尽管围绕Windows 7安全性的推广可能部分地被夸大了,但确实有一系列值得注意的重要的安全上的改善,特别实对于从Windows XP转换到(或考虑)Windows 7的用户。许多安全升级同样存在于Windows Vista,因此Vista用户应该已经熟悉了。
【51CTO.com 独家翻译】微软表示,Windows 7是Windows操作系统有史以来开发出的最安全的版本。这没有什么大不了的,对不对?我相当确定,在过去的十五年里,微软对每个Windows版本都这么声称过,当然这也是一个有效的声明。
你还会期望别的什么?微软会推出一个新的操作系统,并且安全性低于其前身吗?我认为不会。尽管围绕Windows 7安全性的推广可能部分地被夸大了,但确实有一系列值得注意的重要的安全上的改善,特别实对于从Windows XP转换到(或考虑)Windows 7的用户。许多安全升级同样存在于Windows Vista,因此Vista用户应该已经熟悉了。
内存映射文件
作者:admin 日期:2009-10-30
内存映射文件
内存映射文件是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数(CreateFileMapping)。这样,文件内的数据就可以用内存读/写指令来访问,而不是用ReadFile和WriteFile这样的I/O系统函数,从而提高了文件存取速度。
这种函数最适用于需要读取文件并且对文件内包含的信息做语法分析的应用程序,如对输入文件进行语法分析的彩色语法编辑器,编译器等。把文件映射后进行读和分析,能让应用程序使用内存操作来操纵文件,而不必在文件里来回地读、写、移动文件指针。
有些操作,如放弃“读”一个字符,在以前是相当复杂的,用户需要处理缓冲区的刷新问题。在引入了映射文件之后,就简单的多了。应用程序要做的只是使指针减少一个值。
映射文件的另一个重要应用就是用来支持永久命名的共享内存。要在两个应用程序之间共享内存,可以在一个应用程序中创建一个文件并映射之,然后另一个应用程序可以通过打开和映射此文件把它作为共享的内存来使用。
内存映射文件是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数(CreateFileMapping)。这样,文件内的数据就可以用内存读/写指令来访问,而不是用ReadFile和WriteFile这样的I/O系统函数,从而提高了文件存取速度。
这种函数最适用于需要读取文件并且对文件内包含的信息做语法分析的应用程序,如对输入文件进行语法分析的彩色语法编辑器,编译器等。把文件映射后进行读和分析,能让应用程序使用内存操作来操纵文件,而不必在文件里来回地读、写、移动文件指针。
有些操作,如放弃“读”一个字符,在以前是相当复杂的,用户需要处理缓冲区的刷新问题。在引入了映射文件之后,就简单的多了。应用程序要做的只是使指针减少一个值。
映射文件的另一个重要应用就是用来支持永久命名的共享内存。要在两个应用程序之间共享内存,可以在一个应用程序中创建一个文件并映射之,然后另一个应用程序可以通过打开和映射此文件把它作为共享的内存来使用。
VC++实现Win2000下直接读写磁盘扇区
作者:admin 日期:2009-10-25
由于Windows 操作系统在很大程度上采取了访问安全保护机制(例如,在Windows操作系统下不能直接访问物理内存、不能使用各种DOS、BIOS中断等等),使得广大程序设计人员在长时间的开发过程中不知不觉地逐渐养成了这样的潜意识——在Windows操作系统下直接操纵硬件设备是极端困难和非常烦琐的,并将其看作Windows编程的一个禁区。尽管在大多数场合下这样的论断还算是贴切,但也并非对所有的硬件设备访问都那么困难。其实Windows在采取“实保护”措施的同时也提供了另外的一种有别于在DOS下访问硬件设备的方法,即把所有的硬件设备全部看做“文件”,并允许按照对文件的读写方式来对其进行数据存取访问。撰写本文的另外一个目的也就是帮助读者打消在Windows环境下对硬件编程的恐惧心理。
对磁盘扇区数据的访问
前面已经提过,在Windows 下把所有的设备当作文件进行操作。如果对串口进行编程或许不少读者还比较熟悉:对于串行端口1、2,可以用”COM1”、”COM2”作为参数调用CreateFile()函数,这里的”COM1”、”COM2”即以文件存放路径的方式指出了要操作的硬件设备。但是如果需要对磁盘的某个扇区进行读写,可能不少读者不会想到使用CreateFile()函数或是不知如何使用。其实,与对串行端口的访问类似,需要用与文件存放路径相类似的方式指出要操作的硬件设备(硬盘)。但是这里并不是用“DISK1”、“DISK2”等去标识某一块物理存在的硬盘。由于逻辑扇区是存在于逻辑分区上的,因此这里需要以某种特定的格式来指定需要访问的磁盘逻辑分区。对于逻辑分区X,其格式为”\\.\X:”。
对磁盘扇区数据的访问
前面已经提过,在Windows 下把所有的设备当作文件进行操作。如果对串口进行编程或许不少读者还比较熟悉:对于串行端口1、2,可以用”COM1”、”COM2”作为参数调用CreateFile()函数,这里的”COM1”、”COM2”即以文件存放路径的方式指出了要操作的硬件设备。但是如果需要对磁盘的某个扇区进行读写,可能不少读者不会想到使用CreateFile()函数或是不知如何使用。其实,与对串行端口的访问类似,需要用与文件存放路径相类似的方式指出要操作的硬件设备(硬盘)。但是这里并不是用“DISK1”、“DISK2”等去标识某一块物理存在的硬盘。由于逻辑扇区是存在于逻辑分区上的,因此这里需要以某种特定的格式来指定需要访问的磁盘逻辑分区。对于逻辑分区X,其格式为”\\.\X:”。
虚拟内存与物理地址扩展(PAE)
作者:admin 日期:2009-10-25
Windows的虚拟内存并非简单的指位于我们硬盘上的那个pagefile.sys文件,或者是在内存装不下的时候用于应急的“模拟内存”。事实上Windows的每一个进程都会同时用到物理内存和虚拟内存。
[img]http://img.zdnet.com.cn/1/765/liGS541OHroM.jpg[/img]
在Windows系统中,任何一个进程都会被赋予其自己的虚拟地址空间(虚拟内存),该虚拟地址空间可以覆盖了一个相当大的范围。对于一个32位进程,其可以拥有的地址空间为2^32=4GB,这使得一个指针可以使用从00000000h到FFFFFFFFh的4GB范围之内的任何一个值。虽然每一个32位进程可使用4GB的地址空间,但并不意味着每一个进程实际拥有4GB的物理地址空间,该地址空间仅仅是一个虚拟地址空间。此虚拟地址空间只是内存地址的一个范围,进程实际可以得到的物理内存要远小于其虚拟地址空间。进程的虚拟地址空间是为每个进程所私有的,在进程内运行的线程对内存空间的访问都被限制在调用进程之内,而不能访问属于其他进程的内存空间(比如声明这是A进程的4GB,那是B进程的4GB)。这样,在不同的进程中可以使用相同地址的指针来指向属于各自调用进程的内容而不会由此引起混乱。
[img]http://img.zdnet.com.cn/1/765/liGS541OHroM.jpg[/img]
在Windows系统中,任何一个进程都会被赋予其自己的虚拟地址空间(虚拟内存),该虚拟地址空间可以覆盖了一个相当大的范围。对于一个32位进程,其可以拥有的地址空间为2^32=4GB,这使得一个指针可以使用从00000000h到FFFFFFFFh的4GB范围之内的任何一个值。虽然每一个32位进程可使用4GB的地址空间,但并不意味着每一个进程实际拥有4GB的物理地址空间,该地址空间仅仅是一个虚拟地址空间。此虚拟地址空间只是内存地址的一个范围,进程实际可以得到的物理内存要远小于其虚拟地址空间。进程的虚拟地址空间是为每个进程所私有的,在进程内运行的线程对内存空间的访问都被限制在调用进程之内,而不能访问属于其他进程的内存空间(比如声明这是A进程的4GB,那是B进程的4GB)。这样,在不同的进程中可以使用相同地址的指针来指向属于各自调用进程的内容而不会由此引起混乱。
读取物理内存
作者:admin 日期:2009-10-25
大家知道在windows NT中,如果已知虚拟地址可以通过进程页表或者内核提供的MmGetPhysicalAddress来取得对应的物理地址。现在我们反过来看一下,如果已知一个物理内存地 址 (假设地址有效),如何取得物理地址中的内容呢?经过一番思考后,我发现一个方法,但我这里先卖个关子,为什么呢?因为我觉得这个方法有些繁琐。经过和csdn的各位高手讨论之后,加上本人的一共总结出3种方法,现在想和大家分享一下,下面不再废话,立即进入正题 J[方法1]:使用内核提供的MmMapIoSpace函数原来内核早就提供了很简单的接口,就是MmMapIoSpace函数,不过在DDK文档中看到该函数的说明如下:PVOID MmMapIoSpace( IN PHYSICAL_ADDRESS PhysicalAddress, IN ULONG NumberOfBytes,IN MEMORY_CACHING_TYPE CacheType );它只有3个形参,这和实际Masm32v9.0声明的4个形参有所不同,为了确定,我通过 IDA 反汇编证实该函数确实有4个形参。经过测试,我猜测中间的ULONG NumberOfBytes参数实际由64位字节的(两个双字)高低两部分组成,即在Masm32v9.0中有:PVOID MmMapIoSpace( IN PHYSICAL_ADDRESS PhysicalAddress,IN ULONG NumberOfBytesHighPart,IN ULONG NumberOfBytesLowPart,IN MEMORY_CACHING_TYPE CacheType );这样的话调用就很简单了:invoke MmMapIoSpace,_phyaddr,0,4,MmNonCached若成功该函数返回影射后的虚拟地址,否则返回NULL。这样就可以间接达到读取物理地址中内容的第一个方法。但可能我对参数功能的猜测也有错误,如果有误请指出。不胜感谢。[方法2]:通过操作物理内存对象来完成到虚拟地址的影射简单的说这个方法的基本思想如下:a : 取得本进程句柄;b : 取得物理内存对象的句柄;c : 将物理内存地址影射到进程的虚拟地址空间中。下面是我从C改写后的汇编关键代码:;***************************************************************************GetValByPhyAddr proc uses esi edi ebx _phyaddr local hpmem:dword local hp:dword local dwsize:dword local dwbase:dword local dwret:dword local cid:CLIENT_ID local stLR:LARGE_INTEGER local ObjAttrsMem:OBJECT_ATTRIBUTES local ObjAttrsP:OBJECT_ATTRIBUTES mov dwbase,0 mov dwsize,4 push _phyaddr pop stLR.LowPart mov stLR.HighPart,0 invoke PsGetCurrentProcessId mov cid.UniqueProcess,eax mov cid.UniqueThread,0 invoke InitObjAttrs,addr ObjAttrsP,0 ;取得本进程句柄 Invoke ZwOpenProcess,addr hp,PROCESS_DUP_HANDLE,\ addr ObjAttrsP,addr cid invoke InitObjAttrs,addr ObjAttrsMem,addr devphymem ;取得物理对象的句柄 Invoke ZwOpenSection,addr hpmem,\ SECTION_MAP_READ or SECTION_MAP_WRITE,\ addr ObjAttrsMem ;将物理内存地址影射到进程的虚拟地址空间中 invoke ZwMapViewOfSection,hpmem,hp,addr dwbase,\ 0,4,addr stLR,addr dwsize,1,\ MEM_TOP_DOWN,PAGE_READWRITEmov edx,dwbase mov eax,[edx] ;取得影射后对应虚拟地址中的内容 mov dwret,eax ;释放资源 invoke ZwUnmapViewOfSection,hp,addr dwbase invoke ZwClose,hpmem invoke ZwClose,hp mov eax,dwret retGetValByPhyAddr endp;***************************************************************************[方法3]:关闭CPU分页机制达到直接读取物理地址的目的第3种方法就是我前面卖关子的方法 J ,这种方法不依赖于OS,了解保护模式的朋友都知道:进入保护模式后如果关闭分页机制则CPU就会将线形地址直接解释成物理地址。关闭分页很简单,只需3行汇编代码:mov eax,cr0and eax,7fffffffhmov cr0,eax打开分页同样很简单:mov eax,cr0or eax,80000000h mov cr0,eax既然这么简单,为什么我在前面说这个方法有些繁琐呢?原因之一是如果仅仅关闭分页就会使原来依赖于分页的windows变得一团糟,马上会发生著名的蓝屏现象,不爽哦!L那要怎么做呢?经过我的尝试,发现有3点非常重要:1 : 要保证windows处在较高的IRQL级别,防止关闭分页后 被打断;2 : 要将处理分页、读取物理地址的指令块放到线形地址等于 物理地址的页中;3 : 要做好数据的恢复工作。第1点和第3点都容易理解,可能有人会疑问第2点,为什么要放到线形地址等于物理地址的空间中呢?原因其实也很简单,就是一旦关闭分页,所有地址都将以物理地址来解释,如果先前的虚拟地址和物理地址不相同那么很可能就会发生执行错误指令流的问题。(当然也可以不相同,只要你能保证关分页后能执行正确指令即可。)那么如何将物理地址和虚拟地址设置为相同呢?我的方法是将虚拟地址对应地页表PTE中的物理页基址改为包容即可。我随机选择了一个虚拟地址 0x1c00000 ,为了简单它正好是页面的一个基址。下面我构造了它的PTE使得其中的物理也帧指向它:mov edx,0ffdf0000h mov eax,[edx] mov tmp0,eax ; 保存原始值 ;mov dword ptr [edx],1c00027h mov dword ptr [edx],1c00007h ;初始化PTE mov edx,0c03ff7c0h mov eax,[edx] and eax,0fffff000h or eax,7h mov edx,0c030001ch mov ebx,[edx] mov tmp1,ebx ; 保存原始值 mov dword ptr [edx],eax ;刷新TLB mov eax,cr3 mov cr3,eax 最后两句指令在我前一篇: <<Windows内核编程研究一:改变进程PTE>> 中有过说明,这里就不解释了。结束了PTE的设置后,下面就是将指令块拷贝到指定位置,代码如下:mov ecx,slenmov esi,subasmmov edi,1c00000hcldrep movsb而subasm指令块完成的就是实际设置分页和读取物理地址内容的工作,如下:subasm: mov eax,cr0 and eax,7fffffffh ;关闭分页机制 mov cr0,eax jmp SHORT PageCPageC: mov edx,[ecx]mov eax,cr0 or eax,80000000h ;打开分页机制 mov cr0,eax jmp SHORT PageOPageO: jmp edislen = $ - subasm借用一句话作为结尾“…一切都那么清楚明白,然而却无法透彻理解。理解就是改变,就是超越自己已有的认识。”
关于ring3到ring0的代码实现
作者:admin 日期:2009-10-22
GDT详解(全局描述符表)
作者:admin 日期:2009-10-21
在Protected Mode下,一个重要的必不可少的数据结构就是GDT(Global Descriptor Table)。
为什么要有GDT?我们首先考虑一下在Real Mode下的编程模型:
在Real Mode下,我们对一个内存地址的访问是通过Segment:Offset的方式来进行的,其中Segment是一个段的Base Address,一个Segment的最大长度是64 KB,这是16-bit系统所能表示的最大长度。而Offset则是相对于此Segment Base Address的偏移量。Base Address+Offset就是一个内存绝对地址。由此,我们可以看出,一个段具备两个因素:Base Address和Limit(段的最大长度),而对一个内存地址的访问,则是需要指出:使用哪个段?以及相对于这个段Base Address的Offset,这个Offset应该小于此段的Limit。当然对于16-bit系统,Limit不要指定,默认为最大长度64KB,而 16-bit的Offset也永远不可能大于此Limit。我们在实际编程的时候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)来指定Segment,CPU将段积存器中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的Base Address。
到了Protected Mode,内存的管理模式分为两种,段模式和页模式,其中页模式也是基于段模式的。也就是说,Protected Mode的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则这是纯段模式。
既然是这样,我们就先不去考虑页模式。对于段模式来讲,访问一个内存地址仍然使用Segment:Offset的方式,这是很自然的。由于 Protected Mode运行在32-bit系统上,那么Segment的两个因素:Base Address和Limit也都是32位的。IA-32允许将一个段的Base Address设为32-bit所能表示的任何值(Limit则可以被设为32-bit所能表示的,以2^12为倍数的任何指),而不象Real Mode下,一个段的Base Address只能是16的倍数(因为其低4-bit是通过左移运算得来的,只能为0,从而达到使用16-bit段寄存器表示20-bit Base Address的目的),而一个段的Limit只能为固定值64 KB。另外,Protected Mode,顾名思义,又为段模式提供了保护机制,也就说一个段的描述符需要规定对自身的访问权限(Access)。所以,在Protected Mode下,对一个段的描述则包括3方面因素:[Base Address, Limit, Access],它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符。这种情况下,如果我们直接通过一个64-bit段描述符来引用一个段的时候,就必须使用一个64-bit长的段积存器装入这个段描述符。但Intel为了保持向后兼容,将段积存器仍然规定为16-bit(尽管每个段积存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段积存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段积存器来直接引用64-bit的段描述符。
为什么要有GDT?我们首先考虑一下在Real Mode下的编程模型:
在Real Mode下,我们对一个内存地址的访问是通过Segment:Offset的方式来进行的,其中Segment是一个段的Base Address,一个Segment的最大长度是64 KB,这是16-bit系统所能表示的最大长度。而Offset则是相对于此Segment Base Address的偏移量。Base Address+Offset就是一个内存绝对地址。由此,我们可以看出,一个段具备两个因素:Base Address和Limit(段的最大长度),而对一个内存地址的访问,则是需要指出:使用哪个段?以及相对于这个段Base Address的Offset,这个Offset应该小于此段的Limit。当然对于16-bit系统,Limit不要指定,默认为最大长度64KB,而 16-bit的Offset也永远不可能大于此Limit。我们在实际编程的时候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)来指定Segment,CPU将段积存器中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的Base Address。
到了Protected Mode,内存的管理模式分为两种,段模式和页模式,其中页模式也是基于段模式的。也就是说,Protected Mode的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则这是纯段模式。
既然是这样,我们就先不去考虑页模式。对于段模式来讲,访问一个内存地址仍然使用Segment:Offset的方式,这是很自然的。由于 Protected Mode运行在32-bit系统上,那么Segment的两个因素:Base Address和Limit也都是32位的。IA-32允许将一个段的Base Address设为32-bit所能表示的任何值(Limit则可以被设为32-bit所能表示的,以2^12为倍数的任何指),而不象Real Mode下,一个段的Base Address只能是16的倍数(因为其低4-bit是通过左移运算得来的,只能为0,从而达到使用16-bit段寄存器表示20-bit Base Address的目的),而一个段的Limit只能为固定值64 KB。另外,Protected Mode,顾名思义,又为段模式提供了保护机制,也就说一个段的描述符需要规定对自身的访问权限(Access)。所以,在Protected Mode下,对一个段的描述则包括3方面因素:[Base Address, Limit, Access],它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符。这种情况下,如果我们直接通过一个64-bit段描述符来引用一个段的时候,就必须使用一个64-bit长的段积存器装入这个段描述符。但Intel为了保持向后兼容,将段积存器仍然规定为16-bit(尽管每个段积存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段积存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段积存器来直接引用64-bit的段描述符。
调试权限(debug权限)
作者:admin 日期:2009-10-21
windows启动以及exe文件的加载简介
作者:admin 日期:2009-10-14
先来看这样一个过程(非VISTA启动):CPU复位->传统BIOS->MBR->引导扇区->NTLDR->NTOS->SMSS->1:CSRSS 2:WinLogon->1:LSRSS 2:Services 3:LogonUI->2:Services->Svchost 3:LogonUI->USERINT->1:StartUp App 2:Shell
这是非 VISTA 的启动过程,是传统 Windows 的启动,这里有几处必要提到,首先 NTLDR 是一个非常重要的启动文件,在 C 盘下,相关的还有 boot.ini, pagefile.sys, NTDETECT.COM,我们按 F8 或者 F5 时进入安全模式的界面也是它产生的。然后是 NTOSKEL 这就是产生滚动条界面的,这是启动最漫长的过程!我曾经看到过有“高手”说过可以改掉滚动条滚动次数的注册表位置,其实这是不能改的,在这个界面下的过程非常漫长非常复杂,不提。然后是 SMSS 进程,这是第一个真正意义上由 .EXE 产生的进程。然后建出 CSRSS 子系统服务器进程和 WinLogon 进程。最后建出 Shell:Explorer.exe 进程。和本文最相关的就是这个 Shell 了,Shell “命令解释器”它的经典就是 Explorer.exe 。每一个窗口其实就是它创建的一个线程!这是一个非常厉害的多线程设计了!比如我们点击桌面上的图标启动一个程序,就是由它来加载创建的,好了,有了这些就开始来讲本文的其中一个内容:.EXE 的加载过程,然后再由此扩充开。
双击一个 .EXE 图标(操作系统怎么定位哪一个图标不是本文的内容),Shell 调用 CreateProcess 函数,系统找出在调用 CreateProcess 函数时的 .exe 文件,如果文件不在则返回 FALSE 进程创建失败。成功的话则为该进程创建一个新进程内核对象,这个内核对象是由系统内核管理的,关于内核对象也是一个很长的话题,详细叙述的话我写到明天恐怕都写不完这篇文章了,只要知道这个时候如果 CreateProcess 成功的话系统为这个进程创建了一个使用记数为1的进程内核对象,值得一提的是有一个进程内核对象句柄表,这里纪录了所有在该进程中所有创建出来的进程内核对象,但这个东西 Microsoft 没有提供相关文档,我的偶像 Jeffrey Richter 也只是简单的提了一提,接下来系统为这个新进程创建了一个私有地址空间,这就是我们很清楚的 4GB 地址空间! 写到这里先停住,我发现了一个比较大的问题,Shell 调用 CreateProcess 这个函数时其中的参数是哪里来的?我根据 Jeffrey Richter 的一句话也就是系统找出 EXE 文件,如果存在的话则创建成功,那么我进一步推论,那其中的参数很有可能是来自 PE 格式中的信息,可以继续推论下去,研究一下 CreateProcess 的参数再对照一下 PE 格式各个段的包含信息,那应该可以研究出某些东西来,这个细节我是还没有深入对比过,如果有哪位看官知道的话还请赐教!在这里先谢过了!接下来系统会保留一个足够大的区域来存放.exe 文件,默认的话是保存在从OXOO4OOOOO开始的位置,而这个数值是在 exe 这个 PE 结构中的信息,用 /BASE 是可以改的。这里就牵涉到内存映射文件了,不提。我们来研究一下 0X00400000 在内存中的什么区域,看一下 Win32 内存结构中4GB的划分(不再提 98 那破烂了)0x00000000 - 0x0000FFFF 这64KB用于 NULL 指针分配,举一个 Richter 的例子:int *pnSomeInteger = (int*)malloc(sizeof(int)); *pnSomeInteger = 5; 如果 malloc 不能找到足够的内存来分配的话那它就返回 NULL 就是保存在这里了,接下来 0x00010000 - 0x7FFEFFFF 这一段就是 Win32 进程私有的地址空间,刚才的.exe 加载就在这一段空间中,接下来继续加载程序执行所必须的 DLL 文件也是在这个段里,当然在这里还有一些数据需要加载,和为线程开辟的堆栈区,所以进程的这个用户区其实分成了3个部分:文本区包括指令和可读的数据,通常这个段是标记为可读,数据区,这个段对应 PE 中的 DATA_BASS 包含以初始化和未初始化的数据,静态变量也存放在这个区中,最后就是堆栈区,这是为线程分配的默认为1MB,当然是可以改的,在这里包含了函数参数和局部变量,当然也有现场保护时的信息,很关键的一点是 IP 指针也在这里,缓冲区溢出攻击就是在这个区域做文章,俗称:践踏堆栈。好!因为 Win32 中进程是一个惰性的,它什么也不执行,除了 EXE 和 DLL 还有它们所需的数据加载到进程地址空间外每个进程还拥有别的资源,这里只要记住:窗口和钩子是属于线程的外其他都是进程的,所以进程必须有一个线程来执行它的代码,这时候系统就为进程创建一个主线程,但它是调用 C 运行时库函数,这里又有区分 CUI 和 GUI,ANSI 和 UNIODE 的差别,如果以 GUI 和 UNICODE 来讲,它调用的是 wWinMainCTRStartup 这个可执行文件的启动函数。这个函数的功能不多讲,它会进行一系列初始化工作,完了后它将调用我们编写的主函数入口函数比如 wWinMain();最后 exit 调用调用操作系统的ExitProcess 函数,将 nMainRetVal 传递给它,这使得操作系统能够撤销进程并设置它的 exit 代码。到这里启动过程结束!
这是非 VISTA 的启动过程,是传统 Windows 的启动,这里有几处必要提到,首先 NTLDR 是一个非常重要的启动文件,在 C 盘下,相关的还有 boot.ini, pagefile.sys, NTDETECT.COM,我们按 F8 或者 F5 时进入安全模式的界面也是它产生的。然后是 NTOSKEL 这就是产生滚动条界面的,这是启动最漫长的过程!我曾经看到过有“高手”说过可以改掉滚动条滚动次数的注册表位置,其实这是不能改的,在这个界面下的过程非常漫长非常复杂,不提。然后是 SMSS 进程,这是第一个真正意义上由 .EXE 产生的进程。然后建出 CSRSS 子系统服务器进程和 WinLogon 进程。最后建出 Shell:Explorer.exe 进程。和本文最相关的就是这个 Shell 了,Shell “命令解释器”它的经典就是 Explorer.exe 。每一个窗口其实就是它创建的一个线程!这是一个非常厉害的多线程设计了!比如我们点击桌面上的图标启动一个程序,就是由它来加载创建的,好了,有了这些就开始来讲本文的其中一个内容:.EXE 的加载过程,然后再由此扩充开。
双击一个 .EXE 图标(操作系统怎么定位哪一个图标不是本文的内容),Shell 调用 CreateProcess 函数,系统找出在调用 CreateProcess 函数时的 .exe 文件,如果文件不在则返回 FALSE 进程创建失败。成功的话则为该进程创建一个新进程内核对象,这个内核对象是由系统内核管理的,关于内核对象也是一个很长的话题,详细叙述的话我写到明天恐怕都写不完这篇文章了,只要知道这个时候如果 CreateProcess 成功的话系统为这个进程创建了一个使用记数为1的进程内核对象,值得一提的是有一个进程内核对象句柄表,这里纪录了所有在该进程中所有创建出来的进程内核对象,但这个东西 Microsoft 没有提供相关文档,我的偶像 Jeffrey Richter 也只是简单的提了一提,接下来系统为这个新进程创建了一个私有地址空间,这就是我们很清楚的 4GB 地址空间! 写到这里先停住,我发现了一个比较大的问题,Shell 调用 CreateProcess 这个函数时其中的参数是哪里来的?我根据 Jeffrey Richter 的一句话也就是系统找出 EXE 文件,如果存在的话则创建成功,那么我进一步推论,那其中的参数很有可能是来自 PE 格式中的信息,可以继续推论下去,研究一下 CreateProcess 的参数再对照一下 PE 格式各个段的包含信息,那应该可以研究出某些东西来,这个细节我是还没有深入对比过,如果有哪位看官知道的话还请赐教!在这里先谢过了!接下来系统会保留一个足够大的区域来存放.exe 文件,默认的话是保存在从OXOO4OOOOO开始的位置,而这个数值是在 exe 这个 PE 结构中的信息,用 /BASE 是可以改的。这里就牵涉到内存映射文件了,不提。我们来研究一下 0X00400000 在内存中的什么区域,看一下 Win32 内存结构中4GB的划分(不再提 98 那破烂了)0x00000000 - 0x0000FFFF 这64KB用于 NULL 指针分配,举一个 Richter 的例子:int *pnSomeInteger = (int*)malloc(sizeof(int)); *pnSomeInteger = 5; 如果 malloc 不能找到足够的内存来分配的话那它就返回 NULL 就是保存在这里了,接下来 0x00010000 - 0x7FFEFFFF 这一段就是 Win32 进程私有的地址空间,刚才的.exe 加载就在这一段空间中,接下来继续加载程序执行所必须的 DLL 文件也是在这个段里,当然在这里还有一些数据需要加载,和为线程开辟的堆栈区,所以进程的这个用户区其实分成了3个部分:文本区包括指令和可读的数据,通常这个段是标记为可读,数据区,这个段对应 PE 中的 DATA_BASS 包含以初始化和未初始化的数据,静态变量也存放在这个区中,最后就是堆栈区,这是为线程分配的默认为1MB,当然是可以改的,在这里包含了函数参数和局部变量,当然也有现场保护时的信息,很关键的一点是 IP 指针也在这里,缓冲区溢出攻击就是在这个区域做文章,俗称:践踏堆栈。好!因为 Win32 中进程是一个惰性的,它什么也不执行,除了 EXE 和 DLL 还有它们所需的数据加载到进程地址空间外每个进程还拥有别的资源,这里只要记住:窗口和钩子是属于线程的外其他都是进程的,所以进程必须有一个线程来执行它的代码,这时候系统就为进程创建一个主线程,但它是调用 C 运行时库函数,这里又有区分 CUI 和 GUI,ANSI 和 UNIODE 的差别,如果以 GUI 和 UNICODE 来讲,它调用的是 wWinMainCTRStartup 这个可执行文件的启动函数。这个函数的功能不多讲,它会进行一系列初始化工作,完了后它将调用我们编写的主函数入口函数比如 wWinMain();最后 exit 调用调用操作系统的ExitProcess 函数,将 nMainRetVal 传递给它,这使得操作系统能够撤销进程并设置它的 exit 代码。到这里启动过程结束!
调试工具debug的用法
作者:admin 日期:2009-10-13
有不少朋友来信问过我这样一个问题: 怎样才能成为一名优秀的HACKER。我的回答是: 深入的了解汇编知识,学好DEBUG的用法。这次借Pc life的一角之地,我将详细的讲解一下DEBUG的用法。
DEBUG是一个DOS实用程序,是供程序员使用的程序调试工具,可以用它检查内存中任何地方的字节以及修改任何地方的字节.它可以用于逐指令执行某个程序以验证程序运行的正确性,也可以追踪执行过程、比较一个指令执行前后的值以及比较与移动内存中数据的范围,读写文件与磁盘扇区。
DEBUG把所有数据都作为字节序列处理。因此它可以读任何类型的文件。DEBUG可以识别两种数据: 十六进制数据和ASCⅡ码字符。它的显示格式是各个字节的十六进制值以及值在32与126之间的字节的相应ASCⅡ码字符。
在DEBUG中输入数据有两种方法: 提示方法和非提示方法。在用提示方法时,用户可以输入要求输入数据的命令,后跟数据所要输入的地址。然后用户就可以看到该地之中已有内容及一个冒号提示符。此时用户可以在提示符下输入一个新的值或者按下回车键或CTRL+C回到短横(-)提示符。在运用非提示方法时,用户可以输入要输入数据的内存地址以及要输入的字节。但与使用字处理程序或正文编辑程序时不一样,在使用DEBUG时,用户不能直接移动光标到一入口点输入或修改数据,而要一次输入一个或几个字节。
在使用DEBUG时可以只涉及内存中的数据,从而一般都要指定所要处理的内存地址,地址的输入格式是: [段地址]: [位移]。如果没有输入地址,DEBUG将假定为当前内存段,从位于地址100H的字节开始。前100H字节保留给程序段前缀使用,这一专用区域用于建立DOS与程序之间的联系。DEBUG总是用四位十六进制数表示地址。用两位数表示十六进制数据。
DEBUG是一个DOS实用程序,是供程序员使用的程序调试工具,可以用它检查内存中任何地方的字节以及修改任何地方的字节.它可以用于逐指令执行某个程序以验证程序运行的正确性,也可以追踪执行过程、比较一个指令执行前后的值以及比较与移动内存中数据的范围,读写文件与磁盘扇区。
DEBUG把所有数据都作为字节序列处理。因此它可以读任何类型的文件。DEBUG可以识别两种数据: 十六进制数据和ASCⅡ码字符。它的显示格式是各个字节的十六进制值以及值在32与126之间的字节的相应ASCⅡ码字符。
在DEBUG中输入数据有两种方法: 提示方法和非提示方法。在用提示方法时,用户可以输入要求输入数据的命令,后跟数据所要输入的地址。然后用户就可以看到该地之中已有内容及一个冒号提示符。此时用户可以在提示符下输入一个新的值或者按下回车键或CTRL+C回到短横(-)提示符。在运用非提示方法时,用户可以输入要输入数据的内存地址以及要输入的字节。但与使用字处理程序或正文编辑程序时不一样,在使用DEBUG时,用户不能直接移动光标到一入口点输入或修改数据,而要一次输入一个或几个字节。
在使用DEBUG时可以只涉及内存中的数据,从而一般都要指定所要处理的内存地址,地址的输入格式是: [段地址]: [位移]。如果没有输入地址,DEBUG将假定为当前内存段,从位于地址100H的字节开始。前100H字节保留给程序段前缀使用,这一专用区域用于建立DOS与程序之间的联系。DEBUG总是用四位十六进制数表示地址。用两位数表示十六进制数据。