QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com - Y. S3 o( E2 q- _来源:邪恶八进制 中国

& d- _$ `8 M+ D h

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

3 T( Z6 S O6 W6 p- k( B# l/ T) ^

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

' M4 C3 |& a, Y. w

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

& u3 Z+ n2 o# n4 ?" B1 D

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 . `, X/ T. y- u" m. j* b

3 H) `( {# Q0 T* }4 |

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

6 W0 r0 F- D% R0 P' f7 L

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

3 R; q- F- G, n9 G+ ~6 a0 Z$ k

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

" s: I7 n7 z8 ~9 P# U

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

( x! b, C4 P% Y5 Y

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

$ z) q; L! X5 ?8 ]: c. M, F

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

) d+ n5 N$ u/ @5 l: L

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

) ~4 l; d' r0 r

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

, ] z$ G `. c9 I

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

$ O# n2 U1 ?& `

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

& h4 L" {/ W- K) I3 [% F, @

反编汇这个函数得到:

7 y3 ?3 N2 J4 g4 J) `( |

mov eax, 38h

) e; b& ?& [/ [1 l

lea edx, [esp+4]

/ m* d( S; s: e

int 2Eh

, n) {! K- r0 H1 \

ret 28h

. I- s; J4 q) l9 _

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

0 b+ D) H4 o' j. x I( ^9 ~* x; U

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

& S* n4 K# ~; Y

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

4 ^% e0 O; I4 |

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

d# r- H. k6 s& }" D$ b6 `

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

8 y, I$ D: |+ P6 ^4 F( Y

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

7 }) s3 Q4 _& ] n* @; c& h

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

9 @& O5 q% u0 L+ y

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

, q" a4 v1 W* O6 W- Z

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

) R- |: G' m. @- Q2 H n

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

' |& ]) o5 g4 P. v7 m- r ?6 T# U

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

/ F* i/ T) H2 P, n. Q

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

7 F/ a3 e1 o3 ]/ B

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

4 [% _9 T+ l) t1 ]

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

* t& F( @* b. p9 c

typedef NTPROC *PNTPROC;

! C6 ?. a: s4 E! Y! e; q

#define NTPROC_ sizeof (NTPROC)

& x; F6 |$ d; \! \. }

typedef struct _SYSTEM_SERVICE_TABLE

- q/ u7 r! N" h% ?

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

' }: [* c" ~: _4 C4 R

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

5 W4 o' Y2 H. S

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

6 O! s4 }! S4 [/ ]8 v' p* ~/ A

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

& x& D0 n& _8 g

) SYSTEM_SERVICE_TABLE ,

3 G+ b- u8 l* W8 P2 [9 ]9 O( H) y

* PSYSTEM_SERVICE_TABLE ,

' |4 Z7 x, G4 |

* * PPSYSTEM_SERVICE_TABLE ;

' V% L0 S( x. |

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

: ^& E6 A7 [% C) `! b* O

typedef struct _SERVICE_DESCRIPTOR_TABLE

& N: H8 s" f! [

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

0 A4 }6 p& P- V3 I

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

/ e# z: R( c6 G4 k% j

SYSTEM_SERVICE_TABLE Table3; // 未使用

( K- H5 X0 Q1 S/ r! f0 X

SYSTEM_SERVICE_TABLE Table4; // 未使用

0 w7 d* b; ]( K2 T, m# {4 w" y

} SERVICE_DESCRIPTOR_TABLE ,

( G8 M3 n6 U' x) |

* PSERVICE_DESCRIPTOR_TABLE,

1 P5 N _6 q0 J5 c; c' E7 {

* PPSERVICE_DESCRIPTOR_TABLE ;

5 B1 P! i/ W" R4 @0 @* r6 W K

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

2 e, N( p3 w' D: t

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

0 f6 Y$ a( L6 \' i+ g$ q

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

7 J0 q! W. H, c: w! N9 j* m d

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

0 [7 F9 \" W* C" P

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

- o2 U, C9 h1 k+ Q% Z0 `2 f9 `

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

( @5 J8 }7 m& O% h" Q; c( U

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

$ E* E: R% c. \- u; d- W9 ]# r# \

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

$ C. {9 p$ g' q2 J

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

. E' `/ d" G9 S/ u

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

4 q% h2 Z: H' a& R6 C

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。 : h- Y; V5 J$ y2 _+ q0 z6 x8 Y 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

: k% A+ ~1 ~! p. @1 a9 P

前四行是最重要的,对应那四个SDT成员。/ j C0 |1 Q0 H* F+ b; |" y, ? 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

y, D8 i+ j. E6 U% V5 F. [

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

! A7 Q; G2 S" c- h1 ?

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

5 b% Q2 E4 |4 Y+ V+ e+ v' A! u! r

KeAddSystemServiceTable " A& _. d5 C4 e- G2 q2 Y此函数去填充这些位置。

) `& E, Q, n, c1 k. R- [5 g! c, r

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

3 L. L! i# q% P# U; h+ d

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

( Q2 h! t7 k9 n, t2 ~2 Z7 V

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

' W, }9 b5 o) J

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

9 K. p# E7 Q. V0 B

映射至ntoskrnl表格,ID在

! ^3 V$ F+ [4 N e

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

2 a9 z" I) C1 L2 c& k: O7 B

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

0 x4 w3 _0 i9 q* t

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

8 n9 E. X9 n& q( i( z

STATUS_INVALID_SYSTEM_SERVICE。

) G. `+ m, O* A

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

" X+ U& e, K' m# r& I

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

* H! e# q) ?2 L# E) Q I+ d3 g

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

5 m2 K, z3 e# c$ ~, B d

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

7 C; a) j$ o' x5 J& V, M

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

. y# {8 w6 e. J/ C& B

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

" Q" i, s2 Q* a& T% j) f

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

M6 D) L. [" Y: L9 } k

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

3 W$ a5 b) [6 j7 _1 k

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

2 N1 x- C, w" c4 J) `

6 F7 f' e3 M- o! [( W& B- ~ 2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

$ ]- w, n9 U, D- y$ s- H

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

% b* ^, [/ a' l. Z( C6 K; g0 G

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

5 M7 \/ B6 Y: @

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

% q) N+ ~ j# W* v: K* Q3 p

0 m2 }' g" U' d/ `; a7 V" e. x Windows 2000运行时库

: @# S7 u U* l- V

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

# k4 J0 O6 _: m- C F$ ^0 X' G

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

; O0 B3 n1 Z! f5 l6 L n- W" O

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

+ o5 K) I5 w7 \* U

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

, H# H% K0 q5 ^! b! S5 Q

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

' A8 f+ x, `- _- x5 D

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

5 z" Q2 k2 ^+ T/ q, h

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

) W9 ~! V8 q. O" r3 R! s/ I

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

, ^( o7 j2 j) C0 W6 ~) @' w/ B

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

* N+ h0 g" }) h6 o, U B# p% S

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

' z& @% B& {* r' J

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

/ [* R- _+ ?+ k6 ^

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

5 R! _ `1 b' l4 x4 b3 n

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

6 j) h6 I) }: o' } * H/ e7 K, z/ i! U! @/ n" ^6 u* a

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

+ `$ Z+ i9 s7 u X7 j

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

" {) _! m, b1 p

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

$ O S/ ] l, }# B: A$ D( D% d

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

0 V9 r6 K z7 ~4 c& S3 w( _0 S

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

5 |7 l' H& q8 ^8 J6 H

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

7 m* \& y" f7 B4 \

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

1 ~8 x; x* }9 ~4 @

常用数据结构

& }" M6 }" t; j

l 整数

$ ?- z' N( H3 y2 g

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

4 [: X3 b; a( g# y# V/ J) D6 e0 R

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

$ P/ Q) a8 m- K9 @) L! P0 x6 ~

不同。

5 ] g2 B- b8 y, }( P5 E* K

TABLE 2-3. Equivalent Integral Data Types

+ ` }- I$ Y; s& s

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

- q1 _; E e4 I2 u S1 G/ {

8 BYTE unsigned char UCHAR CHAR + \: ?8 d( m8 ?& s V" z16 WORD unsigned short USHORT WCHAR SHORT

, R$ }( `" C: [ J' d: a$ L, m1 A( f

32 DWORD unsigned long ULONG LONG

4 f7 b ?* Z4 ~2 T( Y8 u# d

32 DWORD unsigned int UINT INT

- `1 ^6 T& k2 G) j. e5 r

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

( G0 e" L8 g$ s$ u

80 TBYTE N/A

3 ]" B! T1 s# [

typedef union _LARGE_INTEGER

* |9 ^2 r; t7 C

{ struct{

* r) e2 s* r: e6 j4 w' a) B

ULONG LowPart;

% t( M8 \; [) M6 J% Z

LONG HighPart;};

7 |! `+ j& m) j! x" ~, Z0 r

LONGLONG QuadPart;

/ ?8 F- R! ^# q

}

1 D3 `4 q% o3 n

LARGE_INTEGER , * PULARGE_INTEGER ;

0 G2 b! } A* ?' w L( m

typedef union _ULARGE_INTEGER{

% y' `9 x( y8 A

struct{

* L! e- l' K0 L9 q* u; U

ULONG LowPart;

v" U% B* A) X4 W( i

ULONG HighPart;}

, e# T8 o5 ]4 e+ J( Y

ULONGLONG QuadPart;

6 y3 b( |9 f+ h0 U

}ULARGE_INTEGER, *PULARGE_INTEGER;

1 q; u" q) V, J* o; T" Y

l 字符

$ |/ B5 I. ?% a" |5 k3 v8 L9 B3 X

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

4 }; K' ]0 {0 Y) Q9 D* K$ C' p" j4 Y

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

: V, ~% c0 h& w# s1 _" }

typedef struct _UNICODE_STRING{

* e/ r) G8 f* d

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

! i: q$ Y: J% |* K0 _" |, Y* J

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

1 C9 S- N/ E6 {, h

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

# V# s) N G, u( p: { s# ^

typedef struct _STRING{

0 c- \" T; s, k! q" d3 B

USHORT Length;

! J8 J1 a9 H0 Y! w$ C

USHORT MaximumLength;

' r- M. \8 a9 p2 d

PCHAR Buffer;}STRING, *PSTRING;

9 i9 L; f2 H5 T2 Y8 A

typedef STRING ANSI_STRING, *PANSI_STRING;

2 [: M8 l7 G# W$ m" l5 T

typedef STRING OEM_STRING, *POEM_STRING;

Q6 Q9 E! T) }7 P2 Y/ t

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

# p. @5 i7 M7 X! ]' N

RtlCopyUnicodeString()等等

' p# l+ q! ^ [# x8 j# b" l# u

l 结构

) Y1 T( ^8 c7 I3 k5 g' W9 Q

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

5 w' H$ i- H6 Z- j+ ~

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

% M( f2 B/ a2 W9 }; V

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

1 \) b+ w) e h4 N; X9 \% i0 R

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

2 v- x F9 Y8 J- B' x* X

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

# Y( g% ]/ l& Q6 g( Q# k

typedef struct _OBJECT_ATTRIBUTES

7 X& S, |) T6 X+ u

{

4 i v; ^+ r( O- @

ULONG Length;

/ ?; R- u1 p+ Y

HANDLE RootDirectory;

, P3 H% C/ ?4 H% m, A; M

PUNICODE_STRING ObjectName;

0 t* y/ W, @, E* X5 U2 `, L2 Z7 Y$ P0 O7 }

ULONG Attributes;

, h5 O2 ^7 P# _# k' Y1 A- U6 a

PVOID SecurityDescriptor;

( k3 Z. v$ M7 v; }0 @3 V

PVOID SecurityQualityOfService;

8 e8 m% D! O: O" P' @. t# q2 ?2 C

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

. ?- U9 f( J4 n# A3 A! S5 S

typedef struct _IO_STATUS_BLOCK

9 J2 `# q: r5 A- z1 K- K

{

, V$ V: C$ R4 T2 E: P

NTSTATDS Status;

8 i% B! n$ }% x, k

ULONG Information;

0 I6 P& q* ~0 C+ G* ]2 K

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

a* l8 l" E# S# V6 t; A

typedef struct _LIST_ENTRY

4 Y0 U! s) R2 ]

{

' j+ J( G+ ]# r7 e$ f# n

Struct _LIST_ENTRY *Flink;

2 l* {% d! s9 i

Struct _LIST_ENTRY *Blink;

+ k k& ^9 Z' w$ _( \! F9 M* {

}LIST_ENTRY, *PLIST_ENTRY;

n% p* _1 Y; a; T# C8 Z. [$ l

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

0 {0 _0 A4 r- B$ G! `4 o6 C/ G

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

j0 _6 u& X2 L! D, i

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

* V8 v" a$ N R8 c% x" p: k5 o

typedef struct _CLIENT_ID

$ u* R& ~3 A$ v8 \6 n' H, D9 V

{ HANDLE UniqueProcess;

. y6 i' |$ t* x7 _* ?1 n

HANDLE UniqueThread;

* p9 H1 x6 \0 [0 |& Z

)CLIENT_ID, *PCLIENT_ID;

8 Q* a9 u' ^; v3 ~ c

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

2 Q) C; y1 ^7 [0 r/ l& F

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

% ~9 Q6 V- {4 ^& M; V: X M! Y

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

" |2 x5 a1 l# M; m

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

$ J0 m" |6 v4 s# [

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

! E6 x0 S; R3 n/ M4 e6 E. [

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

8 \' g. m9 K7 R# [

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

4 ~5 M$ d- e% M% K& O& g. u6 u$ H; a; n! m

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

# `0 N4 t$ l* B9 P6 Y0 |

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

- \2 g8 y0 ~9 r+ T9 y% r

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

! @5 A! y9 G0 Z9 [* q* b7 z2 B

! t/ {4 F3 i& j. K本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

3 }4 Q1 b* s b7 i% T

指导% S# S6 ~* y) H+ z7 |

& H! p6 `/ Y1 c. g" ~

$ I/ g5 w0 ]! e$ o$ C4 g1 `

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 19:57 , Processed in 0.329561 second(s), 52 queries .

回顶部