首页
社区
课程
招聘
[旧帖] [原创][原创]学习windows系统调用 0.00雪花
2013-11-5 23:18 5956

[旧帖] [原创][原创]学习windows系统调用 0.00雪花

2013-11-5 23:18
5956
老掉牙的话题了,不过这是学习研究系统的基础。
简单的说说windows系统调用,我们编写win32程序的时候为了完成特定的功能就要去调用win32应用程序接口,就是win32 API,windows通过win32 API调用界面所定义的库函数,这些函数基本上都是系统dll中导出的,然后调用流程一直往系统底层走,知道通过某些特殊的命令进入内核进而完成系统调用,当然不是每一个API都能进入系统调用流程。

这里以ReadFile函数作为研究对象,这个API是kernel32导出的,通过windbg跟踪调用可以直白的看出函数内部调用了NtReadFile,内涵源代码提供了NtReadFile函数,但是用户空间的程序是不可能直接调用内核函数的,其实这个NtReadFile是用户空间提供的一个函数。

以NtReadFile为例:
lkd> u ntdll!ntreadfile
ntdll!NtReadFile:
7c92d9b0 b8b7000000      mov     eax,0B7h
7c92d9b5 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d9ba ff12            call    dword ptr [edx]
7c92d9bc c22400          ret     24h
7c92d9bf 90              nop

看到在对eax赋值之后,将一个函数指针传给了edx。
lkd> dd SharedUserData!SystemCallStub
7ffe0300  7c92e4f0 7c92e4f4 00000000 00000000
7ffe0310  00000000 00000000 00000000 00000000
7ffe0320  00000000 00000000 00000000 00000000
7ffe0330  fb1fc0d5 00000000 00000000 00000000
7ffe0340  00000000 00000000 00000000 00000000
7ffe0350  00000000 00000000 00000000 00000000
7ffe0360  00000000 00000000 00000000 00000000
7ffe0370  00000000 00000000 00000000 00000000

继续反汇编这个地址7c92e4f0 
lkd> u 7c92e4f0 
ntdll!KiFastSystemCall:
7c92e4f0 8bd4            mov     edx,esp
7c92e4f2 0f34            sysenter
ntdll!KiFastSystemCallRet:
7c92e4f4 c3              ret
7c92e4f5 8da42400000000  lea     esp,[esp]
7c92e4fc 8d642400        lea     esp,[esp]
ntdll!KiIntSystemCall:
7c92e500 8d542408        lea     edx,[esp+8]
7c92e504 cd2e            int     2Eh
7c92e506 c3              ret

系统使用快速系统调用实现ring3到ring0的跨越。使用sysenter快速系统调用就如内核,那么系统怎么找到进入内核该执行什么指令呢?

[快速系统调用]

快速系统调用
系统使用sysenter sysexit和三个msr寄存器实现的快速系统调用,具体的流程大致为:
执行sysenter时,cpu进入系统态,随后将SYSENTER_CS_MSR的内容附给CS寄存器,将SYSENTER_EIP_MSR的内容附给EIP,将SYSENTER_CS_MSR + 8附给SS,将SYSENTER_ESP_MSR附给SS寄存器。
1.  CS = SYSENTER_CS_MSR
2.  EIP = SYSENTER_EIP_MSR
3.  SS = SYSENTER_CS_MSR + 8
4.  ESP = SYSENTER_ESP_MSR

因此只要事先设置好MSR就可以顺利进入系统空间从CS:EIP开始执行内核指令
[摘自windows内核情景分析代码-reactOS]
ULONG_PTR NTAPI KiLoadFastSyscallMachineSpecificRegister(IN ULONG_PTR Context)
{
  //设置MSR寄存器
  //CS被设置成0x8, 那么SS就是0x8+0x8 = x010
  Ke386Wrmsr(0x174, 0x8, 0);
  //ESP被设置成一个中立的不属于任何线程的堆栈,作为过渡的系统空间堆栈
  Ke386Wrmsr(0x175, (ULONG)KeGetCurrentPrcb->DpcStack, 0);
  //EIP设置为系统快速调用总入口KiFastCallEntry
  Ke386Wrmsr(0x176, (ULONG)KiFastCallEntry, 0);
  Retun 0;
}

可以看出三个MSR寄存器的编号分别为0x174,0x175,0x176,系统通过Ke386Wrmsr函数对这个三个MSR寄存器赋值完成快速调用前的准备。可以看到EIP指向了KiFastCallEntry这个函数。
lkd> u nt!kifastcallentry
nt!KiFastCallEntry:
8053e540 b923000000      mov     ecx,23h
8053e545 6a30            push    30h
8053e547 0fa1            pop     fs
8053e549 8ed9            mov     ds,cx
8053e54b 8ec1            mov     es,cx
8053e54d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053e553 8b6104          mov     esp,dword ptr [ecx+4]
8053e556 6a23            push    23h

KiFastCallEntry的实现就不详细说了,函数开头将0x30附给了FS,待会在后边会讲解为什么这里首先要将0x30给FS。
以上是通过sysenter进行快速系统调用的过程,其实在以前的系统中也可以通过int 2e中断进入内核,当然现在有些函数也是通过int 2e指令进入内核的,在此不进行举例了。

[int 2e中断指令进入内核]
不得不提的是IDT,系统终端描述表,也可以叫中断向量表,显而易见的是这是一张表,但不是普通的一张表,这个表中的每一项都记录了一个中断编号和对应的中断处理函数。比如我们常用的“int 3”就是调用编号为3的中断处理函数,通过windbg可以看到编号为3的中断处理函数:
lkd> !idt 3
Dumping IDT:
03:  8053f6e4 nt!KiTrap03

可以看到对应的函数是 KiTrap03。那么int 2e也是一样的,它调用的就是编号为2e的中断处理函数,首先看看这个函数到底是什么。
lkd> !idt 2e
Dumping IDT:
2e:  8053e481 nt!KiSystemService

其对应的函数是KiSystemService,那么就是说系统通过KiSystemService进入内核。
接下来我们来模拟系统,看看究竟是如何通过中断编号找到中断处理函数KiSystemService的。

保护模式下的中断的实现方式是通过IDT表来实现,IDT表中存放的是中断描述向量,表中的每个记录长8个字节,其中第0个字节和第一个字节是一个16位的offset,第2,3字节是一个选择字selector,第4,5字节是属性,剩下两个字节也是offset,头尾共四个字节组成了一个32位偏移地址。我们要找的函数地址其实就是要通过这个32位偏移和选择子selector进行定位,具体的过程:
首先要知道IDT的地址,这样才能知道第2e个向量在哪儿
lkd> !pcr
KPCR for Processor 0 at ffdff000:
    Major 1 Minor 1
  NtTib.ExceptionList: f4de9c7c
      NtTib.StackBase: f4de9df0
     NtTib.StackLimit: f4de7000
   NtTib.SubSystemTib: 00000000
        NtTib.Version: 00000000
    NtTib.UserPointer: 00000000
        NtTib.SelfTib: 7ffde000

              SelfPcr: ffdff000
                 Prcb: ffdff120
                 Irql: 00000002
                  IRR: 00000000
                  IDR: ffff20f8
        InterruptMode: 00000000
                  IDT: 8003f400
                  GDT: 8003f000
                  TSS: 80042000

        CurrentThread: 819323a0
           NextThread: 00000000
           IdleThread: 8055a9c0


通过!pcr可以找到处理器对应的IDT(本人测试机器是单核的XP),那么IDT表所在的地址就是8003f400,GDT的地址就是8003f000,那么第2e项可以通过windbg这样找到:
lkd> dq 8003f400+2e*8
8003f570  804dee00`0008f631 804e8e00`0008297c
8003f580  806f8e00`00085d54 81e08e00`000855a4
8003f590  804d8e00`0008ed04 804d8e00`0008ed0e
8003f5a0  804d8e00`0008ed18 804d8e00`0008ed22
8003f5b0  804d8e00`0008ed2c 804d8e00`0008ed36
8003f5c0  806e8e00`0008fef0 81f98e00`000890d4
8003f5d0  81e18e00`0008165c 81ed8e00`00080aac
8003f5e0  81eb8e00`00089744 804d8e00`0008ed72

根据上面所讲的结构,可以看到这里对应的selector = 0x0008,而offset = 804df631。我们要找的函数入口地址其实就是[段基址]+offset,既然找到了offset,那么寻找这个基址就是接下来的任务了。段选择子共16位,其中低三位暂时不管,剩下的13位是一个编号,就是要通过这个编号作索引去GDT(全局描述符)拿到段的信息。同样GDT的每一项占8个字节,我们关心的是第2,3,4,7这四个字节,因为他们组成了段基地址。有上面selector = 0x0008,可以知道索引值为1,也就是要去找GDT第一项的内容:
lkd> dq 8003f000+1*8
8003f008  00cf9b00`0000ffff 00cf9300`0000ffff
8003f018  00cffb00`0000ffff 00cff300`0000ffff
8003f028  80008b04`200020ab ffc093df`f0000001
8003f038  7f40f3fd`e0000fff 0000f200`0400ffff
8003f048  00000000`00000000 80008955`22000068
8003f058  80008955`22680068 00009302`2f40ffff
8003f068  0000920b`80003fff ff0092ff`700003ff
8003f078  80009a40`0000ffff 80009240`0000ffff

根据上面说的GDT结构可以知道基地址是0x0000。那么我们所找到的函数入口地址就是:[基地址]+offset = 0x000 + 804df631 = 0x804df631。反汇编这个地址:
lkd> u 0x804df631
nt!KiSystemService:
804df631 6a00            push    0
804df633 55              push    ebp
804df634 53              push    ebx
804df635 56              push    esi
804df636 57              push    edi
804df637 0fa0            push    fs
804df639 bb30000000      mov     ebx,30h
804df63e 8ee3            mov     fs,bx

果然也还是KiSystemService这个函数。
这里有一个问题,函数什么时候使用int 2eh进行中断系统调用什么时候使用快速系统调用呢?原来在内核初始化的时候,系统会根据是否支持快读调用设置SharedUserData!SystemCallStub的具体指向,也就是说SharedUserData!SystemCallStub可以指向KiFastSystemCall也可以指向KiIntSystemCall,具体指向那一个函数由系统决定。

不会编辑图片,所以上面所说的IDT,GDT的结构有些潦草,其实用图片可能比较直观。还有这个格式....就不说了。帖子中所说的有些地方还是一带而过,但其实里面有很多点都是可以深钻的。

哦,差点忘记了,上面还提到为什么将0x30附给FS。通过对GDT的描述,就能知道为什么这样做。0x30就是一个选择子,根据选择子的格式,可以知道我们需要将3作为索引去查找GDT。
lkd> dq 8003f000+6*8
8003f030  ffc093df`f0000001 7f40f3fd`e0000fff
8003f040  0000f200`0400ffff 00000000`00000000
8003f050  80008954`af000068 80008954`af680068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff
8003f080  80009240`0000ffff 00009200`00000000
8003f090  00000000`00000000 00000000`00000000
8003f0a0  890089a2`d3200068 00000000`00000000

拿到的地址就是ffdff000,眼熟吧--
lkd> !pcr
KPCR for Processor 0 at ffdff000:
    Major 1 Minor 1
  NtTib.ExceptionList: b1ea0c7c
      NtTib.StackBase: b1ea0df0
     NtTib.StackLimit: b1e9d000
   NtTib.SubSystemTib: 00000000
        NtTib.Version: 00000000
    NtTib.UserPointer: 00000000
        NtTib.SelfTib: 7ffde000

              SelfPcr: ffdff000
                 Prcb: ffdff120
                 Irql: 00000000
                  IRR: 00000000
                  IDR: ffffffff
        InterruptMode: 00000000
                  IDT: 8003f400
                  GDT: 8003f000
                  TSS: 80042000

        CurrentThread: 894cc560
           NextThread: 00000000
           IdleThread: 80553740

没错,就是KPCR 的地址。说明进入内核的时候FS指向了KPCR。
好了,内容有些混乱,因为有很多点都比较深入而本人能力有限,加上不好编辑,不管怎么调整都觉得别扭......

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (21)
雪    币: 25
活跃值: (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
squallqz 2013-11-6 10:39
2
0
快成正式会员了,加油~~还有,建议试试dds指令~
雪    币: 0
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水月与欢 2013-11-6 10:51
3
0
汇编语言还没学习,看来是要学习汇编语言了!
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
duwanjiang 2013-11-6 16:14
4
0
正在  学习驱动,有帮助,学习了
雪    币: 85
活跃值: (51)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
grusirna 1 2013-11-6 22:55
5
0
很不错的文章,以问题引出知识点,让人不知不觉进入其中
雪    币: 41
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
夏天VIP 2013-11-7 10:40
6
0
好厉害的样子
雪    币: 195
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
rqnet 2013-11-7 13:06
7
0
又是反编译分析,看不懂啊
雪    币: 29
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
寒窗苦读 2013-11-7 14:53
8
0
楼主这样的贴子,给我的感觉就是,能看懂的不用你那篇贴子人家也知道,看不懂的你这样写人家还是看不懂。难道就不能详细点,将相关的技术点所涉及的概念以及具体语句介绍一下么?象这种记流水账一样的帖子我是看不懂。
雪    币: 43
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cbctan 2013-11-8 12:37
9
0
还是要学习汇编
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
破天大轟 2013-11-9 11:15
10
0
楼主用的是windbg , 为什么我的windbg使用!idt , !pcr 命令却找不到结果: No export pcr found 我的系统是win7 64位。
雪    币: 59
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lylqiang 2013-11-9 16:06
11
0
看不懂汇编,但是支持下
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 00:40
12
0
好的,谢谢指导
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 12:09
13
0
多谢指导哈
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 12:11
14
0
这个可以边看反汇编代码边学习
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 12:15
15
0
多谢大哥鼓励啊
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 12:16
16
0
发此贴的时候比较匆忙,而且涉及的点比较多。小弟谨记教诲,下次一定发更高质量的帖
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-10 12:17
17
0
符号加载了吗,reload一下吧
雪    币: 0
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
水月与欢 2013-11-10 22:45
18
0
恩恩,正在学习。
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
recuritis 2013-11-13 00:22
19
0
正在努力爬进中
雪    币: 25
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cctvbnm 2013-11-13 09:33
20
0
看不太懂。
雪    币: 41
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
马宏敏 2013-11-13 14:37
21
0
看不懂,都是怎么学习的
雪    币: 27
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangyanxu 2013-11-13 21:05
22
0
将理论作为基础,多动手就好
游客
登录 | 注册 方可回帖
返回