QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2667|回复: 0
打印 上一主题 下一主题

浅析本机API

[复制链接]
字体大小: 正常 放大
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2005-1-23 13:27 |只看该作者 |倒序浏览
|招呼Ta 关注Ta

作者:sunwear[E.S.T] shellcoder@163.com / ?. N. F0 Y5 o+ I来源:邪恶八进制 中国

$ ], ~3 E0 b; I+ }

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

) E; J: z* Z. p3 o6 ~. [ }- @

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

, k$ }1 W/ R6 F, z

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

+ g& f& [" L* ]! }0 C0 c

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 8 x& K9 ?4 e1 |0 S& L0 s

3 o; L! p$ c) x3 w1 C+ f

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

, Z$ h7 w, E$ b3 K

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

" a/ V0 [& s0 Y* S) i+ |4 l

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

( c0 V3 J o4 [# ~! j! N

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

: H' p7 t" ?' U9 o

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

7 M/ h5 b+ ~ q" Z9 o3 `) s

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

+ G# y& p/ n- a9 `$ c) S2 H

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

* @) J' w% ]8 U1 y4 z! ]

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

( w4 w% K1 z, R9 k, z% N

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

% V8 L$ v4 R% _! W9 x4 W; u

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

8 v5 z$ d. L0 Z* K

反编汇这个函数得到:

# B3 Z( f( V3 n& H

mov eax, 38h

: G* I' K, s/ g. \8 H+ v- a

lea edx, [esp+4]

9 y$ x6 L8 o# i, o2 q

int 2Eh

& s N [6 i- @- b4 _

ret 28h

! r' `4 k ?- w* q

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

1 {: n8 p% V1 T( L; d. J

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

7 X6 {3 J" z2 |. M* `

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

+ X4 o2 B" ~ Y( C. A: o

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

6 Z, M/ n' @2 p% t U9 U

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

" r1 b* {4 P. k

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

; S8 @ o$ b% V* }, `

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

9 |* b4 G& v- K( O, H0 L4 F

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

+ R* R* V: b" _. L/ Y3 ~: f

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

" g/ L) c5 {) P3 f; F! j

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

j( E5 I8 M" E& i% k# f

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

6 U+ E4 ~; ?# t! U: M+ ?

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

+ S# I6 b1 S5 ^# r$ K4 a

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

4 r7 i2 r9 r% k6 h, M

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

! x+ ]5 h8 \7 a

typedef NTPROC *PNTPROC;

1 m' S ]9 }( J, n5 H2 z

#define NTPROC_ sizeof (NTPROC)

3 A( l7 o5 ?) b+ M6 g1 [- j

typedef struct _SYSTEM_SERVICE_TABLE

$ ]1 U" z# S1 r) m: X

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

# c8 l! } ]# u3 R. Z( W; _

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

8 Y3 w2 Z& |$ F" D( X

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

3 @- E C# D3 A" T; l: x2 L$ Z- c

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

( |+ O) r! F' B* ^/ Y, j

) SYSTEM_SERVICE_TABLE ,

6 `1 ^) N3 |3 f( O$ b1 d/ K

* PSYSTEM_SERVICE_TABLE ,

+ u9 C+ \' ~0 I' q! D# ^

* * PPSYSTEM_SERVICE_TABLE ;

! ]& y8 v5 h, I- s4 t! {

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

* \* U; x! r! f4 g# j$ _# D8 j

typedef struct _SERVICE_DESCRIPTOR_TABLE

0 y7 z) h7 L+ a* {" y9 k( J6 W0 P* A

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

. n5 [5 ?* N* Q% ~1 B1 L* V

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

! A. X+ G( K6 I# ^

SYSTEM_SERVICE_TABLE Table3; // 未使用

8 b1 h, S- I& G+ }

SYSTEM_SERVICE_TABLE Table4; // 未使用

/ U8 e, n% H! _# @

} SERVICE_DESCRIPTOR_TABLE ,

, I6 R- b. Q D S7 e

* PSERVICE_DESCRIPTOR_TABLE,

0 |+ z# F4 N9 L) l6 w- d

* PPSERVICE_DESCRIPTOR_TABLE ;

0 O+ j, t* q# J8 ~) X% r9 E" N

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

- n9 g: J# F' x) U5 X* Y

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

- W8 d, [ V, w: @1 C) X b

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

4 ?1 ^3 c6 e+ T' o" l) v

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

6 i1 b$ Y; R& a+ t# \, @

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

3 |* k: K! A3 i

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

7 e _1 L' E* W/ Z# `1 e, ~0 d+ V$ E

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

P' d3 q" S5 V! K: k

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

# Y, y. m: u2 Y5 D, L w

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

s( W* ~: j/ X( e1 w8 V; k. N1 \

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

6 A" v' H- x% E2 e

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 / |" Y+ ]3 Q7 ]% j2 V u 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

`6 m. e+ X7 |. G. }0 ]$ }+ h5 l

前四行是最重要的,对应那四个SDT成员。 : B$ d' G# d. ^7 ]/ f 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

5 T- }9 o' O& K- `# Q) \" F

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

8 u! A4 V8 a3 ?) g

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

1 } R4 K! U. h& K

KeAddSystemServiceTable; m* u, E+ A6 q4 x, o% J& X2 e 此函数去填充这些位置。

# V2 @: u7 ? s8 p* J: x: ^

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

& l K, d6 m' \" x& b; N8 b

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

; x9 p% K8 v* S; ?9 G

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

: U7 j( e f5 A/ O6 K

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

4 l; a0 _; ]% g1 |* v+ Q9 _

映射至ntoskrnl表格,ID在

. v: @; g1 @9 m) x" b t8 O

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

4 Q! U6 y: \3 v L" w2 W. c6 a

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

9 z% i( w p/ M2 Y

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

4 x$ V+ A$ O# [% g' Y

STATUS_INVALID_SYSTEM_SERVICE。

0 _; V2 P' ^4 }* k3 C- P/ F. C

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

; K6 g, Y5 S d1 T. W

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

0 }9 ^# @. I" N y1 H& m& F

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

" M/ ?1 M% C+ d: \

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

/ L+ n( h3 F5 t7 f, K) T

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

6 I- U% ~ U) I1 i) b

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

8 j. h# T0 B! A% ?) d8 d/ W6 i4 |

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

' l& m/ K: y4 u) H

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

: j( ?! T$ m0 w% l, R; L

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

, \$ r4 s- m7 U

/ i; [/ n% ^/ N- d- U2 W5 E9 p: M7 l, x 2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

" u3 ]6 j; \) J) L; a9 ?. k

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

8 X, I( ?& X1 O( @( c/ {

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

* k# @% x) t3 ?) X

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

4 J6 C/ t# V3 O8 n1 a

. [- h/ ~& z; q0 O% y: c6 H H* F( F Windows 2000运行时库

! s' s, W& [- m) g+ D

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

$ z1 X' A" U9 T! e

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

: J6 @, i; {; a! P

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

+ w7 Q/ ?. ~9 @6 Q

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

" P2 ]2 I" ~$ x* T

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

* t4 F' v4 \6 e, r2 g* s- K% e" h- q |

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

7 \6 F! L6 L- b" y' B% F

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

/ c* P: g [( T& c2 x+ s

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

( v" L* B2 v# J; _

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

& p! G7 F( _! s# e1 q. N

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

6 L& h: [1 v9 H* ~; G7 K# N& Z

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

: [2 n" h8 ]! J) ]2 H

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

6 g; Y( N2 l! _6 s' Y

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

( s/ _* D) k" E h- G$ }' O) K 2 Q2 Q0 I4 q% ?: w b" J

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

! r7 p" G7 q' o. @% F

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

4 E$ U, X: x; o" G

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

" O) c$ d8 {$ E4 d* y6 R, F P6 b# C/ {

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

9 F' I$ G/ S; C) b

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

: C; [4 N7 f: P2 H: w

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

5 g( K5 X$ b) M+ w" v. S1 K. D

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

& C3 T/ O1 \- G1 g" Y9 @- Q

常用数据结构

/ w: J. b) e2 N& R% f4 _

l 整数

: q, ^# U$ C* `

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

7 v8 r7 c2 p2 M4 v9 ~+ D& x- h

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

! U y4 v3 g4 C# w! U

不同。

7 h& J! h% J& M' @

TABLE 2-3. Equivalent Integral Data Types

3 \( V9 l3 T. f0 t% H9 L

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

, U/ {! l+ R: I0 [! P1 u5 _$ g

8 BYTE unsigned char UCHAR CHAR9 X4 I4 _. @' r4 l1 ^: l 16 WORD unsigned short USHORT WCHAR SHORT

8 l! b" K1 o1 d3 Y" K

32 DWORD unsigned long ULONG LONG

) | ?/ v# x% M8 u

32 DWORD unsigned int UINT INT

9 X$ I" c; |$ A; f- y1 `- d

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

: \: f: p$ W3 v6 K9 D4 b( U" k

80 TBYTE N/A

: m9 p l8 x9 X2 n; i" [# I5 i

typedef union _LARGE_INTEGER

8 `" j7 o5 A; w

{ struct{

5 c; O+ H, e: S; v: O' p& {

ULONG LowPart;

' L3 X/ i# R: Y. O O7 G

LONG HighPart;};

) v6 q; j+ U+ r q/ F1 F% L! H

LONGLONG QuadPart;

% m' z: k, {# p6 H

}

* |$ z; v9 u5 I- o( @. x

LARGE_INTEGER , * PULARGE_INTEGER ;

9 C0 v+ D6 |' x4 M* ^. I2 }0 y/ m+ f

typedef union _ULARGE_INTEGER{

6 S- Q! p M t( m f

struct{

6 Y$ t9 c C5 E" {" n! \

ULONG LowPart;

# N3 f5 E# d! e* I U

ULONG HighPart;}

8 ?5 M0 q& Q% n U

ULONGLONG QuadPart;

" p" |/ ^) q+ L8 X

}ULARGE_INTEGER, *PULARGE_INTEGER;

( g' e; _ t: m; t7 v# ^ w

l 字符

" @# P$ s- m8 w7 _

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

. u4 C. E( V# x+ }5 h

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

1 k6 Z, r% Z. \

typedef struct _UNICODE_STRING{

$ f# l* K+ J4 r% x- } j

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

4 `/ G& r. o, o8 F$ o

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

1 o& a2 t; M; Q1 a* ^$ W. V

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

! `: G2 q& H6 m& \: v9 S K0 J2 d4 D

typedef struct _STRING{

h5 \( E+ H( |. k

USHORT Length;

! D- y6 X* f( P/ \) q- w

USHORT MaximumLength;

0 f2 d- I* d2 f# f" I, r8 K; K

PCHAR Buffer;}STRING, *PSTRING;

4 T# {4 m0 l7 I1 L

typedef STRING ANSI_STRING, *PANSI_STRING;

; H1 {. b& ], l! s w' d

typedef STRING OEM_STRING, *POEM_STRING;

% C0 d3 w/ t, x5 B) m% r, ]

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

6 J8 b3 @5 K5 N% o* e6 m' c k

RtlCopyUnicodeString()等等

9 {; T8 K. P# A8 K/ q, I' R

l 结构

" m" v" W; a, F" m

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

$ G2 C- o5 c/ G5 b

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

/ C* {& l6 s: h! }

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

8 |6 v7 l8 D1 t, ~8 g% L, ^, c* q

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

) y; [7 |$ K) k) t

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

/ f" n' z! w( H& [

typedef struct _OBJECT_ATTRIBUTES

/ }+ L& G" r4 V- {& }. O

{

- j% L( t {( p4 t* s

ULONG Length;

6 O+ |$ N# L( n7 _1 @! w4 k

HANDLE RootDirectory;

0 M% n0 J7 ]! S/ a) {

PUNICODE_STRING ObjectName;

7 K3 i0 t' N9 B- D# V

ULONG Attributes;

! r1 L% Z# x2 a/ S0 P

PVOID SecurityDescriptor;

- Y, Z% e3 L2 N+ ^' o

PVOID SecurityQualityOfService;

) J- ~! N( F3 H) f! }; `, n

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

, x/ g! d* ?4 Z6 ^+ O

typedef struct _IO_STATUS_BLOCK

/ x8 t* i* i6 d

{

; J' A+ G" c4 p

NTSTATDS Status;

$ a P$ T4 A+ w [! \- ]! c5 W

ULONG Information;

1 h( Z2 m1 x3 d/ \ Y

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

3 a4 M- |# w! d! }. E

typedef struct _LIST_ENTRY

/ a" F: G5 t, r7 I

{

4 R4 F5 G! ]& y1 l8 _ J' @

Struct _LIST_ENTRY *Flink;

- J; U, a* h/ V; V2 {

Struct _LIST_ENTRY *Blink;

! @3 w$ U! f ^6 a

}LIST_ENTRY, *PLIST_ENTRY;

5 |0 I* L/ n$ j+ R

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

: M5 ?. v! h3 L E( k4 V! ^

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

+ L1 _# X- s" n8 V

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

: a' q l+ i; f1 W

typedef struct _CLIENT_ID

0 N h9 e \& t$ D1 K/ v* p# a

{ HANDLE UniqueProcess;

# f! D, ]( m# U a) G( z2 [

HANDLE UniqueThread;

+ C% `. }( ?' f# P8 H$ \% N

)CLIENT_ID, *PCLIENT_ID;

" q7 a8 f; S% J- m

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

8 u1 X9 a6 G; | p

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

, c A* _3 H2 q" l' T7 I

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

9 X% a' I0 \7 g. ~

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

& }3 y; b" k$ B

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

7 M5 N0 J$ B: M! a# H

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

- Z" I: o3 Z0 k, Q/ I" O. [

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

) N4 w" n2 m O

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

1 w9 n" A% }4 H4 A9 q1 X

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

; H9 a2 j; Q2 e, U, P% Y# Z4 T7 @

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

$ u. t( [' i3 M: z) m

7 E4 A& E, ^& \4 j* M' d4 V; L# H _本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

; s; ~; B7 |6 p" p

指导 . M2 J. {& O: s; f4 s" { f

5 t2 f9 y4 z* q" b: }

. q0 C1 l1 R H' |6 H; N) W

zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-8-14 06:07 , Processed in 0.428345 second(s), 52 queries .

回顶部