数学建模社区-数学中国

标题: 浅析本机API [打印本页]

作者: 韩冰    时间: 2005-1-23 13:27
标题: 浅析本机API

作者:sunwear[E.S.T] shellcoder@163.com . r2 {; w& Y+ [来源:邪恶八进制 中国

" e3 z0 w/ a. c+ {

此文只能说是一篇笔记,是关于本机API的.本机API是除了Win32 API,NT平台开放了另一个基本接口。本

5 k: E" Z* u2 I

机API也被很多人所熟悉,因为内核模式模块位于更低的系统级别,在那个级别上环境子系统是不可见的

9 ~' @. E9 t* M- h8 J9 B

。尽管如此,并不需要驱动级别去访问这个接口,普通的Win32程序可以在任何时候向下调用本机API。并

7 \3 V; \- O. _! ]

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 2 ?" k o2 \* v0 l* F

% ~. N: E& f* d) f. U

User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.d

% [# t) g/ i% ~7 y* Y1 L

ll等dll代表了Win32 API的基本提供者。Win32 API中的所有调用最终都转向了ntdll.dll,再由它转发至

2 Q$ R" k" y2 y/ T+ V, f

ntoskrnl.exe。ntdll.dll是本机 API用户模式的终端。真正的接口在ntoskrnl.exe里完成。事实上,内

4 d. Q$ F% F3 C

核模式的驱动大部分时间调用这个模块,如果它们请求系统服务。Ntdll.dll的主要作用就是让内核函数

+ K3 C3 ~3 z2 q- a, P

的特定子集可以被用户模式下运行的程序调用。Ntdll.dll通过软件中断int 2Eh进入ntoskrnl.exe,就是

Z! g: R* [5 F; R# O4 ~

通过中断门切换CPU特权级。比如kernel32.dll导出的函数DeviceIoControl()实际上调用ntdll.dll中导

* I) }4 \' J* e7 D r

出的NtDeviceIoControlFile(),反汇编一下这个函数可以看到,EAX载入magic数0x38,实际上是系统调

8 P+ |% P3 J3 N. n: K8 A0 V

用号,然后EDX指向堆栈。目标地址是当前堆栈指针ESP+4,所以EDX指向返回地址后面一个,也就是指向

9 k( n9 f7 L# U8 k

在进入NtDeviceIoControlFile()之前存入堆栈的东西。事实上就是函数的参数。下一个指令是int 2Eh,

. ^; Y* l' {- B5 D+ q; Z6 B

转到中断描述符表IDT位置0x2E处的中断处理程序。

4 r- L: d1 a/ w6 s5 a

反编汇这个函数得到:

" B! P6 Z7 x1 ] d6 D1 B5 Z

mov eax, 38h

. V: D* i6 L) y1 v8 o

lea edx, [esp+4]

$ ?7 x& k1 E+ B* h. p0 O9 A7 W3 y

int 2Eh

# } Z0 d o2 E4 S! _! ?

ret 28h

. s6 D, a. f$ z1 B `

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

& u$ r) z2 [' h7 b* b/ C

W2k Native API由248个这么处理的函数组成,比NT 4.0多了37个。可以从ntdll.dll的导出列表中很容易

1 O0 v/ K9 K5 Y9 F" k4 \/ R2 `

认出来:前缀Nt。Ntdll.dll中导出了249个,原因在于NtCurrentTeb()为一个纯用户模式函数,所以不需

" H, C6 D( w- X

要传给内核。令人惊奇的是,仅仅Native API的一个子集能够从内核模式调用。而另一方面,

, x* q0 k* g4 \% ~

ntoskrnl.exe导出了两个Nt*符号,它们不存在于ntdll.dll中: NtBuildNumber, NtGlobalFlag。它们不

+ w* _: P( ^$ K, V$ [; D

指向函数,事实上,是指向ntoskrnl.exe的变量,可以被使用C编译器extern关键字的驱动模块导入。

6 j3 N9 P; v& y: [ k! ]

Ntdll.dll和ntoskrnl.exe中都有两种前缀Nt*,Zw*。事实上ntdll.dll中反汇编结果两者是一样的。而在

0 f% J3 E7 u% {" x- z. o

ntoskrnl.exe中,nt前缀指向真正的代码,而zw还是一个int 2Eh的stub。也就是说zw*函数集通过用户模

5 P$ D' J7 @9 Y5 m# e

式到内核模式门传递的,而Nt*符号直接指向模式切换以后的代码。Ntdll.dll中的NtCurrentTeb()没有相

8 y" ~1 R U8 P% w; F

对应的zw函数。Ntoskrnl并不导出配对的Nt/zw函数。有些函数只以一种方式出现。

" N; x% [ I) x4 f6 u

2Eh中断处理程序把EAX里的值作为查找表中的索引,去找到最终的目标函数。这个表就是系统服务表SST

4 {; I1 u3 ~% ~7 [/ i S

,C的结构SYSTEM_SERVICE_TABLE的定义如下:清单也包含了结构SERVICE_DESCRIPTOR_TABLE中的定义,为

: p2 X3 X0 J3 V7 F

SST数组第四个成员,前两个有着特别的用途。

8 q# ^1 I% e" L) e7 f# ^ o5 ~ e

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

, a& I3 D# q& u0 w, t, ]2 Z

typedef NTPROC *PNTPROC;

4 _" t* \" j; j# [

#define NTPROC_ sizeof (NTPROC)

# M: R: n% W4 I+ B2 a) Z

typedef struct _SYSTEM_SERVICE_TABLE

! j6 X( M+ C6 z1 Y+ f4 W% E

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

4 V. ?- z0 A4 h+ i* |

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

$ r" ]$ T; ?; `; x/ D' u

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

- v" B- ]# D# l/ z3 c) ^ {5 ~

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

) d# C" q# @ A6 l$ Y9 n

) SYSTEM_SERVICE_TABLE ,

6 S$ B# K/ e. w

* PSYSTEM_SERVICE_TABLE ,

$ n* q* V3 d3 S4 ]9 Z, g

* * PPSYSTEM_SERVICE_TABLE ;

' P$ S. l3 J+ D0 v5 L" ]

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

s W* [ V5 D0 T# R; O* O

typedef struct _SERVICE_DESCRIPTOR_TABLE

, K P) _- p7 J a% t$ O4 M( {' a

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

# w/ c" O: \+ P( e+ Q- h

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

$ v) `% p# X8 @( `9 O4 Y' U

SYSTEM_SERVICE_TABLE Table3; // 未使用

9 F3 v+ D! ]$ x7 f* }; n

SYSTEM_SERVICE_TABLE Table4; // 未使用

% q9 T0 r2 U. U. s8 c( h. Q8 D( Q

} SERVICE_DESCRIPTOR_TABLE ,

" V. ~: \* T& }5 G2 h N t0 D

* PSERVICE_DESCRIPTOR_TABLE,

' R/ [& _9 k k2 D) M. K

* PPSERVICE_DESCRIPTOR_TABLE ;

3 s: K0 E% D8 ]# ~4 |4 d

ntoskrnl通过KeServiceDescriptorTable符号,导出了主要SDT的一个指针。内核维护另外的一个SDT,就

, d6 N3 r2 |1 a6 G- j9 r4 K/ I: w

是KeServiceDescriptorTableShadow。但这个符号没有导出。要想在内核模式组件中存取主要SDT很简单

# ~5 ^6 k3 i# E5 `8 D' t8 e

,只需两行C语言的代码:

2 a( Q$ }0 ^& m/ ~0 O1 @

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

: d( O \; s7 B8 l

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

4 }. z" @- f- D, n8 l ]! K

NTPROC为本机 API的方便的占位符,他类似于Win32编程中的PROC。Native API正常的返回应该是一个

5 \$ s& f! B) R T9 ~

NTSTATUS代码,他使用NTAPI调用约定,它和_stdcall一样。ServiceLimit成员有在ServiceTable数组里

3 r3 c; T- X7 `; b& m& j

找到的入口数目。在2000下,默认值是248。ArgumentTable为BYTEs的数组,每一个对应于ServiceTable

6 e3 |( [/ b5 D$ @. ^5 u' W

的位置并显示了在调用者堆栈里的参数比特数。这个信息与EDX结合,这是内核从调用者堆栈copy参数到

. y& M$ S8 C, D# ~2 H- F* D

自己的堆栈所需的。CounterTable成员在free buid的2000中并没有使用到,在debug build中,这个成员

6 a$ p+ l- u8 e! T9 k0 G7 Z8 q, x

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 * t' `) F4 B6 x) d- o; { 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

E3 p3 R4 j6 [) \2 r

前四行是最重要的,对应那四个SDT成员。' U" s: s) y! |& T x1 k 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

* j9 O; n" }0 w0 Z5 @7 l

内核维护的第二个SDT。主要的区别在于后一个包含了win32k.sys的入口,前一个却没有。在这两个表中

9 o2 t2 j& c9 A c

,Table3与Table4都是空的。Ntoskrnl.exe提供了一个方便的API函数。这个函数的名字为:

# [' h- J$ {. b' s: Z4 M" K+ D! j

KeAddSystemServiceTable - ~, k$ Y+ L5 j1 O# u, K5 l此函数去填充这些位置。

! H) k* X& a2 V W% B

2Eh的中断处理标记是KisystemService()。这也是ntoskrnl.exe没有导出的内部的符号,但包含在2k符号

/ r6 B& F" d! J$ L$ a9 C$ g

文件中。关于KisystemService的操作如下:

5 B7 Y, [$ C4 e

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

5 s7 S# h; b+ C

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

- j( s) B) N$ d& }. d

映射至ntoskrnl表格,ID在

" u9 V8 ~) C) l* N& T

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

6 v) k3 l4 |' }( y/ M% \

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

9 W; r4 `! k& e

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

4 x( _) d( h% L- y

STATUS_INVALID_SYSTEM_SERVICE。

9 Z8 g9 j2 o" p4 ^ o$ y

4 检查EAX中的参数堆栈指针与MmUserProbeAddress。这是一个ntoskrnl导出的全局变量。通常等于

1 O: a6 I5 k" W7 C1 R+ Z

0x7FFF0000,如果参数指针不在这个地址之下,返回STATUS_ACCESS_VIOLATION。

8 _2 Y0 q' [9 S2 v. \1 P" E- N" u

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

. m6 t) v! [; R# b2 W

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

; i, W( o, L; r. x& D

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

; d. z. j* }1 i. d- O. l3 H6 Y0 P

从对SDT的讨论可以看到与本机API一起还有第二个内核模式接口。这个接口把Win32子系统的图形设备接

. i. J6 W: E c# J: ~* M9 p

口和窗口管理器和内核模式组件Win32k连接起来。Win32k接口一样是基于int 2eh。本机API的服务号是从

- ]* m( ~$ c- O( |( J0 }

0x0000到0x0fff,win32k的服务号是从0x1000到0x1fff。(ddW32pServiceTable认定win32k.sys的符号可

; d5 k4 W4 o7 S+ P! b; M1 s

用。)win32k总共包含639个系统服务

8 t6 W* @4 h7 S6 y9 ^* s

, Z- {' q$ _6 R5 p' E6 v+ A" k+ [2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

& t8 T9 j% M3 F

而是一个与线程相关的指针。显然,线程可以有不同得SDT相关到自身。线程初试化的时

; A/ q; Y' C+ k4 [# r

候,KeInitializeThread()把KeServiceDescriptorTable写到线程的控制块。尽管这样,这个默认设置之

1 j' b) ?8 y2 e* F' T

后可能被改变为其它值,例如KeServiceDescriptorTableShadow。

! Y/ K( B. e$ K1 }8 h7 H% w/ A

t" K/ t2 B# G9 u2 Z3 r" R: S Windows 2000运行时库

6 L7 a6 w4 T" f4 y

Ntdll.dll至少导出了不少于1179个符号。其中的249/248是属于Nt*/zw*集合。所以还有682个函数不是通

8 c6 B! W' Z- J7 V

过int 2eh门中转。很显然,这么多的函数不依靠2k的内核。

i" e, w& V: `! N9 V& z4 S8 v

其中一些是和c运行时库几乎一样的函数。其实ntoskrnl也实现了一些类似C运行时库的一些函数。可以

* v) e; \$ F t1 O' b7 _/ p

通过ddk里的ntdll.lib来链接和使用这些函数。反汇编ntdll.dll与ntoskrnl.exe的C运行时函数能发现

7 n& f4 m, C0 U; E/ `. b$ t

,ntdll.dll并不是依赖ntoskrnl.exe。这两个模块各自实现了这些函数。

4 n- j9 ~% o1 {9 D5 R- a

除了C运行时库外,2000还提供了一个扩展的运行时函数集合。再一次,ntdll.dll与ntoskrnl.exe各自

- _- X8 K7 V6 Z: x

实现了它们。同样,实现集合有重复,但是并不完全匹配。这个集合的函数都是以Rtl开头的。2000运行

, R$ c, @# [ r$ ]+ J$ C5 c+ R

时库包括一些辅助函数用于C运行时候无法完成的任务。例如有些处理安全事务,另外的操纵2000专用的

* ?1 A" T, T/ F$ L$ Q) S

数据结构,还有些支持内存管理。微软仅仅在DDK中记录了很有用的406个函数中的115个函数。

3 f$ |& H$ W0 W3 I; U5 Y6 i

Ntdll.dll还提供了另外一个函数集合,以__e前缀开头。实际上它们用于浮点数模拟器。

1 P# K/ c5 Z8 @: z, k+ u! t# r

还有很多的函数集合,所有这些函数的前缀如下:

9 s3 b! U' b- s; G2 k

__e(浮点模拟),Cc(Cache管理),Csr(c/s运行时库),Dbg(调试支持),Ex(执行支持),FsRtl(文件系统运行

" z) s- ?( x! ]; ^% b# K3 G5 E

时),Hal(硬件抽象层),Inbv(系统初试化/vga启动驱动程序bootvid.dll),Init(系统初试

0 l6 q6 S4 q7 _ t7 a U% `( A7 a4 D* a2 O/ p& b! R

化),Interlocked(线程安全变量操作),Io(IO管理器),Kd(内核调试器支持),Ke(内核例程),Ki(内核中断处

# A# F. ~: T9 F# a! P5 X

理),Ldr(映象装载器),Lpc(本地过程调用),Lsa(本地安全授权),Mm(内存管理),Nls(国际化语言支持),Nt

1 V, \6 @' v" F+ e, c/ Q

(NT本机API),Ob(对象管理器),Pfx(前缀处理),Po(电源管理),Ps(进程支持),READ_REGISTER_(从寄存器

/ O# m/ P& ]1 D# z8 [' ?$ Y

地址读),Rtl(2k运行时库),Se(安全处理),WRITE_REGISTER_(写寄存器地址),Zw(本机API的替换叫法)

B* [+ f2 {! I k. J5 `

,<其它>(辅助函数和C运行时库)。

8 Z3 I2 R1 h- L5 B

当编写从用户模式通过ntdll.dll或内核模式通过ntoskrnl.exe和2000内核交互的软件的时候,需要处理

$ C p, j! }) G

很多基本的数据结构,这些结构在Win32世界中很少见到。

/ q: W9 R/ }8 M7 _3 C

常用数据结构

0 o/ `1 L& t4 {: j9 f4 [

l 整数

2 v3 R5 \# K% F: d% d1 Z

ANSI字符是有符号的,而Unicode WCHAR是无符号的

/ c% s' V! s: q9 V) i2 ~0 Z

MASM的TBYTE是80位的浮点数,用于高精度浮点运算单元操作,注意它与Win32的TBYTE(text byte)完全

+ K2 g2 Q1 ^! `0 Z& C

不同。

( [* {) ]7 u' ^6 x' y

TABLE 2-3. Equivalent Integral Data Types

4 a, \4 ?( o. i" o

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

1 T) U4 w. x1 L3 W) z3 y/ \

8 BYTE unsigned char UCHAR CHAR o8 Z3 {/ r) Y" @4 E8 i 16 WORD unsigned short USHORT WCHAR SHORT

+ L. a8 r+ o( B7 v# {

32 DWORD unsigned long ULONG LONG

1 F1 _# E2 H- A8 [$ p

32 DWORD unsigned int UINT INT

$ u& `! ?% _0 g

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

* ?2 X" u: `; b/ k }0 C+ |6 m, H$ E8 Q

80 TBYTE N/A

9 Y Z u$ y& @

typedef union _LARGE_INTEGER

; v% v4 u9 k' u# l5 ]

{ struct{

2 Q5 q' B! z; `) \: h3 J9 F

ULONG LowPart;

+ P1 p O$ m- d

LONG HighPart;};

% b' S/ |3 |) E+ V+ j

LONGLONG QuadPart;

# [9 ]3 y- Q8 ~* Q% B/ y6 a( m

}

* f) E8 T9 C) [) D4 g3 Y' s

LARGE_INTEGER , * PULARGE_INTEGER ;

; |$ q/ P) _; P. P

typedef union _ULARGE_INTEGER{

* q" ~& q5 _ R" ]& k1 I

struct{

: T9 W% }8 S0 P, c' b8 i

ULONG LowPart;

4 E+ c8 V" y" ]+ D6 I _

ULONG HighPart;}

8 I9 r) s+ `6 i+ ^

ULONGLONG QuadPart;

- z; z/ v7 v, b$ H. p2 E4 m

}ULARGE_INTEGER, *PULARGE_INTEGER;

# t# A8 ^( a9 ^/ n4 B$ ^' G

l 字符

: ^! y6 b; j( i

Win32编程中PSTR用户CHAR*,PWSTR用于WCHAR*。取决于是否定义了UNICODE,PTSTR解释为PSTR或者

4 ^" q7 w3 V! F) i. |- w" D

PWSTR。在2k内核模式下,常用的数据类型是UNICODE_STRING,而STRING用来表示ANSI字符串:

$ J9 z H7 i& H( P

typedef struct _UNICODE_STRING{

- U) B3 I( V3 K. R

USHORT Length; //当前字节长度,不是字符!!!

" A4 }' u7 Q1 u* ]6 }& w4 L6 G1 b

USHORT MaximumLength; //Buffer的最大字节长度

- B9 v. t9 R. f- m* X

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

3 E4 c2 j7 F' k. b& T5 a7 g# s

typedef struct _STRING{

p# c& s0 F5 G) m3 ^9 |6 t& Z9 `1 U

USHORT Length;

* I- w; |: c$ X! l+ h1 ~: S

USHORT MaximumLength;

2 w( ~ L" X& H: o& q

PCHAR Buffer;}STRING, *PSTRING;

& j- m: F# T# m9 x3 K8 n

typedef STRING ANSI_STRING, *PANSI_STRING;

B$ A1 a1 a% U6 r0 y" b5 B0 M/ l

typedef STRING OEM_STRING, *POEM_STRING;

M) {: a6 \) z1 d1 q) m% w, W

操纵函数:RtlCreatUnicodeString(),RtlInitUnicodeString(),

2 `) e0 R; F0 [1 U l- y2 F

RtlCopyUnicodeString()等等

0 H n+ {$ s2 M2 j

l 结构

( ~9 B0 b" g9 U4 j( b8 r) L

许多内核API函数需要一个固定大小的OBJECT_ATTRIBUTES结构,比如NtOpenFile()。对象的属性是OBJ_*

% L) m! b6 d5 Q; I: g ~& ~$ w

值的组合,可以从ntdef.h中查到。

8 A# A/ Y6 J; H" D# U

IO_STATUS_BLOCK结构提供了所请求操作结果的信息,很简单,status成员包含一个NTSTATUS代码, 如果

) y6 o1 U( m9 j

操作成功 information成员提供特定请求的信息。

/ g& J/ x$ r! {2 R9 c3 i% s

还有一个结构是LIST_ENTRY,这是一个双向环链表。

* v& M3 _2 m3 @5 n# [; R X, q

typedef struct _OBJECT_ATTRIBUTES

; Q! r; |) v% ^; `1 b* c: g o

{

, b( @3 f S6 s) [ o/ H5 y: |9 z6 F

ULONG Length;

4 p% d5 }- {1 i+ Z( `

HANDLE RootDirectory;

$ q+ }/ X9 n; W4 O0 t2 T/ ?

PUNICODE_STRING ObjectName;

3 [6 F( ^" y* ]" v" R

ULONG Attributes;

" z8 d: ]: v* D6 N+ I A4 E. F

PVOID SecurityDescriptor;

2 C! D+ R, N! I; D! h6 K) f$ s

PVOID SecurityQualityOfService;

$ r b! L. W: n& v5 N4 B

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

. y. B- o- _- ~( b% S0 G

typedef struct _IO_STATUS_BLOCK

5 K9 } ` x# a, p1 [7 k

{

# C% Q1 X- U L X6 L

NTSTATDS Status;

d& a* e5 _) C- l

ULONG Information;

6 H% X' E3 ?/ n$ ? T' Y# f

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

; C& N$ V5 Q) V. p* [

typedef struct _LIST_ENTRY

9 X: i5 x8 \) L% ]7 f- m5 g" L/ i

{

: S, f$ u' H1 _0 ~1 s

Struct _LIST_ENTRY *Flink;

. I) e. H- g- N* f

Struct _LIST_ENTRY *Blink;

- U& F B! ]9 U+ r, J7 S: P* `

}LIST_ENTRY, *PLIST_ENTRY;

5 U$ X1 |3 `" `" j5 h

双向链表的典型例子就是进程和线程链。内部变量PsActiveProcessHead是一个LIST_ENTRY结构,在

3 }+ B2 N& J; R$ m k6 a* I% `

ntoskrnl.exe的数据段中,指定了系统进程列表的第一个成员。

0 ?) j. r/ ~ z$ y

CLIENT_ID结构由进程和线程ID组成。

$ W4 }7 T! ?7 S4 M

typedef struct _CLIENT_ID

% O- A" I. z, z- ~& W3 J7 z' z6 V. V

{ HANDLE UniqueProcess;

5 }: I* _3 y- `+ v: D* B: U, D

HANDLE UniqueThread;

+ ^/ Q& T3 C+ F& a8 b& Z& W! @

)CLIENT_ID, *PCLIENT_ID;

8 L& ?/ Y" L' X' f

想要从用户模式调用ntdll.dll中的API函数,必须考虑到以下四点:

/ v3 V: \6 b: x# k

1 SDK头文件没有包括这些函数的原型

! g7 P5 O2 R' o) ^" L0 a% C) ~

2 这些函数使用的若干基本数据类型没有包括在SDK文件中

2 ^7 E5 j/ B1 t# {1 W( S+ [

3 SDK和DDK头文件不兼容,不能在win32的c源文件包含ntddk.h中

8 K- w N: \2 U' Y/ z; D3 v

4 ntdll.lib没有包括在VC的默认导入库列表中。

5 D8 T6 v0 z5 l: a+ x" m( }- {5 z

第4个很容易解决:#progma comment(linker,“/defaultlib:ntdll.lib”)

8 K0 e! l- ?, \/ q# `7 e9 m5 l4 C

缺失的定义比较难解决,最简单的方法是写一个自定义的头文件,刚刚包含需要调用ntdll.dll中函数的

/ Q+ u6 o: [4 q/ h* }

定义。幸运的是,已经在光盘的w2k_def.h文件中做了这个工作。因为这个头文件将用于用户模式和内核

+ k; b1 V* a) y$ {5 r

模式程序,所以必须在用户模式代码中,#include<w2k_def.h>之前#define _USER_MODE_,使得DDK中出

9 L' Q) u8 @. s8 I4 t9 e) L

现而SDK中没有的定义可用。

4 Z2 R1 Z( U, k2 a$ b; O

) n' y$ F6 M+ o 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

, c+ y3 _; y- |# q; K8 w! M8 |

指导0 N( F9 _, g- C2 u# x8 G2 t W

3 y2 ]4 c" t3 o; k, F M% l4 p

% S' Q% a- S+ n% I, A/ h






欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5