QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com , }+ C* Q/ _" s J* T7 J来源:邪恶八进制 中国

5 l9 Y4 u+ c' J1 v5 J

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

: b7 u# x% I- s, i; H: i

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

0 c9 K& C+ p# e5 L( u; T# R

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

4 a' C+ S7 o w" D

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 5 B0 F& e4 @1 ~7 F. d' c( K: s, T

% C6 g8 v$ e, j- F' ]1 ]

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

& Z% O2 @8 A Q

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

7 n4 N5 b& h5 } A

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

' b) Y' }) H6 U1 }* x& m) T" T

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

. X n6 V# ]' w$ U. ]$ V

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

0 R1 H- [1 o% }- o% e2 i

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

% v) R+ d, E# w0 M# W

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

) Z, v4 M2 Z3 F; N

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

+ ^& w2 \; R3 k- h2 l

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

0 X2 P& P, t1 d

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

: I# Z* k4 q+ V1 `# K# C

反编汇这个函数得到:

# x2 l/ O8 }$ `- U c

mov eax, 38h

2 A8 G& R! G2 d/ M

lea edx, [esp+4]

7 D# @; \, Z; Y' @$ x

int 2Eh

0 r; X" w! @7 q b9 `

ret 28h

0 a8 k) D: f# n' b

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

; u k; I$ ?& i7 T+ q7 u& i& q! c+ m

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

0 P- ^* E: v) f6 k

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

' c6 t' O9 X& M: A& D

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

, @) j0 d8 T' x. H1 f e

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

0 I9 e7 I8 n- W& u

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

; [* K8 p# F+ x3 G3 f! F! g: o

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

+ V) @3 a4 X; l s7 `. j# {7 @

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

' p9 z/ y1 E9 U% ^

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

4 E" L" |3 ?* O

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

3 c* H* h w! c$ q) A! ]

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

+ P2 V, }4 X( ~* d. l' X

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

; V4 x8 v2 ], {0 l% H

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

0 o( [+ T' }- o% ]3 U

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

0 d( c2 @: h! O1 f5 m y; ~

typedef NTPROC *PNTPROC;

# d: V6 l$ d; u: `

#define NTPROC_ sizeof (NTPROC)

* K2 l+ b3 l8 H7 P& p% h% H

typedef struct _SYSTEM_SERVICE_TABLE

" [+ R2 R$ D# m

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

- x$ N: e: c0 x- z8 K, l1 V

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

! e% Y4 e, J- j: m* K4 e4 N; m

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

+ E/ b6 g3 V8 G3 |) ]

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

1 y# \, @" X _; d* h

) SYSTEM_SERVICE_TABLE ,

$ P0 `) ^5 ~7 }' R0 h* [8 ~( y! G9 x

* PSYSTEM_SERVICE_TABLE ,

/ G; J4 h$ m4 A/ J* M2 U2 }" b2 r

* * PPSYSTEM_SERVICE_TABLE ;

0 E# N: C+ G% `, |; k

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

) O7 W) O- M0 f: k4 `

typedef struct _SERVICE_DESCRIPTOR_TABLE

, I( p& v5 j) V+ Z9 ]+ O8 z

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

& g# g* m8 u$ ^( R, l+ n" g

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

! H! Q* C- L7 ~$ `

SYSTEM_SERVICE_TABLE Table3; // 未使用

; S/ l) S! r# q# ?0 Q: ^& B

SYSTEM_SERVICE_TABLE Table4; // 未使用

$ h% s8 B1 W* d' W/ `

} SERVICE_DESCRIPTOR_TABLE ,

; ?4 _; T/ S. c& F: q

* PSERVICE_DESCRIPTOR_TABLE,

/ I) G7 N- P. _. D

* PPSERVICE_DESCRIPTOR_TABLE ;

$ t U( p, Q6 g

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

' _+ F0 R7 y. P' B) | d6 e# |

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

( f( v4 [) w* U2 E1 l* D |& m

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

* f9 U' B, Z6 n7 w8 d( l* w- y

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

! p" B7 ^% r" Y# S. O1 R9 p

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

; i7 B5 ^0 Q l' i

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

9 d3 G, w6 N2 G+ D5 o a

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

$ a: K3 L$ n( T3 l! y

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

' t& `* | U' ], m( F

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

3 C. S( }1 A) L8 V9 L7 O: s3 ^. m% B" b

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

2 ~ G( }4 U! t7 D

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 . h3 ^$ @" j A* g2 F" _% G, | 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

6 L" w, r0 I% }, t H) u

前四行是最重要的,对应那四个SDT成员。 $ V" e0 J* J- a: R& F3 }4 ] 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

, n6 J4 \3 F( o7 N% ~

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

! o8 _0 b$ k+ l! Z4 h' x8 j# R, O

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

- b T! p3 q6 `: O& ?4 O$ } D

KeAddSystemServiceTable4 `4 W$ t' W4 Y0 y 此函数去填充这些位置。

, {* J" M5 ]1 C9 ]0 p1 _4 W3 ]

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

" I; U+ c8 f% P/ a; c% K4 q

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

$ p! e2 r8 y) @! l8 ^

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

0 a9 n. x0 c& i

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

. I9 A4 L$ V! {; a8 P/ J) m

映射至ntoskrnl表格,ID在

; C' S l0 Z, y4 a

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

6 J8 |; w. s1 q* R6 ]2 L

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

; z& l0 \/ Y7 _' u( }

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

& r+ X( G% d, m

STATUS_INVALID_SYSTEM_SERVICE。

4 Z7 i) M( z* K( t

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

. M: ?. i% [/ |/ L

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

6 |" m t% p' a6 X4 w# G

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

. Z$ L% f- S4 K7 A3 d/ O

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

/ o" X1 r2 x0 l4 _6 }# J7 t

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

0 G7 R4 |4 W B3 Y( P" ^& @

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

1 {% }0 p) |8 p4 k! n) |5 ]

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

& C$ j5 W2 k! ]2 ]5 E5 }3 X9 x5 J

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

, v/ E. h. m( L; b8 p0 m0 B

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

8 |5 n( ^" D! t; t+ e u

6 I# s( b* l. k$ [ 2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

) v6 J* Q7 H" |

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

" `; O* G0 s) ?* a! i" ^" Q

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

, W$ L; q9 s& n

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

) j3 R% }+ k! d. ?8 b }

. M7 M: N9 q j$ D! A8 h; j Windows 2000运行时库

# m" f. o4 z# ~, j/ O

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

+ E. p, F6 O& U" U; m) }

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

7 ~# Y- h4 v) C+ z' J! e

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

5 v2 N6 o9 w8 a+ i# I" I# k

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

/ n1 K) V. i/ J& z6 {, ~* f

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

9 g* E% s( `; z

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

8 h3 Q0 q; |( D, |1 ^

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

. y9 ^; }0 o5 z

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

7 v V* X! q, r# s+ A3 Q6 h( I

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

0 l+ S$ F9 K6 L

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

" _6 e3 `/ [ s) ]

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

; ?* s" {7 I+ Q

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

4 {3 L% i( p, c* r! \3 }

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

) J# h8 j4 s2 x- ~ k 8 w5 l" k7 N& _7 Q/ I3 q, o

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

# F' Y/ v+ o% W7 r9 M

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

! `. E. n& \3 N \ u

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

+ ]6 T# D& ?/ m) x' @: s8 |6 I

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

$ o5 ?! w& f: \

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

; N' W- p1 d' t$ [8 D1 U `

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

: d$ y) c. e, m

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

" c$ ^7 K4 w; d

常用数据结构

& `# ^/ }& `3 j5 m5 e- o

l 整数

/ Z; M$ p# _: w6 G( O

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

* y( n. U- R/ A4 G a

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

$ j( u$ f! D! D) F$ T* r

不同。

0 J$ ^- [' o7 e

TABLE 2-3. Equivalent Integral Data Types

/ K! ^. g3 N& h {* L" H& f3 V

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

' L/ r% C$ F$ ^1 [: C! W1 ^

8 BYTE unsigned char UCHAR CHAR# L! X5 y; N4 ~5 ~# _- S5 u& x0 ] 16 WORD unsigned short USHORT WCHAR SHORT

* E+ t& [ @- G9 M

32 DWORD unsigned long ULONG LONG

& k/ r" t7 {7 v3 W# F9 S+ q, {/ P5 I

32 DWORD unsigned int UINT INT

6 E6 y4 c8 Q A- f% l9 s/ L

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

- b5 K/ x T& [4 K8 f

80 TBYTE N/A

/ A7 M, n( P* n% N

typedef union _LARGE_INTEGER

9 e- b1 V% ^) O. x2 h2 o

{ struct{

7 I: L( P0 F3 F0 D* C

ULONG LowPart;

- G+ @4 g7 \) `* j. f1 t8 @) R- ^

LONG HighPart;};

" P) v+ b8 a1 Y% p1 Y r }

LONGLONG QuadPart;

9 R! g! k& x3 f! @3 e

}

7 m8 v0 S/ |- B! _6 b

LARGE_INTEGER , * PULARGE_INTEGER ;

1 I6 b* X" E! T) D( H

typedef union _ULARGE_INTEGER{

g. C7 D+ {" h

struct{

, d5 f4 }! V& e" B: q8 U

ULONG LowPart;

2 p0 \( s1 L% Q9 z

ULONG HighPart;}

1 [& x* f9 [7 [4 b

ULONGLONG QuadPart;

1 T9 l v) a2 g* |

}ULARGE_INTEGER, *PULARGE_INTEGER;

. a- a2 l# M7 i' X

l 字符

/ {- r7 ^6 q1 x4 H7 a5 z

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

0 @7 Y$ C" u/ }

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

- b. W2 l3 V4 e5 U# W# s4 J

typedef struct _UNICODE_STRING{

/ x1 D2 E/ h9 c: J

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

* k5 o; a) S8 A' R2 I& f3 k5 Z, c

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

3 W& J% q! y4 s! Z. d

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

' x9 V$ f5 Y. L: A, }1 x, L8 A* o

typedef struct _STRING{

# F5 `6 R7 w3 A3 x9 U- J

USHORT Length;

( _" ]5 i' }7 R$ c$ e% R

USHORT MaximumLength;

$ M! }" b1 S$ G6 H

PCHAR Buffer;}STRING, *PSTRING;

3 U P' Z& c+ A

typedef STRING ANSI_STRING, *PANSI_STRING;

) L7 R. E0 p' |6 u% X2 d+ P9 z

typedef STRING OEM_STRING, *POEM_STRING;

. e! p6 v: q5 q7 ]5 t/ b

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

+ p0 R1 G9 F7 F; l9 E* z

RtlCopyUnicodeString()等等

" @4 A A, W4 v7 d% K! H, `" S

l 结构

5 s: P, L' z# [5 c$ Y% n

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

# V( H; p/ q( [+ R/ m3 V% N' V) i

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

4 ]/ r0 ^/ |5 d4 ?

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

4 w0 L9 X% J3 h: y# S" ]7 d1 R

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

0 D0 X' b" G- P8 x% w8 O4 j

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

& @/ W. _2 ]- L" |5 `

typedef struct _OBJECT_ATTRIBUTES

/ W9 m- v$ {0 X; k _

{

; o0 S# O, W% f9 Q/ q, }7 N4 D1 i/ A

ULONG Length;

+ C1 h& G2 P; M- s n& A w

HANDLE RootDirectory;

* _1 ~' f4 s8 @, y5 J

PUNICODE_STRING ObjectName;

1 t7 `6 [( t+ h* G4 c6 N

ULONG Attributes;

7 q: f- U/ ^+ z0 f' b1 T$ Y9 I

PVOID SecurityDescriptor;

! B* k9 M- }/ D7 N& C

PVOID SecurityQualityOfService;

# D# p8 i4 C% w4 [, f& q. G% J

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

1 \- {8 F4 _) x1 G2 O1 y

typedef struct _IO_STATUS_BLOCK

* o$ u! D# w* z0 J

{

- u4 x( ~' B" l' r8 W$ g

NTSTATDS Status;

; I) y$ ~( }+ l

ULONG Information;

/ y: p4 _4 |# t, q% ]" \( ]

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

$ P' O% B$ B/ q; }# x

typedef struct _LIST_ENTRY

# C. m+ n2 F" u& ]* {

{

9 { O' \% b0 S& n

Struct _LIST_ENTRY *Flink;

- d! d w3 Y/ \/ }9 s+ x

Struct _LIST_ENTRY *Blink;

! @! \+ n, C! @9 |

}LIST_ENTRY, *PLIST_ENTRY;

5 @/ }7 ^- E- M% U

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

' Z8 I9 m$ ^1 x( L, K

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

0 B/ W! G8 A# n3 s. g/ b0 W

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

, c" p# T% b, ]( X* G

typedef struct _CLIENT_ID

" F3 v2 x# u+ S) Y

{ HANDLE UniqueProcess;

2 c' `* x {( D/ b* A- `, ~3 R7 J

HANDLE UniqueThread;

( o. r7 ^" b/ v: U4 X, ?

)CLIENT_ID, *PCLIENT_ID;

6 X7 d* o: A' q( K

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

! I1 X; `- @7 b7 V% H

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

) p2 K$ d6 ]1 y1 c( @0 M1 P

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

! \6 ?- C: f. e0 _& b9 w9 }

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

D: B6 g) P9 z; o

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

0 ?2 M4 n S# o" e' c; ^/ T

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

4 ?7 O+ h* w7 c0 h4 b! L' ^

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

2 q5 N+ `9 l% d( O

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

' t, u- s1 f, I2 V; s! A j

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

; [) o' v. H9 P! p

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

1 d- K( q8 F) }) p

+ Y$ V) ^0 R9 p& \3 Z: r 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

5 O! k# A& o+ v/ m: z$ t

指导4 a! W: F1 f3 k9 S. x s

6 ]& ?) G1 W5 s" K6 |/ i

7 V6 |7 \9 M# i7 `7 }5 Q7 l

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-6-13 03:30 , Processed in 0.423501 second(s), 52 queries .

回顶部