Windows内核基础知识——32位

这次真不会鸽了,64位Windows内核那个API逆向我基础太差,写出来怕笑话,所以重新过一下32位,再接着去学64位
Typora图片无法直接加载出来,只能一个一个的上传图床了。
所以先传一点简单的逆向把,基础知识目前还没时间上传图床,只能先上传下文本了。
emm,API分析的差不多了,后续的学习打算参考Reactor OS、Windows 情景分析、Windows内核原理实现、Windows驱动开发、寒江独钓这些资料先看看,这篇博客等有心情写的时候在写写吧。
可惜的是 内存、调试、异常这一块还没有去逆向过。
异常得抽个时间好好逆一下,毕竟涉及到某个杀软的查杀

系统调用

_KUSER_SHARED_DATA

ring0层和ring3层的共享数据

User层地址为:0x7ffe0000

Kernnel层地址为:0xffdf000

但User层是只读的,在Kernel层是可写的

_Trap_Frame

ring3进ring0需要切换_Trap_Frame

结构如下

而在Windows xp下进入ring0有两种方式,一种是KiSystemService,一种是KiFastCallEntry

KiSystemService

.text:004067C1 _KiSystemService proc near              ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+C↓p
.text:004067C1                                         ; ZwAccessCheck(x,x,x,x,x,x,x,x)+C↓p ...
.text:004067C1
.text:004067C1 arg_0           = dword ptr  4
.text:004067C1
.text:004067C1                 push    0
.text:004067C3                 push    ebp             ; push 0 和下面的push都是填充Trame_Frame
.text:004067C4                 push    ebx
.text:004067C5                 push    esi
.text:004067C6                 push    edi
.text:004067C7                 push    fs
.text:004067C9                 mov     ebx, 30h ; '0'  ; 切换fs寄存器
.text:004067C9                                         ; ring0的fs寄存器指向KPCR
.text:004067CE                 mov     fs, ebx
.text:004067D0                 push    large dword ptr fs:0 ; 将异常Exception链压入栈中
.text:004067D7                 mov     large dword ptr fs:0, 0FFFFFFFFh ; 赋值Exception链为-1
.text:004067E2                 mov     esi, large fs:124h ; 传入KThread结构体
.text:004067E9                 push    dword ptr [esi+140h] ; KThread->PreviousMode  保存调用该函数的调用者的模式是ring0还是ring3
.text:004067EF                 sub     esp, 48h        ; 提升0x48个字节,就是剩下的Trap_Frame的成员
.text:004067F2                 mov     ebx, [esp+68h+arg_0]
.text:004067F6                 and     ebx, 1
.text:004067F9                 mov     [esi+140h], bl  ; 将当前的模式压入KThread->PreviousMode
.text:004067FF                 mov     ebp, esp
.text:00406801                 mov     ebx, [esi+134h]
.text:00406807                 mov     [ebp+3Ch], ebx  ; 将当前KThread的TrapFrame放入TrapFrame->edx中
.text:0040680A                 mov     [esi+134h], ebp ; KThread->TrapFrame=Cur_TrapFrame
.text:0040680A                                         ; 此时的TrapFrame利用链表串了起来
.text:00406810                 cld                     ; Flag->DF=0
.text:00406811                 mov     ebx, [ebp+60h]  ; ebp=TrapFrame->ebp
.text:00406814                 mov     edi, [ebp+68h]  ; edi=TrapFrame->eip
.text:00406817                 mov     [ebp+0Ch], edx  ; TrapFrame->DbgArgPointer=edx(调用者API的第一个参数)
.text:0040681A                 mov     dword ptr [ebp+8], 0BADB0D00h ; TrapFrame->DbgArgMark=0xBADB0D00
.text:00406821                 mov     [ebp+0], ebx    ; [ebp]=TrapFrame->ebp
.text:00406824                 mov     [ebp+4], edi    ; ebp+4(返回地址)=TrapFrame->eip
.text:00406827                 test    byte ptr [esi+2Ch], 0FFh ; 是否为调试模式
.text:0040682B                 jnz     Dr_kss_a        ; 为调试模式则跳出
.text:00406831
.text:00406831 loc_406831:                             ; CODE XREF: Dr_kss_a+10↑j
.text:00406831                                         ; Dr_kss_a+7C↑j
.text:00406831                 sti
.text:00406832                 jmp     loc_406922      ; 进入KiFastEntry
.text:00406832 _KiSystemService endp

KiFastCallEntry

.text:0040688F _KiFastCallEntry proc near              ; DATA XREF: _KiTrap01+71↓o
.text:0040688F                                         ; KiLoadFastSyscallMachineSpecificRegisters(x)+24↓o
.text:0040688F
.text:0040688F var_B           = byte ptr -0Bh
.text:0040688F
.text:0040688F ; FUNCTION CHUNK AT .text:0040685C SIZE 00000024 BYTES
.text:0040688F ; FUNCTION CHUNK AT .text:00406B33 SIZE 00000014 BYTES
.text:0040688F
.text:0040688F                 mov     ecx, 23h ; '#'
.text:00406894                 push    30h ; '0'
.text:00406896                 pop     fs              ; fs=0x30
.text:00406898                 mov     ds, ecx         ; ds=0x23
.text:0040689A                 mov     es, ecx         ; es=0x23
.text:0040689C                 mov     ecx, large fs:40h ; ecx=TSS
.text:004068A3                 mov     esp, [ecx+4]    ; esp=TSS->esp0(将ring0的esp传给esp)
.text:004068A6                 push    23h ; '#'       ; HardWareSS
.text:004068A8                 push    edx             ; HardWareEsp
.text:004068A9                 pushf                   ; TrapFrame->eflag
.text:004068AA
.text:004068AA loc_4068AA:                             ; CODE XREF: _KiFastCallEntry2+23↑j
.text:004068AA                 push    2
.text:004068AC                 add     edx, 8          ; edx=esp+8(ring3 args)
.text:004068AF                 popf
.text:004068B0                 or      [esp+0Ch+var_B], 2
.text:004068B5                 push    1Bh             ; Trap->SegCs
.text:004068B7                 push    dword ptr ds:0FFDF0304h ; _KTRAP_FRAME.Eip = _KUSER_SHARED_DATA.SystemCallReturn
.text:004068BD                 push    0
.text:004068BF                 push    ebp
.text:004068C0                 push    ebx
.text:004068C1                 push    esi
.text:004068C2                 push    edi
.text:004068C3                 mov     ebx, large fs:1Ch
.text:004068CA                 push    3Bh ; ';'
.text:004068CC                 mov     esi, [ebx+124h]
.text:004068D2                 push    dword ptr [ebx]
.text:004068D4                 mov     dword ptr [ebx], 0FFFFFFFFh
.text:004068DA                 mov     ebp, [esi+18h]
.text:004068DD                 push    1
.text:004068DF                 sub     esp, 48h
.text:004068E2                 sub     ebp, 29Ch       ; stack-=0x29C
.text:004068E8                 mov     byte ptr [esi+140h], 1 ; 从3环调用过来
.text:004068EF                 cmp     ebp, esp
.text:004068F1                 jnz     loc_40685C
.text:004068F7                 and     dword ptr [ebp+2Ch], 0
.text:004068FB                 test    byte ptr [esi+2Ch], 0FFh
.text:004068FF                 mov     [esi+134h], ebp
.text:00406905                 jnz     Dr_FastCallDrSave
.text:0040690B
.text:0040690B loc_40690B:                             ; CODE XREF: Dr_FastCallDrSave+10↑j
.text:0040690B                                         ; Dr_FastCallDrSave+7C↑j
.text:0040690B                 mov     ebx, [ebp+60h]
.text:0040690E                 mov     edi, [ebp+68h]
.text:00406911                 mov     [ebp+0Ch], edx
.text:00406914                 mov     dword ptr [ebp+8], 0BADB0D00h
.text:0040691B                 mov     [ebp+0], ebx
.text:0040691E                 mov     [ebp+4], edi
.text:00406921                 sti
.text:00406922
.text:00406922 loc_406922:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:00406922                                         ; _KiSystemService+71↑j
.text:00406922                 mov     edi, eax
.text:00406924                 shr     edi, 8          ; 此时的eax是系统调用号
.text:00406924                                         ; edi=(eax>>8)&0x30
.text:00406927                 and     edi, 30h        ; 查看是Ntoskrl.exe还是Win32k.sys
.text:0040692A                 mov     ecx, edi
.text:0040692C                 add     edi, [esi+0E0h] ; 系统调用号+系统服务表
.text:00406932                 mov     ebx, eax
.text:00406934                 and     eax, 0FFFh
.text:00406939                 cmp     eax, [edi+8]    ; 比较系统调用号是否超过了系统服务表
.text:0040693C                 jnb     _KiBBTUnexpectedRange
.text:00406942                 cmp     ecx, 10h        ; 比较第12位是否为1
.text:00406942                                         ; 决定着是否为Ntoskrl.exe或Win32k
.text:00406945                 jnz     short loc_406962 ; KeSystemCalls
.text:00406947                 mov     ecx, large fs:18h ; KPCR->Self
.text:0040694E                 xor     ebx, ebx
.text:00406950
.text:00406950 loc_406950:                             ; DATA XREF: _KiTrap0E+114↓o
.text:00406950                 or      ebx, [ecx+0F70h]
.text:00406956                 jz      short loc_406962 ; KeSystemCalls
.text:00406958                 push    edx
.text:00406959                 push    eax
.text:0040695A                 call    ds:_KeGdiFlushUserBatch
.text:00406960                 pop     eax
.text:00406961                 pop     edx
.text:00406962
.text:00406962 loc_406962:                             ; CODE XREF: _KiFastCallEntry+B6↑j
.text:00406962                                         ; _KiFastCallEntry+C7↑j
.text:00406962                 inc     large dword ptr fs:638h ; KeSystemCalls
.text:00406969                 mov     esi, edx        ; fs[638h]=KeSystemCalls
.text:0040696B                 mov     ebx, [edi+0Ch]  ; ebx=ArgmentTable
.text:0040696E                 xor     ecx, ecx
.text:00406970                 mov     cl, [eax+ebx]   ; cl=SystemServiceTable.ArgmentTable[eax]
.text:00406970                                         ; 获取参数字节数
.text:00406973                 mov     edi, [edi]
.text:00406975                 mov     ebx, [edi+eax*4] ; 获取函数地址
.text:00406978                 sub     esp, ecx        ; 为参数   拉伸栈空间
.text:0040697A                 shr     ecx, 2          ; 获取参数个数
.text:0040697D                 mov     edi, esp
.text:0040697F                 cmp     esi, ds:_MmUserProbeAddress ; 验证目标地址是否可用
.text:00406985                 jnb     loc_406B33      ; 查看cs是否为01
.text:0040698B
.text:0040698B loc_40698B:                             ; CODE XREF: _KiFastCallEntry+2A8↓j
.text:0040698B                                         ; DATA XREF: _KiTrap0E+10A↓o
.text:0040698B                 rep movsd
.text:0040698D                 call    ebx             ; 调用系统函数
.text:0040698F
.text:0040698F loc_40698F:                             ; CODE XREF: _KiFastCallEntry+2B3↓j
.text:0040698F                                         ; DATA XREF: _KiTrap0E+12A↓o ...
.text:0040698F                 mov     esp, ebp
.text:00406991
.text:00406991 loc_406991:                             ; CODE XREF: _KiBBTUnexpectedRange+38↑j
.text:00406991                                         ; _KiBBTUnexpectedRange+43↑j
.text:00406991                 mov     ecx, large fs:124h ; ecx=KThread
.text:00406998                 mov     edx, [ebp+3Ch]  ; edx=TrapFrame->edx
.text:0040699B                 mov     [ecx+134h], edx ; TrapFrame=Previous_TrapFrame
.text:0040699B _KiFastCallEntry endp

线程切换

本来想逆向一下KiSwapThread的,然后逆着逆着看到了自旋锁,索性把自旋锁也给逆了。

在逆的过程中,还好一年前已经看过这个课程了,所以简单的介绍一些成员

为什么没放逆向的过程,因为我想直接放idb好一些,下面的过程可能还会有些错误。。

过程

线程切换的主要API是KiSwapThread,该API首先会通过KPRCB,找到下一个线程的KTHREAD结构体,然后获取当前CPU编号,随后找到就绪的线程。

接着判断是否查找到,如果查找到了则直接进入KiSwapContext,进行上下文切换,如果没有找到,则需要对空闲线程进行处理,具体操作,目前实力还不行。

然后进入KiSwapContext进入线程上下文切换,其主要操作会切换当前KPRCB离的KTHREAD,接着进入SwapContext

进入后会对KThread->State状态进行赋值为2,切换线程当前的状态为(),紧接着会对DebugActive赋值给KPCR中,在把栈空间进行切换和提升0x210,并查看是否开启了保护模式,接着切换TSS指针,和KTHREAD->TEB和把新的KPCR赋值进去再开启中断。再填充GDT和IDT和TSS里面的cr3,最后把原来的ExceptionList传入新的KPCR中。

逆向分析KiSwapContext和SwapContext函数,解决如下问题:
1、SwapContxt有几个参数,分别是什么?你是如何判断出来参数的?
3
2、SwapContxt在哪里实现了线程切换?
从切换KPCR中的栈空间开始

3、线程切换的时候,会切换Cr3吗?切换Cr3的条件是什么?
会,有3个地方会判断(这个成员代表是否需要切换块意思是否切换cr3)

4、中断门提权时,CPU会从TSS得到ESP0和SS0,TSS中存储的一定是当前线程的ESP0和SS0吗?如何做到的?
是的,因为线程切换回修改KPCR中的TSS指针值,而KPCR就是由当前线程切换的,所以会去TSS中取的值

5、FS:[0]在3环时指向TEB 但是 线程有很多 FS:[0]指向的是哪个线程的TEB 如何做到的?
fs段选择子虽然没有变化,但会改变fs段选择子里的值

6、0环的ExceptionList在哪里备份的?
开头有push ecx
7、IdleThread是什么?什么时候执行?找到这个函数?

8、如何找到下一个就绪线程?
通过KiFindReadyThread来找到就绪线程

APC执行

在逆向TerminateThread/SuspendThread/ResumeThread这三个API中,发现3个API都将会调用KiInsertQueueApc,而该API在ring0中主要是直接对线程的APC中插入一个_KAPC结构体,在介绍过程之前先简单的介绍一下相关的结构体

_KAPC

nt!_KAPC
   +0x000 Type
   +0x002 Size
   +0x004 Spare0                                 找到你提供的APC函数,并不完全等于
   +0x008 Thread                                 APC函数的地址,后面会讲。
   +0x00c ApcListEntry
   +0x014 KernelRoutine
   +0x018 RundownRoutine
   +0x01c NormalRoutine
   +0x020 NormalContext
   +0x024 SystemArgument1
   +0x028 SystemArgument2
   +0x02c ApcStateIndex
   +0x02d ApcMode
   +0x02e Inserted

_KAPC_STATE

nt!_KAPC_STATE
   +0x000 ApcListHead    //2个APC队列 用户APC和内核APC 
   +0x010 Process        //线程所属或者所挂靠的进程
   +0x014 KernelApcInProgress    //内核APC是否正在执行
   +0x015 KernelApcPending    //是否有正在等待执行的内核APC
   +0x016 UserApcPending    //是否有正在等待执行的用户APC

TerminateThread过程

从ring3开始逆的话,其实也差不了太多,因为我还没逆APC执行这一块,主要还是逆的APC插入到执行的一个过程,这样可以真正明白APC。其次是API的逆向,可能只会对重要的逆向,如果流程基本差不多,便不会在详说

1.进入NtTerminateThread后,通过_KPCR.PrcbData.CurrentThread来获取当前ETHREAD,然后通过ObReferenceObjectByHandle来获取目标ETHREAD对象,然后跳入PspTerminateThreadByPointer。

2.进入PspTerminateThreadByPointer会判断当前的fs的ETHREAD和目标线程的ETHREAD是否为同一个,如果为同一个则直接退出,如果不为同一个,判断目标线程是否为系统线程。

3.如果是系统线程则分配一个_KAPC结构体,然后利用API KeInitializeApc进行APC的初始化,KeInsertQueueApc插入APC。

4.接着调用KeForceResumeThread恢复线程执行,该API里面又会调用KiUnlockDispatcherDatabase来执行APC。

SuspendThread

API流程:KiInsertQueueApc

当线程挂起,会被插入APC,但不会调用

ResumeThread

当线程恢复,会调用APC

KiInsertQueueApc

该函数主要是插入APC

过程

1.检查_KAPC.Inserted是否为0,为0代表还未插入,不为0代表插入成功

2.将创建的APC先赋值,这样方便要插入的目标线程,然后赋值了ApcStateIndex,然后通过ApcStatePointer来找到APC_STATE队列。

3.然后判断当前KAPC是用户态APC还是内核态的APC,紧接着通过ApcStateIndex来找到对应的(用户态或者是内核态)APC_STATE,并且插入,并且将_KAPC_Inseted置为1,如果插入的是用户态的APC则还会将UserApcPending为1,这说明当前有未处理完的APC操作

4.然后回判断内核_KAPC.NormalRoutine是否为0,判断是用户APC还是内核APC,如果是内核APC,则判断当前线程是否为running状态,并且把KernelApcPending为1,代表有需要执行的APC,如果是running状态,则APC中断,如果不是running状态则判断其是不是等待状态,判断中断请求等级是不是0,,然后查看NormalRoutine是否为0,如果以上条件都不满足则退出,否则会继续查看当前APC是否可用,是否有内核APC正在执行,如果没有内核APC执行,设置线程优先级和状态。如果是用户态APC,判断当前线程是否为等待,然后查看等待模式是否不为用户导致的阻塞,检查当前线程是否可以被APC唤醒和APC是否还有执行,如果都满足,则跳入设置线程优先级和状态。

5.如果APC在运行并且,则进入APC中断

6.如果NormalRoutine为0,则进入循环,该循环会遍历当前线程的APC队列,直到找到下一个 NormalRoutine 为空的APC。

KiDeliverApc

该函数主要是执行APC和构造ring3APC环境的

过程

该API主要是执行APC的,APC分为用户APC和内核APC,内核APC相对简单,判断玩是否被禁止执行后,再直接执行就可以,而用户APC需要先执行完内核APC再去执行用户APC,这当中会有ring0和ring3的切换,所以该API在末尾会调用KiInitializeUserApc来构造ring3的环境,用户后门的环境切换,并且会把ntdll里的函数KiUserApcDispatcher来执行用户APC。

执行内核APC的代码段

xor     cl, cl          ; NewIrql
mov     byte ptr [esi+48h], 1
call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
push    [ebp+temp_kacp_systemArgument2]
push    [ebp+temp_kapc_systemArgument1]
push    [ebp+temp_kapc_normalContext]
call    [ebp+temp_kapc_normalRoutine] ; 执行真正的APC
mov     cl, 1           ; NewIrql
call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
mov     [ebp+LockHandle.OldIrql], al

NtReadVirtulMemoery

会调用KeStackAttachProcess

KeStackAttachProcess

该API跟挂靠相关,会通过当前线程里APC_STATE里的ApcStateIndex来判断当前线程是否挂靠了,然后调用KiAttachProcess,如果挂靠了会传入一个还未填充完的APC_STATE,否则传入备份的SavedApcState。KiAttachProcess首先会调用KiMoveApcState拷贝一份APC_STATE,然后将当前线程的APC_STATE进行清理,并在挂靠情况下会有不一样的赋值

mov     [esi+_ETHREAD.Tcb.ApcStatePointer], eax
mov     [esi+13Ch], ebx
mov     [esi+_ETHREAD.Tcb.ApcStateIndex], 1

接着会调用KiSwapProcess,这个API主要就是切换进程的cr3也就是切换KPCR里的TSS里的cr3部分。同时该函数还会修改GDT和IDT表里面的部分值。

KeUnstackDetachProcess

异常

Windows在处理异常时,主要会经过如下步骤

异常记录、异常分发、异常处理

而在进行这些步骤时有几个很重要的结构体

type struct _EXCEPTION_RECORD        
{                                
    DWORD ExceptionCode;                //异常代码
    DWORD ExceptionFlags;                //异常状态
    struct _EXCEPTION_RECORD* ExceptionRecord;    //下一个异常
    PVOID ExceptionAddress;                //异常发生地址
    DWORD NumberParameters;            //附加参数个数
    ULONG_PTR ExceptionInformation 
    [EXCEPTION_MAXIMUM_PARAMETERS];        //附加参数指针    
}  
typedef struct _EXCEPTION_REGISTRATION_RECORD {
        struct _EXCEPTION_REGISTRATION_RECORD *Next;
        PEXCEPTION_ROUTINE Handler;
    } EXCEPTION_REGISTRATION_RECORD;

int 0

在逆之前,我们需要知道0号异常是除0异常

1.0号异常对应的函数是_KiTrap00,首先会保存触发异常时的环境Trap_Frame

2.判断RPL是否为0,如果为0则直接进入CommonDispatchException,如果是3则判断是否为CS段寄存器是否为0x1B,如果是则进入CommonDispatchException,如果不是则跳入Vdm那一块地方,并将eax设置为0xc000094(未处理异常)进入CommonDispatchException.

CommonDispatchException

1.先初始化结构体_Exception_Record

2.判断调用者是不是ring3来的,如果是ring3过来的,则eax=0xffff

3.调用KiDispatchException

KiDispatchException

内核模式派发

1.先将KPCR里的KeExceptionDispatchCount派发数量加一,并判断是否为用户模式进入的

2.如果是内核模式则会判断是否为调试状态,接着调用KeContextFromKFrames将Trap_Frame,也就是将调用者之前的环境拷贝一份放入Context,然后再判断一次调用者是否为用户模式,再Context里的Eip减一

3.接着检查内核调试器是否存在,如果不存在,就调用RtlDispatchException

4.调用完后查看异常是否被正确处理,并再检查一遍内核调试器是否存在,如果被正确处理,则将Context拷贝一份到Trap_Frame里面,否则调试器不存在蓝屏。

用户模式派发

1.构造了一份用户模式下的TrapFrame后,调用Kei386EoiHelper进入KeUserExceptionDispatcher

2.KeUserExceptionDispatcher会调用RtlDispatchException处理完用户模式下的异常后,如果没有问题,就会调用ZwContinue来恢复Trap_Frame里的eip,然后再回到原来的TrapFrame中

3.如果处理时出错了,此时调用RtlRaiseException,再一次触发异常。

RtlDispatchException

这个函数有个循环,对异常链表的循环,然后通过遍历链表先执行RtlpExceptionLogCount来拷贝异常日志,然后执行异常函数,执行完异常处理后,再把异常处理的结构也记录到最后一个异常日志中。如果异常函数再执行完后,成功返回1,遍历下一个,失败返回其他值,又会进入新的异常会调用RtlRaiseException。

本文链接:

http://pyozo.top/index.php/archives/11/