数学建模社区-数学中国

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

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

作者:sunwear[E.S.T] shellcoder@163.com4 h- A8 M1 h1 y/ g; i" ~ 来源:邪恶八进制 中国

& z0 `, U. @% O& Z8 W8 S

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

9 M3 X8 e5 I/ y

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

" C8 D2 j: G! A4 D/ U' v

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

2 o& k6 C0 ^- h# G, [% I5 ?% B, r: R

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 & H! f8 J$ I' a7 S8 \: Q' I

Q9 f- N0 _1 v

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

# u6 U0 B5 ~* H6 n

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

0 [, k6 X1 N6 ]5 R/ m

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

6 A5 |. P3 K" |# N

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

3 D8 f; e v C" q

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

! q+ ~# y6 I! a* W% ]4 \$ E

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

. N" C P! ~% C

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

2 T% K0 ^2 J3 k* B" K

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

" y1 ~3 s' Z E( B

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

1 ]$ y( b i* e; E

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

6 T" F( y# p: @+ z8 B$ {

反编汇这个函数得到:

% `+ [/ H! v' o" O P: G

mov eax, 38h

: x) F/ w0 k8 r/ M1 M4 ~& {

lea edx, [esp+4]

* n, a' i4 l% x: l, R

int 2Eh

9 q$ [# E! i" ]; G) D

ret 28h

8 x l- u3 W8 C; q

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

' n# f( s9 w7 V6 X- ~+ _% d$ ]4 G

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

* @; Z6 G2 z9 ?, Q, i! s

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

( a4 _% |$ V8 O; h) [

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

. r+ |8 f( [" P% S: S: a `

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

, O( t4 k1 x2 E" F4 I& O0 r

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

4 ` ^$ L) E4 Q) j# a3 q0 n, j' p7 ?

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

4 \ K$ p. [/ o8 G- u |& u& V) K/ P

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

+ C# c4 ?, j5 K1 `

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

- a, p8 a- Y- o7 ^ A

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

0 o% M( F& e: ?& Q& [

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

& w3 a: f" C% H! `# A6 h& G- h

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

5 D. ]8 E8 Q: r

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

` }2 E; F; M# p

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

9 T C& a- h w% K, c$ k/ m

typedef NTPROC *PNTPROC;

( Z* s7 a: c. [8 x( }

#define NTPROC_ sizeof (NTPROC)

( @7 Q- |/ I# y5 F) Y

typedef struct _SYSTEM_SERVICE_TABLE

8 J9 K: W- I9 `8 ]" Q6 k

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

2 z% T l/ D& ^4 |% W1 A3 x6 F

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

3 \1 y6 k* H& _2 X8 J+ u

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

8 @$ ]$ W6 d2 z: }: W h! X# ?

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

' e& H' N) J% R# F

) SYSTEM_SERVICE_TABLE ,

1 g- }1 w+ A* R6 M) M' X

* PSYSTEM_SERVICE_TABLE ,

5 E9 B( k6 G' T0 ]. j* w

* * PPSYSTEM_SERVICE_TABLE ;

R8 K$ n3 |1 b; t8 r3 {# t, `

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

8 X4 K5 P6 V2 B) s& i: q# O5 K

typedef struct _SERVICE_DESCRIPTOR_TABLE

* n" ?$ H5 o) A9 l8 z

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

5 x8 M: ?2 B4 S0 ?9 M

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

8 B$ z( S4 n: P# W2 ~( c# [1 m& n

SYSTEM_SERVICE_TABLE Table3; // 未使用

+ @ t/ v ]2 `6 _& L5 k

SYSTEM_SERVICE_TABLE Table4; // 未使用

' V% Q2 [/ | i& @

} SERVICE_DESCRIPTOR_TABLE ,

" y5 Q+ Z- m3 O' u+ l o

* PSERVICE_DESCRIPTOR_TABLE,

3 _) V- ^/ P$ m; s( w4 c9 `

* PPSERVICE_DESCRIPTOR_TABLE ;

' P; J$ }& l5 W5 h- F! \

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

0 ~/ I% h: b4 ]; T4 ~7 C

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

, L, ?. I/ y4 L0 t+ B, M- B" _& I

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

5 ~" @; L6 T9 |0 d9 _

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

, r. S/ J6 t+ H3 ?* F5 `4 o2 r2 {

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

& `8 Z$ s/ z" R4 E( B

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

2 b ?- c5 `, o

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

, U& a$ i8 a' q8 J* ]

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

6 G& X, \$ T2 B& r) L

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

5 Y9 _0 L' X* D$ U- q8 F" O, s

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

" Y8 T3 O1 H8 d" T/ G

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 ! h) U; K" z( W# B3 ~ 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

3 l/ C/ o/ ? Q8 f, M/ Q

前四行是最重要的,对应那四个SDT成员。 7 w9 O2 @4 m% X2 D+ x# Q 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

- ^$ `3 _+ \2 x9 ~/ x( u

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

6 \+ ?5 v9 U8 Q# w4 f

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

5 s8 v4 Q/ q0 O0 x& l% S3 l) ?

KeAddSystemServiceTable 2 J; ?5 ^' c$ }( W5 s$ I此函数去填充这些位置。

8 B1 o. }! t( Y+ |7 [: t

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

- l" B7 _% y6 E* x% i; n

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

' g, B- V7 Z0 y1 T

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

+ U+ l9 Y+ U5 @% Y( r

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

, i; k, @. o+ y

映射至ntoskrnl表格,ID在

6 G2 Q! l, l7 |$ v5 T& t; x+ f

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

8 p' z2 ^: E. T8 N

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

8 D- U- X) L8 g( `

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

9 W! m9 O5 n9 p Q" H7 v

STATUS_INVALID_SYSTEM_SERVICE。

5 G- D3 B: \" s

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

8 K, R3 R7 P; j& y

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

Y. P" T& L: f8 F4 s: J3 N

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

0 I1 f! h* B. ?2 [' \

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

" I: D' o8 N/ M; d* W7 z( Q

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

0 e+ s" T4 N) O; r' `. d

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

" u1 U, M2 [3 ^; ?( M3 g

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

& x# C" r# u, _# r+ n1 U

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

1 Z& Y8 d+ Z; ~6 ~. D% ^; @" H

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

6 ~# e S8 _# Y& u( e

) u! ? }" G; S) g* F2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

: k4 _5 a0 g/ E. R2 U( V

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

7 T) j# B' c. n+ |2 o- G* V, T+ W

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

. B6 r: S) J. n) b- U

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

$ s& d' ]* `1 L7 z9 y

3 O: Q$ j6 \: g: L9 C8 k Windows 2000运行时库

8 X' ~. x O2 d# n

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

- w" {/ Q# \& ?# t

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

" F1 d: M/ C X {' P4 X

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

7 j- A2 h7 a5 [

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

9 }) D l ~7 ?. P/ a

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

8 ?- s7 F9 `/ D

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

' j! b" t. W% _6 y# w4 t

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

0 W( K8 u0 J+ y& c" }. m3 L C* N

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

7 S7 G) E. u6 P& \9 k" t$ O

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

3 e! b; z4 ^* z5 |, k6 U2 Q, [/ G

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

3 k1 m% Z# s4 M! P

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

/ _9 }6 a/ f% P) w$ K

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

4 w: q5 e) K8 G* j& A( a

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

& E, H3 Y, _* O$ k, T/ d) `# S% N, g; B

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

7 v3 A* d/ ]9 v g+ u3 ?

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

( f* ~4 p: H5 q1 f. T

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

: g X$ v5 a5 n- d

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

( _3 C8 h+ B8 C2 q b) U# _/ w

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

! S5 }0 s' D0 K' I

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

9 y+ @0 B n6 |* q

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

: k/ d8 D4 h) K2 \) V+ [6 x2 c0 w& Y

常用数据结构

* ]0 M: }. v1 f' ^$ {; `9 n7 d

l 整数

/ @& y8 o7 V7 k# C& Z4 J( v" ^

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

! W2 f5 u9 f8 t: s, y3 O% z: u, w$ q

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

& Q2 g8 G G7 J+ L+ v

不同。

& d% f6 f) W& J4 ~- z

TABLE 2-3. Equivalent Integral Data Types

" I4 A1 R' ]* x. B9 G! u

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

: M5 n7 _; X5 F9 M8 ]2 w

8 BYTE unsigned char UCHAR CHAR/ T( W4 d: a; v% Y 16 WORD unsigned short USHORT WCHAR SHORT

2 k: W% ]6 a6 e. H

32 DWORD unsigned long ULONG LONG

. F, Z* U+ K5 W7 Z1 p

32 DWORD unsigned int UINT INT

. Z z. Q% r p$ y8 @' Z8 i

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

+ S% ?5 M" l: d) J. z

80 TBYTE N/A

; s/ E/ b& h5 Y0 r* R

typedef union _LARGE_INTEGER

" k0 O# t# x3 f( h7 y7 U0 Q

{ struct{

$ K' [1 Y E* b8 O# {$ l* s; H

ULONG LowPart;

3 X" X' n$ e7 h: ^

LONG HighPart;};

& d% c6 |8 A3 Z, O& ^5 I* [

LONGLONG QuadPart;

' _6 A% d& i4 p0 {

}

& b% `4 S/ L0 K( ^ z

LARGE_INTEGER , * PULARGE_INTEGER ;

' u; e4 ]3 r: E. ^# y0 }% r

typedef union _ULARGE_INTEGER{

7 e# e' R* e( M4 N4 e* Y6 q

struct{

, R9 Q& I! }7 Y) C$ ^( M( s

ULONG LowPart;

! K5 Y& E# F# B; P

ULONG HighPart;}

3 R+ B; _8 y. t* ~+ l, |

ULONGLONG QuadPart;

# Y, z- }) a" Z$ K

}ULARGE_INTEGER, *PULARGE_INTEGER;

0 g- j! H [! d4 i5 ~, n

l 字符

, `8 @ c: B5 c# N, U

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

0 Y' y8 J% x' b4 l# l) P

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

' K9 w- G1 N- r2 ~) m

typedef struct _UNICODE_STRING{

9 o% b+ J( Z' Q, U

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

# D9 _, u3 E/ i4 H

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

6 h) F0 C: ^3 P$ r

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

4 b- v3 ?# h1 j( p* Y. ]! O/ u

typedef struct _STRING{

( G: C- t4 |" e+ O+ L7 i1 [

USHORT Length;

$ I; ?8 Z( j( n! r' }

USHORT MaximumLength;

+ d% J4 Y( D0 `$ b, v1 t& r* Q

PCHAR Buffer;}STRING, *PSTRING;

* ]+ m( I/ K3 b' S9 {

typedef STRING ANSI_STRING, *PANSI_STRING;

5 b" [9 r3 k- |* B* H2 Q

typedef STRING OEM_STRING, *POEM_STRING;

) \" d# s9 }5 ^3 V" o' n( M) L

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

0 s' @/ e' C; ^% P

RtlCopyUnicodeString()等等

+ A( B6 C7 t3 E3 J# p

l 结构

/ s6 l; k. h G& x3 z) j# T; O

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

% n4 B1 q& J$ {+ T

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

; {9 ~+ k. M/ r- B' ~" n

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

5 t+ m6 W" S- c% S9 ` W& e

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

+ u+ S$ l( [& b `. i+ _& _" ^

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

9 _: `7 q# n* H! Q7 _, z8 @

typedef struct _OBJECT_ATTRIBUTES

& o+ }5 g& R. P e" N% P7 ^8 R

{

8 @$ u% Q2 |- m- d, ?; M

ULONG Length;

# r' m5 C4 M$ W

HANDLE RootDirectory;

! z& P; o: D- ?3 G% s& o

PUNICODE_STRING ObjectName;

& y4 Z& X/ U. C( ]1 u/ Y( P

ULONG Attributes;

6 |. @2 f1 Q) V, X( O

PVOID SecurityDescriptor;

2 B- o" H) j$ \0 c3 E

PVOID SecurityQualityOfService;

; Y v; z0 J! U. \0 d" m

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

3 b- p$ |7 [: ]/ |

typedef struct _IO_STATUS_BLOCK

. p O1 v% H1 h: |, B6 `9 O; ^4 |

{

* @4 L& S! [ G

NTSTATDS Status;

+ G6 ^9 x+ N( W$ Q! L! U. K

ULONG Information;

, _+ F& m0 {; Q! ^

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

/ t0 a ]4 Z2 L6 j5 G1 V

typedef struct _LIST_ENTRY

0 O; X7 U2 P! h* I7 w/ u# W

{

! N$ P- b0 }% G" U

Struct _LIST_ENTRY *Flink;

- L: V4 z8 N& l* J- [5 R3 v

Struct _LIST_ENTRY *Blink;

g8 I" w, ^. Z, x6 r! k! @

}LIST_ENTRY, *PLIST_ENTRY;

" T5 Z! e* _. J0 o3 {3 |2 E

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

* m- u4 l/ Q: D9 W" z1 Q( Z

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

% y% B9 X% i$ @

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

7 b# ~: [4 R5 [

typedef struct _CLIENT_ID

! y4 k/ [8 a, ^# T' ]8 f4 K; i( F

{ HANDLE UniqueProcess;

, u' A n, A7 m y: s

HANDLE UniqueThread;

" y9 @: ~7 |8 s# E

)CLIENT_ID, *PCLIENT_ID;

$ e- g) S, y2 y/ ]& a6 A! j

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

$ \' x w' d- I4 I% C

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

7 u8 D7 [# c9 `5 E( n7 l: v

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

; h4 u' t* y* A8 L( G

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

" }! U1 S4 g, W8 s1 C2 D" d0 g

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

1 k5 [* ^; n8 |. m% M

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

+ ?! O) B& g N* T8 V- `+ N

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

6 c; B6 q$ W0 ~5 w

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

# J: m3 |2 f, A B; b* q" c7 [% ]

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

8 F7 O' h6 n: a& q8 z9 _7 u$ V

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

) x: ^" P7 |7 i+ ^2 R9 [

& G! K' n" ~% I! _ 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

: P2 O9 G) ~, f& ]7 V. c# |6 j

指导( K4 T9 C3 f( i# Y, `+ l

/ U ]' g) f% n9 _0 u0 `4 H/ b

0 h& }/ c6 m% P






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