关于Int 2E

下面是反汇编ntdll.dll的NtCreateEvent部分 NtCreateEvent调用了int 2E

Exported fn(): NtCreateEvent  -  Ord:005Ah
Exported fn(): ZwCreateEvent 
-  Ord:02E2h
:77F83219 B81E000000         mov eax, 0000001E
:77F8321E 8D542404           lea edx, dword ptr [esp
+ 04 ]
:77F83222 CD2E             
int  2E
:77F83224 C21400             ret 
0014


int 2e的使用方法:

mov eax, service_id
lea edx, service_param
int  2e



Windows 2000 int 2e 功能表
共248个
EAX = function number
EDX = address of parameter block
0x0   AcceptConnectPort
0x1   AccessCheck
0x2   AccessCheckAndAuditAlarm
0x3   AccessCheckByType
0x4   AccessCheckByTypeAndAuditAlarm
0x5   AccessCheckByTypeResultList
0x6   AccessCheckByTypeResultListAndAuditAlarm
0x7   AccessCheckByTypeResultListAndAuditAlarmByHandle
0x8   AddAtom
0x9   AdjustGroupsToken
0xa   AdjustPrivilegesToken
0xb   AlertResumeThread
0xc   AlertThread
0xd   AllocateLocallyUniqueId
0xe   AllocateUserPhysicalPages
0xf   AllocateUuids
0x10 AllocateVirtualMemory
0x11 AreMappedFilesTheSame
0x12 AssignProcessToJobObject
0x13 CallbackReturn
0x14 CancelIoFile
0x15 CancelTimer
0x16 CancelDeviceWakeupRequest
0x17 ClearEvent
0x18 Close
0x19 CloseObjectAuditAlarm
0x1a CompleteConnectPort
0x1b ConnectPort
0x1c Continue
0x1d CreateDirectoryObject
0x1e CreateEvent                 请注意这里




Ntdll.dll通过软件中断int 2Eh进入ntoskrnl.exe,就是通过中断门切换CPU特权级。比如kernel32.dll导出的函数DeviceIoControl()实际上调用ntdll.dll中导出的NtDeviceIoControlFile(),反汇编一下这个函数可以看到,EAX载入magic数0x38,实际上是系统调用号,然后EDX指向堆栈。目标地址是当前堆栈指针ESP+4,所以EDX指向返回地址后面一个,也就是指向在进入 NtDeviceIoControlFile()之前存入堆栈的东西。事实上就是函数的参数。下一个指令是int 2Eh,转到中断描述符表IDT位置0x2E处的中断处理程序。

反编汇这个函数得到:

mov eax, 38h

lea edx, [esp
+ 4 ]

int  2Eh

ret 28h



当然int 2E接口不仅仅是简单的API调用调度员,他是从用户模式进入内核模式的main gate。

W2k Native API由248个这么处理的函数组成,比NT 4.0多了37个。可以从ntdll.dll的导出列表中很容易认出来:前缀Nt。Ntdll.dll中导出了249个,原因在于 NtCurrentTeb()为一个纯用户模式函数,所以不需要传给内核。令人惊奇的是,仅仅Native API的一个子集能够从内核模式调用。而另一方面,ntoskrnl.exe导出了两个Nt*符号,它们不存在于ntdll.dll中: NtBuildNumber, NtGlobalFlag。它们不指向函数,事实上,是指向ntoskrnl.exe的变量,可以被使用C编译器extern关键字的驱动模块导入。 Ntdll.dll和ntoskrnl.exe中都有两种前缀Nt*,Zw*。事实上ntdll.dll中反汇编结果两者是一样的。而在 ntoskrnl.exe中,nt前缀指向真正的代码,而zw还是一个int 2Eh的stub。也就是说zw*函数集通过用户模式到内核模式门传递的,而Nt*符号直接指向模式切换以后的代码。Ntdll.dll中的 NtCurrentTeb()没有相对应的zw函数。Ntoskrnl并不导出配对的Nt/zw函数。有些函数只以一种方式出现。

2Eh中断处理程序把EAX里的值作为查找表中的索引,去找到最终的目标函数。这个表就是系统服务表SST,C的结构 SYSTEM_SERVICE_TABLE的定义如下:清单也包含了结构SERVICE_DESCRIPTOR_TABLE中的定义,为SST数组第四个成员,前两个有着特别的用途。

typedef NTSTATUS (NTAPI  * NTPROC) ( ) ;

typedef NTPROC 
* PNTPROC;

#define  NTPROC_ sizeof (NTPROC)

typedef 
struct  _SYSTEM_SERVICE_TABLE

{ PNTPROC ServiceTable; // 这里是入口指针数组

PDWORD CounterTable; 
// 此处是调用次数计数数组

DWORD ServiceLimit ; 
// 服务入口的个数

PBYTE ArgumentTable; 
// 服务参数字节数的数组

) SYSTEM_SERVICE_TABLE ,

* PSYSTEM_SERVICE_TABLE ,

* * PPSYSTEM_SERVICE_TABLE ;

/ / _ _ _ _ _ _ _ _ _ _ _ _

typedef 
struct _SERVICE_DESCRIPTOR_TABLE

{ SYSTEM_SERVICE_TABLE ntoskrnl ; // ntoskrnl所实现的系统服务,本机的API}

SYSTEM_SERVICE_TABLE win32k; 
// win32k所实现的系统服务

SYSTEM_SERVICE_TABLE Table3; 
// 未使用

SYSTEM_SERVICE_TABLE Table4; 
// 未使用

}
 SERVICE_DESCRIPTOR_TABLE ,

* PSERVICE_DESCRIPTOR_TABLE,

* PPSERVICE_DESCRIPTOR_TABLE ;
ntoskrnl通过KeServiceDescriptorTable符号,导出了主要SDT的一个指针。内核维护另外的一个SDT,就是 KeServiceDescriptorTableShadow。但这个符号没有导出。要想在内核模式组件中存取主要SDT很简单,只需两行C语言的代码:

 

 

extern  PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

PSERVICE_DESCRIPTOR_TABLE psdt
=  KeServiceDescriptorTable;




NTPROC为本机 API的方便的占位符,他类似于Win32编程中的PROC。Native API正常的返回应该是一个NTSTATUS代码,他使用NTAPI调用约定,它和_stdcall一样。ServiceLimit成员有在 ServiceTable数组里找到的入口数目。在2000下,默认值是248。ArgumentTable为BYTEs的数组,每一个对应于 ServiceTable的位置并显示了在调用者堆栈里的参数比特数。这个信息与EDX结合,这是内核从调用者堆栈copy参数到自己的堆栈所需的。 CounterTable成员在free buid的2000中并没有使用到,在debug build中,这个成员指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。
  可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有前四行是最重要的,对应那四个SDT成员。
  运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为内核维护的第二个SDT。主要的区别在于后一个包含了win32k.sys的入口,前一个却没有。在这两个表中,Table3与Table4都是空的。Ntoskrnl.exe提供了一个方便的API函数。这个函数的名字为:

  KeAddSystemServiceTable
此函数去填充这些位置。

2Eh的中断处理标记是KisystemService()。这也是ntoskrnl.exe没有导出的内部的符号,但包含在2k符号文件中。关于KisystemService的操作如下:

1 从当前的线程控制块检索SDT指针

2 决定使用SDT中4个SST的其中一个。通过测试EAX中递送ID的第12和13位来决定。ID在0x0000-0x0fff的映射至ntoskrnl表格,ID在

0x1000与0x1ffff的分配给win32k表格。剩下的0x2000-0x2ffff与

0x3000-0x3ffff则是Table3和Table4保留。

3 通过选定SST中的ServiceLimit成员检查EAX的0-11位。如果ID超过了范围,返回错误代码为STATUS_INVALID_SYSTEM_SERVICE。

4 检查EAX中的参数堆栈指针与MmUserProbeAddress。这是一个ntoskrnl导出的全局变量。通常等于0x7FFF0000,如果参数指针不在这个地址之下,返回STATUS_ACCESS_VIOLATION。

5 查找ArgumentTable中的参数堆栈的字节数,从调用者的堆栈copy所有的参数至当前内核模式堆栈。

6 搜索serviceTable中的服务函数指针,并调用这个函数。

7 控制转到内部的函数KiserviceExit,在此次服务调用返回之后。

 

你可能感兴趣的:(逆向工程)