首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第12期->技术专题
作者:WebCrazy(tsu00@263.net)
日期:2000-08-15
本文假设您已经了解Windows NT/2000系统体系,对Windows NT/2000内部KPEB/KTEB等数据结构与内核工作方式已有一定的概念,对80x86保护模式,Intel/AT&T格式汇编语言有过学习,能熟练使用SoftICE for Windows NT,且曾经接触过Microsoft Visual Studio及其附带工具,翻阅过Linux内核代码,如果您对这些方面不甚了解,请自行参阅相关书籍。
环境切换(Context Switch)牵涉到很多方面的内容,本文仅对与其有关的几个数据进行详细的讨论,并给出取得这些数据的部分程序段,还列出Windows 2000的少量环境切换代码。另外文中讨论的系统内部数据均未来自Microsoft官方文档,在Windows NT/2000的下个版本甚至目前各版本间均会有差别,所以我尽量详细的将文中所涉及的软硬件列于下面,所有因硬件体系、软件版本不同等因素引起的差异,请自行根据您的情况予以调整。
⊙ x86平台单处理机Windows 2000 Server Build 2195
⊙ Numega SoftICE 4.05 for Windows NT/2000 Build 334
⊙ Linux 2.0.30内核
⊙ Datarescue IDA 4.0.4.362
⊙ Microsoft Visual Studio 6.0 SP3
⊙ Windows 2000 DDK
80x86产生的环境切换有以下几种可能:
1.当前任务执行一个FAR CALL或JMP指令,而选择器指向一个TSS描述符或一个任务门。
2.当前任务执行IRET指令返回先前任务,IRET只在EFLAGS寄存器中的NT位置1时产生切换。
3.发生一个中断或异常情况,并且IDT项是个任务门。
Linux内核中有如下代码:
/*
/usr/src/linux/include/asm-i386/system.h
仅列出单处理器实现代码
*/
#define switch_to(prev,next) do { \
__asm__("movl %2,"SYMBOL_NAME_STR(current_set)"\n\t" \
"ljmp %0\n\t" \
"cmpl %1,"SYMBOL_NAME_STR(last_task_used_math)"\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
: /* no outputs */ \
:"m" (*(((char *)&next->tss.tr)-4)), \
"r" (prev), "r" (next)); \
/* Now maybe reload the debug registers */ \
.
.
.
} \
} while (0)
这段代码使用了上面讨论的第一种情况。
众所周知,Linux是个开放源代码的操作系统,而Microsoft则没如此“大方”,但我们仍能对其进行些逆向工程,可喜的是网上目前已经有很多人对此有过研究,现摘录Mark Russinovich部分成果(http://www.sysinternals.com/tips.htm):
//
// NT's main
// NTOSKRNL main
//
int main( boot parameters )
{
//
// Fire up NT!
//
KiSystemStartup();
return 0;
}
从中可看出ntoskrnl.exe(PE格式,可方便的使用反汇编工具进行分析)是NT OSLOADER真正调用内核的开始,其对文件对象(File)、作业对象(Job)、进程对象(Process)、线程对象(Thread)、纤程对象(Fiber)、文件映射对象(FileMapping)、事件对象(Event)、互斥对象(Mutex)、信号对象(Semaphore)等许多内核对象进行管理,其也负责线程调度,内存管理,进程间通信等所有操作系统功能,让它们协调工作,我们要讨论的线程切换代码也在此模块中。
用IDA等对ntoskrnl.exe进行反汇编所得的结果,其分析的工作量恐怕大家都是可想而知的。在我们讨论Windows 2000环境切换详细代码前,还是先让我们看看以下几个重要的与环境切换有关的系统数据:
1 进程Context
进程Context是指80x86在保护模式下内存分页机制中当前进程的页目录所在的物理地址,其存放在系统CR3寄存器中,在Windows 2000中所处的位置为KPEB偏移后18h处,看看SoftICE的输出结果吧(限于篇幅,我对输出结果进行了删减,但仍对重要数据进行注解,应注意的是与您当前运行的程序等系统环境密切相关,随机性很强,下同):
:cpu //显示当前cpu的寄存器值
Processor 00 Registers
----------------------
CS:EIP=0008:80069582 SS:ESP=0010:8046FD98
EAX=8046BDF0 EBX=FFDFF000 ECX=FFDFF878 EDX=0000BA5A
ESI=8046BDF0 EDI=8046BB60 EBP=FFDFF800 EFL=00000213
DS=0023 ES=0023 FS=0030 GS=0000
CR0=8000003B PE MP TS ET NE PG
CR2=76EE18EC
CR3=00030000
|
|_当前进程的CR3
CR4=000002D1 VME PSE MCE PGE
.
.
.
:proc idle
Process KPEB PID Threads Pri User Time Krnl Time Status
*Idle 8046BB60 0 1 0 00000000 0000BA5A Running
| |
| |_Idle进程的KPEB
|_系统中当前进程(SoftICE中用不同颜色突出,且前面有个*)
:dd 8046bb60+18 l 4 //dd Idle's KPEB+18h
0010:8046BB78 00030000 00000000 00000000 00000000 ................
|
|_Idle进程Context
:addr
CR3 LDT Base:Limit KPEB Addr PID Name
00030000 FE4E1C60 0008 System
02D59000 FF8E6540 0090 smss
01D41000 FF8E17E0 00AC csrss
00686000 FE51BAE0 00C0 winlogon
0095D000 FF8A7AE0 00DC services
0276E000 FF8A5D60 00E8 lsass
00394000 FF881020 0180 svchost
02CAE000 FF884020 01A4 SPOOLSV
00882000 FF85B560 01D0 msdtc
02993000 FF83F020 0238 svchost
00D2F000 FF83D760 024C llssrv
0063A000 FF837860 0274 regsvc
02EFA000 FF6ECD60 0318 dfssvc
00A5E000 FF823A20 0328 inetinfo
03612000 FF6AF860 0384 explorer
003A2000 FF68E460 03B4 internat
003A7000 FF68CD60 0130 OSA
008C1000 FF6769A0 03E8 svchost
01BAA000 FF65A020 01C0 cmd
00822000 FF86C960 038C conime
03362000 FF6B3540 0388 notepad
*00030000 8046BB60 0000 Idle
|
|__当前进程Context,是不是与上一命令输出结果一致。
可以用同样的方法进行再次进行验证。
2.Context Switches Times 线程已被操作系统调度次数
当每次操作系统调用线程时,都会将这个值加一的。Visual Studio所附的工具Spy++,在Thread窗口中Thread Properties中Context Switches指出系统中该线程已调度的次数(Spy++只有在Windows NT/2000中运行时才会显示出这个值,9x中则没有)。Switch Times在系统中所处的位置在KTEB的偏移4ch处。
:thread idle
TID Krnl TEB StackBtm StkTop StackPtr User TEB Process(Id)
0000 8046BDF0 8046D040 80470040 8046FD90 00000000 Idle(00)
:dd 8046bdf0+4c l 4
0010:8046BE3C 0000E778 00000000 00000002 00000000 x...............
|
|_指出当前Idle线程已经被系统调用0E778h(十进制59256)次了。用e命令改改再看看Spy++的输出结果!
3.线程所属进程的KPEB与进程名 //分别位于KTEB+44h与KPEB+1fch处
具体见我在《再谈Windows NT/2000内部数据结构》(nsfocus Magazine 11)一文所述。
应该指出的是,以上讨论只是针对Windows 2000 Server Build 2195的,如果您的系统不是的话或想知道如何得到这些值的具体位置,请参阅我以下的叙述的方法:
通过找突破口,正像上面我所描述的Context Switches Times在Spy++中显示的一样,然后可以用逆向工程法,我也是用这个方法来取得这个具体位置的。
举个例子吧!我曾经对SoftICE for NT中的addr命令输出结果(包含进程Context)来自何处感到困惹,也曾经在国外的一些著名的新闻组中提问过,不过至今仍没人应答(可能是我的英文水平太差,人家看不懂什么意思吧!)
addr命令输出结果见上。
后来无奈之下我还是想到CR3(存放进程页目录物理地址的寄存器)应与特定的进程有关,其应该存放在KPEB结构中(实际上的确是这样的)。而如果真是这样的话,不是只要枚举(Enum)出系统中所有的KPEB,则能得到所有的CR3值(当然前提是找出其相对KPEB的偏移值),相应的使用我在《再谈Windows NT/2000内部数据结构》(nsfocus Magazine 11)的方法就可以取出所有进程的进程名了吗?(PID也是一样的)。
我在通过分析PSAPI.DLL中枚举系统进程的函数后(EnumProcesses等),发现系统启动后的第一个进程system的KPEB是存放在ntoskrnl.exe导出的PsInitialSystemProcess指出的地址处的,而系统中各个KPEB由一链表联结着,至于链表的定义在Windows 2000 DDK中的ntdef.h中如下定义的:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
有了KPEB,跟踪相应的代码(这段反汇编代码我将在下面列出),就能找出偏移地址18h处的CR3值。
以上操作,在Windows 9x中可由Kernel32.dll中序号为1的Undocumented函数(NONAME,Softice Export列表中显示为ORD_0001)实现,但因为Windows 9x与NT/2000的内核的不同,Softice for 9x与NT的addr命令输出的格式也完全不同。至于在Windows NT/2000中不知道是否有现成的函数可以得到结果,至少现在我也没找到,这可是题外话。
因为上面的叙述还是比较抽象,我还是将SoftICE的输出结果列于此,更利于理解:
:dd PsInitialSystemProcess l 4
0008:8046A844 FE4E1C60 E1000968 00000000 00000000 `.N.h...........
|
|_System进程的KPEB
:dd @PsInitialSystemProcess+18 l 4
0008:FE4E1C78 00030000 00000000 00000000 00000000 ................
|
|_System进程的Context
:dd @PsInitialSystemProcess+9c l 4 //9ch是PID相对KPEB的位置
0008:FE4E1CFC 00000008 FF8E65E0 8046A180 00000000 .....e....F.....
|
|_System进程PID
:dd @PsInitialSystemProcess+1fc l 10 //1fch是Process Name相对KPEB的位置
0008:FE4E1E5C 74737953 00006D65 00000000 00000000 System..........
|
System进程Process Name_|
:? @(@PsInitialSystemProcess+a0)-a0 //计算System指向的下一个进程的KPEB,0a0h是链状结构相对KPEB的偏移
FF8E6540 4287522112 (-7445184) "
版权所有,未经许可,不得转载