古月不傲 发表于 2020-2-23 20:53

函数调用过程

本帖最后由 古月不傲 于 2020-2-24 12:24 编辑

随便拖入一个程序到IDA中
#include <stdio.h>
#include <stdlib.h>

#include <Windows.h>

HANDLE hEvent = NULL;

DWORD Thread(LPVOID lpParamter)
{
      SetEvent(hEvent);
      WaitForSingleObject(hEvent, -1);
      
      printf("线程正常执行\n");
      return 0;
}

int main(void)
{
      hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Thread, NULL, 0, NULL);
      WaitForSingleObject(hThread, -1);
      printf("主线程结束\n");

      system("pause");
      return 0;
}
以WaitForSingleObject这个函数为例
1.首先它调用了Kernel32.dll导出的WaitForSingleObject
2.然后调用了Kernel32.dll导出的WaitForSingleObjectEx
3.接着调用了Ntdll.dll导出的NtWaitForSingleObject
4.接着调用了Ntdll.dll导出的KiFastSystemCall
5.通过快速调用 sysenter:
6.调用ntkrnlpa.exe导出的KiFastCallEntry
7.最终调用NtWaitForSingleObject
硬件方式读取MSR的值 msr.174 = cscs + 8 = ss msr.175 = esp msr.176 = eip windbg指令rdmsr wrmsr
构造0环cs ss esp eip 因为是3环进0环嘛 总不能还用3环的堆栈对吧。
kd> dt _KTRAP_FRAME
ntdll!_KTRAP_FRAME
   +0x000 DbgEbp         : Uint4B                        //调试等其他作用
   +0x004 DbgEip         : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs      : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0            : Uint4B
   +0x01c Dr1            : Uint4B
   +0x020 Dr2            : Uint4B
   +0x024 Dr3            : Uint4B
   +0x028 Dr6            : Uint4B
   +0x02c Dr7            : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx            : Uint4B
   +0x040 Ecx            : Uint4B
   +0x044 Eax            : Uint4B
   
   +0x048 PreviousPreviousMode : Uint4B                //windows中非易失性寄存器需要在中断例程中先保存
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi            : Uint4B
   +0x058 Esi            : Uint4B
   +0x05c Ebx            : Uint4B
   +0x060 Ebp            : Uint4B
   +0x064 ErrCode          : Uint4B
   
   +0x068 Eip            : Uint4B                        //中断发生时,保存被中断的代码段和地址,iret返回到此地址
   +0x06c SegCs            : Uint4B
   +0x070 EFlags         : Uint4B
   +0x074 HardwareEsp      : Uint4B                        //中断发生时,若发生权限切换,则要保存旧的堆栈
   +0x078 HardwareSegSs    : Uint4B
   
   +0x07c V86Es            : Uint4B                        //虚拟8086方式下,交换需要保存段寄存器
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B
但是windows并没有使用msr的esp的值 而是通过kpcr.tss.esp的值构造堆栈 而这个值呢又指向了一个叫做ktrap_frame的结构体 +0x07c V86Es 的位置
所以一切的一切就是构造ktrap_frame结构体 它保存了3环寄存器的信息
现在eip也设置好了 而这个eip的值指向了一个叫做KiFastCallEntry的函数 也就是说任何3环进0环 或者0环调用API都要通过这种方式首先经过KiFastCallEntry
下面来看看这个关键函数到底干了什么
nt!KiFastCallEntry:
80542520 b923000000      mov   ecx,23h                                       
80542525 6a30            push    30h
80542527 0fa1            pop   fs                                                        //fs = 30
80542529 8ed9            mov   ds,cx                                                //ds = 23
8054252b 8ec1            mov   es,cx                                                //es = 23
8054252d 648b0d40000000mov   ecx,dword ptr fs:                //ecx = kpcr.TSS                       
80542534 8b6104          mov   esp,dword ptr                 //esp = TSS.esp                      TSS.esp->ktrap_frame.V86Es
80542537 6a23            push    23h                                                //ktrap_frame.HardwareSegSs = 23
80542539 52            push    edx                                                //ktrap_frame.HardwareEsp = edx   mov   edx,esp
8054253a 9c            pushfd                                                                //ktrap_frame.EFlags = eflag
8054253b 6a02            push    2                                                        //ktrap_frame.SegCs = 2
8054253d 83c208          add   edx,8                                                //3环第一个参数的地址                 edx + 0 = esp   edx + 4 = 返回地址
80542540 9d            popfd                                                                //pop eflag = 2
80542541 804c240102      or      byte ptr ,2                        //or ktss.Esp0 + 1, 2
80542546 6a1b            push    1Bh                                                //ktrap_frame.SegCs = 1Bh
80542548 ff350403dfff    push    dword ptr ds:        //ktrap_frame.Eip = kuser_shared_data.SystemCallReturn
8054254e 6a00            push    0                                                        //ktrap_frame.ErrCode = 0
80542550 55            push    ebp                                                //ktrap_frame.Ebp = ebp
80542551 53            push    ebx                                                //ktrap_frame.Ebx = ebx
80542552 56            push    esi                                                //ktrap_frame.Esi = esi
80542553 57            push    edi                                                //ktrap_frame.Edi = edi
80542554 648b1d1c000000mov   ebx,dword ptr fs:                //ebx = kpcr.SelfPcr
8054255b 6a3b            push    3Bh                                                //ktrap_frame.SegFs = 3Bh
8054255d 8bb324010000    mov   esi,dword ptr         //kprcb.CurrentThread
80542563 ff33            push    dword ptr                         //ktrap_frame.ExceptionList = kpcr.NtTib.ExceptionList
80542565 c703ffffffff    mov   dword ptr ,0FFFFFFFFh        //kpcr.NtTib.ExceptionList = -1
8054256b 8b6e18          mov   ebp,dword ptr         //kthread.InitialStack 栈底指针
8054256e 6a01            push    1                                                        //ktrap_frame.PreviousPreviousMode = 1
80542570 83ec48          sub   esp,48h                                        //指向ktrap_frame.DbgEbp 也就是0x00
80542573 81ed9c020000    sub   ebp,29Ch                                        //指向ktrap_frame.DbgEbp 也就是0x00
80542579 c6864001000001mov   byte ptr ,1                //kthread.PreviousMode = 1 0代表0环 1代表3环
80542580 3bec            cmp   ebp,esp                                        //检查栈底是否等于栈顶
80542582 758d            jne   nt!KiFastCallEntry2+0x49 (80542511)
80542584 83652c00      and   dword ptr ,0                //and 指向ktrap_frame.Dr7, 0
80542588 f6462cff      test    byte ptr ,0FFh        //判断kthread.DebugActive是不是-1
8054258c 89ae34010000    mov   dword ptr ,ebp        //kthread.TrapFrame = ktrap_frame
80542592 0f8538feffff    jne   nt!Dr_FastCallDrSave (805423d0)//调试状态 跳转 压入dr0 - dr7调试寄存器
80542598 8b5d60          mov   ebx,dword ptr         //ebx = ktrap_frame.Ebp       
8054259b 8b7d68          mov   edi,dword ptr         //edi = ktrap_frame.Eip
8054259e 89550c          mov   dword ptr ,edx        //ktrap_frame.DbgArgPointer = 3环第一个参数的地址
805425a1 c74508000ddbbamov   dword ptr ,0BADB0D00h//ktrap_frame.DbgArgMark = 0BADB0D00h
805425a8 895d00          mov   dword ptr ,ebx                //ktrap_frame.DbgEbp = ktrap_frame.Ebp
805425ab 897d04          mov   dword ptr ,edi                //ktrap_frame.DbgEip = ktrap_frame.Eip
805425ae fb            sti                                                                //开启中断
//前面一段都是构造ktrap_frame结构体 也就是为了保存3环的数据
805425af 8bf8            mov   edi,eax                                        //系统服务号       
805425b1 c1ef08          shr   edi,8                                                //右移8位
805425b4 83e730          and   edi,30h                                        //与30                不是0 就是10
805425b7 8bcf            mov   ecx,edi                                        //ecx = edi
805425b9 03bee0000000    add   edi,dword ptr         //edi = kthread.ServiceTable
                                                                                                                                        unsigned int *ServiceTableBase;      // 系统服务表基址
                                                                                                                                        unsigned int *ServiceCounterTableBase; // 系统服务表被调用的次数
                                                                                                                                        unsigned int NumberOfServices;         // 系统服务表函数个数
                                                                                                                                        unsigned char *ParamTableBase;         // 系统服务表函数参数大小 (字节)
805425bf 8bd8            mov   ebx,eax                                        //ebx = 系统服务号
805425c1 25ff0f0000      and   eax,0FFFh                                        //系统服务号保留低12位
805425c6 3b4708          cmp   eax,dword ptr                 //判断 eax-ServiceTable.NumberOfServices
805425c9 0f8333fdffff    jae   nt!KiBBTUnexpectedRange (80542302)//如果大于等于 跳转
805425cf 83f910          cmp   ecx,10h                                        //判断ecx 是否 = win32k.sys里面的函数
805425d2 751b            jne   nt!KiFastCallEntry+0xcf (805425ef)不是跳转ntkrnlpa.exe
805425d4 648b0d18000000mov   ecx,dword ptr fs:                //ecx = _NT_TIB
805425db 33db            xor   ebx,ebx                                        //ebx = 0
805425dd 0b99700f0000    or      ebx,dword ptr
805425e3 740a            je      nt!KiFastCallEntry+0xcf (805425ef)
805425e5 52            push    edx
805425e6 50            push    eax
805425e7 ff1548d75580    call    dword ptr //跳转win32k.sys
805425ed 58            pop   eax
805425ee 5a            pop   edx
805425ef 64ff0538060000inc   dword ptr fs:
805425f6 8bf2            mov   esi,edx                                        //esi = 3环第一个参数的地址
805425f8 8b5f0c          mov   ebx,dword ptr         //ebx = ServiceTable.ParamTableBase
805425fb 33c9            xor   ecx,ecx                                        //ecx = 0
805425fd 8a0c18          mov   cl,byte ptr                 //cl= ParamTableBase 取参数的字节个数
80542600 8b3f            mov   edi,dword ptr                 //edi = ServiceTable.ServiceTableBase
80542602 8b1c87          mov   ebx,dword ptr         //ebx = ServiceTable.ServiceTableBase[系统服务号]
80542605 2be1            sub   esp,ecx                                        //提升参数堆栈 cl字节
80542607 c1e902          shr   ecx,2                                                //ecx右移2位       rep movs
8054260a 8bfc            mov   edi,esp                                        //edi = esp
8054260c 3b3534315680    cmp   esi,dword ptr //用户参数越界检查
80542612 0f83a8010000    jae   nt!KiSystemCallExit2+0x9f (805427c0)//结束 跳转
80542618 f3a5            rep movs dword ptr es:,dword ptr //mov , ecx--
8054261a ffd3            call    ebx                                                //调用系统服务号的函数
总结 任何3环程序只要调用系统关键函数都要通过以上这种方式一步一步调用 0环直接调用内核模块 以此说明3环约束非常多 相当麻烦。
实验环境xp 有的人问为啥不搞64位 32位都搞不明白 64位不现实的 就这样把。

weilaikeji007 发表于 2020-2-23 21:26

感谢楼主。已学习

帝王Burlk 发表于 2020-3-11 13:15

楼主顶啊 粉一个
页: [1]
查看完整版本: 函数调用过程