QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com 4 B% y& D) S0 c6 b来源:邪恶八进制 中国

/ _ ?+ L& X0 E" U2 O! k+ Z+ K

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

2 |$ z0 S% t3 i) P

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

. \3 T4 g2 [: k3 ^$ N

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

' u3 [& D, R! H3 P/ \) Q2 i

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 % B$ J* ~: I2 K+ J6 ]3 H$ r

% ]8 f6 H, g( P& O/ P- s

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

4 r* l4 T, z) I3 t5 t

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

6 {, Y0 q5 k5 |

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

P h$ N6 u) B' Z+ I3 ~

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

: K& F$ d1 B# L" ~# E) S7 @

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

1 W1 A" s: Y; N& L V4 Z

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

5 P+ i9 E( }& Q

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

3 m2 W. ?. D; k B4 J0 H

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

% v( R, |. L; Q: j+ k0 @

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

; @4 K# J! p( N' f; E

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

# f; G8 Q) }. n: q; W

反编汇这个函数得到:

) Y* d+ U0 m0 L7 v$ G$ s/ ~

mov eax, 38h

$ y) ^7 F% q. R* t% l' ?; t! j

lea edx, [esp+4]

% ], P0 l- T3 H- z

int 2Eh

) ?" H6 Q& a2 b0 s

ret 28h

& N0 a: H: d$ K5 p3 I

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

& t2 E$ { \( N! \

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

5 f. o1 p0 Z; i5 o" z

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

( g0 Y+ F8 J& ?' |/ ~+ p8 m- c$ H

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

: G& v0 B* u9 g) o: i P

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

; ?) t: U! r( ~' q+ `/ Y

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

1 g* H3 `6 R5 X4 e3 p, O

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

6 O! M4 G: v" G- @4 ]" x& u

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

9 a; w$ V. j, O) f8 d/ a

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

, e3 \4 s) B- U

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

1 }' C x; N) G6 i. Q. L

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

5 h- w: d. {) J2 J' L

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

( E4 V8 I3 W2 ]. } ]

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

# j' h% n( u1 _( c& A$ u

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

x0 X; n8 o( I- ]; A

typedef NTPROC *PNTPROC;

, d' u+ n. z' W& h

#define NTPROC_ sizeof (NTPROC)

/ v% Q6 z; F/ _5 Q5 X% ~1 `

typedef struct _SYSTEM_SERVICE_TABLE

. ?7 _! J" f9 K, \

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

2 N, ]+ z* c7 V& ?

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

( n/ R6 m. k2 A) \0 S7 U! y* d0 t- Q

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

7 f' m& ^' T- s7 y

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

1 d- X: R. r2 e/ s7 F; k

) SYSTEM_SERVICE_TABLE ,

# ^, l! q, v4 ?2 w- D. S# C7 s

* PSYSTEM_SERVICE_TABLE ,

' y: N4 j& @# R0 B$ Q$ k

* * PPSYSTEM_SERVICE_TABLE ;

0 h) |- H, u4 c, i$ i- _+ @

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

0 X( Y- {. u9 Y

typedef struct _SERVICE_DESCRIPTOR_TABLE

( a( H3 o0 h5 p8 g3 a( l

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

" L5 a `0 @1 h" @: U

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

5 w5 E4 N6 Q7 h1 H9 u1 h0 x

SYSTEM_SERVICE_TABLE Table3; // 未使用

2 i a7 ]4 W6 {& i

SYSTEM_SERVICE_TABLE Table4; // 未使用

+ T! ^5 q* v y+ Q4 I W- A

} SERVICE_DESCRIPTOR_TABLE ,

$ ?# Z" O! C3 ^4 ]

* PSERVICE_DESCRIPTOR_TABLE,

+ V7 S W, X% f+ S2 I7 D1 n

* PPSERVICE_DESCRIPTOR_TABLE ;

" G: i7 R1 f# \9 |1 |" |

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

4 @7 ^: a2 h" R1 P x! v

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

1 m2 A, [5 G) R2 s# G# I w. d

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

$ z7 Z( k7 r/ W. y( `

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

% t9 q4 L+ Y* t3 [

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

( i2 _8 `% k" B- X8 g0 ]* I

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

6 p( \4 b( N4 T) K

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

" S0 g, u& ?) i

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

2 x( J0 l6 |" B- V# }

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

: ]4 O. W/ L, `) X

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

2 b0 _* I3 z! h- E4 f" {: \

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 % D9 s: l8 E5 w Y 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

+ z/ Y+ w/ Z6 F* F: F; I" \# m

前四行是最重要的,对应那四个SDT成员。 " w' Y9 f( T& F% `; Y- I 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

, l0 [1 v/ z0 `4 d/ t% j

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

$ O; o2 ]4 x9 q# y

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

0 O4 ~ _1 t- H% j. r. O

KeAddSystemServiceTable / h& |" X: H. r. Q5 ]- `5 \此函数去填充这些位置。

: |8 h4 v0 Q6 W9 P

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

0 u( _! U, I/ L

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

9 E% a: G6 p; o

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

6 {1 {1 B- I$ [

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

7 J8 `' X9 n* m1 ~

映射至ntoskrnl表格,ID在

1 l$ Q+ n6 P, [ s6 z

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

8 i, r X# @+ h6 M: x2 E$ M

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

+ N! w. {8 e5 {

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

6 n V8 K/ L% d, k2 v: F

STATUS_INVALID_SYSTEM_SERVICE。

$ q. i( ]7 q$ u: o3 m0 P

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

' i8 @$ O0 _4 X

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

5 s) U/ R' ~2 w

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

* V5 v/ r. d/ `6 P: P4 x

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

, K) u' D7 z" j

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

, f- e3 K/ ^, w6 v

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

X9 R$ M H" b$ O2 |, p# F5 x/ a

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

# o0 U+ R/ }) H' L1 }$ I D, a

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

* O7 y5 A* i5 g7 B z* Q& d; l

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

: k$ P1 Y" p; X+ ]6 B

. g/ V6 k/ b% d4 `2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

! g( U- h3 x" q: T/ m

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

" m4 j" U% E8 V5 B2 l( l7 [# g) u

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

9 S& v1 G C0 F# s x

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

: i% @% c; B+ }& \

; v; A7 a4 I0 DWindows 2000运行时库

/ m, ^8 ?! u" K% v$ o! a7 i% s

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

- Y1 Y4 Q& q) B* D7 H2 D

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

- j. m6 P! \+ S( J9 s! R- y

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

' M* V! a4 Q& C8 \# M" [

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

. N$ n% e( m) D

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

F+ a( Y5 ?1 G4 l% k+ c

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

6 [# C' A# ]/ k5 R; d3 p7 ?

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

. d1 O7 ^7 X4 V: x G- I9 z8 r* c

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

Z' J3 I- |+ c

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

2 D" Z+ f1 d2 }5 [% Y, p5 Z8 W

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

( b& Z! E) c5 K% f: H

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

& b" C9 _% a- n U+ `- q+ H

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

8 J0 c5 H: P3 o0 n! d! T* K

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

8 H0 E4 N5 l5 F& T" G " S: ^9 V9 F" H: p7 E4 K2 v% @

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

% h! Y# n6 G1 D0 L; ~; Y1 A% h

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

0 d& m2 ~' _5 j+ m4 W6 g

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

2 s1 x$ L/ |) h0 c

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

* _6 W/ s( e2 a& o( J/ c

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

$ o, x d/ @5 t7 A* i) ]

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

6 q9 {8 X0 d4 v, I e

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

U3 Q* k* p1 {6 k! `

常用数据结构

& `- B$ T8 f; ~

l 整数

0 S% r/ p; w0 i$ ~, L q- ~

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

; f# {( T* K0 s

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

I! l/ q% D; r# P' s6 O

不同。

, ?$ o3 p/ [! H0 U# j4 a: `/ C! X4 f

TABLE 2-3. Equivalent Integral Data Types

* ^ R1 b, b: }+ R6 B

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

) {) F1 m/ O0 k3 x# D! ]; N! R

8 BYTE unsigned char UCHAR CHAR 7 k* N& e ]- W2 r' s16 WORD unsigned short USHORT WCHAR SHORT

+ K, d c; ~9 v

32 DWORD unsigned long ULONG LONG

- W- k- g5 F& I2 Q9 m6 c% Z

32 DWORD unsigned int UINT INT

7 L$ j! K* @! m, U

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

4 O. q# z- W, d) h( l: G

80 TBYTE N/A

! ~( K; r2 `1 a$ Q, I

typedef union _LARGE_INTEGER

! q! o, m J+ X( m# v2 O

{ struct{

0 W( s: L3 l* ]* X# l

ULONG LowPart;

" o; C& I2 N/ T/ g: `( S- I

LONG HighPart;};

# w' W+ I- v# t* |. T. N0 E

LONGLONG QuadPart;

( V4 z7 N! r4 Z: F( j0 m

}

1 S1 W) o- E9 n! w

LARGE_INTEGER , * PULARGE_INTEGER ;

6 c. \- q0 W/ U

typedef union _ULARGE_INTEGER{

' ]2 g" T1 P4 {* d% D) b3 H$ O

struct{

3 Z; p& G: w; q' F

ULONG LowPart;

1 v1 Z$ F6 s; Z6 H' h, T) ?

ULONG HighPart;}

4 b% I) r/ u6 q$ f- s0 {; G

ULONGLONG QuadPart;

9 Y0 V2 V/ {' U8 m6 i6 B

}ULARGE_INTEGER, *PULARGE_INTEGER;

9 i7 C$ X( l( X# l. {: |9 L1 e

l 字符

8 `! |7 V* [' Z: g0 |& x

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

, u) L( f6 G& v7 L

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

2 j+ i" p: q1 u# ]( v" e2 D

typedef struct _UNICODE_STRING{

$ Y6 w6 j5 J* d! `8 m4 t

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

2 _2 g# J$ J2 p; h& T* `

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

/ y+ A# _$ S s$ W

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

3 ^7 I1 p7 @, U& g

typedef struct _STRING{

! d9 r5 S4 X% J' D, P+ Z

USHORT Length;

2 V# R- N3 J# H! U4 |

USHORT MaximumLength;

) o, I6 Q; m. L. R L

PCHAR Buffer;}STRING, *PSTRING;

# X+ ~( y$ ^; H- `

typedef STRING ANSI_STRING, *PANSI_STRING;

; G# c. M: @( m* Y# W

typedef STRING OEM_STRING, *POEM_STRING;

3 q9 d8 r% [& U5 p

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

2 g( Y1 [3 o0 X' c

RtlCopyUnicodeString()等等

+ J7 [' u+ l' Q' H5 H1 u6 L4 [

l 结构

g* b6 n, D& o2 R+ p" _; G2 b8 C

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

; a' B7 F/ ]" O& U+ j$ b _# r

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

7 u! H/ e8 K: u5 }

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

& g& u8 V; h7 ^

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

4 K |* _( T$ R) `$ k" y

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

' y: o" A- \* c& g* V7 ?' ?

typedef struct _OBJECT_ATTRIBUTES

: b% n8 {1 [. O) o3 ~- U w

{

( g: ] z% [2 q2 J& i

ULONG Length;

* u" A) O, Z! O2 |$ b

HANDLE RootDirectory;

3 ?& {% V+ U% s. m- H

PUNICODE_STRING ObjectName;

# v( ~% n# Y# {" i6 G4 L

ULONG Attributes;

2 e7 {1 p: @9 j/ f' |4 U* t2 g

PVOID SecurityDescriptor;

0 F+ M4 T# E- H; G9 A

PVOID SecurityQualityOfService;

" d" t) r ~ Q/ ^ d. R

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

s3 U5 U" Q% R2 b k" @1 S

typedef struct _IO_STATUS_BLOCK

6 s+ ~+ |. \, Z* ^! F: g5 @8 a5 Z$ a

{

7 `! l, r& R$ g7 a& z1 a

NTSTATDS Status;

2 z7 V7 R8 |- J" P% C! k

ULONG Information;

" q9 Z# K' `4 n) Y0 G! r4 [

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

9 Z( Q% a6 [+ Q3 @! h3 j

typedef struct _LIST_ENTRY

; x3 h' E Q/ c) }9 l. Y# R

{

$ j" S& T* ]& A: u! S; t

Struct _LIST_ENTRY *Flink;

' P7 C' X$ s; g# V

Struct _LIST_ENTRY *Blink;

% b) K$ f, J7 L7 N# J7 n

}LIST_ENTRY, *PLIST_ENTRY;

X. T4 i+ q$ w- w/ j) J

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

8 F# O2 t2 T- `+ S9 e# U: c

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

) c( H# Y X j) |+ P

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

, K! c3 C! B4 ~, c

typedef struct _CLIENT_ID

. q3 \8 l9 o9 ^: t

{ HANDLE UniqueProcess;

o% `2 h0 q8 |: r$ ~" r

HANDLE UniqueThread;

( G0 J$ m* P2 e

)CLIENT_ID, *PCLIENT_ID;

6 ?9 r: H' n& j: e% i

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

! Z8 k0 _7 p( c" z- A% y

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

/ A1 X. x0 x8 c- ~* y2 ~9 d# `

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

# ^7 M3 |' r" R; {) j d

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

1 ~( m5 m; R4 g* L( j

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

# r" D5 w5 l& K9 J/ i+ l

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

4 I/ M5 n0 n% L: k. P: c4 o

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

3 ~* o0 s; s( B, Z/ l2 r

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

I+ f& _# _0 B2 c/ K {- x

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

$ E1 h+ S4 E& r% b1 i5 T; W

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

9 N+ g( F% _- g. y

, t: @0 ? c# Z7 g/ G- K' S7 E 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

7 ] N4 d& V: L- k1 P" L, k

指导 0 [& f& j% a, f7 Q3 m

, ?! c- [" ]3 T! K; n+ p* A

5 ^! Z' N& X0 B

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-4-18 18:03 , Processed in 0.305269 second(s), 52 queries .

回顶部