函数调用过程
本帖最后由 古月不傲 于 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位不现实的 就这样把。 感谢楼主。已学习 楼主顶啊 粉一个
页:
[1]