|
内核之旅 揭开系统神秘的面纱(上)
好的资料可以给大家带来技术上面更多的想法,《深入解析Windows操作系统》是一本以Linux系统为基础讲解OS架构与硬件关系的图书,对于喜欢Linux的朋友可以考虑阅读该书,在《Unix操作系统设计》中由于以Unix V为版本讲解的Unix系统,多少有些许看得不尽人意的地方;而目前针对FreeBSD的资料比较少,所以如果大家想从整个系统的角度来分析一个类Unix系统的话,这本书将是一个好资料,而且还会帮你纠正很多写代码的弊病。
如果说Intel出了汇编语言,那么《80x86汇编语言与计算机体系结构》则是告诉你汇编程序是怎么编写的,每个指令在何种情况下占用了几个字节,一个程序在编译、连接后的opcode(机器码)是如何产生的,作者会很细心的告诉你一个mov指令占用一个时钟周期,在数据传值上经常使用mov、xchg,还会告诉你mov指令占用的opcode字节。
(一)需要理解的几个概念
在开始针对Windows的机制分析前,需要注意一些基础概念,有了这些概念,然后结合工具的使用,会有利于对Windows机制的分析。
API:Application Program Interface,应用程序接口,系统中有几千个API函数。
Plat software Development Kit:平台开发套间,在开发VC程序时,最好的习惯是安装最新的SDK包,里面的内容绝对比你安装过时的MSDN多很多,同时还有很多例子代码告诉你Windows程序的规范是怎么写的。
DDK:设备驱动开发包。
进程的概念:
进程只是一个容器而已,因为这个容器用来存放主线程,在Windows程序中,无论是main、WinMain函数创建的都不是进程,而是主线程,主线程才为一个进程内的主体。理解线程的概念,对于开发Windows程序有很大的帮助。进程创建后,根据其各种环境条件创建了主线程,主线程也分为两个部分,User Mode Thread(用户模式线程)和Kernel Mode Thread(内核模式线程)。对于主线程而言,我们可做的坏事情(从线程角度考虑,抛弃进程的各种环境条件)不是很多,当我们调用CreateThread函数创建一个线程,或者创建一个线程池时,我们可以做的坏事情就会相对较多,因为MS提供了一部分管理线程的函数,同时也公布了一组管理线程的内核数据结构,该数据结构为CONTEXT;从CONTEXT数据结构是依靠CPU类型来看,该结构的确是有些底层了;对于该结构的描述,在《Windows核心编程》有所阐述。通过初始化一个CONTEXT结构,通过GetThreadContext函数可以获取当前线程的CPU寄存器状态,通过SetThreadContext函数我们可以设置当前线程的CPU寄存器状态。不过这里有些麻烦的是,当我们使用GetThreadContext和SetThreadContext函数时,线程必须是不活动的,因为一个用户态可见的活动线程是异步执行的,可能你使用上述函数时得到的值与实际值有出入。前段时间在搞线程池时问过同事关于异步的问题,同事给我了很好的答案,通过一段时间的编写测试代码,对该处有了更深的认识。
线程的概念:
有必要把下面的内容单独出来,上面只是通过主线程的一些看法,以及CONTEXT结构的特殊性。既然谈及到CONTEXT结构,有个比较有意思的事情,我昨天早上起床背了几个单词后突然想起了这个结构,于是我把ntoskrnl.exe进行了逆向,在ntoskrnl.exe内也存在一个CONTEXT结构,而且与用户态的结构值完全相同,但这并不说明内核态会引用用户态的该结构,前面提及的该结构底层的说法只是说明该结构保存获得的内容比较底层而已。如果大家认真看过《Windows核心编程》中关于对该结构描述时,作者夸张的做法后,其实作者有些地方也不是很对的,当然每个人理解技术的情况不同,如果这里解释有误,还是需要大家的指点。
提及CONTEXT结构,而且上面也提及可以通过Get(Set)ThreadContext函数实现对该结构的修改,那么我们是否可以实现通过对该结构的操作来实现用户态下线程的暂停和恢复运行?答案是可以,但是需要进行特殊的处理,因为线程是异步执行的,其堆栈环境也不象进程那样稳定,换句话说进程只要主线程在运行,那么其堆栈环境会稳定的存在;但是线程(指使用CreateThread创建的线程)与之不同,一旦线程返回结束,或者调用线程结束函数,则在线程创建时的堆栈会自动被内核清理掉。但是使用CONTEXT结构来管理线程的话,就需要对线程的运行机理非常的熟悉,否则很难实现对其管理。
要想在用户态下实现对多个线程的控制(这里的控制不是指线程同步,线程同步是比较容易实现的,很多资料都有详细的讲解),最好的也是唯一的办法(用户态)就是ResumeThread和SuspendThread,因为Windows是一个多任务强占式操作系统,所以在线程调度时你很难控制内核模式下线程的情况,所以在用户态下只能使用上面的这两个函数恢复和暂停线程。上面说到线程有用户态和内核态的区分,所以当使用SuspendThread函数暂停线程时,仅仅暂停了用户态线程;也即用户态线程暂停了,在内核态它还是活跃的;但这足够了,最起码我们可以保证我们在用户态下的程序能够按照我们的想法运行了,至于内核态如何,那就是OS的事情了。
线程包括:
一组代码CPU状态的寄存器值;
两个栈,用户态栈和内核态栈;
TLS(Thread Local Storage),私有存储区域;
线程唯一标识符,线程ID;
线程自己的运行环境;
虚拟内存:
这个概念等同于Unix下的swap(交换分区),针对该问题的描述,我们先以通用的说法来理解。在OS内一个程序的执行,并没有使用实际的物理地址,程序的执行通过OS与CPU来结合执行,在OS中存在一种分页机制,用于映射32位程序的地址到物理地址;若程序运行时占很大空间,可以使用swap(*nix)、PageFile.sys(Win)来进行虚拟内存映射,这些都通过分页机制来实现。
在Windows中,使用内存管理器(Memory Manager)来实现内存映射,同时也起到保护的作用。这种保护、映射机制使得一个进程不会闯入另一个进程,也不会修改内核的数据。
要通俗的理解的话,可以把虚拟内存机制考虑为用户态调试时可见的内存地址,但并非真实物理内存地址,然后通过Memory Manager进行内存映射。
/*++ Virtual Memory Layout on x86 is: +------------------------------------+ 00000000 | | | | | | | User Mode Addresses | | | | All pages within this range | | are potentially accessible while | | the CPU is in USER mode. | | | | | +------------------------------------+ 7ffff000 | 64k No Access Area | +------------------------------------+ 80000000 | | | NTLDR loads the kernel, HAL and | | boot drivers here. The kernel | | then relocates the drivers to the | | system PTE area. | | | | Kernel mode access only. | | | | When possible, the PFN database & | | initial non paged pool is built | | here using large page mappings. | | | +------------------------------------+ | | | Additional system PTEs, system | | cache or special pooling | | | +------------------------------------+ | | | System mapped views. | | | +------------------------------------+ | | | Session space. | | | +------------------------------------+ C0000000 | Page Table Pages mapped through | | this 4mb region | | Kernel mode access only. | | | +------------------------------------+ C0400000 | HyperSpace - working set lists | | and per process memory management | | structures mapped in this 8mb | | region. | | Kernel mode access only. | +------------------------------------+ C0C00000 | System Cache Structures | | reside in this 4mb region | | Kernel mode access only. | +------------------------------------+ C1000000 | System cache resides here. | | Kernel mode access only. | | | | | +------------------------------------+ E1000000 | Start of paged system area | | Kernel mode access only. | | | | | +------------------------------------+ | | | System PTE area - for mapping | | kernel thread stacks and MDLs | | that require system VAs. | | Kernel mode access only. | | | +------------------------------------+ | | | NonPaged System area | | Kernel mode access only. | | | +------------------------------------+ FFBE0000 | Crash Dump Driver area | | Kernel mode access only. | +------------------------------------+ FFC00000 | Last 4mb reserved for HAL usage | +------------------------------------+ --*/
(二)Windows体系结构
内核部分:
Ntoskrnl.exe:执行体和内核,上层为执行体,主要提供导出函数(API、设备驱动)、只能在内核运行的函数等;下层为内核,由一些函数组成,同时负责对底层硬件结构的支持。
Hal.dll:硬件抽象层,在该层实现了对各种硬件的支持;实际上Hal.dll与Ntoskrnl.exe是相互依赖的关系,通过他们的结合,实现了Windows的平台移植。
Win32k.sys:包括内部支持函数,内核设备驱动程序;包括,GUI实现部分,收集鼠标键盘事件并处理,屏幕输出等。
核心DLL:Kernel32.dll、Advapi32.dll、User32.dll、Gdi32.dll。
用户态部分:
系统支持进程,Winlogon.exe(登陆)、System Process(空闲进程)、Interrupts(中断,空闲进程)、DPCs(延迟过程调用,空闲进程)、System(内核模式系统线程)、smss.exe(会话管理器)。
服务进程:services.exe。
环境子系统:csrss.exe(环境子系统,在win中很重要的一部分)。
用户态应用程序。
可使用Process Explore查看,下载地址:www. sysinternals.com。
重点理解:
Ntdll.dll,用户态需要与内核交互的API函数都通过Ntdll.dll进行,首先用户态API函数接口都通过Ntdll.dll交付给内核态,然后内核态返回信息给Ntdll.dll,再由Ntdll.dll把信息传递给用户态程序。可见该dll的重要性。
内核之旅 揭开系统神秘的面纱(下)
FreeBSD篇
(一) 基础概念
Unix系统在系统架构上与Windows操作系统存在很多不同之处,但底层代码对硬件的控制都不会存在很大的区别;至于如何对比Unix、Windows系统架构的不同之处,只能等该文档的最后几篇文档来简单的书写。
备注:在写该文档时,我使用的FreeBSD版本是4.7版,老了一些,由于以前很多代码都在4.7版本上完成,也就没有再更换最新版本,如果大家对FreeBSD感兴趣的话,可以使用最新版本的BSD系统。
A:源代码布局
在开始分析BSD的内核前,我们需要了解BSD内核代码的各个部分分别在那些内核代码文件夹内;由于BSD属于开源OS,我们不需要象搞Windows那样搞逆向工程,可以通过分析内核代码来熟悉BSD的内核功能,在以后的一些文档中,如果时间允许,也会结合FreeBSD官方文档,对FreeBSD的内核代码进行简单的分析。
在FreeBSD中内核代码存放在/usr/src内,其层次结构如下所示:(摘自BSD官方站点)
bin/ 在 /bin 中的文件的源代码
contrib/ 由其他开发组织维护的源代码
crypto/ 与密码学有关的源代码
etc/ 在 /etc 中的文件的源代码
games/ 在 /usr/games 中的文件的源代码
gnu/ 采用 GNU Public License 授权的工具
include/ 在 /usr/include 中的文件的源代码
kerberos5/ 第 5 版 Kerberos 的源代码
lib/ 在 /usr/lib 中的文件的源代码
libexec/ 在 /usr/libexec 中的文件的源代码
release/ 用于制作 FreeBSD 发行版本的文件
rescue/ 建造系统时 /rescue中的工具
sbin/ 在 /sbin 中的文件的源代码
secure/ FreeSec 的源代码
share/ 在 /usr/share 中的文件的源代码
sys/ 内核的源代码文件
tools/ 用于维护和自动测试 FreeBSD 的工具
usr.bin/ 在 /usr/bin 中的文件的源代码
usr.sbin/ 在 /usr/sbin 中的文件的源代码
B:调试工具
在BSD下面的调试工具为GDB,由于本人的懒惰,并没有再关心过其它的调试工具;以前曾试图使用Linux下面的类似SoftIce的工具,但发现始终没有在Windows下使用SoftIce那样舒服;于是继续使用GDB。KGDB可以进行内核调试,但必须为双机调试。一般在针对*NIX的调试中,多数公司会采取硬件调试技术,当然如果不允许使用硬件调试技术,也只能采取折中的双机调试办法。
在BSD下面还有很多不错的小工具可以使用,下面我列举几个经常用到的工具:
readelf:分析elf文件。Eg:readelf –h /bin/ls
ktrace:跟踪程序执行过程。Eg:ktrace ls
kdump:打印程序执行过程的ktrace记录。Eg:kdump
kldload:加载内核模块
kldstat:内核模块状态
kldunload:卸载内核模块
kldconfig:内核模块配置
man:如果你想知道ls命令的具体使用说明,使用man命令吧,man命令可以告诉你很多事情。
其它命令:请搜索/bin /sbin /usr/bin /use/sbin目录。
(二) 如何着手分析一个UNIX
了解OS的系统结构是分析一个OS的最好办法,我不知道大家喜欢从何处开始着手;我的习惯是从OS启动开始着手分析,从OS的启动过程开始分析,了解OS的启动阶段,加载模块,可以帮助我们找到实际分析问题时的切入点。例如:FreeBSD是如何开始从BIOS加载初始化代码的,FreeBSD从何处开始实现demon程序的,FreeBSD是从何处开始初始化内核代码的,等等问题;都可以在启动过程中找到答案。了解了OS的启动过程后,我们可以有针对性的分析了;例如:我想分析TCP/IP在内核的实现部分,就结合源代码去仔细的分析;由于我们熟悉了OS的启动过程,我们要分析的TCP/IP部分的代码在何处被加载执行我们也会有比较粗浅的认识,然后再逐步的深入。
当然以上只是我个人的一些观点,如果大家有好的办法,可以推荐下。
我们该文档的内容,将从以下几个部分阐述,写完以下部分的文档,整个BSD的内核系列简单文档也就结束了。
A: 系统结构
熟悉系统结构,有助于我们了解操作系统的组成模块。
B: FreeBSD启动过程
熟悉启动过程,可以使我们知道我们感兴趣的部分在那里开始被加载的。
C: SYSCALL系统调用
熟悉SYSCALL系统调用,可以帮助我们了解一个用户态程序怎么与内核态交互,并实现程序功能。
D: KERNEL模块
熟悉KERNEL模块,可以更深的了解BSD这个OS。
E: 驱动模块
熟悉驱动模块,熟悉驱动的编写有助于朝系统级编程靠拢的稍微近些。
F: TCP/IP的*BSD实现
额外的章节,只是想分析TCP/IP而已。
|