可能这些已经没有什么,但对俺菜鸟来说学习一下应该还可以的。

MY BLOG:http://hi.baidu.com/hljleo
MY QQ:554920269

下面就是RING 3 无驱动怎么进入RING 0的知识要点




1 通过工作区\\Device\\PhysicalMemory操作内存,用到的函数流程NtOpenSection(),NtMapViewOfSection()。所以
依靠这种方法我们可以进入RAM的任何地址。
然而在使用这个工作区之前必须保证我们有权限使用它,可以通过一些函数来获得管理员权限:GetSecurityInfo(),SetEntriesInAcl(),SetSecurityInfo()。

2 在进入RING 0之前我们还必须知道目标虚拟地址映射后的物理地址的准确位置,所以我们必须找到进程的页目录。这里涉及到虚拟地址到物理地址的映射,简单解释一下:

A  CPU从当前运行进程的CR3寄存器中得到页目录。在这个目录中,虚拟地址的高10位让CPU找到页表的地址。
B  当找到了页表之后,在这张表中,CPU根据相应的虚拟地址进入下一步定位页。
C  当定位好了页之后,CPU使用虚拟地址的低12位作为页的偏移量。

所以如果我们知道了页目录的位置,我们也就知道了虚拟地址映射后的物理位置。页目录地址存在CR3寄存器中,我们是没有权限在user-mode直接获得的。
以下就是如何获得页目录的一些知识:

现在是认为知道我们进程的页目录在物理RAM中的地址,假使我们目标虚拟地址是V,映射页目录到虚拟地址D,把D认为是1024个DWORD。因此D中的(V>>22)项的高20位是物理页,一张页表,对应的虚拟地址V。现在映射这张页表到虚拟地址T,把T想象成一个1024 DWORD的数组,T中第 ((V>>12)&0x3FF) 项的高20位是虚拟地址V所代表的一个物理页,也就是我们目标虚拟地址映射后的物理地址。

在X86,进程的页目录是从虚拟地址0XC0300000开始,内存管理器从0XC0000000开始映射页表的。

问题的关键在于我们不知道进程页目录的物理地址,为找到页目录,必须做如下工作:
将RAM中的每一页都映射到我们进程的地址空间中,扫描一下内存,总之我们应该会找到页目录的。

假设我们检查的物理页是P,并且P被映射进入虚拟地址V中,我们把V想象成一个1024 DWORD的数组。如果P是我们进程中含有页目录的一个物理页的话,有如下结论:

A.V的第0x300项的高20位肯定等于P。
B.V中第0x300项的最低位肯定被设置了,因为它表明了此页存在于RAM中。
C.如果P是一个页目录,那么V中的第(V>>22)项表示是一个页表--对应于虚拟地址V它的本身。同时这个页表毫无疑问的被装载进入RAM中。因此,V中第(V>>22)项的最低位肯定被设置了。
如果找不到就换下一页。

3 知道了页目录的物理地址,我们就可以找到我们所感兴趣的虚拟地址到物理地址的映射,我们是得到GDT的物理地址

,有了GDT 的物理地址我们就可以无驱动进入内核了。因为我们可以通过GDT中的调用门来实现不同特权级的代码段之间进行控制访问,每一种门描述符中都有present位代表该描述符是否存在。它在每个描述符中的位置都是一样的,因此我们可以根据这个present位来在GDT中找出一个空白位置,在这个位置里面添加我们自己定义的描述符。

我们可以将描述符的selector 域设置为0X8(表明将会执行特权指令),同时它的offset_low和offse_high域分别对应

于我们即将要调用的函数的低16位和高16位。
至于要调用的函数实现的功能这就随个人不同的兴趣而定,比如说可以获得中断信息等等。
下面是主要程序代码的实现:

(1)寻找页目录:
 for(x=0;x<meminfo.dwTotalPhys;x+=0x1000)//经过修改后运行这些代码的进程,其虚拟地址的范围从0--0x1000将成

为进程地址空间中有效的虚拟地址范围
   
{
//映射当前页进入 RAM
MappedSize=4096; phys.QuadPart=x; DirectoryMappedAddress=0;
status = NtMapViewOfSection(Section, (HANDLE) -1, &DirectoryMappedAddress, 0L,MappedSize, &phys, 

&MappedSize, ViewShare,0, PAGE_READONLY);
if(status)continue;
entry=(DWORD*)DirectoryMappedAddress;

//得到偏移量
DirectoryOffset=(DWORD)DirectoryMappedAddress;
TableOffset=(DWORD)DirectoryMappedAddress;
DirectoryOffset>>=22;//第>>22项高20位是页表的物理地址
TableOffset=(TableOffset>>12)&0x3ff;//第>>12)&0x3FF项的高20位是虚拟地址的物理页的位置,虚拟地址中 第((V>>12)&0x3FF)项的低位必定被设置


//为了找到页目录,我们需要作出判断,即页目录的第 0x300项的高20位对应的是 我们要找页目录物理地址自身,如果是找到页目录的位置,如果不是进入下一页
// 当然该项的存在位必须被设置

if((entry[0x300]&0xfffff000)!=x ||(entry[0x300]&1)!=1 || (entry[DirectoryOffset]&1)!=1)
{NtUnmapViewOfSection((HANDLE) -1, DirectoryMappedAddress);continue;}


//映射页表
MappedSize=4096; 
phys.QuadPart=(entry[DirectoryOffset]&0xfffff000); 
TableMappedAddress=0;
status = NtMapViewOfSection(Section, (HANDLE) -1, &TableMappedAddress, 0L,MappedSize, &phys, 

&MappedSize, ViewShare,0, PAGE_READONLY);

if(status){NtUnmapViewOfSection((HANDLE) -1, DirectoryMappedAddress);continue;}

//如果找到页表---虚拟地址第(V>>12)&0x3ff页表项的高20位必须是页目录的地址
//当然存在位同样应该被设置,,现在找到真正页目录的位置

entry=(DWORD*)TableMappedAddress;
if((entry[TableOffset]&1)==1 && (entry[TableOffset]&0xfffff000)==x)found++;


NtUnmapViewOfSection((HANDLE) -1, TableMappedAddress);

//页目录已经找到,退出循环
if(found)break;
NtUnmapViewOfSection((HANDLE) -1, DirectoryMappedAddress);  
}

(2)知道了页目录的物理地址,我们就可以找到我们所感兴趣的虚拟地址到物理地址的映射,下面是得到GDT的物理地址,并设置自己的调用门,现在我们就可以无驱动进入内核了。


_asm
{
sgdt gdtr  //得到 gdtr 寄存器的内容保存于内存单元中,即得到 GDT 基地址与界限
lea eax,gdtr
mov ebx,dword ptr[eax+2]//高32位指出GDT在物理存取器中存放的基地址
mov gdtbase,ebx
}


DirectoryOffset=gdtbase;TableOffset=gdtbase;
DirectoryOffset>>=22;
TableOffset=(TableOffset>>12)&0x3ff;//保证GDT从一个页开始

entry=(DWORD*)DirectoryMappedAddress;

//映射页表- 第(V-22)页目录项的高20位是物理地址
MappedSize=4096;
 phys.QuadPart=(entry[DirectoryOffset]&0xfffff000); 
 TableMappedAddress=0;

status = NtMapViewOfSection(Section, (HANDLE) -1, &TableMappedAddress, 0L,MappedSize, &phys, 

&MappedSize, ViewShare,0, PAGE_READONLY);

//物理页是 第 (V>>12)&0x3ff页表项的高20位 - -这就是我们所想要的
entry=(DWORD*)TableMappedAddress;
physgdtbase=(entry[TableOffset]&0xfffff000);

NtUnmapViewOfSection((HANDLE) -1, TableMappedAddress);

NtUnmapViewOfSection((HANDLE) -1, DirectoryMappedAddress);

// 映射 gdt
phys.QuadPart=physgdtbase;MappedSize=4096;

NtMapViewOfSection(Section, (HANDLE) -1, (PVOID*)&GdtMappedAddress, L,MappedSize, &phys, &MappedSize, 

ViewShare,0, PAGE_READWRITE);
gdtbase&=0xfff;
GdtMappedAddress+=gdtbase;

  gate=(CallGateDescriptor * )GdtMappedAddress;

//在GDT中寻找空闲入口,即Present位为0的

selector=1;
while(1)
{
if(!gate[selector].present)break;
selector++;
}

// 设置 call gate
gate[selector].offset_low  = (WORD) ((DWORD)kernelfunction & 0xFFFF);//将会调用任意函数的地址,这里是

kernelfunction这个函数
gate[selector].selector     = 8;
gate[selector].param_count  = 1; //we will pass a parameter
gate[selector].unused    = 0;
gate[selector].type       = 0xc;     //0xc是 32-bit callgate 标志
      
gate[selector].dpl          = 3;      // 必须是 3
gate[selector].present      = 1;
gate[selector].offset_high = (WORD) ((DWORD)kernelfunction >> 16);
       


NtUnmapViewOfSection((HANDLE) -1, GdtMappedAddress);
CloseHandle(Section);
...........这里可以调用一些自己实现的函数。


很多细节的东西应该讲得不大好,希望大牛们能指出来,让俺菜鸟学习学习。

  • 标 题:答复
  • 作 者:hljleo
  • 时 间:2008-06-03 11:34:02

昨天弄的时候忘记上传源码了,,不好意思

上传的附件 进入RING0.rar