☆ 利用调用门从Ring 3进入Ring 0
观察用户空间程序memdump.exe执行时CS、DS、FS所对应的段描述符:
--------------------------------------------------------------------------
> memdump -l 8
GDTR.base = 0x8003F000
GDTR.limit = 0x03FF
CURRENT_CS = 0x001B
CURRENT_DS = 0x0023
CURRENT_ES = 0x0023
CURRENT_SS = 0x0023
CURRENT_FS = 0x0038
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 00 00 00 00 00 00 00 00 ........
> memdump -l 8 -a 0x8003F018
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 FB CF 00 ........
> memdump -l 8 -a 0x8003F020
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 F3 CF 00 ........
> memdump -l 8 -a 0x8003F038
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF 0F 00 E0 FD F3 40 7F ......@.
--------------------------------------------------------------------------
代码段、数据段描述符的DPL都是3,均覆盖整个4G空间。选择子等于0x001B,并非对
应第0x001B个(从0计)描述符,有时候容易忘了这茬,其对应的描述符所在线性地址
为:
GDTR.base + ( CURRENT_CS & 0xfff8 ) -> 0x8003F018
再比如FS对应的是GDT中第7个描述符:
0x0038 >> 3 -> 0x0007
可能有人奇怪了,数据段描述符覆盖整个4G空间并且DPL等于3,那岂非任一用户空间
程序都可读写访问整个4G空间,包括高2G的内核空间。微软没这么傻,事实上除段级
保护外,IA-32还同时提供页级保护,Windows内核启用了分页机制,其页级保护将阻
止Ring 3代码访问内核空间。页级保护只对Ring 3代码有意义,对Ring 2、1、0代码
没有任何意义,后者对页级保护来说统称为系统特权级,而前者称为用户特权级。系
统特权级代码对任意页拥有读、写、执行权限。
本小节的目标很简单,在不写驱动程序的情况下读访问任意线性地址,而不是局限在
[0x80000000, 0xa0000000)区间上。为达目标,现在有两条路,一是修改页级保护,
二是让自己的代码拥有系统特权级。页目录、页表在0xc0300000、0xc0000000,就前
几节所演示的技术而言,没法简单修改页级保护,就算有办法,也太过麻烦并且冒很
大风险。事实上我们只有一条路,让自己的代码拥有Ring 0权限。
crazylord演示了一种技术([3])。他利用/Device/PhysicalMemory在GDT中搜索P位为
0的空闲描述符,然后将这个空闲描述符设置成DPL等于3的调用门,用户空间Ring 3
代码通过这个调用门获取Ring 0权限,于是内核空间完全暴露在我们面前。
前面有一节中已经看到内核中代码段选择子等于0x0008,其对应的描述符如下:
--------------------------------------------------------------------------
> memdump -l 8 -a 0x8003F008
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 9B CF 00 ........
--------------------------------------------------------------------------
与0x8003F018相比,仅仅是DPL不同,0x8003F008的DPL是0。Windows内核采用基本平
坦模式,一个函数在这两种代码段选择子下对应一样的段内偏移,这样就很容易设置
调用门。
如果调用门导致向内层特权级跃迁,必然发生堆栈切换。压栈的形参将从Ring 3的堆
栈复制到Ring 0的堆栈中,并且CS随EIP一起压栈,我们不能依赖编译器处理形参。
dump.c演示了如何在用户空间编程中调用内核函数nt!MmGetPhysicalAddress。因为
现在进入Ring 0已不成问题,如果能获取nt!MmGetPhysicalAddress的线性地址就搞
定。crazylord对LoadLibrary()理解有问题,他在Phrack Magazine 59-0x10([3])中
这部分代码是错误的,后来为了让他的代码跑起来,他硬性定义了一个ntoskrnl.exe
基址:
/*
* default base address for ntoskrnl.exe on win2k
*
#define BASEADD 0x7FFE0000
ntoskrnl.exe的基址不可能低于0x80000000。dump.c中LocateNtoskrnlEntry()才是
正确的实现。
顺带在这里验证线性地址0xc0300000的确指向页目录,办法就是先取CR3的物理地址,
然后调用nt!MmGetPhysicalAddress( 0xc0300000 ),看返回的物理地址是否与CR3一
致。
h0ck0r@smth可能是要搞破解吧,他折腾过将内核中的驱动dump出来。当时提问如何
访问[0x80000000, 0xa0000000)以外的内核空间线性地址。后来他的实现就是直接在
Ring 0代码中访问这些线性地址。我当时想绕了,先调用nt!MmGetPhysicalAddress
再利用/Device/PhysicalMemory,事实上h0ck0r@smth的办法更直接。dump.c演示了
他的办法。
dump.c没有考虑太多边界情形,可能会导致不可预知的后果,比如[-a Address]指定
0,结果dump.exe在Ring 0中立即终止,没有回到Ring 3来,于是所征用的空闲描述
符得不到释放,此时我用kd手工释放,最简单的办法将相应描述符的第6字节(从1计)
清为0x00即可。
演示程序是为Unix程序员小闹Windows而写,注释冗长可以理解,Windows程序员勿怪。
Unix程序员如无IA-32基础,万勿执行dump.exe!
--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7
* cl dump.c /Os /G6 /W3 /Fadump.asm
*
* Usage: dump [-h] [-g Gdtrbase] [-a Address] [-l Length]
*/
/*
* 名为dump.c,实则与dump非紧藕合,dump功能仅为其中一种演示而已。
*
* 该程序仅为演示用途,其潜在的危险由使用者本人承担,否则请勿执行之。
*
* 由于参考太多源代码,我不是太清楚该将哪些作者的名字列于此处:
*
* crazylord <crazylord@minithins.net>
* Gary Nebbett
* h0ck0r@smth
* Mark E. Russinovich
* tsu00 <tsu00@263.net>
*
* 这是此番学习笔记中惟一列举源作者的C程序。总之,该程序与我没有太大关系,
* 就不贪天功为己有了,顺带少些风险,上场当念下场时。
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <windows.h>
#include <aclapi.h>
#include <memory.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/subsystem:console" )
#pragma comment( lib, "advapi32.lib" )
typedef LONG NTSTATUS;
#define NT_SUCCESS(status) ((NTSTATUS)(status)>=0)
#define RING0_CODE_SELECTOR ((unsigned short int)0x0008)
/*
*************************************************************************
* ntdef.h
*/
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
/*
* Valid values for the Attributes field
*/
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
#define OBJ_KERNEL_HANDLE 0x00000200L
#define OBJ_FORCE_ACCESS_CHECK 0x00000400L
#define OBJ_VALID_ATTRIBUTES 0x000007F2L
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;
/*
* ntdef.h
*************************************************************************
*/
/*
*************************************************************************
* <<Windows NT/2000 Native API Reference>> by Gary Nebbett
*/
typedef enum _SECTION_INHERIT
{
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
/*
* 虽然本程序用不到这么多枚举值,还是列出一份最完整的。这个程序本身不求完
* 美,尽可能多地保留一些未文档化的参考资料。
*/
typedef enum _SYSTEM_INFORMATION_CLASS // Q S
{
SystemBasicInformation, // 00 Y N
SystemProcessorInformation, // 01 Y N
SystemPerformanceInformation, // 02 Y N
SystemTimeOfDayInformation, // 03 Y N
SystemNotImplemented1, // 04 Y N
SystemProcessesAndThreadsInformation, // 05 Y N
SystemCallCounts, // 06 Y N
SystemConfigurationInformation, // 07 Y N
SystemProcessorTimes, // 08 Y N
SystemGlobalFlag, // 09 Y Y
SystemNotImplemented2, // 10 Y N
SystemModuleInformation, // 11 Y N
SystemLockInformation, // 12 Y N
SystemNotImplemented3, // 13 Y N
SystemNotImplemented4, // 14 Y N
SystemNotImplemented5, // 15 Y N
SystemHandleInformation, // 16 Y N
SystemObjectInformation, // 17 Y N
SystemPagefileInformation, // 18 Y N
SystemInstructionEmulationCounts, // 19 Y N
SystemInvalidInfoClass1, // 20
SystemCacheInformation, // 21 Y Y
SystemPoolTagInformation, // 22 Y N
SystemProcessorStatistics, // 23 Y N
SystemDpcInformation, // 24 Y Y
SystemNotImplemented6, // 25 Y N
SystemLoadImage, // 26 N Y
SystemUnloadImage, // 27 N Y
SystemTimeAdjustment, // 28 Y Y
SystemNotImplemented7, // 29 Y N
SystemNotImplemented8, // 30 Y N
SystemNotImplemented9, // 31 Y N
SystemCrashDumpInformation, // 32 Y N
SystemExceptionInformation, // 33 Y N
SystemCrashDumpStateInformation, // 34 Y Y/N
SystemKernelDebuggerInformation, // 35 Y N
SystemContextSwitchInformation, // 36 Y N
SystemRegistryQuotaInformation, // 37 Y Y
SystemLoadAndCallImage, // 38 N Y
SystemPrioritySeparation, // 39 N Y
SystemNotImplemented10, // 40 Y N
SystemNotImplemented11, // 41 Y N
SystemInvalidInfoClass2, // 42
SystemInvalidInfoClass3, // 43
SystemTimeZoneInformation, // 44 Y N
SystemLookasideInformation, // 45 Y N
SystemSetTimeSlipEvent, // 46 N Y
SystemCreateSession, // 47 N Y
SystemDeleteSession, // 48 N Y
SystemInvalidInfoClass4, // 49
SystemRangeStartInformation, // 50 Y N
SystemVerifierInformation, // 51 Y Y
SystemAddVerifier, // 52 N Y
SystemSessionProcessesInformation // 53 Y N
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION // Information Class 11
{
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
/*
* <<Windows NT/2000 Native API Reference>> by Gary Nebbett
*************************************************************************
*/
/*
*************************************************************************
观察用户空间程序memdump.exe执行时CS、DS、FS所对应的段描述符:
--------------------------------------------------------------------------
> memdump -l 8
GDTR.base = 0x8003F000
GDTR.limit = 0x03FF
CURRENT_CS = 0x001B
CURRENT_DS = 0x0023
CURRENT_ES = 0x0023
CURRENT_SS = 0x0023
CURRENT_FS = 0x0038
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 00 00 00 00 00 00 00 00 ........
> memdump -l 8 -a 0x8003F018
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 FB CF 00 ........
> memdump -l 8 -a 0x8003F020
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 F3 CF 00 ........
> memdump -l 8 -a 0x8003F038
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF 0F 00 E0 FD F3 40 7F ......@.
--------------------------------------------------------------------------
代码段、数据段描述符的DPL都是3,均覆盖整个4G空间。选择子等于0x001B,并非对
应第0x001B个(从0计)描述符,有时候容易忘了这茬,其对应的描述符所在线性地址
为:
GDTR.base + ( CURRENT_CS & 0xfff8 ) -> 0x8003F018
再比如FS对应的是GDT中第7个描述符:
0x0038 >> 3 -> 0x0007
可能有人奇怪了,数据段描述符覆盖整个4G空间并且DPL等于3,那岂非任一用户空间
程序都可读写访问整个4G空间,包括高2G的内核空间。微软没这么傻,事实上除段级
保护外,IA-32还同时提供页级保护,Windows内核启用了分页机制,其页级保护将阻
止Ring 3代码访问内核空间。页级保护只对Ring 3代码有意义,对Ring 2、1、0代码
没有任何意义,后者对页级保护来说统称为系统特权级,而前者称为用户特权级。系
统特权级代码对任意页拥有读、写、执行权限。
本小节的目标很简单,在不写驱动程序的情况下读访问任意线性地址,而不是局限在
[0x80000000, 0xa0000000)区间上。为达目标,现在有两条路,一是修改页级保护,
二是让自己的代码拥有系统特权级。页目录、页表在0xc0300000、0xc0000000,就前
几节所演示的技术而言,没法简单修改页级保护,就算有办法,也太过麻烦并且冒很
大风险。事实上我们只有一条路,让自己的代码拥有Ring 0权限。
crazylord演示了一种技术([3])。他利用/Device/PhysicalMemory在GDT中搜索P位为
0的空闲描述符,然后将这个空闲描述符设置成DPL等于3的调用门,用户空间Ring 3
代码通过这个调用门获取Ring 0权限,于是内核空间完全暴露在我们面前。
前面有一节中已经看到内核中代码段选择子等于0x0008,其对应的描述符如下:
--------------------------------------------------------------------------
> memdump -l 8 -a 0x8003F008
PageSize = 0x00001000
AllocationGranularity = 0x00010000
byteArray [ 8 bytes ] ->
00000000 FF FF 00 00 00 9B CF 00 ........
--------------------------------------------------------------------------
与0x8003F018相比,仅仅是DPL不同,0x8003F008的DPL是0。Windows内核采用基本平
坦模式,一个函数在这两种代码段选择子下对应一样的段内偏移,这样就很容易设置
调用门。
如果调用门导致向内层特权级跃迁,必然发生堆栈切换。压栈的形参将从Ring 3的堆
栈复制到Ring 0的堆栈中,并且CS随EIP一起压栈,我们不能依赖编译器处理形参。
dump.c演示了如何在用户空间编程中调用内核函数nt!MmGetPhysicalAddress。因为
现在进入Ring 0已不成问题,如果能获取nt!MmGetPhysicalAddress的线性地址就搞
定。crazylord对LoadLibrary()理解有问题,他在Phrack Magazine 59-0x10([3])中
这部分代码是错误的,后来为了让他的代码跑起来,他硬性定义了一个ntoskrnl.exe
基址:
/*
* default base address for ntoskrnl.exe on win2k
*
#define BASEADD 0x7FFE0000
ntoskrnl.exe的基址不可能低于0x80000000。dump.c中LocateNtoskrnlEntry()才是
正确的实现。
顺带在这里验证线性地址0xc0300000的确指向页目录,办法就是先取CR3的物理地址,
然后调用nt!MmGetPhysicalAddress( 0xc0300000 ),看返回的物理地址是否与CR3一
致。
h0ck0r@smth可能是要搞破解吧,他折腾过将内核中的驱动dump出来。当时提问如何
访问[0x80000000, 0xa0000000)以外的内核空间线性地址。后来他的实现就是直接在
Ring 0代码中访问这些线性地址。我当时想绕了,先调用nt!MmGetPhysicalAddress
再利用/Device/PhysicalMemory,事实上h0ck0r@smth的办法更直接。dump.c演示了
他的办法。
dump.c没有考虑太多边界情形,可能会导致不可预知的后果,比如[-a Address]指定
0,结果dump.exe在Ring 0中立即终止,没有回到Ring 3来,于是所征用的空闲描述
符得不到释放,此时我用kd手工释放,最简单的办法将相应描述符的第6字节(从1计)
清为0x00即可。
演示程序是为Unix程序员小闹Windows而写,注释冗长可以理解,Windows程序员勿怪。
Unix程序员如无IA-32基础,万勿执行dump.exe!
--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7
* cl dump.c /Os /G6 /W3 /Fadump.asm
*
* Usage: dump [-h] [-g Gdtrbase] [-a Address] [-l Length]
*/
/*
* 名为dump.c,实则与dump非紧藕合,dump功能仅为其中一种演示而已。
*
* 该程序仅为演示用途,其潜在的危险由使用者本人承担,否则请勿执行之。
*
* 由于参考太多源代码,我不是太清楚该将哪些作者的名字列于此处:
*
* crazylord <crazylord@minithins.net>
* Gary Nebbett
* h0ck0r@smth
* Mark E. Russinovich
* tsu00 <tsu00@263.net>
*
* 这是此番学习笔记中惟一列举源作者的C程序。总之,该程序与我没有太大关系,
* 就不贪天功为己有了,顺带少些风险,上场当念下场时。
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <windows.h>
#include <aclapi.h>
#include <memory.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/subsystem:console" )
#pragma comment( lib, "advapi32.lib" )
typedef LONG NTSTATUS;
#define NT_SUCCESS(status) ((NTSTATUS)(status)>=0)
#define RING0_CODE_SELECTOR ((unsigned short int)0x0008)
/*
*************************************************************************
* ntdef.h
*/
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
/*
* Valid values for the Attributes field
*/
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
#define OBJ_KERNEL_HANDLE 0x00000200L
#define OBJ_FORCE_ACCESS_CHECK 0x00000400L
#define OBJ_VALID_ATTRIBUTES 0x000007F2L
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;
/*
* ntdef.h
*************************************************************************
*/
/*
*************************************************************************
* <<Windows NT/2000 Native API Reference>> by Gary Nebbett
*/
typedef enum _SECTION_INHERIT
{
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
/*
* 虽然本程序用不到这么多枚举值,还是列出一份最完整的。这个程序本身不求完
* 美,尽可能多地保留一些未文档化的参考资料。
*/
typedef enum _SYSTEM_INFORMATION_CLASS // Q S
{
SystemBasicInformation, // 00 Y N
SystemProcessorInformation, // 01 Y N
SystemPerformanceInformation, // 02 Y N
SystemTimeOfDayInformation, // 03 Y N
SystemNotImplemented1, // 04 Y N
SystemProcessesAndThreadsInformation, // 05 Y N
SystemCallCounts, // 06 Y N
SystemConfigurationInformation, // 07 Y N
SystemProcessorTimes, // 08 Y N
SystemGlobalFlag, // 09 Y Y
SystemNotImplemented2, // 10 Y N
SystemModuleInformation, // 11 Y N
SystemLockInformation, // 12 Y N
SystemNotImplemented3, // 13 Y N
SystemNotImplemented4, // 14 Y N
SystemNotImplemented5, // 15 Y N
SystemHandleInformation, // 16 Y N
SystemObjectInformation, // 17 Y N
SystemPagefileInformation, // 18 Y N
SystemInstructionEmulationCounts, // 19 Y N
SystemInvalidInfoClass1, // 20
SystemCacheInformation, // 21 Y Y
SystemPoolTagInformation, // 22 Y N
SystemProcessorStatistics, // 23 Y N
SystemDpcInformation, // 24 Y Y
SystemNotImplemented6, // 25 Y N
SystemLoadImage, // 26 N Y
SystemUnloadImage, // 27 N Y
SystemTimeAdjustment, // 28 Y Y
SystemNotImplemented7, // 29 Y N
SystemNotImplemented8, // 30 Y N
SystemNotImplemented9, // 31 Y N
SystemCrashDumpInformation, // 32 Y N
SystemExceptionInformation, // 33 Y N
SystemCrashDumpStateInformation, // 34 Y Y/N
SystemKernelDebuggerInformation, // 35 Y N
SystemContextSwitchInformation, // 36 Y N
SystemRegistryQuotaInformation, // 37 Y Y
SystemLoadAndCallImage, // 38 N Y
SystemPrioritySeparation, // 39 N Y
SystemNotImplemented10, // 40 Y N
SystemNotImplemented11, // 41 Y N
SystemInvalidInfoClass2, // 42
SystemInvalidInfoClass3, // 43
SystemTimeZoneInformation, // 44 Y N
SystemLookasideInformation, // 45 Y N
SystemSetTimeSlipEvent, // 46 N Y
SystemCreateSession, // 47 N Y
SystemDeleteSession, // 48 N Y
SystemInvalidInfoClass4, // 49
SystemRangeStartInformation, // 50 Y N
SystemVerifierInformation, // 51 Y Y
SystemAddVerifier, // 52 N Y
SystemSessionProcessesInformation // 53 Y N
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION // Information Class 11
{
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
/*
* <<Windows NT/2000 Native API Reference>> by Gary Nebbett
*************************************************************************
*/
/*
*************************************************************************