前言
还记得我们之前的那个中断提权吗?我们修改了 idtr 表来把我们自己的中断函数进行执行。再此之前我们提到了在中断函数处理时我们无法再中断,但是在内核中存在有如下两个指令,可以让硬件中断变得可以执行或者是禁止执行
需要注意的是,当我们在内核模式下执行的时候应该尽快的恢复中断,如果长时间禁止中断时,我们可能会影响操作系统中其他动作的执行(如鼠标移动等等),此时的操作系统便会变得不稳定。
长时间的禁止硬件中断会影响内部时钟混乱
在此次实验中,我们将允许中断,进行观察对应的现象
实验过程
在实验开始之前,我们需要将虚拟机中的ntkrnlpa.exe
提取出来,我们可以利用PCHunter
来对其进行定位。
我们找到对应的程序后,将其拖入IDA进行反编译分析
我们选择 _KiFastCallEntry
函数进行分析,此函数通过中断门进入 Ring 0,同时将 Ring 3 的 esp、eip、ss、cs、eflags 进行保存
大致可以得到以下信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| .text:80542520 _KiFastCallEntry proc near ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o .text:80542520 ; _KiTrap01+74↓o .text:80542520 .text:80542520 var_B= byte ptr -0Bh .text:80542520 anonymous_0= dword ptr -8 .text:80542520 anonymous_1= dword ptr -4 .text:80542520 .text:80542520 ; FUNCTION CHUNK AT .text:805424ED SIZE 00000026 BYTES .text:80542520 ; FUNCTION CHUNK AT .text:805427C0 SIZE 00000014 BYTES .text:80542520 .text:80542520 B9 23 00 00 00 mov ecx, 23h ; '#' .text:80542525 6A 30 push 30h ; '0' .text:80542527 0F A1 pop fs ; fs设置为0x30 为0环数据段 .text:80542529 8E D9 mov ds, ecx ; ds设置为0x23 为3环数据段 .text:8054252B 8E C1 mov es, ecx ; es = 0x23 .text:8054252D 64 8B 0D 40 00 00 00 mov ecx, large fs:40h ; 进入0环,fs:[0]指向KPCR表 _KPCR.TSS .text:80542534 8B 61 04 mov esp, [ecx+4] ; TSS:esp0 即切换到0环堆栈 .text:80542537 6A 23 push 23h ; '#' ; 3环的 ss 压入 .text:80542539 52 push edx ; 3环esp .text:8054253A 9C pushf ; 将三环的eflags压入栈 .text:8054253A .text:8054253B .text:8054253B loc_8054253B: ; CODE XREF: _KiFastCallEntry2+23↑j .text:8054253B 6A 02 push 2 .text:8054253D 83 C2 08 add edx, 8 ; Add .text:80542540 9D popf ; 设立新eflags = 0x2 清空0环标志位 .text:80542541 80 4C 24 01 02 or byte ptr [esp+1], 2 ; 设置IF=1,进行屏蔽中断 .text:80542546 6A 1B push 1Bh .text:80542548 FF 35 04 03 DF FF push dword ptr ds:0FFDF0304h ; _KUSER_SHARED_DATA.SystemCallReturn .text:8054254E 6A 00 push 0 .text:80542550 55 push ebp .text:80542551 53 push ebx .text:80542552 56 push esi .text:80542553 57 push edi .text:80542554 64 8B 1D 1C 00 00 00 mov ebx, large fs:1Ch ; ebx 指向 _KPCR .text:8054255B 6A 3B push 3Bh ; ';' .text:8054255D 8B B3 24 01 00 00 mov esi, [ebx+124h] ; KPCR.KPRCB.CurrentThread 获取当前线程 .text:80542563 FF 33 push dword ptr [ebx] ; _KPCR.NtTib.ExceptionList .text:80542565 C7 03 FF FF FF FF mov dword ptr [ebx], -1 ; _KPCR.NtTib.ExceptionList=-1 .text:8054256B 8B 6E 18 mov ebp, [esi+18h] ; _KPCR._KPRCB.CurrentThread.InitialStack .text:8054256E 6A 01 push 1 ; _KTRAP_FRAME.PreviousPreviousMode = 1,表示从3环来 .text:80542570 83 EC 48 sub esp, 48h ; esp 指向 _KTRAP_FRAME .text:80542573 81 ED 9C 02 00 00 sub ebp, 29Ch ; Integer Subtraction .text:80542579 C6 86 40 01 00 00 01 mov byte ptr [esi+140h], 1 ; CurrentThread.PreviousMode = 1,表示从3环调用来 .text:80542580 3B EC cmp ebp, esp ; 判断ebp与esp是否都指向_KTRP_FRAME .text:80542582 75 8D jnz short loc_80542511 ; Jump if Not Zero (ZF=0) .text:80542582 .text:80542584 83 65 2C 00 and dword ptr [ebp+2Ch], 0 ; _KTRAP_FRAME.Dr7 = 0 .text:80542588 F6 46 2C FF test byte ptr [esi+2Ch], 0FFh ; Logical Compare .text:8054258C 89 AE 34 01 00 00 mov [esi+134h], ebp ; CurrentThread.TrapFrame = ebp,即指向当前 _KTRAP_FRAME .text:80542592 0F 85 38 FE FF FF jnz Dr_FastCallDrSave ; 如果DebugActive == 1(被调试),那么跳转到 Dr_FastCallDrSave .text:80542592 ; Dr_FastCallDrSave 的功能是保存调试寄存器 .text:80542592 .text:80542598 .text:80542598 loc_80542598: ; CODE XREF: Dr_FastCallDrSave+10↑j .text:80542598 ; Dr_FastCallDrSave+7C↑j .text:80542598 8B 5D 60 mov ebx, [ebp+60h] .text:8054259B 8B 7D 68 mov edi, [ebp+68h] .text:8054259E 89 55 0C mov [ebp+0Ch], edx ; _KTRAP_FRAME.DbgArgPointer = edx, 保存3环参数指针 .text:805425A1 C7 45 08 00 0D DB BA mov dword ptr [ebp+8], 0BADB0D00h .text:805425A8 89 5D 00 mov [ebp+0], ebx ; _KTRAP_FRAME.DbgEbp = _KTRAP_FRAME.Ebp .text:805425AB 89 7D 04 mov [ebp+4], edi ; _KTRAP_FRAME.DbgEip = _KTRAP_FRAME.Eip .text:805425AE FB sti ; Set Interrupt Flag
|
我们将之前实验一的中断处理函数修改为:
1 2 3 4 5 6 7 8 9 10
| void __declspec(naked) IdtEntry() { __asm { push 0x30 pop fs ;仿照 _KiFastCallEntry 修改fs sti ;开启中断 L: jmp L iretd ; 中断返回 } }
|
我们编译运行,可以发现程序运行但是占用了100%的CPU
此处但是虚拟机并不同于我们之前设置的循环就直接卡死,可以看到虚拟机可以甚至可以调出任务管理器,我们而也可以通过 WinDbg 对其进行断下,而不是直接的 Busy 状态。同时此处产生了一个问题,我们无法关闭这个测试程序。无论我们是直接关闭或者是通过任务管理器,都是关不掉对应程序的,我们都可以看到在后台会存在着对应的进程。
不会卡死是因为我们开起了中断,在 Ring 0 中允许中断是十分危险的。
为什么会无法对其进行关闭呢?在操作系统中存在有个APC队列的东西,我们可以通过WinDbg观察其结构
同时需要注意的是线程是不能被“杀掉”、“挂起”、“恢复”的,线程在执行的时候自己占据着CPU,别人不能控制它,所以说线程如果想“死”,一定是自己执行代码把自己杀死,不存在“他杀”的情况。如果我们需要改变一共线程的行为,我们需要给他一个函数,让其进行调用。
在正常情况下,我们掉用关闭的线程是上述代码从 Ring 0 到 Ring 3 进行返回的时候调用。 对于上述的例子,我们设置了一个循环,将其进行了卡死,使其无法回到 Ring 3,进而造成了伪卡死状态(CPU占用100%,但是其他进程仍然有机会得到指令)。
此时如果关机或者重启可能会造成卡死现象
那么我们有什么方式可以将其进行处理一下?答案是有的,因为我们之前开启了中断,意味着我们可以使用 WinDbg 在 Ring 0 进行中断,我们之前的函数处理的地址为:0x00401040
,我们对该地址处的东西进行反汇编,可以看到如下界面
我们通过 WinDbg 将 jmp 处的汇编指令进行修改,将其改为 nop,那么便解除了对应的卡死状态,使其返回 Ring 3 时,可以调用相应的线程结束函数,使其”自我结束“。
因为 ebfe 为双字,所以我们此时使用 ew 进行修改
此时我们便在任务管理器中看不到其身影了,已经成功的对其进行关闭。
总结
在此次实验中我们在 Ring 0 开中断, 允许线程调度, 制造了一个在 Ring 0 的死循环, 不能被杀死的进程。同时简单的了解了一下进程结束的相关东西以及Ring 3 到 Ring 0 的大致切换过程。