数学建模社区-数学中国

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

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

作者:sunwear[E.S.T] shellcoder@163.com* C$ y+ _; |( i6 ] 来源:邪恶八进制 中国

S+ B1 d8 h, G+ |

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

" v V4 H7 x% z& g

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

* \$ F4 m. [! O, Y5 d( j7 N

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

1 a u* V* r4 d/ v! u2 ^& |1 L6 c* Q! I

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 6 [! U$ X+ [/ L# h- |4 w5 A

2 s: N i5 ?2 i7 u. e

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

' E$ n) V( H# p* }

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

6 O" z' S; y" J' y

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

5 V% b( Z! U5 L+ t

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

+ [% w4 S3 i# W2 R" J' a! Z

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

& D5 j; B2 y+ a

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

5 z' x- J3 a1 I" _) Y$ P& x

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

% O# i% _5 q% j/ F9 E& [

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

# x8 D% U- E7 s- ?+ _. g$ E

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

/ B$ v( X; t2 i% w

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

: s7 `0 B& N5 D( ?# v

反编汇这个函数得到:

3 V4 Y$ }+ ~3 }; n) T* A, u

mov eax, 38h

! Y$ B2 [( u& Z( m, B) G7 P( m7 d

lea edx, [esp+4]

( S- i, g) x R3 P: d) S- u

int 2Eh

1 V0 y+ u+ w" e8 p+ F4 _) c

ret 28h

) Q' Q* ^* ^- o2 X- u

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

! ^+ W6 Y2 K6 p" X

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

1 A0 c) U' i- a% s; {* U4 T

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

- M# Q. l- I% x0 l" L2 L

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

* L7 c) ?3 p: b- U L( C- O( f

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

j& x# e/ l3 f, |

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

1 D2 k/ t) @% ^* s( U7 Z6 q

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

' Q( P! S# m2 O1 I, t# Q

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

& t/ P s+ r! x% P' B- ^1 {# ]

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

. q/ k8 Z2 O# P5 N+ N

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

! p# P5 c# c0 N |

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

0 i0 i1 N3 @" n

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

3 t7 |: C* p0 j2 u1 X) S" E4 }

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

# g7 H& D% O$ A$ `

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

- A. `8 ~$ S6 i5 @. z! P3 M( F2 f

typedef NTPROC *PNTPROC;

; s" `- `5 l) R$ U' u6 W

#define NTPROC_ sizeof (NTPROC)

4 l2 u" j8 @7 [( t' R

typedef struct _SYSTEM_SERVICE_TABLE

m( a4 v& n, F; p, |7 y

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

- S0 g% \; R- I

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

: D+ E& [( u4 h1 z

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

0 e# y' c: I$ t+ {3 [% Q

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

+ d% E. Z& V4 j& G8 w, {

) SYSTEM_SERVICE_TABLE ,

# F5 T/ c5 [7 Z1 X# m7 T

* PSYSTEM_SERVICE_TABLE ,

- k+ u# n/ `8 D' P* e/ w# h

* * PPSYSTEM_SERVICE_TABLE ;

% g. z' g; u) b6 d) M9 U; C

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

4 l4 s5 U6 M. V0 q7 T0 \

typedef struct _SERVICE_DESCRIPTOR_TABLE

( X9 e4 M7 ]$ t+ S% y& k

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

' a% B1 g$ A' J [( {: E0 c

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

8 j5 y; N; }3 A' g$ Q0 Z$ w

SYSTEM_SERVICE_TABLE Table3; // 未使用

9 J6 M' l% Q& ~# @5 @

SYSTEM_SERVICE_TABLE Table4; // 未使用

: j% c( W+ a6 s

} SERVICE_DESCRIPTOR_TABLE ,

6 R( d$ r9 L+ i& G" i

* PSERVICE_DESCRIPTOR_TABLE,

3 u- u2 F3 |: u" Z/ C

* PPSERVICE_DESCRIPTOR_TABLE ;

( e$ B: I5 z b9 f5 W

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

1 U& M8 H Q) N" K6 |! v7 O# Q6 U

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

0 F3 m) _% U9 }( V, Y) K4 r

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

! u$ K _4 A+ R7 m& N7 f

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

3 L$ H; v% x) x) A5 F( C" M- \% Q

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

: S5 T+ r. q6 S! V

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

% r6 b# ^0 ~" x' L0 ~" l7 e

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

T7 @. F+ _; d4 P" ?& @

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

" V) D6 R7 W# J) E

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

/ g( p1 N- D; M, {! M4 y: v

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

- }/ n/ F% }" b2 k2 u6 n

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。& [4 H5 Z3 c# d5 W4 g5 H& q 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

0 k2 i) V) W8 l t

前四行是最重要的,对应那四个SDT成员。4 G: k% [# D# C, g4 P3 e9 M 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

& Q2 p6 a' {. G5 f# B9 f, M

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

7 {3 D- t* t( ?/ @7 @

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

5 I5 d3 @3 Q' _# |

KeAddSystemServiceTable 1 z; h) A$ V: g* d: H此函数去填充这些位置。

1 B# Z4 H& A5 K$ d; S; b+ h

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

- z( |1 v' w- S6 R& b8 _

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

+ S o& P$ d% I p

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

# y0 {" T% s3 N9 Z( c ~

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

0 ^; l( _3 ^* Z2 \* k

映射至ntoskrnl表格,ID在

- l2 F1 o* r* K, `# u/ [& K

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

1 y* y( E8 v( G1 p+ b

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

$ X% L! O& y2 G

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

% Z1 r1 K) h: ]+ K% v: ]1 R

STATUS_INVALID_SYSTEM_SERVICE。

+ N- F$ L; N3 @5 T

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

7 d2 h% l' q( R+ V: u( D& h

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

7 {$ \) e6 Y: z4 e: ]

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

$ ~; K$ r. x$ \8 y* Q

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

/ c, S3 [( v# K" F0 x9 N

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

2 C5 R k) Z9 J& l

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

+ l: Y4 r6 I' G7 E7 K3 z/ N

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

! [, k F7 c& Y1 [) H# ]

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

8 T8 k9 J3 m3 l9 _& y$ u

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

7 d V3 ~+ f/ t4 n2 B

0 P$ R h, E) t! ~2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

$ N5 n( N3 Z* X) h

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

A7 k5 g0 l! k% l( q% \

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

! ^$ D' [* d9 c6 A8 X$ v' _

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

7 O# r- a! N! |# h

2 S, J0 s$ x6 E* N/ k' YWindows 2000运行时库

( c! t6 ~6 M; m2 i8 }

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

" Y `) X6 k, I8 t2 M

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

' o$ L: G. D( R( p

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

- d6 E- M+ \; H1 m/ c l

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

4 y/ J" x- ]7 G% _( Z% m

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

/ u5 R; s5 r. X- _! q! F0 l$ y8 Z3 U, K

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

- D" O$ K0 o; y2 @& r+ ~# U( E

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

3 |, `& O, Q4 g

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

. R& i. k. @5 D8 t" `

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

# Z/ D( e# K2 G! E6 ^9 Y. ?+ C0 m: H" R7 A

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

% a: t$ S. F3 T2 t/ t! V0 H! Y

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

! ^! n J0 }3 E* K- M% A

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

% w7 S- ~1 z# g# l/ ?; L

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

' W4 Q; o. i2 A0 z$ P6 M- f 4 n( j0 R. ]) i$ f: `

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

& Q4 L. i) t% d5 m3 b2 B: e$ S

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

3 _/ N) ^. q2 l; v5 a, ?: D

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

1 S# C! |$ @3 {$ d

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

: ~% k" O8 ]# a% [ r& Z! N

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

5 n+ r- f8 q+ d8 P* j9 x& G5 }

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

* O3 L3 o7 C' H9 n

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

5 ~5 o3 W6 C( J& F2 z; g* D- i

常用数据结构

# w. |8 `/ t5 o9 Y: ]! B# k3 n. A2 l

l 整数

' m" T6 W( k" a" }8 _" Y

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

! Y9 s+ w- i- ^! J- [' ^

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

. o4 W) K1 N1 S4 i# k

不同。

* O R' b% ~' z* ^( b1 j

TABLE 2-3. Equivalent Integral Data Types

( O+ U* N Z6 ^4 F! h$ T

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

; W c- H/ Q( x4 H

8 BYTE unsigned char UCHAR CHAR 8 p4 z& E+ w y% g# }, s& A/ l/ T3 X4 n16 WORD unsigned short USHORT WCHAR SHORT

0 N# a3 ~+ e2 w+ h- r% m

32 DWORD unsigned long ULONG LONG

# u+ p2 t( z9 j

32 DWORD unsigned int UINT INT

1 N0 ` y; i6 Y6 p

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

1 I; i* [4 h- i

80 TBYTE N/A

( `/ b8 h Z( e$ l Z3 R

typedef union _LARGE_INTEGER

0 S1 i+ Y' c2 c) e" n

{ struct{

/ U# C. u. T5 l4 U$ v- D

ULONG LowPart;

. F# n1 T' A; p( o ~0 m

LONG HighPart;};

; `2 p: W: g/ T S" w

LONGLONG QuadPart;

' y: d c' m" ~

}

6 U! X" d- O T8 j2 p% i

LARGE_INTEGER , * PULARGE_INTEGER ;

( x8 m' C6 g6 \8 `8 b- |8 c

typedef union _ULARGE_INTEGER{

_" D* v4 Z( b* y2 g1 l

struct{

; }$ Z- t* _9 |1 y1 e- a3 P' @

ULONG LowPart;

3 P$ c1 r' M) U, H. }$ D

ULONG HighPart;}

% O) Y x5 @8 j2 s& G) _7 }- N

ULONGLONG QuadPart;

5 y& p& o& J% Z

}ULARGE_INTEGER, *PULARGE_INTEGER;

0 a* Q" _% A; \% b& w8 T4 T1 l

l 字符

H: P0 R5 B9 c" ^/ A

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

- S) k$ o# i. [/ K$ w" T

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

% _: x3 N% u& L) X& r

typedef struct _UNICODE_STRING{

* D5 r; w1 A0 }

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

- y {, M5 M- K c' m. a

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

& ]$ G, b8 E" L% R$ Y

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

) ]0 o6 |- h2 `. q; [9 d

typedef struct _STRING{

. S: p# T5 F& T: ]2 B7 n

USHORT Length;

7 c9 Y/ J3 E& P5 A9 ?8 f/ I% B

USHORT MaximumLength;

( G/ z$ ^! m8 \7 `

PCHAR Buffer;}STRING, *PSTRING;

, V& `$ w: y, D$ ^( F0 G

typedef STRING ANSI_STRING, *PANSI_STRING;

# v0 C4 z/ }8 g$ X i+ [

typedef STRING OEM_STRING, *POEM_STRING;

8 h* l4 G& }9 D5 v; J) g0 G

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

" W8 t' J, ^9 z" U3 i

RtlCopyUnicodeString()等等

5 X$ K$ z. u- v' z2 f+ e9 E

l 结构

. s' n+ e0 \% ?

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

" C& a. a2 R7 b; ~1 l: g4 y! Q* j

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

; y2 S* q, v0 M! `) v

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

* {' r( I; t2 z, C! G

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

( o. x7 L0 ~8 k5 z3 m

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

0 e) S% v% a, Z/ T% s0 j5 {+ z

typedef struct _OBJECT_ATTRIBUTES

0 ~4 P% r/ q) J4 J7 i

{

! B3 a6 Q$ S$ y+ P, r

ULONG Length;

, v9 W% ~5 C6 d+ F E9 n

HANDLE RootDirectory;

5 Q2 |! o+ J2 s1 I4 k) Y

PUNICODE_STRING ObjectName;

/ x3 m1 R2 ~5 F3 ? h7 H2 s

ULONG Attributes;

) L4 I) @0 |$ y; f4 M. W& @! B

PVOID SecurityDescriptor;

+ t& L, R! Y- D6 t

PVOID SecurityQualityOfService;

7 y' o. \4 [& r) Y ^1 t

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

$ p6 U( b5 W- Q% c& {4 h

typedef struct _IO_STATUS_BLOCK

; }3 \2 D. N; ?6 S

{

/ D+ F `0 x P. n* s

NTSTATDS Status;

- K8 M- V5 g6 S& ]7 \8 ^( k. I m6 ^

ULONG Information;

0 F# N; L4 Q, e( c! N9 c# p

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

8 q P* c4 P/ q( Y8 m

typedef struct _LIST_ENTRY

7 b; T- p' M1 y; Y4 n8 R

{

. l( i% P6 Y' t w' x: A; q( S

Struct _LIST_ENTRY *Flink;

" g8 }) g( z" ]) W0 x

Struct _LIST_ENTRY *Blink;

0 G: I L2 Z# t, n0 }

}LIST_ENTRY, *PLIST_ENTRY;

5 S3 \# }8 B5 Q+ Y! p

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

: @9 N+ A! ]: M' h U0 M: N

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

! ~- e$ e& v4 S# Z) J% y

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

! ^" l8 m* [! m: W$ Q

typedef struct _CLIENT_ID

v8 ~) y2 o, w) ^7 r6 ^( e$ \6 X

{ HANDLE UniqueProcess;

) y1 q! J" l% u- I

HANDLE UniqueThread;

6 P/ Z7 j! D5 b8 b

)CLIENT_ID, *PCLIENT_ID;

C/ A! V9 ~: F* [0 N! `! L( H- ]

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

/ B, j# f0 ~3 C$ t7 H

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

. `7 i" Q+ ?0 i. ^

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

% C. z) T7 Q- }+ |

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

; C/ i3 d) V7 I8 V, B! V/ r# I7 u1 n4 G

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

" p) _7 C5 m; g7 G# H

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

1 L) }2 W" I( [& o

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

0 t' i! n, o' w3 |2 O1 y) d

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

5 U* f. ~: T1 b3 k: l5 e

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

$ i G/ b+ Y6 i

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

& M9 J! x9 R& J5 c9 o9 M' T+ E. s

' L* [" @: N. H+ d% G 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

8 w* H3 T0 F/ W7 \9 }5 X5 T8 H

指导 , s5 Q. ]( O: z) }6 z1 @, X

7 v# T3 R! A5 \" N$ r. a

; ~' H: H! X1 l, {# h/ I. n






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