查看原文
其他

WindowsXp 系统调用课堂学习笔记

wx_六耳 看雪学院 2019-05-26



一、实验环境



  1. 系统:32位 Windows xp sp3

  2. 虚拟机:VMware workstation

  3. 调试器

Ollybdg 1.1

IDA pro 6.8

Win dbg



二、关于3环和0环



  1. 0环,ring0,运行内核程序,kernel层。

  2. 3环,ring3,运行应用程序,user层。

  3. 程序由3环进入0环主要问题

1)程序进入0环后,原来3环的寄存器会保存到什么地方?

2)程序进入0环后,程序要去哪里执行,也就是新的eip的值从哪里取呢?

3)程序进入0环后,权限发生切换,必然需要新的堆栈,那么新的esp的值来自哪里呢?

4)权限切换后,需要新的cs和ss,那么cs和ss段寄存器的值哪里获得?


带着上面的问题,我们利用调试工具来追踪一下函数ReadProcessMemary


如下图所示:


   


三、函数ReadProcessMemary追踪



1、函数说明


ReadProcessMemory归属为编程中的内存操作函数, 其作用为根据进程句柄读入该进程的某个内存空间; 函数原型为

BOOL ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);


由布尔声明可以看出, 当函数读取成功时返回1, 失败则返回0, 具体参数含义将在下文中指出。



2、函数追踪


利用IDA PRO 追踪 ReadProcessMemory 函数


打开IDA Pro ,载入kernel32.dll,搜索函数 ReadProcessMemory 获得函数汇编代码如下




通过分析上述代码:ReadProcessMemory 函数一共完成如下2件事


函数 NtReadVirtualMemory 位于 ntdll.dll,利用 IDA PRO 打开 ntdll.dll,继续追踪函数 NtReadVirtualMemory



分析上述的汇编代码共完成如下两件事:


注意这个地址0x7FFE0300,这里就是user层进入kernel层的关键,在详细说明这个地址之前我们要先介绍一个结构体。



3、结构体_KUSER_SHARED_DATA


在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据


它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在 User 和 Kernel 层地址分别为:

User 层地址为:0x7ffe0000

Kernnel 层地址为:0xffdf0000


通过winbdg查看,可以发现user层地址0x7ffe0000 和kernel层地址0xffdf0000的内容是一样的,虽然指向的是同一个物理页,但在User 层是只读的,在Kernnel层是可写的。



地址0x7ffe000 指向了结构体 _KUSER_SHARED_DATA,下图为结构体成员:



注意:地址为0x7ffe000 + 0x300,指向的结构体 _KUSER_SHARED_DATA 的成员SystemCall, 这里就是user层进入kernel层的关键。



4、地址 0x7FFE0300 到底存储的是什么?


如果cpu支持快速调用,那么系统会将 ntdll.dll!KiFastSystemCall() 的函数地址写到位置0x7FFE0300


如果cpu不支持快速调用,那么会将 ntdll.dll!KiIntSystemCall() 的函数地址写到位置 0x7FFE0300


也就是说 0x7FFE0300,会根据CPU是否支持快速调用,从而选择不同的函数进入kernel层。


注意:上述两个函数都不是内核函数,是ntdll.dll中的应用层的函数。



5、如何判断自己的cpu是否支持快速调用?


打开ollybdg;

打开一个exe文件;

清空edx;

修改当前程序的指令为cpuid;

执行cpuid  查看edx的值,拆解edx,如果第11位等于1,说明当前的CPU支持快速调用。


非快速调用

如果cpu不支持快速调用,那么user层进入kernel层会调用函数ntdll.dll!KiIntSystemCall()



6、追踪函数!KiIntSystemCall()


打开IDA pro 载入ntdll.dll



通过上述汇编代码,int  2eh  发现,这里是通过中断门2e进入kernel层

我们知道中断门位于IDT表中,利用winbdg查看idt表,十六进制2e等于十进制46,就是




上图中蓝色部分所对应的中断门。通过拆分中断门 8054ee00·00082611


获得eip = 80542611

Cs (代码段选择子)= 0008

Ss (堆栈段选择子)和esp 由tss 段提供。


也就是说程序下一步会跳转到地址为80542611的位置上,这里指向了正真的内核函数 KiSystemService



由于ss和esp的是从tss段中获取到的,所以这里涉及到了内存查找,所以说不是快速调用。



7、快速调用


如果cpu支持快速调用,那么user层进入kernel层会调用函数ntdll.dll!KiFastSystemCall()


打开IDA pro 载入ntdll.dll  追踪函数 KiFastSystemCall()



可以发现,这里使用的是指令sysenter,进入Kernel


说明:

在执行sysenter指令之前,操作系统必须指定0环的CS段、SS段、EIP以及ESP.


这些值在MSR寄存器中。


查看MSR寄存器


kd> rdmsr 174 //查看CS

kd> rdmsr 175 //查看ESP

kd> rdmsr 176 //查看EIP

SS = cs+8



可以发现这里的eip = 0x805426e0 指向 KiFastSystemCall()





8、为什么叫快速调用


中断门进0环,需要的CS、EIP在IDT表中,需要查内存获取SS和ESP,(SS与ESP由TSS提供)


而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质都是获得CS,SS,ESP,EIP的值,只是一个查内存,慢一点,一个查cpu寄存器,快一点,本质是一样的!



9、函数KiSystemService分析


在分析函数KiSystemService之前需要先说明几个结构体。



kd> u 80542611 l40                                      保存三环的寄存器到_Trap_Frame结构体中

nt!KiSystemService:                                              

80542611 6a00          push    0                          ;保存到_Trap_Frame+0x064的位置

80542613 55              push    ebp                    ;保存到_Trap_Frame+0x060的位置

80542614 53              push    ebx                     ;保存到_Trap_Frame+0x05c的位置

80542615 56              push    esi                      ;保存到_Trap_Frame+0x058的位置

80542616 57              push    edi                      ;保存到_Trap_Frame+0x054的位置

80542617 0fa0            push    fs                              ;保存到_Trap_Frame+0x050的位置

80542619 bb30000000      mov     ebx,30h                   

8054261e 668ee3          mov     fs,bx                    ;修改段寄存器fs

                                                                 通过段选择子:0x30获得index = 6

                                                                 查找GDT表获得段描述符

                                                                 获得段描述符为:ffc093df`f0000001

                                                                 拆分段描述符:获得base:ffdff000

                                                                 地址ffdff000指向结构体_KPRC

                                                                 fs:[0]指向_KPRC

80542621 64ff3500000000  push    dword ptr fs:[0]               ;保存老的异常列表

80542628 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh       ;设置ExceptionList为-1

80542633 648b3524010000  mov     esi,dword ptr fs:[124h]           ;esi 指向_KTHREAD 线程结构体的地址

8054263a ffb640010000    push    dword ptr [esi+140h]                  ;保存先前模式,位置_KTHREAD + 140H

80542640 83ec48          sub     esp,48h                      ;提升堆栈,esp指向_Trap_Frame+0的位置

80542643 8b5c246c        mov     ebx,dword ptr [esp+6Ch]        ;获得原来三环的cs

80542647 83e301          and     ebx,1                          ;如果cpl = 0x0,ebx =0

                                                                 ;如果cpl = 0x11,ebx =1

8054264a 889e40010000    mov     byte ptr [esi+140h],bl    ;设置先前模式

80542650 8bec            mov     ebp,esp                 ;提升堆栈 ebp指向_Trap_Frame+0x00

80542652 8b9e34010000    mov     ebx,dword ptr [esi+134h]       取出原来的TrapFrame结构体地址

80542658 895d3c          mov     dword ptr [ebp+3Ch],ebx       保存原来的TrapFrame结构体地址

8054265b 89ae34010000    mov     dword ptr [esi+134h],ebp      将现在的TrapFrame的结构体地址写入到_KTHREAD+0X134的位置

80542661 fc              cld                               

80542662 8b5d60          mov     ebx,dword ptr [ebp+60h]       ;取出原来3环的ebp

80542665 8b7d68          mov     edi,dword ptr [ebp+68h]        ;取出原来3环的eip

80542668 89550c          mov     dword ptr [ebp+0Ch],edx       ;写入三环的参数指针

8054266b c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h  

80542672 895d00          mov     dword ptr [ebp],ebx             ;写入原来3环的ebp

80542675 897d04          mov     dword ptr [ebp+4],edi    ;写入原来3环的eip

80542678 f6462cff        test    byte ptr [esi+2Ch],0FFh       ;判断是否是调试模式

8054267c 0f858afeffff    jne     nt!Dr_kss_a (8054250c)       

80542682 fb              sti                                

80542683 e9e7000000      jmp     nt!KiFastCallEntry+0x8f (8054276f)


分析上述代码:


1) 地址80542611 –80542617


80542611 6a00            push    0

80542613 55              push    ebp

80542614 53              push    ebx

80542615 56              push    esi

80542616 57              push    edi

80542617 0fa0            push    fs

  

是将三环寄存器的值保存到结构体_TrapFrame 中,就是保存现场。


2)fs:[0]指向_KPRC


80542619 bb30000000      mov     ebx,30h                        

8054261e 668ee3          mov     fs,bx               ;修改段寄存器fs


(a)通过段选择子:0x30


根据段选择子结构,拆分段选择子:0x30 = 0b  0000 0000 0011 0000

获得index = 0b0110 = 6

RPL=0

GDT=0


(b)根据index = 6 查找GDT表获得段描述符





(c)获得段描述符为:ffc093df`f0000001


(d)拆分段描述符:获得base:ffdff000



(e)地址ffdff000指向结构体_KPRC


所以:fs:[0]指向_KPRC


3) 地址:80542621 – 80542628


80542621  push    dword ptr fs:[0]           

80542628  mov dword ptr fs:[0],0FFFFFFFFh ;设置ExceptionList为-1


因为fs:[0] 指向结构体_KPCR,他的第一个成员就是结构体_NT_TIB,结构体_NT_TIB的第一个成员是ExceptionList,所以:push    dword ptr fs:[0] 的功能就是保存老的异常链表。


mov dword ptr fs:[0],0FFFFFFFFh ,就是设置新的异常列表为-1。


4) 80542633  mov     esi,dword ptr fs:[124h]


fs:[0]指向了结构体_KPCR,  fs:[124h] 指向了_KTHREAD

也就是所esi 指向结构体_KTHREAD


5)  地址80542650-8054265


80542650   mov     ebp,esp                                                //提升堆栈,此时esp = ebp指向结构体_TrapFrame的首地址

80542652   mov     ebx,dword ptr [esi+134h]          //取出原来的_TrapFrame结构体地址

80542658   mov     dword ptr [ebp+3Ch],ebx          //保存原来的_TrapFrame结构体地址

8054265b   mov     dword ptr [esi+134h],ebp          //将新_TrapFrame结构体地址写入到结构体_KTHREAD +0x134的位置




6) 代码跳转到 nt!KiFastCallEntry+0x8f (8054276f)



10、系统服务表


在分析函数 nt!KiFastCallEntry+0x8f (8054276f) 之前要先简绍一下系统服务表


Windows共计两张系统服务表


结构体 SystemServiceDescriptorTable


typedef struct SystemServiceDescriptorTable
{
    UINT    *ServiceTableBase;                              //指向函数地址表
    UINT    *ServiceCounterTableBase;               //记录被访问次数
    UINT    NumberOfService;                                //函数地址表共计多少个函数
    UCHAR    *ParameterTableBase;                            //指向函数参数表
}SystemServiceDescriptorTable,*PSystemServiceDescriptorTable;


成员* ServiceTableBase指向函数地址表,函数地址表是UINT类型的数组,储存内核函数的地址。


成员*ParameterTableBase指向函数参数表,UCHAR类型数组,存储函数参数地址。


我们可以通过eax传递进来的内核函数号,查找函数地址表和函数参数表,获得内核函数的地址和参数。


内核函数号的使用方法:第12位决定查那张系统服务表,0-11位决定函数地址,和参数地址。


以ReadProcessMemary 的内核函数号0x000000BA为列

他的第12位等于0 ,说明查找一张内核服务表


后12位等于0BA,说明函数地址位于函数地址表的0x0BA项,函数参数地址为函数参数表的第0X0BA项。



 


11、函数 nt!KiFastCallEntry+0x8f (8054276f) 分析


8054276f 8bf8            mov     edi,eax              ;获得内核函数编号  值为0x00ba

80542771 c1ef08          shr     edi,8           ;edi = 0

80542774 83e730          and     edi,30h             ;edi = 0

80542777 8bcf            mov     ecx,edi              ;ecx = 0

80542779 03bee0000000    add     edi,dword ptr [esi+0E0h]         ;esi指向KTHREAD  [esi + 0e0h]获得系统服务表的地址 ;edi指向系统服务表                                     

8054277f 8bd8            mov     ebx,eax            ;ebx = 00ba

80542781 25ff0f0000      and     eax,0FFFh           ;取后12位

80542786 3b4708          cmp     eax,dword ptr [edi+8]    ;[edi+8]就是ServiceLimit,判断函数编号是否超出范围

80542789 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (805424c2)        ;异常处理程序

8054278f 83f910          cmp     ecx,10h             ;判断是否是第一张系统服务表

80542792 751b            jne     nt!KiSystemServiceAccessTeb+0x12 (805427af)

80542794 648b0d18000000  mov     ecx,dword ptr fs:[18h]  

8054279b 33db            xor     ebx,ebx                       地址8054276f

mov     edi,eax

         获得内核调用号



1)  地址 80542771 - 80542779


80542771 c1ef08          shr     edi,8

80542774 83e730          and     edi,30h   

80542777 8bcf            mov     ecx,edi    

80542779 03bee0000000    add     edi,dword ptr [esi+0E0h]

esi指向KTHREAD  [esi + 0e0h]获得系统服务表的地址

edi指向系统服务表


2) 地址:8054277f - 80542781


8054277f     mov     ebx,eax

80542781     and     eax,0FFFh

         

获取内核调用号的后12位,用于在函数地址表查找函数地址和函数参数表中查找函数参数的地址。


3)  地址80542792


jne     nt!KiSystemServiceAccessTeb+0x12 (805427af)

         跳转到系统服务表处理函数 !KiSystemServiceAccessTeb


12、函数 nt!KiSystemServiceAccessTeb+0x12 分析


805427af 64ff0538060000  inc     dword ptr fs:[638h]     fs:[0]指向_KPRC,fs:[638h]指向结构体_KPRCB  +0x518 KeSystemCalls

805427b6 8bf2            mov     esi,edx                       edx:三环参数的指针  


说明:edx为三环参数的指针,esi = edx,esi指向三环程序的参数。


805427b8 8b5f0c          mov     ebx,dword ptr [edi+0Ch]


说明:因为edi指向系统服务表,[edi+0ch]就是系统服务表的成员ArgmentTable,ebx =函数参数表的地址    


805427bb 33c9            xor     ecx,ecx                                                                     

805427bd 8a0c18          mov     cl,byte ptr [eax+ebx]     


说明:Eax + Ebx函数参数表的地址 + 偏移 ,取值后获得函数参数的个数        


805427c0 8b3f            mov     edi,dword ptr [edi]           取出系统服务表的地址 ServiceTableBase的值

805427c2 8b1c87          mov     ebx,dword ptr [edi+eax*4] 


说明:通过系统调用号,查找系统服务表,找到函数地址,将地址存储到ebx中    


805427c5 2be1            sub     esp,ecx                                         提升堆栈,提升高度为cl,就是参数的个数    

805427c7 c1e902          shr     ecx,2                                               参数的总长度/4 == 参数的个数 

805427ca 8bfc            mov     edi,esp                                          设置要copy的目的地址。就是刚才提上堆栈esp的位置        

805427cc f6457202        test    byte ptr [ebp+72h],2                                                                 

805427d0 7506            jne     nt!KiSystemServiceAccessTeb+0x3b (805427d8)                                                                     

805427d2 f6456c01        test    byte ptr [ebp+6Ch],1                                                                 

805427d6 740c            je      nt!KiSystemServiceCopyArguments (805427e4)                                 ;copy参数   

805427d8 3b3534315680    cmp     esi,dword ptr [nt!MmUserProbeAddress (80563134)]          ;esi:三环参数指针;判断三环地址是否越界    

805427de 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8054298c)                                                             ;越界就退出        

nt!KiSystemServiceCopyArguments:                                                                                 


805427e4 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]                     

说明:将三环的参数copy到0环的堆栈


805427e6 ffd3            call    ebx


说明:调用0环对应的函数


到此我们完成了3环进0环整个过程的追踪。



四、总结   


                                           

Ntdll.dll 中的 API 都只不过是一个简单的包装函数而已,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(也就是在 SSDT 数组中的索引值)存放到寄存器 EAX 中,并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中索引对应的内核函数,然后调用他。 



五、附件



1、结构体_KPCR  


kd> dt _KPCR                                                

nt!_KPCR                                              

  +0x000 NtTib            : _NT_TIB                                                  

  +0x01c SelfPcr          : Ptr32 _KPCR                                              

  +0x020 Prcb             : Ptr32 _KPRCB                                                  

  +0x024 Irql             : UChar                                                

  +0x028 IRR              : Uint4B                                                    

  +0x02c IrrActive        : Uint4B                                                

  +0x030 IDR              : Uint4B                                                    

  +0x034 KdVersionBlock   : Ptr32 Void                                                  

  +0x038 IDT              : Ptr32 _KIDTENTRY                                                  

  +0x03c GDT              : Ptr32 _KGDTENTRY                                              

  +0x040 TSS              : Ptr32 _KTSS                                                      

  +0x044 MajorVersion     : Uint2B                                              

  +0x046 MinorVersion     : Uint2B                                              

  +0x048 SetMember        : Uint4B                                                    

  +0x04c StallScaleFactor : Uint4B                                                    

  +0x050 DebugActive      : UChar                                              

  +0x051 Number           : UChar                                                    

  +0x052 Spare0           : UChar                                                      

  +0x053 SecondLevelCacheAssociativity : UChar                                                    

  +0x054 VdmAlert         : Uint4B                                                      

  +0x058 KernelReserved   : [14] Uint4B                                                

  +0x090 SecondLevelCacheSize : Uint4B                                                  

  +0x094 HalReserved      : [16] Uint4B                                              

  +0x0d4 InterruptMode    : Uint4B                                                      

  +0x0d8 Spare1           : UChar                                                      

  +0x0dc KernelReserved2  : [17] Uint4B                                                

  +0x120 PrcbData         : _KPRCB


2、结构体_NT_TIB


kd> dt _NT_TIB                                            

ntdll!_NT_TIB                                      

  +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD                                          

  +0x004 StackBase        : Ptr32 Void                                      

  +0x008 StackLimit       : Ptr32 Void                                        

  +0x00c SubSystemTib     : Ptr32 Void                                        

  +0x010 FiberData        : Ptr32 Void                                        

  +0x010 Version          : Uint4B                                    

  +0x014 ArbitraryUserPointer : Ptr32 Void                                              

  +0x018 Self             : Ptr32 _NT_TIB                                          //结构体指针 指向自己


3、结构体_KPRCB


kd> dt _KPRCB                                              

ntdll!_KPRCB                                                

  +0x000 MinorVersion     : Uint2B                                              

  +0x002 MajorVersion     : Uint2B                                              

  +0x004 CurrentThread    : Ptr32 _KTHREAD                                               //当前CPU所执行线程的_ETHREAD  

  +0x008 NextThread       : Ptr32 _KTHREAD                                             //下一个_ETHREAD        

  +0x00c IdleThread       : Ptr32 _KTHREAD                                      //当所以线程都执行完了CPU就可以执行这个

  +0x010 Number           : Char                                      //CPU编号    

  +0x011 Reserved         : Char                                                


......完整代码查看原文....



                                            

4、结构体_ETHREAD


kd> dt _ETHREAD                                                  
nt!_ETHREAD                                              
  +0x000 Tcb              : _KTHREAD                                              
  +0x1c0 CreateTime       : _LARGE_INTEGER                                                    
  +0x1c0 NestedFaultCount : Pos 0, 2 Bits                                                
  +0x1c0 ApcNeeded        : Pos 2, 1 Bit                                                      
  +0x1c8 ExitTime         : _LARGE_INTEGER                                                    
  +0x1c8 LpcReplyChain    : _LIST_ENTRY                                                      
  +0x1c8 KeyedWaitChain   : _LIST_ENTRY                                                    
  +0x1d0 ExitStatus       : Int4B                                                  


......完整代码查看原文....

                                     

5、 结构体_KTHREAD


kd> dt _KTHREAD                                                  

nt!_KTHREAD                                                

  +0x000 Header           : _DISPATCHER_HEADER                                                  

  +0x010 MutantListHead   : _LIST_ENTRY                                                    

  +0x018 InitialStack     : Ptr32 Void                                                      

  +0x01c StackLimit       : Ptr32 Void                                                  

  +0x020 Teb              : Ptr32 Void                                              

......完整代码查看原文....


                                     



本文由看雪论坛 wx_六耳 原创

转载请注明来自看雪社区



往期热门阅读:



扫描二维码关注我们,更多干货等你来拿!




    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存