int 2e 简单说来就是发起一个软件中断,中断号是2e ,具体执行什么功能就要看运行环境,不同的操作系统就不相同,同一个系统也可能因为不同的软件而不同。
在驱动实现的时候,可能需要调用许多的Undocument API函数,例如NtWriteVirtualMemory, ZwCreateProcess 等等,但是这些函数往往都会触及到一些权限问题,今天就具体谈谈调用NtCreateEvent。
 下面是反汇编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                 //今天的主角

0x1f createeventpair
0x20 createfile
0x21 createiocompletion
0x22 createjobobject
0x23 createkey
0x24 createmailslotfile
0x25 createmutant
0x26 createnamedpipefile
0x27 createpagingfile
0x28 createport
0x29 createprocess
0x2a createprofile
0x2b createsection
0x2c createsemaphore
0x2d createsymboliclinkobject
0x2e createthread
0x2f createtimer
0x30 createtoken
0x31 createwaitableport
0x32 delayexecution
0x33 deleteatom
0x34 deletefile
0x35 deletekey
0x36 deleteobjectauditalarm
0x37 deletevaluekey
0x38 deviceiocontrolfile
0x39 displaystring
0x3a duplicateobject
0x3b duplicatetoken
0x3c enumeratekey
0x3d enumeratevaluekey
0x3e extendsection
0x3f filtertoken
0x40 findatom
0x41 flushbuffersfile
0x42 flushinstructioncache
0x43 flushkey
0x44 flushvirtualmemory
0x45 flushwritebuffer
0x46 freeuserphysicalpages
0x47 freevirtualmemory
0x48 fscontrolfile
0x49 getcontextthread
0x4a getdevicepowerstate
0x4b getplugplayevent
0x4c gettickcount
0x4d getwritewatch
0x4e impersonateanonymoustoken
0x4f impersonateclientofport
0x50 impersonatethread
0x51 initializeregistry
0x52 initiatepoweraction
0x53 issystemresumeautomatic
0x54 listenport
0x55 loaddriver
0x56 loadkey
0x57 loadkey2
0x58 lockfile
0x59 lockvirtualmemory
0x5a maketemporaryobject
0x5b mapuserphysicalpages
0x5c mapuserphysicalpagesscatter
0x5d mapviewofsection
0x5e notifychangedirectoryfile
0x5f notifychangekey
0x60 notifychangemultiplekeys
0x61 opendirectoryobject
0x62 openevent
0x63 openeventpair
0x64 openfile
0x65 openiocompletion
0x66 openjobobject
0x67 openkey
0x68 openmutant
0x69 openobjectauditalarm
0x6a openprocess
0x6b openprocesstoken
0x6c opensection
0x6d opensemaphore
0x6e opensymboliclinkobject
0x6f openthread
0x70 openthreadtoken
0x71 opentimer






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,在此次服务调用返回之后。 

int 2E 指令是未公开指令,eax 中存放函数号,EDX中存放函数地址,可以用来调用naked API。

因本人对这方面了解得也不是很多,所以只能简单的说说,就到这吧,大家见笑了~~