QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com 9 @% y1 a% e! ]+ Z! s w6 B来源:邪恶八进制 中国

) `8 A" P+ A# q( }

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

8 D. N: E* q2 |- d7 B

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

" [) w' ?" @# i, I$ A! @

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

9 c/ `+ B3 j! q; h5 S2 ~

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 - T# H; m$ }7 a

! O3 s% c- t) U3 H% b( P, \

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

: a w4 p9 `1 b( E% g

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

7 h8 V. I0 X0 T+ O1 Q7 I5 o

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

# Q" u: N F$ u& @6 @

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

" a4 t/ R' \7 j/ |8 S

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

3 [! c+ ]2 O: } B

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

! Q4 Y5 P7 \, `! K* L/ l. Y

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

6 \3 C* h- K4 H, x( {% V+ h( X6 }; D: n

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

) m* T' `$ E1 v+ M* s& ^

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

, W7 w7 U/ _) E5 U

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

, A. i u6 H! F& t* L' R. t0 F

反编汇这个函数得到:

1 |$ Z3 g4 `4 z& f: E

mov eax, 38h

4 N9 I' b/ C! {/ u" d5 T- |

lea edx, [esp+4]

$ T, y8 p! p" o( F

int 2Eh

. n5 {$ s$ T6 F3 l8 {2 k

ret 28h

3 | j4 `4 s1 k( V. T" C7 p8 B

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

4 G0 y" r3 c7 P0 L/ C9 ?: y, ]

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

! d& U% a/ P3 m( e0 G

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

; v8 ~( C& [+ p' }' ?

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

% b+ j& B. N$ K$ p

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

3 g6 W8 _8 M' ]7 |0 @3 u

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

- s0 T( Z4 g7 T2 d1 e' `% G: S

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

$ O+ D6 {) F" T- o9 ]4 [# G

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

_' L# s3 x7 y$ z2 e F* G) ]4 s

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

. `; _# F$ [: Q- z& M

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

* S) ^# \! x' d

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

6 O9 o, m( P3 I* j

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

) k6 R n2 f$ @5 `+ s, F& T% Z

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

7 D% G* |# [8 }# [; \1 u8 s2 q; F

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

. O3 E. }; M& h" m7 G

typedef NTPROC *PNTPROC;

/ O0 r6 \8 g6 B

#define NTPROC_ sizeof (NTPROC)

* w6 d! r, Z3 @, V5 W& h% T8 t1 g

typedef struct _SYSTEM_SERVICE_TABLE

- ~( B, Q# T: V! K% j

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

8 m6 w" I- n- s* O5 j

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

" G* ?4 ]7 F! ]

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

4 X: a7 `0 V+ N6 w: Y& R

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

7 b7 h6 L8 d5 f

) SYSTEM_SERVICE_TABLE ,

9 A; D$ N- e. F, t! ]

* PSYSTEM_SERVICE_TABLE ,

( V5 O/ f1 k* \7 ^2 f4 |/ Z" K

* * PPSYSTEM_SERVICE_TABLE ;

* U5 v, R5 E3 P5 k% M2 B+ A6 j

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

6 n' g" z' c8 X

typedef struct _SERVICE_DESCRIPTOR_TABLE

( j, g+ b/ h% U

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

8 U9 K7 r' g* e

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

( N: Q7 q+ \$ S* E; H8 c- _; T9 b6 I" s

SYSTEM_SERVICE_TABLE Table3; // 未使用

/ f9 h$ D2 i# q! e

SYSTEM_SERVICE_TABLE Table4; // 未使用

8 ^4 D- ^( R b+ y; d+ J. R: r: J

} SERVICE_DESCRIPTOR_TABLE ,

7 i0 N8 Z2 F I* e4 x0 Q8 O- n

* PSERVICE_DESCRIPTOR_TABLE,

& u! I7 f N" [/ G! S3 \: d

* PPSERVICE_DESCRIPTOR_TABLE ;

1 r# g' B8 e+ b

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

6 p$ R9 q% {* G' V

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

) s1 U. T* }5 b! i( e; C

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

9 l1 w' E0 y, g& w: T

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

$ E* `$ K) {! z5 t' G* P$ T9 A

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

5 X% b" ]2 e3 A2 s: x ?

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

$ `' `- B8 B7 ~% m

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

! _- }" h! ~7 |. w+ W9 s% R

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

- C& M4 Y7 F# a3 L! U

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

3 j1 ?2 E; @1 `

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

+ g* M6 {( Z# @& O1 H Z3 \

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 & w! T7 `) T4 ^( i" P* U7 A 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

- C& ~) M& h: ]& z

前四行是最重要的,对应那四个SDT成员。. `5 j3 r6 T { 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

- |8 h# |& Q- S; N

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

) n/ F4 k# z; E- U% P+ e6 F) a- q M. T

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

' O: O) x4 r; i2 R1 Z& b

KeAddSystemServiceTable, L& U, Q5 m8 n! p( l 此函数去填充这些位置。

4 \# R& [# ?$ C

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

/ O: B, B, Y7 e* n; \

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

2 ?; g) a5 f7 p# W4 c2 J" z4 B

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

) }! d; T4 i, h+ x( J$ c4 [

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

3 p% |; M* ~2 D8 k* o! e! m- B

映射至ntoskrnl表格,ID在

* r# V% g& w- _0 S& }

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

( l* ]3 i8 m+ Y5 Q

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

% ? I( `& }: ^/ W0 E, r( O

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

8 O: p$ j0 I/ w8 g+ U' ] R

STATUS_INVALID_SYSTEM_SERVICE。

0 G# O8 O1 m" h2 e" k+ L) ? w; I- i& o

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

j# f6 K' s5 E8 |

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

) q( X! H) ?# w0 X2 h7 |$ C

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

9 Z9 m t3 P3 u' R$ l3 O9 r6 H! r

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

' S% h4 _4 ]9 a |

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

' e1 D2 f9 n$ L% y, D6 L: ]

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

( u9 }0 j- ^' i

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

, E9 j# }, i9 k8 X* m5 j

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

" A/ T6 \7 d( N8 A7 |" _

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

% O3 @% `/ K* L- n8 b

* c' j9 {; C( c; p/ E+ ~ 2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

; D) R4 v9 O, N. y' N' A# b

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

5 \8 U+ G7 @" f" v3 M/ H, ^8 v

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

; D8 m# M, V" e/ @( U

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

4 F: C5 W* Z z2 N8 g) y1 [/ D

: v- ^0 u% V1 H0 x) b7 W6 F: G) BWindows 2000运行时库

- \7 A* ? r1 k4 |

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

, G2 G: B5 c! S$ @. O9 ^

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

# l) j( K' V! D; J/ D( J2 V% ~

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

" r# ~5 G" u6 t1 z8 f

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

0 g7 F) N! W# t/ }6 a5 k. h) a+ r

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

/ D" a9 n `5 F# @5 ]

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

3 W1 T% B" C1 _

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

) A- r& I# {2 d0 v% y1 A

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

8 O9 S" f8 ?" j% p

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

5 X0 n9 W8 _3 \) g

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

8 d! g0 d; q2 I; N

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

, y; ^6 d* V6 f, o; q

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

% O" \6 [ j; U' h

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

" Q, a1 e2 n: q/ A. r0 y" o e6 S" C0 b9 a

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

& d2 {( w7 r0 g

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

% s' a0 c9 j' i9 y& r$ t5 Z2 ]

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

4 Z5 |: z& U8 E# b

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

& y& ^8 G" T: D9 t( F

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

8 z1 J9 `; k& s4 U! u

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

* I7 w1 ]$ n, v. e' [; g

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

% \8 u: V8 ?6 Y L8 A

常用数据结构

, w$ B! T/ x; n& Q: E" t ^

l 整数

' R R7 O1 f T# m% e

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

' N7 y+ }" b9 [( N9 ~" {# G

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

/ [5 R! O" v( q

不同。

& |/ p. i/ E8 S

TABLE 2-3. Equivalent Integral Data Types

0 S0 e- h5 [1 ]$ p) T

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

; |$ p0 ~4 u5 m& F1 ~* j

8 BYTE unsigned char UCHAR CHAR ~- L3 y2 b* u: X1 Q% T* G16 WORD unsigned short USHORT WCHAR SHORT

3 Z4 H0 S- Z0 _# L, V5 H

32 DWORD unsigned long ULONG LONG

% t, X" ^/ ^ h' a: @

32 DWORD unsigned int UINT INT

: a* i2 M$ a5 c, b% G1 `

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

% s5 Q) _# l* F" Z' ^

80 TBYTE N/A

. R# ^) I1 z. L( s& F% i

typedef union _LARGE_INTEGER

1 f+ ^5 S' I/ W3 D! {2 M- A

{ struct{

8 C8 h" _& j: S& Y e

ULONG LowPart;

+ v$ b& O1 n8 _2 d

LONG HighPart;};

& |' U' C+ r9 r8 O; H! r

LONGLONG QuadPart;

/ y3 B N3 _; N4 W3 M

}

: ?- U( V* i4 h% x0 [+ ]

LARGE_INTEGER , * PULARGE_INTEGER ;

& v" |2 ^6 y* s( o6 k! s* B

typedef union _ULARGE_INTEGER{

+ S" t5 A) V- m+ }

struct{

* P5 ]# F5 P$ \* _* U% g& t

ULONG LowPart;

8 Y$ r3 K v8 w8 m$ i7 I

ULONG HighPart;}

' {! F, q, d" W+ P: S

ULONGLONG QuadPart;

& j. `, ^$ G, T) t

}ULARGE_INTEGER, *PULARGE_INTEGER;

/ E7 J! `0 H- U# D: M

l 字符

& P; f8 m) [: v* ?4 _7 f" k e

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

y" C- ]) g7 T4 Q

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

8 J9 t6 l8 b8 W: Z# a

typedef struct _UNICODE_STRING{

, L& P7 }( z5 ^# _" ~( [

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

5 V8 P! Z( b h7 {/ r5 ^( G& D

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

# Y% R- o, D d

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

2 ^: N- i. x0 g7 x. H

typedef struct _STRING{

8 u7 X# b+ \% n0 i

USHORT Length;

! o% e, o# l i; ~ I/ |

USHORT MaximumLength;

2 \$ Q5 A5 G7 I

PCHAR Buffer;}STRING, *PSTRING;

* d8 b" D& T' K1 }

typedef STRING ANSI_STRING, *PANSI_STRING;

( ^ {* c- x- U

typedef STRING OEM_STRING, *POEM_STRING;

" s, b9 ^6 ?( |) c1 ~, k: F

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

; l3 e, e8 Q9 Y

RtlCopyUnicodeString()等等

: G( J& b6 k y9 R% c" q

l 结构

& [9 |! j4 u4 Z3 S# e

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

7 L9 b8 o4 g* E# B

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

" }& Q1 h! ]7 {; F0 s+ E

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

8 W, F5 |* W+ m% W) b/ R$ \' |. V+ T

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

2 l# ]3 _9 O, I

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

- |4 Q) T* I( z/ V( g$ Q. J+ z

typedef struct _OBJECT_ATTRIBUTES

9 Q% x! e. M C% v- I& A

{

" x8 r) Z( r+ ~

ULONG Length;

8 y& u2 u+ g& d {

HANDLE RootDirectory;

% J1 z' t6 K( K# I

PUNICODE_STRING ObjectName;

* y" G* k2 O' x2 S2 V

ULONG Attributes;

; W5 E7 X( c; n

PVOID SecurityDescriptor;

/ d% @ w" M0 t5 h* p) E- h3 K+ N* h

PVOID SecurityQualityOfService;

! h' v( ~' c$ R: O/ i

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

& N3 u4 c- O. Y8 W& [2 }7 ]7 ]

typedef struct _IO_STATUS_BLOCK

3 {2 j# t) `; E+ v$ k4 v" d! b

{

, {" f7 O$ a. _- O3 \

NTSTATDS Status;

) s2 J; Y( v7 e8 [$ n, o

ULONG Information;

" z4 J+ J; u$ b: y; d! J9 |! P

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

5 A5 [( t% x6 [! h7 |1 Q& l/ _/ U" k

typedef struct _LIST_ENTRY

7 J) [- Y7 V' o* J' U6 Y3 @

{

7 w9 \) u6 x e3 _2 t' H

Struct _LIST_ENTRY *Flink;

: P9 p% s5 l( B# [# m9 Y# H- J

Struct _LIST_ENTRY *Blink;

; k( H( a/ i+ ^; V# j

}LIST_ENTRY, *PLIST_ENTRY;

" s& n8 ]$ A/ |) D8 ^

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

" F9 T3 U; t! d

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

' M+ n5 a5 G; [! u# s

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

9 U6 E' L! p4 h* C

typedef struct _CLIENT_ID

" n j, V; @- c7 G

{ HANDLE UniqueProcess;

* M/ ~+ R" ^, D7 P' t$ k

HANDLE UniqueThread;

0 c/ \, N& i8 i2 s; y

)CLIENT_ID, *PCLIENT_ID;

4 b" d/ v% l7 w/ S

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

8 b1 i, u4 T' u2 x) {

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

% f8 @6 n5 U5 g$ T

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

]0 k7 t' T$ b' V1 Q

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

* |1 I& ~) j! j/ C

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

7 `6 d) T! v" R- G, h1 ^/ \5 t) S

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

& q4 I X/ o: `& `7 _3 ]) Z

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

: t! m; P8 j& w- M! Q0 I& ~0 K

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

3 i' ]1 x- X! \- ?, I

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

S4 m, ^$ i3 Z

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

$ I. |. z- K* L$ X* [) H! n

0 c9 m5 y0 W' _- |- d. \ 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

1 E2 z" a8 t" \# f! G, ~2 t$ m

指导! U5 D( o2 p% |8 k B

, @/ `0 Y: f1 r) y" i1 l3 D

; E. |5 ~' L% j

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 07:14 , Processed in 2.748831 second(s), 51 queries .

回顶部