可能这些已经没有什么,但对俺菜鸟来说学习一下应该还可以的。
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);
...........这里可以调用一些自己实现的函数。
很多细节的东西应该讲得不大好,希望大牛们能指出来,让俺菜鸟学习学习。
- 标 题:RING 3 无驱动进入RING 0
- 作 者:hljleo
- 时 间:2008-06-02 18:52:54
- 链 接:http://bbs.pediy.com/showthread.php?t=65906