|
作者:sunwear[E.S.T] shellcoder@163.com
# n& T6 a7 }8 ~" {+ M5 B$ q2 j来源:邪恶八进制 中国 ) ^% \% Z" A% F% c: c
此文只能说是一篇笔记,是关于本机API的.本机API是除了Win32 API,NT平台开放了另一个基本接口。本 ' O8 x9 H+ H; e) r+ W0 V
机API也被很多人所熟悉,因为内核模式模块位于更低的系统级别,在那个级别上环境子系统是不可见的
* O6 b2 z+ o) M4 d. }。尽管如此,并不需要驱动级别去访问这个接口,普通的Win32程序可以在任何时候向下调用本机API。并 - P# M& _8 v: m
没有任何技术上的限制,只不过微软不支持这种应用开发方法。 , c; C! v- K7 Q: V- ~6 U
3 S X# x+ j* f$ K& I
User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.d
* H) _+ ~! C% ^4 a) Z( |. s' D3 q( dll等dll代表了Win32 API的基本提供者。Win32 API中的所有调用最终都转向了ntdll.dll,再由它转发至 9 J) r) r- r1 M6 W5 f
ntoskrnl.exe。ntdll.dll是本机 API用户模式的终端。真正的接口在ntoskrnl.exe里完成。事实上,内
7 ^. ?: e H. k核模式的驱动大部分时间调用这个模块,如果它们请求系统服务。Ntdll.dll的主要作用就是让内核函数
+ w* z1 u0 f- E+ d4 A/ w) n0 A4 [的特定子集可以被用户模式下运行的程序调用。Ntdll.dll通过软件中断int 2Eh进入ntoskrnl.exe,就是
7 Z+ J% t0 e& m9 Z; U0 ^' g通过中断门切换CPU特权级。比如kernel32.dll导出的函数DeviceIoControl()实际上调用ntdll.dll中导
% c7 f% q' {; x# @; J出的NtDeviceIoControlFile(),反汇编一下这个函数可以看到,EAX载入magic数0x38,实际上是系统调 1 j2 i, M' q+ m" N5 ]; E
用号,然后EDX指向堆栈。目标地址是当前堆栈指针ESP+4,所以EDX指向返回地址后面一个,也就是指向
& ~" y" S$ r5 C+ q0 N/ A/ D; K在进入NtDeviceIoControlFile()之前存入堆栈的东西。事实上就是函数的参数。下一个指令是int 2Eh, & r4 Y) L- z7 R7 J- x( p
转到中断描述符表IDT位置0x2E处的中断处理程序。 ! ]+ j: Z* e. m' [& W3 i
反编汇这个函数得到: " i3 J2 `* |# A' V# i$ M6 v
mov eax, 38h
& i1 J( r+ ?- d+ G5 i( B4 D; @lea edx, [esp+4]
$ a7 x7 ~, ^. o( x/ y- K Wint 2Eh
! J; ^6 b9 D& Zret 28h 1 N# d1 {/ L4 S7 `. a# X0 F" I! T
当然int 2E接口不仅仅是简单的API调用调度员,他是从用户模式进入内核模式的main gate。 9 z/ T- l5 w) T u$ A7 S
W2k Native API由248个这么处理的函数组成,比NT 4.0多了37个。可以从ntdll.dll的导出列表中很容易
6 `5 y/ o+ }% H% I/ S认出来:前缀Nt。Ntdll.dll中导出了249个,原因在于NtCurrentTeb()为一个纯用户模式函数,所以不需
- {8 W! ?, }( D& I要传给内核。令人惊奇的是,仅仅Native API的一个子集能够从内核模式调用。而另一方面,
" @2 N3 n% z, o6 M% tntoskrnl.exe导出了两个Nt*符号,它们不存在于ntdll.dll中: NtBuildNumber, NtGlobalFlag。它们不 0 o* F, J. I/ {! E
指向函数,事实上,是指向ntoskrnl.exe的变量,可以被使用C编译器extern关键字的驱动模块导入。
3 J6 ~: h6 x. M6 Z% a& P# r" r) YNtdll.dll和ntoskrnl.exe中都有两种前缀Nt*,Zw*。事实上ntdll.dll中反汇编结果两者是一样的。而在
3 J' e6 O3 ~5 P: Mntoskrnl.exe中,nt前缀指向真正的代码,而zw还是一个int 2Eh的stub。也就是说zw*函数集通过用户模
9 }( {" N. Y7 H; T# e1 N式到内核模式门传递的,而Nt*符号直接指向模式切换以后的代码。Ntdll.dll中的NtCurrentTeb()没有相
9 j6 R* n$ i; N1 H, j# d% t R对应的zw函数。Ntoskrnl并不导出配对的Nt/zw函数。有些函数只以一种方式出现。
* C9 \+ ?5 I. A2Eh中断处理程序把EAX里的值作为查找表中的索引,去找到最终的目标函数。这个表就是系统服务表SST ) U( r$ u; @3 P, ~- i
,C的结构SYSTEM_SERVICE_TABLE的定义如下:清单也包含了结构SERVICE_DESCRIPTOR_TABLE中的定义,为
; N0 }! `! ]2 A- g( QSST数组第四个成员,前两个有着特别的用途。
7 u! e% ]* W, X! p* e- btypedef NTSTATUS (NTAPI *NTPROC) ( ) ; 3 u7 ]6 o% p3 D2 u7 {
typedef NTPROC *PNTPROC;
- _) ^; w% J! g, H7 o9 s% n#define NTPROC_ sizeof (NTPROC)
$ p4 b( f+ f% W9 Y. r/ btypedef struct _SYSTEM_SERVICE_TABLE
1 ?# t, L4 [: w{ PNTPROC ServiceTable; // 这里是入口指针数组 6 Q- v' E4 q- x+ e. p
PDWORD CounterTable; // 此处是调用次数计数数组
( f* a+ C# J, e/ c& t2 nDWORD ServiceLimit ; // 服务入口的个数
' F. `: d5 U. ]PBYTE ArgumentTable; // 服务参数字节数的数组
- D9 i0 U) g9 A) ]9 d( u0 X& K* ?) SYSTEM_SERVICE_TABLE , ) |, N$ G! T- Z8 V- H( I* J( a, R! _
* PSYSTEM_SERVICE_TABLE , , U6 I3 \" t' `7 c2 k( M
* * PPSYSTEM_SERVICE_TABLE ; 1 L( `5 r A$ z- ?
/ / _ _ _ _ _ _ _ _ _ _ _ _
' A8 l. x5 M7 K( n; {2 Gtypedef struct _SERVICE_DESCRIPTOR_TABLE
% G/ T* i% R3 z& Z% [) l' A{ SYSTEM_SERVICE_TABLE ntoskrnl ; // ntoskrnl所实现的系统服务,本机的API} 4 t' @! U+ `* j4 _: N) H4 g7 u6 ~4 W
SYSTEM_SERVICE_TABLE win32k; // win32k所实现的系统服务 , V2 M ]1 V, B9 Z# X1 ~
SYSTEM_SERVICE_TABLE Table3; // 未使用 ' i# p' A; |1 q* ^3 q: n
SYSTEM_SERVICE_TABLE Table4; // 未使用
, G D+ {; d; {$ Q4 Y f+ \} SERVICE_DESCRIPTOR_TABLE ,
- D. m9 \! J4 r4 ~6 S V0 f* PSERVICE_DESCRIPTOR_TABLE,
/ _3 U6 p1 l/ z* PPSERVICE_DESCRIPTOR_TABLE ; " C+ b5 C! k- I W! u8 H" y
ntoskrnl通过KeServiceDescriptorTable符号,导出了主要SDT的一个指针。内核维护另外的一个SDT,就
Q/ b$ W% z$ d4 i3 b8 d: n是KeServiceDescriptorTableShadow。但这个符号没有导出。要想在内核模式组件中存取主要SDT很简单 ' W. M4 m) W6 A
,只需两行C语言的代码: $ Y, U9 C) C: ?% M0 U5 w
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
( i$ g! s( Q+ O& {/ e( @PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;
* D0 l7 D+ @. C. _NTPROC为本机 API的方便的占位符,他类似于Win32编程中的PROC。Native API正常的返回应该是一个
5 ?. o* n* ~$ O( G9 u! i8 B9 n0 SNTSTATUS代码,他使用NTAPI调用约定,它和_stdcall一样。ServiceLimit成员有在ServiceTable数组里
8 A- T% c( d$ o$ A找到的入口数目。在2000下,默认值是248。ArgumentTable为BYTEs的数组,每一个对应于ServiceTable 3 _4 j0 P3 r+ J- P
的位置并显示了在调用者堆栈里的参数比特数。这个信息与EDX结合,这是内核从调用者堆栈copy参数到 # @. t7 I4 n+ z0 [: q& J
自己的堆栈所需的。CounterTable成员在free buid的2000中并没有使用到,在debug build中,这个成员 1 ~$ H5 d7 T/ ~
指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。% O' J1 ]6 k1 L3 {" b
可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有 1 U0 X6 x* s9 P$ B8 K
前四行是最重要的,对应那四个SDT成员。1 c/ h" A8 ^+ d( L/ P7 I
运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为 * s$ ?8 C3 t- C) H' X A* ~
内核维护的第二个SDT。主要的区别在于后一个包含了win32k.sys的入口,前一个却没有。在这两个表中 + g, t$ ~- y$ f8 m4 ^8 F! l" p
,Table3与Table4都是空的。Ntoskrnl.exe提供了一个方便的API函数。这个函数的名字为: " U+ E$ b7 ]! `* v( F, {( P0 w
KeAddSystemServiceTable$ z7 Y. t$ `# Z* ~2 | q
此函数去填充这些位置。 ) O# f } |% K1 [
2Eh的中断处理标记是KisystemService()。这也是ntoskrnl.exe没有导出的内部的符号,但包含在2k符号
! Z/ {. Y. E; G2 i文件中。关于KisystemService的操作如下: 4 L# U# Z O2 [0 d3 V( w5 S
1 从当前的线程控制块检索SDT指针
: E N' v, E/ A8 D' F2 决定使用SDT中4个SST的其中一个。通过测试EAX中递送ID的第12和13位来决定。ID在0x0000-0x0fff的
. K! M4 O8 z8 d) \% s' I映射至ntoskrnl表格,ID在
1 J! X/ R9 D* G) x0 [2 u4 u0x1000与0x1ffff的分配给win32k表格。剩下的0x2000-0x2ffff与
1 R! S' f7 {, O; D0x3000-0x3ffff则是Table3和Table4保留。
# D7 |" W9 q, u' C/ R3 通过选定SST中的ServiceLimit成员检查EAX的0-11位。如果ID超过了范围,返回错误代码为
( d% ~, G5 s. [5 K. K! cSTATUS_INVALID_SYSTEM_SERVICE。 & |* V7 Y! {2 l3 W* \
4 检查EAX中的参数堆栈指针与MmUserProbeAddress。这是一个ntoskrnl导出的全局变量。通常等于
% y( k/ v! i( }9 x1 A9 x( a0x7FFF0000,如果参数指针不在这个地址之下,返回STATUS_ACCESS_VIOLATION。
$ [. X8 E: d% H8 X3 o) J, N5 查找ArgumentTable中的参数堆栈的字节数,从调用者的堆栈copy所有的参数至当前内核模式堆栈。
/ O1 ^' }9 ]# R# v9 b4 I6 搜索serviceTable中的服务函数指针,并调用这个函数。 & p n8 U6 \- [) h7 i9 U8 ~
7 控制转到内部的函数KiserviceExit,在此次服务调用返回之后。 % n& K% G+ B2 U) }+ x& S' C
从对SDT的讨论可以看到与本机API一起还有第二个内核模式接口。这个接口把Win32子系统的图形设备接 ! e6 P" C, r( Y9 K5 I
口和窗口管理器和内核模式组件Win32k连接起来。Win32k接口一样是基于int 2eh。本机API的服务号是从 , \8 o4 r4 Z' {' K# F& a# q& i
0x0000到0x0fff,win32k的服务号是从0x1000到0x1fff。(ddW32pServiceTable认定win32k.sys的符号可
( T* R7 F! U1 l4 J5 I用。)win32k总共包含639个系统服务。 ) y$ o j: D" _9 L! e" y4 `1 }
$ L6 I+ U+ b; w+ a
2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。 " U0 D/ N5 T8 P o! P9 y5 U7 O) R
而是一个与线程相关的指针。显然,线程可以有不同得SDT相关到自身。线程初试化的时 # K3 u( Y" t# ?( R
候,KeInitializeThread()把KeServiceDescriptorTable写到线程的控制块。尽管这样,这个默认设置之
1 [5 E0 i" f d& K/ }# Q- T, m' h$ a后可能被改变为其它值,例如KeServiceDescriptorTableShadow。
: Y. y# I" X. B/ n( F/ c) d
: K3 f% e9 f0 ]Windows 2000运行时库
K# T8 C8 t, H" p UNtdll.dll至少导出了不少于1179个符号。其中的249/248是属于Nt*/zw*集合。所以还有682个函数不是通
7 O/ f% ^+ l& ~ g9 c5 n2 n过int 2eh门中转。很显然,这么多的函数不依靠2k的内核。
# D$ n0 {* V) Q! M其中一些是和c运行时库几乎一样的函数。其实ntoskrnl也实现了一些类似C运行时库的一些函数。可以
; h" V, @2 [/ ]通过ddk里的ntdll.lib来链接和使用这些函数。反汇编ntdll.dll与ntoskrnl.exe的C运行时函数能发现 8 A1 z- b/ E# t% J) f
,ntdll.dll并不是依赖ntoskrnl.exe。这两个模块各自实现了这些函数。 + u- x- p9 Z2 R/ H' e+ L9 b
除了C运行时库外,2000还提供了一个扩展的运行时函数集合。再一次,ntdll.dll与ntoskrnl.exe各自 $ v# v- o( S7 l% _( U0 H3 S' F
实现了它们。同样,实现集合有重复,但是并不完全匹配。这个集合的函数都是以Rtl开头的。2000运行
/ `( u: d/ X- ]' K7 |时库包括一些辅助函数用于C运行时候无法完成的任务。例如有些处理安全事务,另外的操纵2000专用的 7 b$ ^4 @6 O' O) ~" `
数据结构,还有些支持内存管理。微软仅仅在DDK中记录了很有用的406个函数中的115个函数。
" x- ^! N+ n" t Y+ G) }Ntdll.dll还提供了另外一个函数集合,以__e前缀开头。实际上它们用于浮点数模拟器。
$ S, P- s" K$ h还有很多的函数集合,所有这些函数的前缀如下: . c6 a0 B; c3 O) U5 ]* |( R
__e(浮点模拟),Cc(Cache管理),Csr(c/s运行时库),Dbg(调试支持),Ex(执行支持),FsRtl(文件系统运行 7 K/ G$ K5 n! y8 L3 L) r6 }
时),Hal(硬件抽象层),Inbv(系统初试化/vga启动驱动程序bootvid.dll),Init(系统初试
0 ^8 _7 ~- \. j. D* d$ ^; ^6 P, f' d" E; Y7 m
化),Interlocked(线程安全变量操作),Io(IO管理器),Kd(内核调试器支持),Ke(内核例程),Ki(内核中断处 ! }3 m' W5 p: k9 u. ~2 p! D
理),Ldr(映象装载器),Lpc(本地过程调用),Lsa(本地安全授权),Mm(内存管理),Nls(国际化语言支持),Nt
a" S# T' Y5 v: \/ [% t(NT本机API),Ob(对象管理器),Pfx(前缀处理),Po(电源管理),Ps(进程支持),READ_REGISTER_(从寄存器
+ k7 ]# z6 N0 R: ?; E2 G6 Q地址读),Rtl(2k运行时库),Se(安全处理),WRITE_REGISTER_(写寄存器地址),Zw(本机API的替换叫法)
5 O4 |$ u3 g* @8 p, _,<其它>(辅助函数和C运行时库)。 1 E) D/ ]* ~: J- v M: _8 y. }* G
当编写从用户模式通过ntdll.dll或内核模式通过ntoskrnl.exe和2000内核交互的软件的时候,需要处理
* l8 J% Y* \: i/ L5 r9 y9 Q3 a5 p很多基本的数据结构,这些结构在Win32世界中很少见到。 & Z7 p) B- G* ~* e, t0 l
常用数据结构
. ^, I) V: [) R/ {3 sl 整数 , h- F) W+ h0 {
ANSI字符是有符号的,而Unicode WCHAR是无符号的 5 W% I. P3 X. ]0 p+ h5 O
MASM的TBYTE是80位的浮点数,用于高精度浮点运算单元操作,注意它与Win32的TBYTE(text byte)完全 . ~' [6 G( E7 m4 c; D) p6 x
不同。
8 ], w" d }- L7 S/ q: I; d& U0 STABLE 2-3. Equivalent Integral Data Types - [$ \, h. g( O7 M, R C. `
BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED % Y2 S e2 b% r2 p# L, M- h
8 BYTE unsigned char UCHAR CHAR& y# T! M5 C/ F6 C7 `
16 WORD unsigned short USHORT WCHAR SHORT
2 p$ Q& \$ V" u/ [9 M32 DWORD unsigned long ULONG LONG
7 V4 l: A/ S2 x; O. _32 DWORD unsigned int UINT INT
9 \( G3 Y! y0 p Q: k7 G) x64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG ( b7 x0 D8 ~0 [! Y
80 TBYTE N/A + {- ?6 Y K# J( |! o
typedef union _LARGE_INTEGER
: w4 u% A7 n4 u{ struct{ 4 m6 C) o: N% D% V7 ~: w
ULONG LowPart; 4 h. ~/ p* {8 T) M1 t4 C
LONG HighPart;};
' P' r/ x0 B8 n% h( r0 ~9 FLONGLONG QuadPart; ' l+ |" v) Z2 ~% o+ U4 X8 Y' A
}
1 U1 _9 x# Y/ [& U+ J3 z, B8 pLARGE_INTEGER , * PULARGE_INTEGER ; ) a7 c6 z+ E' S9 _; `
typedef union _ULARGE_INTEGER{ + X9 O( k- V. o4 g- i) Q( w' ^# j h
struct{ & \ [2 q1 A+ k" Q! C' W1 h) `
ULONG LowPart; # v8 S9 c- Q0 u O1 J
ULONG HighPart;} 8 p6 \8 S% ]$ z/ ~
ULONGLONG QuadPart; 7 G& P9 \' L+ w& P0 b! {
}ULARGE_INTEGER, *PULARGE_INTEGER;
- |) K- S7 z. n& I7 K/ ~l 字符
' [1 P0 b m+ Y! C Win32编程中PSTR用户CHAR*,PWSTR用于WCHAR*。取决于是否定义了UNICODE,PTSTR解释为PSTR或者
- R7 o% U0 U7 w9 T$ z! `# w! YPWSTR。在2k内核模式下,常用的数据类型是UNICODE_STRING,而STRING用来表示ANSI字符串: . M; k2 V5 W! G9 L8 {. _
typedef struct _UNICODE_STRING{
5 F0 f. k+ ^6 ?1 {1 N7 A1 RUSHORT Length; //当前字节长度,不是字符!!!
( j2 m: ?# E# T, ?6 zUSHORT MaximumLength; //Buffer的最大字节长度 3 ]# K6 A) S: p% X+ D
PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;
5 X0 _! D" B6 X [8 l, S# M9 Q; Q; Gtypedef struct _STRING{
1 m- n; k4 g: M2 Y+ ~) @USHORT Length; 4 p) y, A$ O5 E# R5 n! Z, \. }: }
USHORT MaximumLength; " B7 y" L( |, o: O' m! I/ D
PCHAR Buffer;}STRING, *PSTRING; ! c! e- _$ G! o; i0 ~" G+ L# L0 p
typedef STRING ANSI_STRING, *PANSI_STRING;
1 C0 Z- S; k+ o, t) D. U6 n) mtypedef STRING OEM_STRING, *POEM_STRING;
; a+ G$ x: q V# E6 P K操纵函数:RtlCreatUnicodeString(),RtlInitUnicodeString(),
* T- b, G w* WRtlCopyUnicodeString()等等 7 V& i. x0 {: `7 T/ E3 k
l 结构 " j% e( F* c, m" e( @+ c/ i; ?
许多内核API函数需要一个固定大小的OBJECT_ATTRIBUTES结构,比如NtOpenFile()。对象的属性是OBJ_*
+ x5 Q- `# h! d$ v$ [值的组合,可以从ntdef.h中查到。
3 J0 |5 h) Z% Q. |0 E( }3 [2 ZIO_STATUS_BLOCK结构提供了所请求操作结果的信息,很简单,status成员包含一个NTSTATUS代码, 如果
5 V: D( X: P$ Z操作成功 information成员提供特定请求的信息。
. p: J' z3 C, k- o1 T; W还有一个结构是LIST_ENTRY,这是一个双向环链表。 : o; {: x, M# V1 y _) `! x
typedef struct _OBJECT_ATTRIBUTES
! `- k. ~9 T/ |% Y& H2 q x{
% N# U1 K: e: T/ X3 l# d$ c ~4 Z2 `ULONG Length;
! D1 s3 }: u2 I; b5 l' p. dHANDLE RootDirectory; , _% N n+ N1 O) a
PUNICODE_STRING ObjectName; - N; z$ M2 _: |
ULONG Attributes;
/ r+ o/ |7 ]+ ~; |! }$ _/ ?9 gPVOID SecurityDescriptor; 8 y/ M, ?. C: y/ N, d, A
PVOID SecurityQualityOfService; ( d) H0 p+ v: `1 }- n
} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES; * L; S, D4 e! w0 M9 z7 F
typedef struct _IO_STATUS_BLOCK
7 M: E- z' r- |; J; A1 y" a% N( K1 I{
, e. f' C& R' Q5 e! UNTSTATDS Status;
6 Z& r/ N2 Q' E) E- N$ W9 RULONG Information; , Y8 J# @, V6 @
}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;
0 d+ @' A7 b4 Otypedef struct _LIST_ENTRY : O! D6 @, o$ T% ~6 N! B
{
7 l5 ]) o6 o* X/ L$ H: D# wStruct _LIST_ENTRY *Flink; 2 W6 v$ x' Q! {1 T& h
Struct _LIST_ENTRY *Blink; ( h% T+ k" S4 X: M. }. {
}LIST_ENTRY, *PLIST_ENTRY;
6 ~- ?* l6 ~1 z3 l' D1 t双向链表的典型例子就是进程和线程链。内部变量PsActiveProcessHead是一个LIST_ENTRY结构,在
) h( s4 Y6 D# ^+ ]9 D, T0 }ntoskrnl.exe的数据段中,指定了系统进程列表的第一个成员。 ( `; n6 G0 O8 Q, g
CLIENT_ID结构由进程和线程ID组成。 / d( v: N7 q) F0 P0 v+ n$ Y
typedef struct _CLIENT_ID
' O, Y# D- U9 L! O+ K{ HANDLE UniqueProcess; ) ~2 R/ Z$ u% N& v) Y8 x
HANDLE UniqueThread; ! c) M; h3 b- i3 n
)CLIENT_ID, *PCLIENT_ID; 1 z/ j) p4 o# r4 V& {7 j
想要从用户模式调用ntdll.dll中的API函数,必须考虑到以下四点: 1 k4 [: o8 E# ~3 x
1 SDK头文件没有包括这些函数的原型 9 K& t; t! r0 q/ C
2 这些函数使用的若干基本数据类型没有包括在SDK文件中
* }5 g2 k* ]+ r, X3 A3 J- e+ T1 R3 SDK和DDK头文件不兼容,不能在win32的c源文件包含ntddk.h中
; o7 P& {" v6 B, a9 R ?5 k8 m4 ntdll.lib没有包括在VC的默认导入库列表中。 7 Y/ A+ d: a) k- E
第4个很容易解决:#progma comment(linker,“/defaultlib:ntdll.lib”)
4 B0 M5 w/ X. t缺失的定义比较难解决,最简单的方法是写一个自定义的头文件,刚刚包含需要调用ntdll.dll中函数的 - {8 h, a" I0 ^) s9 O
定义。幸运的是,已经在光盘的w2k_def.h文件中做了这个工作。因为这个头文件将用于用户模式和内核 ( d7 e/ ?. Y5 b' |# n+ v
模式程序,所以必须在用户模式代码中,#include<w2k_def.h>之前#define _USER_MODE_,使得DDK中出
2 ^& l9 j3 L& E2 E( L" C' B f: z现而SDK中没有的定义可用。 + `# P' ?9 O4 ~, ^
3 }& y5 w7 D1 T2 v. c" u& c
本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的
5 s" g& A- J; j* s9 o指导
# W; a# w. D2 U* l0 P/ I8 Y. R$ ~5 Z 2 x d4 n4 @4 R. d+ [, O
7 o8 J5 ^9 F! P: I# N: B2 s
|