zoukankan      html  css  js  c++  java
  • 系统调用学习笔记

    开始学内核的时候,一定会讲从ring3到ring0的调用,但是网上很多的文章讲的模棱两可,这次记录下我对系统调用的研究。。。。。。。。

    一个线程由用户态进入内核态的途径有3种典型的方式:

    1、  主动通过int 2e(软中断自陷方式)或sysenter指令(快速系统调用方式)调用系统服务函数,主动进入内核

    2、  发生异常,被迫进入内核

    3、  发生硬件中断,被迫进入内核

    现在的cpu调用api进入内核都是通过sysenter指令(AMD的处理器是syscall)进入到ring0,系统在启动的时候会根据cpuid指令来测试是否支持sysenter指令,不能的话就会通过古老的int 0x2e来进行系统调用。。。。。

    为了IDT的学习,我把分析的重点放在int 0x2e上

    我们来看下r3下api的调用流程

    如ReadFile函数调用系统服务函数NtReadFile

    Kernel32.ReadFile()  //点号前面表示该函数的所在模块

    {

    //所有Win32 API通过NTDLL中的系统服务存根函数调用系统服务进入内核

    NTDLL.NtReadFile();

    }

    NTDLL.NtReadFile()

    {

       Mov eax,152   //我们要调用的系统服务函数号,也即SSDT表中的索引,记录在eax中

       If(cpu不支持sysenter指令)

       {

          Lea edx,[esp+4] //用户空间中的参数区基地址,记录在edx中

          Int 2e  //通过该自陷指令方式进入KiSystemService,‘调用’对应的系统服务

       }

      Else

       {

          Lea edx,[esp +4] //用户空间中的参数区基地址,记录在edx中

          Sysenter //通过sysenter方式进入KiFastCallEntry,‘调用’对应的系统服务

       }

       Ret 36 //不管是从int 2e方式还是sysenter方式,系统调用都会返回到此条指令处

    Int 2e的内部实现原理:

    该指令是一条自陷指令,执行该条指令后,cpu会自动将当前线程的当前栈切换为本线程的内核栈(栈分用户栈、内核栈),保存中断现场,也即那5个寄存器。然后从该cpu的中断描述符表(简称IDT)中找到这个2e中断号对应的函数(也即中断服务例程,简称ISR),jmp 到对应的isr处继续执行,此时这个ISR本身就处于内核空间了,当前线程就进入内核空间了

    Int 2e指令可以把它理解为intel提供的一个内部函数,它内部所做的工作如下

    Int 2e

    {

     //这些都是cpu硬件帮我们完成的

       Cli  //cpu一中断,立马自动关中断

       Mov esp, TSS.内核栈地址 //切换为内核栈,TSS中记录了当前线程的内核栈地址

       Push SS

       Push esp //可能会有疑问,由于 Mov esp, TSS.内核栈地址,这里压入的是内核下的esp,怎么不是用户层的esp,接下来就会讲解原因

       Push eflags

       Push cs

      Push eip  //这5项工作保存了中断现场【标志、ip、esp】

      Jmp  IDT[中断号]  //跳转到对应本中断号的isr

    }

    我们来手工设置一个除0错误的异常,在0号中断处理程序处下个断点来验证我们的猜想(在0x2e下了之后虚拟机系统老中断

    #include "stdafx.h"
    int main(int argc, char* argv[])
    {
        int a=9;
        int b=0;
        int c=a/b;//在这里设置一个断点
        return 0;
    }

     调试之后记录一下当前的寄存器值

     

    同时我们用windbg在0号中断处理程序处下个断点

    kd> !idt 0
    
    Dumping IDT:
    
    00:    8053f23c nt!KiTrap00
    
    kd> bp 8053f23c nt!KiTrap00
                              ^ Range error in 'bp 8053f23c nt!KiTrap00'
    kd> bp 8053f23c 
    breakpoint 0 redefined

    运行测试程序后,会发现windbg断点命中

    Breakpoint 0 hit
    nt!KiTrap00:
    8053f23c 6a00            push    0

    我们用dd esp命令查看堆栈来验证int 0x2e所做的操作

    kd> dd esp
    b237adcc  0040103a 0000001b 00010212 0012ff28
    b237addc  00000023 00000000 00000000 00000000

    由于0号异常是在执行idv这条指令产生的,

    所以eip==dword ptr [esp]
      cs=
    dword ptr [esp+4]
      eflags第16位做了下改变
    用户层esp=
    dword ptr [esp+c]
    ss=
    dword ptr [esp+0x10]

    11: int c=a/b;
    00401036 mov eax,dword ptr [ebp-4]
    00401039 cdq
    0040103A idiv eax,dword ptr [ebp-8]

    现在线程已经进入内核态,开始执行int 0x2e的中断处理程序,

    我们用windbg来慢慢分析中断处理程序的代码(win xp下)

    kd> u 8053e521 l30
    nt!KiSystemService:
    8053e521 6a00            push    0
    8053e523 55              push    ebp
    8053e524 53              push    ebx
    8053e525 56              push    esi
    8053e526 57              push    edi
    8053e527 0fa0            push    fs
    8053e529 bb30000000      mov     ebx,30h
    8053e52e 668ee3          mov     fs,bx  //使fs指向KPCR结构
    8053e531 ff3500f0dfff    push    dword ptr ds:[0FFDFF000h]//压入当前的exceptionlist,不是很懂
    8053e537 c70500f0dfffffffffff mov dword ptr ds:[0FFDFF000h],0FFFFFFFFh//设置新的exceptionlist
    kd> dt _KPCR  PrcbData.
    nt!_KPCR
       +0x120 PrcbData  : 
          +0x000 MinorVersion : Uint2B
          +0x002 MajorVersion : Uint2B
          +0x004 CurrentThread : Ptr32 _KTHREAD
    
    
    8053e541 8b3524f1dfff    mov     esi,dword ptr ds:[0FFDFF124h]//使esi指向ETHREAD线程对象
    kd> dt _KTHREAD
    nt!_KTHREAD
    +0x140 PreviousMode
    8053e547 ffb640010000    push    dword ptr [esi+140h]//压入线程的上个模式是r3还是r0
    8053e54d 83ec48          sub     esp,48h//为调试寄存器腾出空间,后面再解释
    ----------------至此没有push的操作,不过形成了一个trap帧,保存了用户空间的寄存器,用于从r0到r3的恢复----
    我画了个堆栈图看下这个来自用户空间构造的这个trap帧

    8
    053e550 8b5c246c mov ebx,dword ptr [esp+6Ch] 8053e554 83e301 and ebx,1 //0环的最低位为0,3环的最低位为1 8053e557 889e40010000 mov byte ptr [esi+140h],bl//通过判断cs的值来设置previousmode了 8053e55d 8bec mov ebp,esp //ebp保存trap帧 8053e55f 8b9e34010000 mov ebx,dword ptr [esi+134h]//+0x134 TrapFrame        : Ptr32 _KTRAP_FRAME 8053e565 895d3c mov dword ptr [ebp+3Ch],ebx//将上个TrapFrame放入trap帧 8053e568 89ae34010000 mov dword ptr [esi+134h],ebp//设置当前的TrapFrame 8053e56e fc cld     //改变DF标志位 8053e56f 8b5d60 mov ebx,dword ptr [ebp+60h] 8053e572 8b7d68 mov edi,dword ptr [ebp+68h] 8053e575 89550c mov dword ptr [ebp+0Ch],edx//edx指向用户空间的参数 8053e578 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h 8053e57f 895d00 mov dword ptr [ebp],ebx 8053e582 897d04 mov dword ptr [ebp+4],edi//这几句感觉没啥用 8053e585 f6462cff test byte ptr [esi+2Ch],0FFh //
    +0x02c DebugActive : UChar
    8053e589 0f858dfeffff    jne     nt!Dr_kss_a (8053e41c)//线程处于调试状态 则跳转

    
    
    我们来看下nt!Dr_kss_a 的代码//
    这是在IDA中看到的,我们大致看一下
    还记得上面的sub esp,48吗,如果线程被调试,就会将dr0-dr3 dr6-dr7保存在trap帧
    text:
    004423E0 Dr_kss_a proc near ; CODE XREF: _KiSystemService+62j .text:004423E0 test dword ptr [ebp+70h], 20000h .text:004423E7 jnz short loc_4423F3 .text:004423E7 .text:004423E9 test byte ptr [ebp+6Ch], 1 .text:004423ED jz loc_442546 .text:004423ED .text:004423F3 .text:004423F3 loc_4423F3: ; CODE XREF: Dr_kss_a+7j .text:004423F3 mov ebx, dr0 .text:004423F6 mov ecx, dr1 .text:004423F9 mov edi, dr2 .text:004423FC mov [ebp+18h], ebx .text:004423FF mov [ebp+1Ch], ecx .text:00442402 mov [ebp+20h], edi .text:00442405 mov ebx, dr3 .text:00442408 mov ecx, dr6 .text:0044240B mov edi, dr7 .text:0044240E mov [ebp+24h], ebx .text:00442411 mov [ebp+28h], ecx .text:00442414 xor ebx, ebx .text:00442416 mov [ebp+2Ch], edi .text:00442419 mov dr7, ebx .text:0044241C mov edi, large fs:20h .text:00442423 mov ebx, [edi+2F4h] .text:00442429 mov ecx, [edi+2F8h] .text:0044242F mov dr0, ebx .text:00442432 mov dr1, ecx .text:00442435 mov ebx, [edi+2FCh] .text:0044243B mov ecx, [edi+300h] .text:00442441 mov dr2, ebx .text:00442444 mov dr3, ecx .text:00442447 mov ebx, [edi+304h] .text:0044244D mov ecx, [edi+308h] .text:00442453 mov dr6, ebx .text:00442456 mov dr7, ecx .text:00442459 jmp loc_442546 .text:00442459 .text:00442459 Dr_kss_a endp
     
    8053e58f fb              sti//开启中断,至此Trap帧构造完毕
    //其实windows有个Trap帧结构,上面的push过程既是按照这个结构来的
    //我你们可以看到上面的trap帧构造就是按照这个结构来的
    typedef struct _KTRAP_FRAME //Trap现场帧
    {
       ------------------这些是KiSystemService保存的---------------------------
        ULONG DbgEbp;
        ULONG DbgEip;
        ULONG DbgArgMark;
        ULONG DbgArgPointer;
        ULONG TempSegCs;
        ULONG TempEsp;
        ULONG Dr0;
        ULONG Dr1;
        ULONG Dr2;
        ULONG Dr3;
        ULONG Dr6;
        ULONG Dr7;
        ULONG SegGs;
        ULONG SegEs;
        ULONG SegDs;
        ULONG Edx;//xy 这个位置不是用来保存edx的,而是用来保存上个Trap帧,因为Trap帧是可以嵌套的
        ULONG Ecx; //中断和异常引起的自陷要保存eax,系统调用则不需保存ecx
        ULONG Eax;//中断和异常引起的自陷要保存eax,系统调用则不需保存eax
        ULONG PreviousPreviousMode;
        struct _EXCEPTION_REGISTRATION_RECORD FAR *ExceptionList;//上次seh链表的开头地址
        ULONG SegFs;
        ULONG Edi;
        ULONG Esi;
        ULONG Ebx;
    ULONG Ebp;
    ----------------------------------------------------------------------------------------
    ULONG ErrCode;//发生的不是中断,而是异常时,cpu还会自动在栈中压入对应的具体异常码在这儿
    -----------下面5个寄存器是由int 2e内部本身保存的或KiFastCallEntry模拟保存的现场---------
        ULONG Eip;
        ULONG SegCs;
        ULONG EFlags;
        ULONG HardwareEsp;
      ULONG HardwareSegSs;//还记得int 0x2e所做的操作,按照那个操作应该是压入内核层的esp和ss的,但是压入的却是用户层 根据Hardware这个词 我猜想应该是cpu的硬件支持来保存了用户层的esp和ss
    ---------------以下用于用于保存V86模式的4个寄存器也是cpu自动压入的------------------- ULONG V86Es; ULONG V86Ds; ULONG V86Fs; ULONG V86Gs; } KTRAP_FRAME, *PKTRAP_FRAME;
    
    8053e590 e9d8000000      jmp     nt!KiFastCallEntry+0x8d (8053e66d)//跳转到ssdt的表寻找处理函数去了

     我们接下来分析一下这个查表的过程

     

    kd> u nt!KiFastCallEntry+0x8d l30
    nt!KiFastCallEntry+0x8d:
    8053e66d 8bf8            mov     edi,eax
    8053e66f c1ef08          shr     edi,8.//将系统调用号右移8位
    //我们需要明白系统调用号的前12位表示要调用哪个API,位12 和位13表示调用哪个ssdt
    //这2位如果为00 调用ssdt 如果为01 调用shadowssdt 似乎10 ,11没用到 8053e672 83e730 and edi,30h //判断是属于哪张表ssdt或者shadowssdt edx=0或者0x10 我在想根据ServiceTable获取Shadowssdt的地址的想法是不是根据这个来的 8053e675 8bcf mov ecx,edi
    kd> dd nt!KeServiceDescriptorTableShadow
    80554060  80502bbc 00000000 0000011c 80503030
    80554070  bf99ce00 00000000 0000029b bf99db10

      kd> dd nt!KeServiceDescriptorTable
      805540a0 80502bbc 00000000 0000011c 80503030

    typedef struct _SERVICE_DESCRIPTOR_TABLE {
    PULONG ServiceTable;
    PULONG CounterTable;
    ULONG TableSize;
    /*
    * Table containing the number of bytes of parameters the handler
    * function takes.
    */
    PUCHAR ArgumentTable;
    } SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

    
    
    8053e677 03bee0000000    add     edi,dword ptr [esi+0E0h]// +0x0e0 ServiceTable 
    8053e67d 8bd8            mov     ebx,eax
    8053e67f 25ff0f0000      and     eax,0FFFh//判断调用号的后12位,判断用哪个API
    8053e684 3b4708          cmp     eax,dword ptr [edi+8]//与TableSize做比较 防止调用号越界
    8053e687 0f8345fdffff    jae     nt!KiBBTUnexpectedRange (8053e3d2)
    8053e68d 83f910          cmp     ecx,10h
    8053e690 751a            jne     nt!KiFastCallEntry+0xcc (8053e6ac)//成立 执行跳转
    8053e692 8b0d18f0dfff    mov     ecx,dword ptr ds:[0FFDFF018h]
    8053e698 33db            xor     ebx,ebx
    8053e69a 0b99700f0000    or      ebx,dword ptr [ecx+0F70h]
    8053e6a0 740a            je      nt!KiFastCallEntry+0xcc (8053e6ac)
    8053e6a2 52              push    edx
    8053e6a3 50              push    eax
    8053e6a4 ff15e4405580    call    dword ptr [nt!KeGdiFlushUserBatch (805540e4)]
    8053e6aa 58              pop     eax
    8053e6ab 5a              pop     edx
    8053e6ac ff0538f6dfff    inc     dword ptr ds:[0FFDFF638h]//+0x518 KeSystemCalls : Uint4B 增加调用系统服务次数

    8053e6b2 8bf2 mov esi,edx //此时edx指向用户空间的参数块 我们发现在前面的汇编代码从没用过edx 这就是汇编的好处 可以精确的控制寄存器
    8053e6b4 8b5f0c          mov     ebx,dword ptr [edi+0Ch]//ssdt的函数参数表
    8053e6b7 33c9            xor     ecx,ecx
    8053e6b9 8a0c18          mov     cl,byte ptr [eax+ebx]//查找参数个数表
    8053e6bc 8b3f            mov     edi,dword ptr [edi]
    8053e6be 8b1c87          mov     ebx,dword ptr [edi+eax*4]//获取调用号函数的地址 比如NtOpenProcess
    8053e6c1 2be1            sub     esp,ecx
    8053e6c3 c1e902          shr     ecx,2 //获取调用号的参数个数
    8053e6c6 8bfc            mov     edi,esp
    8053e6c8 3b35d49a5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80559ad4)]//是否在有效用户地址空间
    8053e6ce 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053e87c)
    8053e6d4 f3a5            rep movs dword ptr es:[edi],dword ptr [esi] //复制用户空间的参数到堆栈中
    8053e6d6 ffd3            call    ebx  //调用ssdt的函数

    8053e6d8 8be5 mov esp,ebp //ebp指向Trap帧 8053e6da 8b0d24f1dfff mov ecx,dword ptr ds:[0FFDFF124h] 8053e6e0 8b553c mov edx,dword ptr [ebp
    +3Ch] 8053e6e3 899134010000 mov dword ptr [ecx+134h],edx //恢复上个TrapFrame nt!KiServiceExit: 8053e6e9 fa cli 8053e6ea f7457000000200 test dword ptr [ebp+70h],20000h //网上说检查APC请求 搞不懂 8053e6f1 7506 jne nt!KiServiceExit+0x10 (8053e6f9)
    //这是ZwOpenProcess的代码
    text:00440B20                 mov     eax, 0BFh
    .text:00440B25                 lea     edx, [esp+ProcessHandle]
    .text:00440B29                 pushf
    .text:00440B2A                 push    8  //压入的是8  代替了cs的位置
    .text:00440B2C                 call    _KiSystemService
    
    
    
    
    8053e6f3 f6456c01        test    byte ptr [ebp+6Ch],1 //判断cs  如果来自内核模式 会进行跳转
    8053e6f7 7457            je      nt!KiServiceExit+0x67 (8053e750) //来自内核模式执行跳转
    8053e6f9 8b1d24f1dfff    mov     ebx,dword ptr ds:[0FFDFF124h]
    8053e6ff c6432e00        mov     byte ptr [ebx+2Eh],0
    8053e703 807b4a00        cmp     byte ptr [ebx+4Ah],0
    8053e707 7447            je      nt!KiServiceExit+0x67 (8053e750)//也是检查apc的 不管它
    //这是一段处理apc的代码
    8053e709 8bdd mov ebx,ebp
                  mov dword ptr [ebx+44h],eax
    8053e70e c743503b000000 mov dword ptr [ebx+50h],3Bh

      8053e715 c7433823000000 mov dword ptr [ebx+38h],23h
      8053e71c c7433423000000 mov dword ptr [ebx+34h],23h
      8053e723 c7433000000000 mov dword ptr [ebx+30h],0
      8053e72a b901000000 mov ecx,1
      8053e72f ff15f4864d80 call dword ptr [nt!_imp_KfRaiseIrql (804d86f4)]
      8053e735 50 push eax
      8053e736 fb sti
      8053e737 53 push ebx
      8053e738 6a00 push 0
      8053e73a 6a01 push 1
      8053e73c e8fd02fcff call nt!KiDeliverApc (804fea3e)
      8053e741 59 pop ecx
      8053e742 ff151c874d80 call dword ptr [nt!_imp_KfLowerIrql (804d871c)]
      8053e748 8b4344 mov eax,dword ptr [ebx+44h]
      8053e74b fa cli
      8053e74c ebab jmp nt!KiServiceExit+0x10 (8053e6f9)
      8053e74e 8bff mov edi,edi

     */


      8053e750 8b54244c mov edx,dword ptr [esp+4Ch]  
      8053e754 648b1d50000000 mov ebx,dword ptr fs:[50h]
      8053e75b 64891500000000 mov dword ptr fs:[0],edx//恢复exceptionlis
      8053e762 8b4c2448 mov ecx,dword ptr [esp+48h]
      8053e766 648b3524010000 mov esi,dword ptr fs:[124h] //esi又开始指向ETHREAD了
      8053e76d 888e40010000 mov byte ptr [esi+140h],cl
      8053e773 f7c3ff000000 test ebx,0FFh
      8053e779 7579 jne nt!KiSystemCallExit2+0x17 (8053e7f4)
      8053e77b f744247000000200 test dword ptr [esp+70h],20000h
      8053e783 0f85eb080000 jne nt!Kei386EoiHelper+0x12c (8053f074)
      8053e789 66f744246cf9ff test word ptr [esp+6Ch],0FFF9h
      8053e790 0f84b4000000 je nt!KiSystemCallExit2+0x6d (8053e84a)
      8053e796 66837c246c1b cmp word ptr [esp+6Ch],1Bh

    053e79c 660fba64246c00 bt word ptr [esp+6Ch],0

    8053e7a3 f5 cmc

    8053e7a4 0f878e000000 ja nt!KiSystemCallExit2+0x5b (8053e838)

    8053e7aa 66837d6c08 cmp word ptr [ebp+6Ch],8 //判断cs的值 判断上次的模式是r3还是r0

    8053e7af 7405 je nt!KiServiceExit+0xcd (8053e7b6)//来自用户空间的调用不成立

    8053e7b1 8d6550 lea esp,[ebp+50h] //从此处开始恢复用户空间的寄存器了

    8053e7b4 0fa1 pop fs

    //如果是来自内核空间的调用,会跳转到此处

    8053e7b6 8d6554 lea esp,[ebp+54h]

    8053e7b9 5f pop edi

    8053e7ba 5e pop esi

    8053e7bb 5b pop ebx

    8053e7bc 5d pop ebp    //从栈顶恢复寄存器

    8053e7bd 66817c24088000 cmp word ptr [esp+8],80h

    8053e7c4 0f87c6080000 ja nt!Kei386EoiHelper+0x148 (8053f090) //不懂

    8053e7ca 83c404 add esp,4

    8053e7cd f744240401000000 test dword ptr [esp+4],1 //判断cs  多次判断参数了

    nt!KiSystemCallExitBranch:

    8053e7d5 7506 jne nt!KiSystemCallExit2 (8053e7dd)  //成立,来自用户空间的调用通过KiSystemCallExit2返回

    //来自内核空间的调用执行此处

     

    //这是ZwOpenProcess的代码
    text:00440B20                 mov     eax, 0BFh
    .text:00440B25                 lea     edx, [esp+ProcessHandle]
    .text:00440B29                 pushf
    .text:00440B2A                 push    8  //压入的是8  代替了cs的位置
    .text:00440B2C                 call    _KiSystemService
    .text:00440B1D                 retn    10h

    8053e7d7 5a pop edx  //按照ZwOpenProcess压入的堆栈框架  此处保存的是调用完KiSystemService后的返回地址

    8053e7d8 59 pop ecx

    8053e7d9 9d popfd  //弹出eflags寄存器

    8053e7da ffe2 jmp edx //跳转到 ret 10h指令

     ----------------------------------------------------------------------------------------------------------

     

    nt!KiSystemCallExit:

    //iret的执行代码

    //对于实地址模式中断返回,IRET 指令从堆栈将返回指令指针、返回代码段选择器以及 EFLAGS 映像分别弹入 EIP、CS 以及 EFLAGS 寄存器,然后恢复执行中断的程序或过程。如果返回到另一个特权级别,则在恢复程序执行之//前,IRET 指令还从堆栈弹出堆栈指针与 SS

    8053e7dc cf iretd  //进行中断返回

     

    nt!KiSystemCallExit2:

    8053e7dd f644240901 test byte ptr [esp+9],1 //不知道为什么可以判断eflags的TF标志位可以判断是否通过自陷指令进入的

    8053e7e2 75f8 jne nt!KiSystemCallExit (8053e7dc)//来自自陷指令时 TF==1 进行跳转

    //来自sysenter指令的返回

    8053e7e4 5a pop edx

    8053e7e5 83c404 add esp,4

    8053e7e8 80642401fd and byte ptr [esp+1],0FDh

    8053e7ed 9d popfd

    8053e7ee 59 pop ecx

    8053e7ef fb sti

    8053e7f0 0f35 sysexit

    
    

     

    //最后总结一下 使用汇编写这个中断处理可以对寄存器达到很好的控制 在调用ssdt之前edx是没有改变的 因为它指向用户空间的参数
    //eax在调用ssdt之后也是没有改变的 它包含了返回的结果
    需要呢
    //我们发现形成的TrapFrame中是没有edx和ecx的 edx只是承担着指向用户空间的参数的责任 可以不必push 为啥ecx也不

     

  • 相关阅读:
    Python下载安装
    批量修改样式及全选反选
    小99
    练习题
    练习
    对象、函数
    操作document对象练习
    练习题
    0513-2
    0513-1
  • 原文地址:https://www.cnblogs.com/Reverser/p/4442351.html
Copyright © 2011-2022 走看看