数学建模社区-数学中国

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

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

作者:sunwear[E.S.T] shellcoder@163.com - k; ^( `! B: H6 D5 c6 Q2 ]6 w来源:邪恶八进制 中国

- h3 h) Z3 A/ K3 O

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

6 i/ H) N0 }, C% L) |9 D$ @

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

8 t D( a P6 U5 {& P3 r

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

d) l# v: Y" M, j( m. z$ l1 u

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 - J# @5 I* h) ^

$ p/ `! X" p# v2 s& T' g+ y g# x, H

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

[: ^, K3 @ k

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

' v2 t- H. v# s

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

2 E: l7 h$ W) I

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

& q" M! X3 ?, [/ p7 x; B, e

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

K' j4 C- G* }; L7 T/ ]

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

" y) _8 O4 R9 e" F

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

1 i. C y5 _! s. L# H" L

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

5 M3 R) x# g) O0 P

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

2 E7 u$ ?) C7 f! Y2 P9 h- s4 d

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

} {+ W0 f$ P9 D# ~2 N

反编汇这个函数得到:

/ ` e. L4 Q0 X0 r, I7 g

mov eax, 38h

3 |( B4 r0 O+ d' Z1 T

lea edx, [esp+4]

) ]% w C; m! ~/ y ]

int 2Eh

3 A9 j, I: ^# ]- m: [1 y) Q3 W

ret 28h

4 d8 N* g& z% s% d9 @0 x" n, q! c

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

" ]8 |5 o8 I2 m$ Y) b0 c! f- r

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

: F4 `7 K1 m( z1 l6 f4 J0 b _

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

4 i7 T0 O! q0 g) |4 x% c

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

" h7 R5 P4 P8 o

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

1 V( Y- j q* v$ u6 E( ]' I. d

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

6 h$ e% D* ?# A: d- g @3 H6 U

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

- W& F8 E4 t( P# F

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

$ U; U; m: v( d! j. }. ]7 m

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

+ Y! P; m+ S2 g" Q9 x8 ?

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

# z- x* c" B6 X N. ^

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

4 S/ M6 T: a% u) q

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

; U8 h1 Y, F6 \8 Q* Y0 n: H

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

3 c8 \3 Q* F: }/ u

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

$ @; J* t( T2 S. n- r/ L

typedef NTPROC *PNTPROC;

9 |" D, G V+ s

#define NTPROC_ sizeof (NTPROC)

* N: u4 l4 g' w8 g( a. [

typedef struct _SYSTEM_SERVICE_TABLE

) ^7 d" _5 Y2 s. s

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

5 W! o0 \9 G6 P3 g2 o. R; Z# f1 W

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

% x2 G* H: ], v5 W; O

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

' k$ ~. Y6 [& G5 r& x- r

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

- z4 L2 x1 {' O( g/ ^5 O6 _6 R

) SYSTEM_SERVICE_TABLE ,

) |7 I8 w. [1 \ {- j, K0 L

* PSYSTEM_SERVICE_TABLE ,

( J4 _1 E2 S, e j

* * PPSYSTEM_SERVICE_TABLE ;

" D3 w# J2 C7 y$ h7 |

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

9 L; D2 ^+ g( v( s3 ]7 L& a3 b

typedef struct _SERVICE_DESCRIPTOR_TABLE

1 c" X! F; {5 p7 j

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

% a- X& F: R+ V2 _; O" a5 @

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

0 I- c2 m+ F# y; [ [1 Z

SYSTEM_SERVICE_TABLE Table3; // 未使用

: z" p% k- j% K

SYSTEM_SERVICE_TABLE Table4; // 未使用

2 j7 k* f7 p+ `& t% G) ]. z

} SERVICE_DESCRIPTOR_TABLE ,

1 T4 v( j" [' j/ L2 z2 f4 y2 N

* PSERVICE_DESCRIPTOR_TABLE,

! i# C. u) f) X# E( i: \

* PPSERVICE_DESCRIPTOR_TABLE ;

5 S+ X+ |# p9 h: f& k$ Q! ?8 l

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

6 X+ x( {2 ^0 |4 y& q; B2 g

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

: s5 L! e+ @+ R' n0 J9 E1 z

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

, E: B. S- o% X! ^# S5 i2 k8 t

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

$ f6 X2 }0 D) Q1 U+ _% u! ]

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

6 L( s7 ?6 r, W1 q

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

% f9 O) o, X J9 I# e2 q" C

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

1 L' Y& j; o" x! h' e

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

[; Y7 }. c1 d' @+ [7 T

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

. [, E& w# F. u+ c8 Y

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

# i& k( }! N, z0 {( M6 G9 l& y

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。9 j0 i8 i% n! f 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

) _. ?! R" f7 x) r p

前四行是最重要的,对应那四个SDT成员。- ]5 S8 Z D& r5 I# U& _/ z 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

9 r! z) \9 r7 U- C! A

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

. R% d4 @! u! k

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

B, n0 V6 e' R4 t" W( p

KeAddSystemServiceTable $ o+ ^) o2 V0 L* u6 V4 C5 ^6 C* R此函数去填充这些位置。

+ y' W3 q* ?# T; i1 q

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

& k( y) q! ^* s0 H+ x7 M

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

$ }! r7 w. H: u

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

2 c s2 [* J2 p

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

3 e1 k$ j. {- H7 `

映射至ntoskrnl表格,ID在

% M( W7 c R& `' s( s1 t; h- F

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

1 o" h# }/ E+ s% K h, f

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

( e4 ~2 d$ i: Y0 V, f8 J' {% y

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

, D+ D4 d3 }# \8 a

STATUS_INVALID_SYSTEM_SERVICE。

% P; o1 v. _7 S+ _$ R$ t

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

% X% A5 }! n- ^

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

+ i; C4 Z: `+ T+ f* S/ K

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

5 E8 j+ O; b1 W( Z+ i- F& z

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

* w% L- F# `* L' d1 \' g

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

4 }: O: {2 ~4 F8 q

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

+ ~: M5 I# S# S( d! l' |- L

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

; W8 p- q) [! {, c- c

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

' s3 v* c# L9 i5 w: q

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

. W% l. V' T0 i/ g- b) s

# B5 Z M, |- F 2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

6 \4 E! o5 ?# V: v- P/ D

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

, ~! o: n0 G3 d# d" n2 C

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

1 J, z& p7 j9 H5 R5 e8 X

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

* y* }' M/ a$ e! w' k4 w

0 ]* `7 E5 Y/ p: q$ H1 C3 pWindows 2000运行时库

1 K8 z9 E0 g" ?0 C0 t) y& P1 ~ m8 B

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

0 ]( t$ x$ Q5 O- ^6 S [

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

7 [# S; M7 c' I* T! D

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

. N: Z" \6 d( J2 x, e

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

) v8 _, [8 T: J8 r3 I% A g, h- O

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

4 D, B+ m6 c9 I% ?# l

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

* u+ b6 e8 _1 S& c! r- e

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

3 l- X% u$ q6 K% @/ c

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

# a; _: m9 u8 H3 ?# Q

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

) e2 U# [1 H8 c( q' P" b: d

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

6 t/ Q1 c0 g2 a1 @

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

. r5 Z# z) c' o1 `4 e

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

: b9 p2 G, i/ E8 _7 j

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

! ?) }+ p! [5 p1 L6 H* U: I 8 m+ o& ~ k$ h# Y; B* ]2 E) ]

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

! q/ {: X' E+ y! N5 X; P3 c

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

; W6 l9 \4 n- N+ z; N

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

) ~% O V. ]- K4 j8 a5 E

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

) U6 c, N% J. r% x

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

$ X) w# V. r u8 r

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

: V8 s; D& B8 J: R" E

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

* s3 T% a4 T9 W ?

常用数据结构

/ @; P( Q H. i

l 整数

3 y6 Z& w, m- j$ t* b' Y) F8 C2 d! [3 |

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

9 b. z* p; z, `9 e. ^

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

( U2 s8 x6 s% r6 y1 s

不同。

8 G4 G$ m9 M8 h) k# v0 i1 l+ @

TABLE 2-3. Equivalent Integral Data Types

3 {( I4 @ ]% [( O5 J" }. t$ f

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

9 V* V$ {9 I6 e# A0 v9 x

8 BYTE unsigned char UCHAR CHAR' N m: ~. m2 | 16 WORD unsigned short USHORT WCHAR SHORT

8 X& r$ Y: v5 ~$ S: ]2 w) y

32 DWORD unsigned long ULONG LONG

0 }" S5 H( [! V+ `5 O

32 DWORD unsigned int UINT INT

* \" K1 D- o$ _: o* q$ b

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

$ W' V1 c' L2 E9 J5 {- b. _

80 TBYTE N/A

: S/ g' O3 A+ k4 C9 ^

typedef union _LARGE_INTEGER

" ^' b/ U. D7 e# z8 Z% K9 X- o# O

{ struct{

6 ?$ S) ~' I5 x& z9 x% Z! H- `

ULONG LowPart;

9 x4 R# o& i0 e! x' K6 Z

LONG HighPart;};

0 D! j6 c1 U& s2 w( j; m

LONGLONG QuadPart;

! t' J% m3 o& ~4 d7 q& Y b( n

}

4 L2 V8 k- S; e; G, A

LARGE_INTEGER , * PULARGE_INTEGER ;

5 O, Z6 H4 Y* n

typedef union _ULARGE_INTEGER{

2 P5 Q( U5 ? A& U: o, W

struct{

+ n! l) A: ?, E/ q6 T( W: p3 G4 ]* [

ULONG LowPart;

. c# N. N9 T* z$ d9 ~3 ]

ULONG HighPart;}

' B1 s& Y' g Y

ULONGLONG QuadPart;

. U0 N) C% k6 O7 }# |: a

}ULARGE_INTEGER, *PULARGE_INTEGER;

: W' d) [$ G. G

l 字符

, ~8 I8 o3 A, A3 ^% N

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

/ r9 b- l" m6 C8 k1 ^. P3 R

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

, N& R. E9 f& U5 N, n

typedef struct _UNICODE_STRING{

$ [+ p: M- k# ^

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

! S/ X, s% T8 Z7 l

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

- Q! Q, q3 z' N6 G0 x. Y6 |

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

) r4 ~& _7 `$ v! d6 `5 z

typedef struct _STRING{

# v ^1 B! ?9 t4 p& l0 e$ d

USHORT Length;

. Z3 b1 H9 z0 v, `; R

USHORT MaximumLength;

( P" }' M9 j9 d; v9 n" F

PCHAR Buffer;}STRING, *PSTRING;

, L9 [. q. n; z9 ~0 @0 W# |. t

typedef STRING ANSI_STRING, *PANSI_STRING;

% m e7 Q- V% Y. H" l+ i1 [

typedef STRING OEM_STRING, *POEM_STRING;

8 ?5 D7 F) n& ~7 u7 O4 T9 o

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

+ s; m' r% x H$ A) [

RtlCopyUnicodeString()等等

6 |3 ]/ J( C" J: Q* f7 U

l 结构

! u( U2 ^& M# \. b

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

2 ]' R0 R# X" R8 X Q% y- d. B) R

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

1 Y6 v4 ? H* K/ e( G' e# \0 h

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

0 ~% w' Q/ O" C6 O3 |

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

4 B; `9 O) P8 l. j& T

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

h/ H0 W; K$ \( o# ?( l

typedef struct _OBJECT_ATTRIBUTES

: X, I- ]; f' r$ y4 C- Z ^0 v8 N( s

{

9 U( W( J/ { G( E/ G; X: g9 M

ULONG Length;

( i* r' h8 L- Q$ Q3 s

HANDLE RootDirectory;

& q T" [, u2 x% ]! S' s& O

PUNICODE_STRING ObjectName;

; q p( B9 B; T4 Z0 E

ULONG Attributes;

& I$ c$ |* M' w$ p0 \% Y2 f

PVOID SecurityDescriptor;

M! }. `7 g2 n( D

PVOID SecurityQualityOfService;

: v5 {; v0 z' O+ o1 v9 v0 a

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

9 ]" S8 ~6 i7 M/ G h

typedef struct _IO_STATUS_BLOCK

: ]+ r; }/ m1 @+ K4 U! E

{

# X5 n6 A( c. x' f( }

NTSTATDS Status;

9 G3 W% Y2 f8 A. w- q2 C( A

ULONG Information;

+ M0 _7 K0 u* v& G& a3 l

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

q- C( S8 q% x2 R( }/ z

typedef struct _LIST_ENTRY

. Y% Q% G6 d9 A6 Y6 @' o# ]

{

; G( E1 f2 K6 r# [& s

Struct _LIST_ENTRY *Flink;

& @0 i( i" s+ Z7 [9 V I& a

Struct _LIST_ENTRY *Blink;

! e4 A8 b1 s) U* m5 W z4 ^9 g

}LIST_ENTRY, *PLIST_ENTRY;

! O7 Z h0 {( L8 R. h8 [* @1 q

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

; c1 X$ B1 r- n& G; l/ x

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

, Q. F0 L3 ]0 n3 m- b5 Z8 W3 y, D

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

F% ~5 p# X* k# N

typedef struct _CLIENT_ID

( X3 K. V1 I6 U3 u5 O, L

{ HANDLE UniqueProcess;

' @ j' |5 ~/ E/ f4 m* O

HANDLE UniqueThread;

4 y# D- N: f" q2 c) U

)CLIENT_ID, *PCLIENT_ID;

( m: K; z/ Q6 N9 ^

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

! V8 E" u. x( \

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

3 D8 y- p% F* h; x3 z1 F

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

$ `# v0 }+ z% _6 ~) k

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

* K" O, `" H( s- |

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

% u3 p: y, d2 X/ k& L

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

& t0 C* z! V. M9 f

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

$ V! j+ P1 c0 d& y$ K3 H

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

j6 M4 ~2 [" m: d ?

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

" b, \- V; N$ Z2 ^/ c( F

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

+ P$ V- l6 \' E

3 P7 J. K/ j" F. l1 R' U9 ~ 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

9 r5 O& E) @- o, v2 n6 S: _

指导; j, `9 N+ j* |; R

4 K- `0 M L1 w% W( C1 M

: H4 d/ r/ T* k* @! b7 _7 K" p






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