[转载]Windows NT/2000/XP下不用驱动的Ring0代码实现

简介: 大家知道,Windows NT/2000为实现其可靠性,严格将系统划分为内核模式与用户模式,在i386系统中分别对应CPU的Ring0与Ring3级别。Ring0下,可以执行特权级指令,对任何I/O设备都有访问权等等。
Windows NT/2000/XP下不用驱动的Ring0代码实现      
            WebCrazy(http://webcrazy.yeah.net/

    大家知道,Windows NT/2000为实现其可靠性,严格将系统划分为内核模式与用户模式,在i386系统中分别对应CPU的Ring0与Ring3级别。Ring0下,可以执行特权级指令,对任何I/O设备都有访问权等等。要实现从用户态进入核心态,即从Ring 3进入Ring 0必须借助CPU的某种门机制,如中断门、调用门等。而Windows NT/2000提供用户态执行系统服务(Ring 0例程)的此类机制即System Service的int 2eh中断服务等,严格的参数检查,只能严格的执行Windows NT/2000提供的服务,而如果想执行用户提供的Ring 0代码(指运行在Ring 0权限的代码),常规方法似乎只有编写设备驱动程序。本文将介绍一种在用户态不借助任何驱动程序执行Ring0代码的方法。

    Windows NT/2000将设备驱动程序调入内核区域(常见的位于地址0x80000000上),由DPL为0的GDT项8,即cs为8时实现Ring 0权限。本文通过在系统中构造一个指向我们的代码的调用门(CallGate),实现Ring0代码。基于这个思路,为实现这个目的主要是构造自己的CallGate。CallGate由系统中叫Global Descriptor Table(GDT)的全局表指定。GDT地址可由i386指令sgdt获得(sgdt不是特权级指令,普通Ring 3程序均可执行)。GDT地址在Windows NT/2000保存于KPCR(Processor Control Region)结构中(见《再谈Windows NT/2000环境切换》)。GDT中的CallGate是如下的格式:
None.gif    typedef  struct
ExpandedBlockStart.gif    
InBlock.gif        unsigned short  offset_0_15;
InBlock.gif        unsigned short  selector;
InBlock.gif
InBlock.gif        unsigned char    param_count : 4;
InBlock.gif        unsigned char    some_bits   : 4;
InBlock.gif
InBlock.gif        unsigned char    type        : 4;
InBlock.gif        unsigned char    app_system  : 1;
InBlock.gif        unsigned char    dpl         : 2;
InBlock.gif        unsigned char    present     : 1;
InBlock.gif    
InBlock.gif        unsigned short  offset_16_31;
ExpandedBlockEnd.gif     }
 CALLGATE_DESCRIPTOR;
    GDT位于内核区域,一般用户态的程序是不可能对这段内存区域有直接的访问权。幸运的是Windows NT/2000提供了一个叫PhysicalMemory的Section内核对象位于\Device的路径下。顾名思义,通过这个Section对象可以对物理内存进行操作。用objdir.exe对这个对象分析如下:
None.gif    C:\NTDDK\bin>objdir /D \Device
None.gif
None.gif    PhysicalMemory                   
None.gif        Section
None.gif        DACL - 
None.gif           Ace[ 0] - Grant - 0xf001f - NT AUTHORITY\SYSTEM
None.gif                             Inherit: 
None.gif                             Access: 0x001F  and  ( D RCtl WOwn WDacl )
None.gif
None.gif           Ace[ 1] - Grant - 0x2000d - BUILTIN\Administrators
None.gif                             Inherit: 
None.gif                             Access: 0x000D  and  ( RCtl )
   从dump出的这个对象DACL的Ace可以看出默认情况下只有SYSTEM用户才有对这个对象的读写权限,即对物理内存有读写能力,而Administrator只有读权限,普通用户根本就没有权限。不过如果我们有Administrator权限就可以通过GetSecurityInfo、SetEntriesInAcl与SetSecurityInfo这些API来修改这个对象的ACE。这也是我提供的代码需要Administrator的原因。实现的代码如下:
None.gif   VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
ExpandedBlockStart.gif    
InBlock.gif
InBlock.gif       PACL pDacl=NULL;
InBlock.gif       PACL pNewDacl=NULL;
InBlock.gif       PSECURITY_DESCRIPTOR pSD=NULL;
InBlock.gif       DWORD dwRes;
InBlock.gif       EXPLICIT_ACCESS ea;
InBlock.gif
InBlock.gif       if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
InBlock.gif                  NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)
ExpandedSubBlockStart.gif          
InBlock.gif             printf( "GetSecurityInfo Error %u\n", dwRes );
InBlock.gif             goto CleanUp;
ExpandedSubBlockEnd.gif           }

InBlock.gif
InBlock.gif       ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
InBlock.gif       ea.grfAccessPermissions = SECTION_MAP_WRITE;
InBlock.gif       ea.grfAccessMode = GRANT_ACCESS;
InBlock.gif       ea.grfInheritance= NO_INHERITANCE;
InBlock.gif       ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
InBlock.gif       ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
InBlock.gif       ea.Trustee.ptstrName = "CURRENT_USER";
InBlock.gif
InBlock.gif
InBlock.gif       if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)
ExpandedSubBlockStart.gif          
InBlock.gif             printf( "SetEntriesInAcl %u\n", dwRes );
InBlock.gif             goto CleanUp;
ExpandedSubBlockEnd.gif           }

InBlock.gif
InBlock.gif       if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)
ExpandedSubBlockStart.gif          
InBlock.gif             printf("SetSecurityInfo %u\n",dwRes);
InBlock.gif             goto CleanUp;
ExpandedSubBlockEnd.gif           }

InBlock.gif
InBlock.gif    CleanUp:
InBlock.gif
InBlock.gif       if(pSD)
InBlock.gif          LocalFree(pSD);
InBlock.gif       if(pNewDacl)
InBlock.gif          LocalFree(pSD);
ExpandedBlockEnd.gif     }

None.gif
None.gif    这段代码对给定HANDLE的对象增加了如下的ACE: 
None.gif
None.gif    PhysicalMemory                   
None.gif        Section
None.gif        DACL - 
None.gif           Ace[ 0] - Grant - 0x2 - WEBCRAZY\Administrator
None.gif                             Inherit: 
None.gif                             Access: 0x0002     // SECTION_MAP_WRITE
None.gif

   这样我们在有Administrator权限的条件下就有了对物理内存的读写能力。但若要修改GDT表实现Ring 0代码。我们将面临着另一个难题,因为sgdt指令获得的GDT地址是虚拟地址(线性地址),我们只有知道GDT表的物理地址后才能通过\Device\PhysicalMemory对象修改GDT表,这就牵涉到了线性地址转化成物理地址的问题。我们先来看一看Windows NT/2000是如何实现这个的:
None.gif    kd> u nt!MmGetPhysicalAddress l 30
None.gif    ntoskrnl!MmGetPhysicalAddress:
None.gif    801374e0 56               push    esi
None.gif    801374e1 8b742408         mov     esi,[esp+0x8]
None.gif    801374e5 33d2             xor     edx,edx
None.gif    801374e7 81fe00000080     cmp     esi,0x80000000
None.gif    801374ed 722c             jb    ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
None.gif    801374ef 81fe000000a0     cmp     esi,0xa0000000
None.gif    801374f5 7324             jnb   ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
None.gif    801374f7 39153ce71780     cmp     [ntoskrnl!MmKseg2Frame (8017e73c)],edx
None.gif    801374fd 741c             jz    ntoskrnl!MmGetPhysicalAddress+0x2b (8013751b)
None.gif    801374ff 8bc6             mov     eax,esi
None.gif    80137501 c1e80c           shr     eax,0xc
None.gif    80137504 25ffff0100       and     eax,0x1ffff
None.gif    80137509 6a0c             push    0xc
None.gif    8013750b 59               pop     ecx
None.gif    8013750c e8d3a7fcff       call    ntoskrnl!_allshl (80101ce4)
None.gif    80137511 81e6ff0f0000     and     esi,0xfff
None.gif    80137517 03c6             add     eax,esi
None.gif    80137519 eb17             jmp   ntoskrnl!MmGetPhysicalAddress+0x57 (80137532)
None.gif    8013751b 8bc6             mov     eax,esi
None.gif    8013751d c1e80a           shr     eax,0xa
None.gif    80137520 25fcff3f00       and     eax,0x3ffffc
None.gif    80137525 2d00000040       sub     eax,0x40000000
None.gif    8013752a 8b00             mov     eax,[eax]
None.gif    8013752c a801             test    al,0x1
None.gif    8013752e 7506             jnz   ntoskrnl!MmGetPhysicalAddress+0x44 (80137536)
None.gif    80137530 33c0             xor     eax,eax
None.gif    80137532 5e               pop     esi
None.gif    80137533 c20400           ret     0x4
None.gif
    从这段汇编代码可看出如果线性地址在0x80000000与0xa0000000范围内,只是简单的进行移位操作(位于801374ff-80137519指令间),并未查页表。我想Microsoft这样安排肯定是出于执行效率的考虑。这也为我们指明了一线曙光,因为GDT表在Windows NT/2000中一般情况下均位于这个区域(我不知道/3GB开关的Windows NT/2000是不是这种情况)。

    经过这样的分析,我们就可以只通过用户态程序修改GDT表了。而增加一个CallGate就不是我可以介绍的了,找本Intel手册自己看一看了。具体实现代码如下:
ExpandedBlockStart.gif    typedef  struct gdtr 
InBlock.gif        short Limit;
InBlock.gif        short BaseLow;
InBlock.gif        short BaseHigh;
ExpandedBlockEnd.gif     }
 Gdtr_t, *PGdtr_t;
None.gif
None.gif    ULONG MiniMmGetPhysicalAddress(ULONG virtualaddress)
ExpandedBlockStart.gif    
InBlock.gif        if(virtualaddress<0x80000000||virtualaddress>=0xA0000000)
InBlock.gif           return 0;
InBlock.gif        return virtualaddress&0x1FFFF000;
ExpandedBlockEnd.gif     }

None.gif
None.gif    BOOL ExecRing0Proc(ULONG Entry,ULONG seglen)
ExpandedBlockStart.gif    
InBlock.gif       Gdtr_t gdt;
InBlock.gif       __asm sgdt gdt;
InBlock.gif     
InBlock.gif       ULONG mapAddr=MiniMmGetPhysicalAddress(gdt.BaseHigh<<16U|gdt.BaseLow);
InBlock.gif       if(!mapAddr) return 0;
InBlock.gif
InBlock.gif       HANDLE   hSection=NULL;
InBlock.gif       NTSTATUS status;
InBlock.gif       OBJECT_ATTRIBUTES        objectAttributes;
InBlock.gif       UNICODE_STRING objName;
InBlock.gif       CALLGATE_DESCRIPTOR *cg;
InBlock.gif
InBlock.gif       status = STATUS_SUCCESS;
InBlock.gif   
InBlock.gif       RtlInitUnicodeString(&objName,L"\\Device\\PhysicalMemory");
InBlock.gif
InBlock.gif       InitializeObjectAttributes(&objectAttributes,
InBlock.gif                                  &objName,
InBlock.gif                                  OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
InBlock.gif                                  NULL,
InBlock.gif                                 (PSECURITY_DESCRIPTOR) NULL);
InBlock.gif
InBlock.gif       status = ZwOpenSection(&hSection,SECTION_MAP_READ|SECTION_MAP_WRITE,&objectAttributes);
InBlock.gif
ExpandedSubBlockStart.gif       if(status == STATUS_ACCESS_DENIED)
InBlock.gif          status = ZwOpenSection(&hSection,READ_CONTROL|WRITE_DAC,&objectAttributes);
InBlock.gif          SetPhyscialMemorySectionCanBeWrited(hSection);
InBlock.gif          ZwClose(hSection);
InBlock.gif          status =ZwOpenSection(&hSection,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&objectAttributes);
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif       if(status != STATUS_SUCCESS)
ExpandedSubBlockStart.gif         
InBlock.gif            printf("Error Open PhysicalMemory Section Object,Status:%08X\n",status);
InBlock.gif            return 0;
ExpandedSubBlockEnd.gif          }

InBlock.gif      
InBlock.gif       PVOID BaseAddress;
InBlock.gif
InBlock.gif       BaseAddress=MapViewOfFile(hSection,
InBlock.gif                     FILE_MAP_READ|FILE_MAP_WRITE,
InBlock.gif                     0,
InBlock.gif                     mapAddr,    //low part
InBlock.gif
                     (gdt.Limit+1));
InBlock.gif
InBlock.gif       if(!BaseAddress)
ExpandedSubBlockStart.gif          
InBlock.gif             printf("Error MapViewOfFile:");
InBlock.gif             PrintWin32Error(GetLastError());
InBlock.gif             return 0;
ExpandedSubBlockEnd.gif           }

InBlock.gif
InBlock.gif       BOOL setcg=FALSE;
InBlock.gif
InBlock.gif       for(cg=(CALLGATE_DESCRIPTOR *)((ULONG)BaseAddress+(gdt.Limit&0xFFF8));(ULONG)cg>(ULONG)BaseAddress;cg--)
ExpandedSubBlockStart.gif           if(cg->type == 0)
InBlock.gif             cg->offset_0_15 = LOWORD(Entry);
InBlock.gif             cg->selector = 8;
InBlock.gif             cg->param_count = 0;
InBlock.gif             cg->some_bits = 0;
InBlock.gif             cg->type = 0xC;          // 386 call gate
InBlock.gif
             cg->app_system = 0;      // A system descriptor
InBlock.gif
             cg->dpl = 3;             // Ring 3 code can call
InBlock.gif
             cg->present = 1;
InBlock.gif             cg->offset_16_31 = HIWORD(Entry);
InBlock.gif             setcg=TRUE;
InBlock.gif             break;
ExpandedSubBlockEnd.gif           }

InBlock.gif
ExpandedSubBlockStart.gif       if(!setcg)
InBlock.gif            ZwClose(hSection);
InBlock.gif            return 0;
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif       short farcall[3];
InBlock.gif
InBlock.gif       farcall[2]=((short)((ULONG)cg-(ULONG)BaseAddress))|3;  //Ring 3 callgate;
InBlock.gif

InBlock.gif       if(!VirtualLock((PVOID)Entry,seglen))
ExpandedSubBlockStart.gif          
InBlock.gif             printf("Error VirtualLock:");
InBlock.gif             PrintWin32Error(GetLastError());
InBlock.gif             return 0;
ExpandedSubBlockEnd.gif           }

InBlock.gif
InBlock.gif       SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
InBlock.gif
InBlock.gif       Sleep(0);
InBlock.gif
InBlock.gif       _asm call fword ptr [farcall]
InBlock.gif
InBlock.gif       SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL);
InBlock.gif
InBlock.gif       VirtualUnlock((PVOID)Entry,seglen);
InBlock.gif
InBlock.gif       //Clear callgate
InBlock.gif
       *(ULONG *)cg=0;
InBlock.gif       *((ULONG *)cg+1)=0;
InBlock.gif
InBlock.gif       ZwClose(hSection);
InBlock.gif       return TRUE;
InBlock.gif
ExpandedBlockEnd.gif     }

None.gif
None.gif
    我在提供的代码中演示了对Control Register与I/O端口的操作。CIH病毒在Windows 9X中就是因为获得Ring 0权限才有了一定的危害,但Windows NT/2000毕竟不是Windows 9X,她已经有了比较多的安全审核机制,本文提供的代码也要求具有Administrator权限,但如果系统存在某种漏洞,如缓冲区溢出等等,还是有可能获得这种权限的,所以我不对本文提供的方法负有任何的责任,所有讨论只是一个技术热爱者在讨论技术而已。谢谢! 

    参考资料:
      1.Intel Corp<<Intel Architecture Software Developer's Manual,Volume 3>> 
目录
相关文章
|
2月前
|
数据安全/隐私保护 虚拟化 Windows
如何在 VM 虚拟机中安装 Windows XP 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows XP 操作系统保姆级教程(附链接)
|
2月前
|
数据可视化 Python Windows
使用 Python 代码在 windows 控制台打印正弦三角函数
使用 Python 代码在 windows 控制台打印正弦三角函数
20 0
|
4月前
|
开发工具 数据安全/隐私保护 C++
windows openssl安装和基本使用(代码演示)
本文主要讲到了openssl的基本使用方法,开发环境为windows,开发工具为VS2019.本文主要是说明openssl如何使用,不介绍任何理论知识,如果有不懂的,请自行百度。个人建议下一个everything查询工具,真的很好用,比window自带的查询快了很多,可以查询自己想要的文件
198 0
windows openssl安装和基本使用(代码演示)
|
5月前
|
监控 安全 API
5.9 Windows驱动开发:内核InlineHook挂钩技术
在上一章`《内核LDE64引擎计算汇编长度》`中,`LyShark`教大家如何通过`LDE64`引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的`InlineHook`函数挂钩其实与应用层一致,都是使用`劫持执行流`并跳转到我们自己的函数上来做处理,唯一的不同的是内核`Hook`只针对`内核API`函数,但由于其身处在`最底层`所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。
44 1
5.9 Windows驱动开发:内核InlineHook挂钩技术
|
5月前
|
监控 API C++
8.4 Windows驱动开发:文件微过滤驱动入门
MiniFilter 微过滤驱动是相对于`SFilter`传统过滤驱动而言的,传统文件过滤驱动相对来说较为复杂,且接口不清晰并不符合快速开发的需求,为了解决复杂的开发问题,微过滤驱动就此诞生,微过滤驱动在编写时更简单,多数`IRP`操作都由过滤管理器`(FilterManager或Fltmgr)`所接管,因为有了兼容层,所以在开发中不需要考虑底层`IRP`如何派发,更无需要考虑兼容性问题,用户只需要编写对应的回调函数处理请求即可,这极大的提高了文件过滤驱动的开发效率。
44 0
|
5月前
|
监控 Windows
7.4 Windows驱动开发:内核运用LoadImage屏蔽驱动
在笔者上一篇文章`《内核监视LoadImage映像回调》`中`LyShark`简单介绍了如何通过`PsSetLoadImageNotifyRoutine`函数注册回调来`监视驱动`模块的加载,注意我这里用的是`监视`而不是`监控`之所以是监视而不是监控那是因为`PsSetLoadImageNotifyRoutine`无法实现参数控制,而如果我们想要控制特定驱动的加载则需要自己做一些事情来实现,如下`LyShark`将解密如何实现屏蔽特定驱动的加载。
33 0
7.4 Windows驱动开发:内核运用LoadImage屏蔽驱动
|
1月前
|
监控 安全 API
7.3 Windows驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章`《内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要注意的是该回调函数内无法进行拦截,如需要拦截则需写入返回指令这部分内容将在下一章进行讲解,本章将主要实现对模块的监视功能。
38 0
7.3 Windows驱动开发:内核监视LoadImage映像回调
|
5月前
|
监控 安全 API
7.2 Windows驱动开发:内核注册并监控对象回调
在笔者上一篇文章`《内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止的目的。
31 0
7.2 Windows驱动开发:内核注册并监控对象回调
|
5月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
31 1
7.6 Windows驱动开发:内核监控FileObject文件回调
|
5月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
50 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调