首页
社区
课程
招聘
[分享]windowsXp 系统调用课堂学习笔记
发表于: 2018-5-5 07:49 7734

[分享]windowsXp 系统调用课堂学习笔记

2018-5-5 07:49
7734

目录

Windows程序3环进0环追踪记录... 2

一、         实验环境... 2

二、         关于3环和0环... 2

三、         函数ReadProcessMemary追踪... 4

1、      函数说明... 4

2、      函数追踪... 4

3、      结构体_KUSER_SHARED_DATA.. 5

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

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

6、      追踪函数!KiIntSystemCall(). 7

7、      快速调用... 8

8、      为什么叫快速调用... 9

9、      函数KiSystemService分析... 9

10、         系统服务表... 15

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

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

四、         总结... 18

五、         附件... 18

1、      结构体_KPCR. 18

2、      结构体_NT_TIB. 19

3、      结构体_KPRCB. 19

4、      结构体_ETHREAD.. 21

5、      结构体_KTHREAD.. 23

Windows程序3环进0环追踪记录

1.         系统:32位 Windows xp sp3

2.         虚拟机:VMware workstation

3.         调试器

Ollybdg 1.1

IDA pro 6.8

Win dbg

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

如下图所示

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

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

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

1)         hProcess  句柄号

2)         lpBaseAddress  要读取的地址

3)         lpBuffer             out 类型 数据缓冲区,用于返回数据

4)         nSize                            in 类型  读取多少数据

5)         lpNumberOfBytesRead             out类型 

利用IDA PRO 追踪ReadProcessMemory函数

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

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

1)         将5个参数push进入堆栈

2)         调用函数NtReadVirtualMemory

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

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

1)         MOV EAX,0Bah                  将函数调用好存入eax中

2)         MOV EDX,7FFE0300h   将参数指针放到地址为:7ffe0300h 的地方

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

在 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层的关键。

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

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

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

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

打开ollybdg

打开一个exe文件,

清空edx

修改当前程序的指令为cpuid

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

非快速调用

如果cpu不支持快速调用,那么user层进入kernel层会调用函数ntdll.dll!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段中获取到的,所以这里涉及到了内存查找,所以说不是快速调用。

如果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()

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

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

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

1)         _KPCR CPU相关结构体

2)         _ETHREAD 线程相关的结构体

3)         _TrapFrame 用于三环进0环时,保存3环寄存器的值。下图就是_TrapFrame结构体

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)

在分析函数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项。

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

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 数组中索引对应的内核函数,然后调用他。 

kd> dt _KPCR                                                 

收藏
免费 3
支持
分享
最新回复 (13)
雪    币: 258
活跃值: (124)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
406
2
mark  一下    这是我学习滴水公司的课程所记录的,感谢海哥。
2018-5-5 07:51
0
雪    币: 62
活跃值: (662)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
看到分析的是ReadProcessMemory还不确定是滴水,直到看到TrapFrame结构体的那张图就确定了。
2018-5-5 08:03
0
雪    币: 193
活跃值: (2336)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
新手感谢分享。
2018-5-6 20:05
0
雪    币: 300
活跃值: (2337)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢楼主分享
2018-5-6 20:39
0
雪    币: 10314
活跃值: (3157)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
6
感谢分享!
2018-5-6 22:50
0
雪    币: 2223
活跃值: (85)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
wx_六耳 mark 一下 这是我学习滴水公司的课程所记录的,感谢海哥。
感谢海东老师,感谢楼主分享~
2018-5-7 13:48
0
雪    币: 3727
活跃值: (3847)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
感谢分享!
2018-5-7 13:56
0
雪    币: 139
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
文章写的很好,但是我有个小疑问,在0环调用完代码后,如何返回到3环?    CS段、SS段、EIP以及ESP这几个值怎么还原到3环的状态?
2018-5-9 23:06
0
雪    币: 9
活跃值: (175)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
谢谢分享
2018-5-11 23:34
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
厉害,放代码出来给下载 嘛
2018-9-21 19:31
0
雪    币: 1558
活跃值: (3309)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
谢谢楼主,正在学系统调用课程,逆不出来内核代码,参考你的代码清晰多了
2019-4-22 18:55
0
雪    币: 300
活跃值: (2337)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark
2019-4-23 08:10
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
楼主有心了
最后于 2019-6-2 21:09 被crack主动脉编辑 ,原因:
2019-5-27 17:00
0
游客
登录 | 注册 方可回帖
返回
//