标 题: 【分享】自己的kmd驱动ring0 参考的wowocock前辈的方法,做了一点注释
作 者: Troy
时 间: 2008-01-28,01:39
链 接: http://bbs.
为了尊重wowocock前辈 我把wowocock的代码贴在我代码后面。
毕竟是发表我自己的吧.
在看完wowocock大哥的代码后。因为有一些地方
说得不是很明白 所以自己动手测试了一次 并自己写了一个类试的代码。
加上了比较详细的注释 希望大家看起来不费力
废话不多说了。代码开始 大家可以保存为.bat文件.调试是设置了编译器环境变量。
代码分为驱动代码 装载驱动的程序代码 和测试代码
下面为驱动部分。
;@echo off
;goto make
.386
.model flat, stdcall
option casemap:none
;>>>>>>>>>>>>>>>STRUCT>>>>>>>>>>>>>>>>>>>>>>>>>>>>
CALLGATE
STRUCT
OFFSETL
DW
0
SELECTOR
DW
0
DCOUNT
DB
0
GTYPE
DB
0
OFFSETH
DW
0
CALLGATE
ENDS
DESCRIPTOR
STRUCT
LIMITL
DW
0
BASEL
DW
0
BASEM
DB
0
ATTRIBUTES
DW
0
BASEH
DB
0
DESCRIPTOR
ENDS
;>>>>>>>>>>>>>>>>>equ>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
CODES_SEL
=
3E8H
CODE_TYPE
=
0CF9AH
GATE_TYPE
=
0ECH
.code
DriverEntry:
pushfd
pushad
push edx
sgdt [esp-2]
pop edx
;全局描述符表基地址
mov eax,edx
;存放全局描述符开始地址
mov BYTE ptr [edx],0C3H
;在全句描述符表开始地址
;存放retn指令机器码,全
;句描述符表第一个描述符
;为空表述符,被保留,没被用
;书上说这个描述符应该全为0
;这里看出 可以写东西进去。
mov ecx,3E0h
;定位自己的门描述符
add edx,ecx
;让描述位置在全局描述
assume edx:ptr CALLGATE;符的3E0这个位置
cmp [edx].SELECTOR,CODES_SEL
jz ToEnd
mov [edx].OFFSETL,ax
;创建门描述符
mov [edx].SELECTOR,CODES_SEL
mov [edx].DCOUNT,0
mov [edx].GTYPE,GATE_TYPE
shr eax,16
mov [edx].OFFSETH,ax
;描述符的偏移地址其实就是上面说到的
;那个retn指令地址,当call 这个门的时候
;只从堆栈弹出EIP地址,所以CS寄存器没被
;覆盖,所以还是ting0权限
add edx,8
assume edx:ptr DESCRIPTOR
mov [edx].LIMITL,0FFFFH
;创建自己的代码段描述符
mov [edx].BASEL,0
mov [edx].BASEM,0
mov [edx].ATTRIBUTES,CODE_TYPE
mov [edx].BASEH,0
ToEnd:
popad
popfd
mov eax,0C0000182h
ret 8
end DriverEntry
;如果设置了环境变量 就把代码直接保存为mykmd.bat 然后双击 就OK
;没有设置环境变量的可以按下面方法编译,具体编译方法不用我介绍了吧
:make
set drv=mykmd
ml /nologo /c /coff %drv%.bat
link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause
;这里开始为测试代码部分
;@echo off
;goto make
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
szOK
db
'成功访问rc0寄存器',0
szFormat
db
'cr0=%X',0
szBuffer
db
50 dup (?)
CallGate_Sel
dd
0
dw
03E3H
;调用门的选择子
.code
start:
call FWORD ptr CallGate_Sel
;进入ring0
;有兴趣自己也可以创建一个0级堆栈 呵呵。。
;我这里就用ring3的堆栈吧
mov eax,esp
;保存ring0堆栈
mov esp,[esp+4] ;装入ring3堆栈地址
;解释下这里为什么是+4 因为call 调用门会把
;ss esp cs eip分别放入堆栈 在驱动程序里面
;已经有一句retn指令把eip弹出来了。所以这里
;就是加4 得到ring3堆栈地址
push eax
mov eax,cr0
;ring0下才可以访问的寄存器
pop esp
;;;开发返回ring3
push offset ring3
;正如刚刚所说 已经弹出了eip地址。
;用retf返回肯定出错。所以还是手
;动帮忙来恢复堆栈 压入ring3下的
;代码地址吧
retf
ring3:
invoke
wsprintf,addr szBuffer,addr szFormat,eax
invoke
MessageBox,0,addr szBuffer,addr szOK,0
invoke
ExitProcess,0
end start
:make
set drv=s
ml /nologo /c /coff %drv%.bat
link /nologo /subsystem:windows %drv%.obj
del %drv%.obj
echo.
pause
最后 前辈这里没有提到的。.装载驱动的程序。
;@echo off
;goto make
.386
.model flat, stdcall
option casemap:none
include windows.inc
include kernel32.inc
include user32.inc
include advapi32.inc
includelib kernel32.lib
includelib user32.lib
includelib advapi32.lib
.data
szError
db
'装载驱动失败',0
szOK1
db
'驱动安装成功',0
szOK
db
'OH YEAH.~~!',0
szMyWdm
db
'mykmd.sys',0
szMyWdnName
db
'mykmd',0
szBuffer
db
MAX_PATH dup (0)
hSCManager
dd
?
hService
dd
?
.code
start:
invoke
OpenSCManager,NULL,NULL,SC_MANAGER_CREATE_SERVICE
.if !eax
invoke
MessageBox,0,addr szError,0,MB_ICONERROR or MB_OK
jmp Err
.endif
mov hSCManager,eax
push ecx
invoke
GetFullPathName,addr szMyWdm,sizeof szBuffer,addr szBuffer,esp
pop ecx
invoke
CreateService,hSCManager,addr szMyWdnName,addr szMyWdm,SERVICE_START or DELETE,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_IGNORE,addr szBuffer,0,0,0,0,0
.if !eax
invoke
MessageBox,0,addr szError,0,MB_ICONERROR
jmp Err
.endif
mov hService,eax
invoke
StartService, hService, 0, NULL
invoke
DeleteService, hService
invoke
CloseServiceHandle, hService
invoke
CloseServiceHandle, hSCManager
invoke
MessageBox,0,addr szOK1,addr szOK,0
Err:
invoke
ExitProcess,0
end start
;如果设置了环境变量 就把代码直接保存为LoadDriver.bat 然后双击 就OK
;没有设置环境变量的可以按下面方法编译,具体编译方法不用我介绍了吧
:make
set drv=LoadDriver
ml /nologo /c /coff %drv%.bat
link /nologo /subsystem:windows %drv%.obj
del %drv%.obj
echo.
pause
;说明下 如果用bat文件自动编译
把驱动代码改成mykmd.bat 测试代码改为s.bat
装载驱动程序代码改为LoadDriver.bat 分别双击就OK
下面给出wowocock大哥的代码.
其中测试部分的几个字符串定义可能忘了加' ' 两个单引号。
为了尊重作者..我还是没改动代码。请大家自己加上吧.
顺便说下。如果转载 我的代码给大家参考 所以随便怎么都可以
但是 wowocock的文章 请尊重下作者 把文章COPY全
谢谢
原著:wowocock
http://www.
用汇编写个最小的WDM驱动程序进RING0
在WINDOWS 2000/XP/2003里进入RING0是很多人的梦想,但实现这个梦想并不容易,
因为基于NT内核的 WINDOWS 2000/XP/2003对自己保护得非常好,这与WINDOWS 9X
有很大不同,WINDOWS 9X的GDT,IDT,LDT等重要的系统表格是可以被RING3的代码
轻易修改的,因此在WINDOWS 9X里任何程序都可以在GDT,IDT,LDT里构造自己的
调用门/中断门/陷阱门而进入RING0。在WINDOWS 2000/XP/2003里似乎只有写驱动
才能进入RING0了,当然还可以利用系统漏洞进入RING0,本人就发现了2个漏洞可
进入RING0,但本文只讨论写驱动的方式。
现在介绍用汇编语言写普通WINDOWS应用程序的书也不少,用汇编语言写WINDOWS
程序的程序员也越来越多,但遗憾的是这些书介绍的程序都是运行在RING3上的,
我等技术追求者怎能受制于RING3而无所作为?为了写出令人惊叹的程序(比如病毒,
反病毒,防火墙等等),就必须要进入RING0。但是有关用汇编语言写WDM驱动程序
的书和例子非常少,我找到的绝大多数都是用C写的,而且编译也很麻烦,对于汇编
语言这种最适合写系统程序的语言来说是很遗憾的。为此我特意用我所知道的知识
写出本文,意在抛砖引玉,共同进步。
好了,先来看看下面的例子:
.586p
.model flat,stdcall
option casemap:none
.code
start:
nop
nop
nop
nop
pushfd
pushad
push edx
sgdt [esp-2]
pop edx
mov eax,edx
mov ecx,3e0h
.if dword ptr [edx+ecx+2]!=0ec0003e8h
mov byte ptr [edx],0c3h
mov word ptr [edx+ecx],ax
shr eax,16
mov word ptr [edx+ecx+6],ax
mov dword ptr [edx+ecx+2],0ec0003e8h
mov dword ptr [edx+ecx+8],0000ffffh
mov dword ptr [edx+ecx+12],00cf9a00h
.endif
popad
popfd
xor eax,eax
ret 8
end start
把以上的汇编代码保存到文件mywdm.asm,然后用MASM 6.14按照下面的方法编译:
ml /c /coff /Cp mywdm.asm
link /subsystem:windows /driver:wdm /release /out:mywdm.sys mywdm.obj
就会在当前目录下生成一个mywdm.sys文件,这是一个没有导入导出和资源的纯代码
驱动程序,非常短小精悍。如果导入了ntoskrnl.exe和hal.dll里的函数,或者导出
给别人调用的函数,它将是一个功能全面的WDM驱动。
mywdm.sys的功能是在GDT中创建一个3级调用门和一个0级32位代码段描述符,3级调用
门的选择子是3E3,0级32位代码段的选择子是3E8。
好了,现在执行mywdm.sys,但驱动程序是不能直接执行的,所以要构造它的执行环境。
先在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services中建立一个mywdm
子键,然后建立3个DWORD型的键值:
ErrorControl=0
Start=3 (如果Start=1,mywdm.sys会随电脑启动而自动装入)
Type=1
再把mywdm.sys复制到system32\drivers目录里,然后重新启动(好象不重新启动也可以,
对这个我不太清楚),运行net start mywdm,结果屏幕上显示:
“发生系统错误 1058。
无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动。”
如果你轻信微软的这句话的话,你就到此为止了!别被微软欺骗,因为我们的代码已经
执行过了,3级调用门和0级32位代码段描述符已经被建立在GDT中,而错误提示是在我们
的代码执行后弹出来的,此时不管是成功提示还是错误提示对我们来说都已太晚了。不信,
你可以用SOFT ICE看一看GDT就知道我所言不虚!如果Start=1,系统不会提示错误而运行
mywdm.sys。
好了,既然GDT中已经有了我们的调用门,那么意味着我们写的应用程序可以自由地在
RING3和RING0之间切换,但如何使用这个调用门还是有讲究的,它的使用方法是:
...
call 3e3:00000000 ;机器码 9A 00 00 00 00 E3 03
;此时已经进入RING0,CS=3E8,跟着要切换堆栈
mov eax,esp
mov esp,[esp+4]
push eax
;这里加入你要在RING0里执行的代码
;准备返回RING3
pop esp
push offset ring3
retf
ring3:
;此时回到RING3
...
为什么要切换堆栈?为什么要建立我们自己的0级32位代码段?直接使用操作系统的0级32位
代码段(选择子=8)不可以吗?在这里我要说明一下:WINDOWS 2000/XP/2003对自己保护得
非常好,就算普通应用程序能进入RING0,但还是在用户区(代码和数据的地址都小于80000000H),
所以WINDOWS 2000/XP/2003会认为是非法进入RING0的,就有可能产生页故障,表现是调用子程序
或访问内存时就会触发页故障,解决的方法是切换堆栈;不用操作系统的0级32位代码段(选择子
为8),用我们建立的0级32位代码段(选择子=3E8)。
本文例子生成的mywdm.sys大小是1536字节,如果你对PE文件很了解,可以手工给mywdm.sys减肥,
但要注意的是减肥后要把正确的CHECKSUM值填入PE头的CHECKSUM字段中,否则操作系统是不会装入
mywdm.sys执行的。我就把mywdm.sys减肥到只有368字节(只有DOS头,PE头+节表,60H字节的重定
位表和代码,应该是世界上最小的驱动程序了)。完全可以把这个只有368字节的驱动程序包含在
自己的程序中,在需要是把它还原到SYSTEM32\DRIVERS目录里。
计算CHECKSUM的方法是:把PE文件的所有字用ADC相加,然后得到的结果再和文件大小ADC相加,
这个结果就是CHECKSUM值。网上也可以找到计算CHECKSUM的工具。
RING3测试程序如下:
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
szExceptionCaused db Exception Caused - could not switch to ring 0,0
szError
db Error,0
MsgCaption
db Test,0
MsgBoxText
db cr0=%8x,0
tmp db 50 dup(90)
Callgt dd 0
dw 3e3h
.code
ExceptCallBack PROC
invoke
MessageBoxA, 0, addr szExceptionCaused,addr szError, 0
invoke ExitProcess, -1
ret
ExceptCallBack ENDP
start:
push
offset ExceptCallBack
call
SetUnhandledExceptionFilter
call
fword ptr [Callgt]
;use callgate to Ring0!
Ring0:
mov eax,esp
;save ring0 esp
mov esp,[esp+4];->ring3 esp
push eax
mov eax,cr0
pop esp
;restore ring0 esp
push offset Ring3
retf
Ring3:
invoke wsprintfA,addr tmp,addr MsgBoxText,eax
invoke MessageBox, NULL,addr tmp, addr MsgCaption, MB_OK
Exit:
invoke ExitProcess,NULL
end start
嘿嘿,大家以后就可以方便的写2K/XP/2003下的RING0病毒了。
看这篇文章还不懂我就没招了。。