QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com ( ^) p) e5 E$ J8 a2 p% P来源:邪恶八进制 中国

+ ?+ B B7 i5 v& J

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

F* l; y0 [! z$ ]9 G0 ^3 N+ c

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

& n/ h: [) V3 ~* Q

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

* d) g9 ^( M Y9 Q) V. N9 ?

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 $ x( S# Z! P0 B8 G1 p

$ O& H q- V: I( S

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

* u4 _9 U' v" `: ]

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

3 A8 A3 l+ D7 ^: b2 T \! i! f

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

, y9 d9 P" L* j/ _* u3 \

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

. c4 C' L1 F( t: `0 y+ Y2 I T

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

% E3 K+ v( N- D, N) S$ L7 J* e

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

8 W# U8 h C$ f$ M6 r2 g7 N

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

, y" ~: ^9 p$ ^, m" ~

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

% e1 p6 k' K6 O% U

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

9 U3 A: C1 I7 Y. n. W: f- c

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

- V* _( a" `: B9 u( G4 J

反编汇这个函数得到:

; W, }1 y+ t# v

mov eax, 38h

6 Y, z; t# d: h! ]/ N% J

lea edx, [esp+4]

* m3 N6 v+ X+ p- m

int 2Eh

' ^4 P, L) _; q T

ret 28h

- J) P; L% ^, C* ^7 F. \+ f

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

& _# v; a8 J( W# y

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

- b# ~" Q# r0 K6 L+ E

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

! ?0 P6 N3 R3 S, @; m& m

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

! q( J: n3 O% c* e& I9 ]

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

5 f( Z2 R5 N; A3 z& D8 u

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

1 q+ W4 P, h6 l. H$ @8 o

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

5 |6 H6 q3 M! j I! n b

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

* X0 W8 u: r- O6 P1 F1 }

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

2 B; Q+ X& M- Z T4 u- B

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

: {4 ^+ f: ~: Z

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

2 [& K% T- V0 z0 J* J1 @

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

0 | N9 A3 V4 f8 M. `

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

: {5 a( m* m0 s& @2 l$ m

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

; @. I( l2 b* w0 U

typedef NTPROC *PNTPROC;

4 @! m8 M# H5 M+ o, i

#define NTPROC_ sizeof (NTPROC)

! t- ?3 [, l2 K6 v! m |3 {

typedef struct _SYSTEM_SERVICE_TABLE

6 D7 O, I' o2 c" n' Q1 ~3 o7 p

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

. @0 o. d; G% G3 y4 u, N$ R2 J K

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

: A" s. q# Q# }

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

: G5 u9 x B* e! y

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

8 B/ c6 H* i- D& b) o8 P( l4 p

) SYSTEM_SERVICE_TABLE ,

, E8 N/ h; q0 U" g- G

* PSYSTEM_SERVICE_TABLE ,

8 M3 q! f6 u) O

* * PPSYSTEM_SERVICE_TABLE ;

% F6 ~1 ^$ b' h) F/ M

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

; F: C m4 Y- Y& D' I! x! e

typedef struct _SERVICE_DESCRIPTOR_TABLE

# L* _; h3 y2 N: e2 C, E

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

5 n) i% E9 o& b8 p, i

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

/ H# i# b+ U, q4 E0 {4 s5 u

SYSTEM_SERVICE_TABLE Table3; // 未使用

! C' G! l, c0 }6 X

SYSTEM_SERVICE_TABLE Table4; // 未使用

0 J9 G5 F3 F3 c1 ]+ c4 r$ _

} SERVICE_DESCRIPTOR_TABLE ,

5 K2 C! N" v/ @8 m5 Y. ^9 E

* PSERVICE_DESCRIPTOR_TABLE,

" |7 q# A* E3 v9 N* x& D

* PPSERVICE_DESCRIPTOR_TABLE ;

! P, R* W9 ]+ N- d, o

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

: q u! i: [; w* E2 Y; Y- `/ U5 x

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

1 p6 f0 P. T' }+ u" E

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

- }5 b+ S" o* P+ @, A% G& ?

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

8 a" D }8 k" A# F. r0 o( K

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

9 D/ {% F% u ~2 k, O1 w* v Z

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

! [7 o. t% _: b1 o" Y/ w8 E/ l

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

) Y8 t# ?8 E4 A, {/ u/ h

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

" n! C1 X S7 k

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

8 {+ j7 l! g: p4 T& }7 k1 v8 O$ B

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

# P! C$ ^# v+ `4 a0 v

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。1 w- T' J9 O; X) q2 q) r 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

3 z: ~) I1 h& x) }- s8 V2 G

前四行是最重要的,对应那四个SDT成员。 3 D( l% k" Y! b 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

% J" z$ z2 B( f5 s f$ F

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

5 e2 j6 t0 @* _; A

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

6 ~1 H# O1 G4 j. o: i

KeAddSystemServiceTable ) ?2 H( H- x4 \* w/ U; P2 c此函数去填充这些位置。

% Q, k3 V: k% \: G

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

1 z8 R2 x3 }; d+ |

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

( U* C- `& C& O3 u4 E6 L

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

) h5 K3 ?8 ~' {/ R* ]9 P! q

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

! S& U0 r) ^7 E* }" V

映射至ntoskrnl表格,ID在

$ t) m+ y, O$ Q( M, V

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

4 k; r9 _) y. V

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

) f- i" B/ {$ S

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

* m4 i' I, P ~

STATUS_INVALID_SYSTEM_SERVICE。

7 T! H1 h9 Q* I/ _/ F9 o/ d4 N; W

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

# t1 p, V2 q& m: r

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

+ j7 U6 d) O2 O1 f

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

* D' }3 Q6 o0 M! q( A

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

* U% q3 i, r# D$ ^1 f+ W

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

8 W# h% O5 U' J. S9 [* a4 u

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

: V3 L0 _, l3 B( z1 S9 }

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

' ^: g, r0 y7 l/ k P# f# k

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

3 C) Y1 l1 Q! E# d9 b

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

7 [' m* o, ~$ h- ~) r

9 K/ b8 U/ z9 z( w0 t, G2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

0 F4 ?3 C0 t4 x r" ?: F

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

8 ~9 X# u3 d4 ]2 R3 m

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

2 @) J" z! m6 r/ f* t

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

" z" C0 Z' x( r. Q

; B; }! o, z- z' [Windows 2000运行时库

! v; n' }: `3 M0 Y% E U

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

' ]/ W: V! Z% N6 m+ q. N

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

4 a* [2 U k; o

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

) E+ \- H6 o6 a5 J# ]: l6 B

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

3 W" |! h- F# w6 Z

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

, U7 r+ l8 i; Y8 c% s7 Q

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

7 A9 a9 i; y5 z) s* [) w6 u

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

8 K' n) \: Q: P( o. ?$ [, ?- F. f

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

/ e" y$ H" G' p" t8 q* j

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

' W/ C) P- j6 d8 N

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

7 d5 x5 C6 p: T" J! ^8 g

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

! D& b: U( B; g# u: c( Q& s/ e

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

! g. u6 q2 X/ n5 p

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

0 {0 }! \$ R' m) F2 @7 l6 K # g# A0 ^: P, D& A5 f

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

; k' _0 v0 v. x! P4 f

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

' w- Q! T' u7 r6 A8 X/ m

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

+ Z- B, D) Q, M$ G0 d

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

/ ^% Q' D' @" n' u

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

; N4 c a. {6 ]7 D8 D

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

; w. t4 H# W2 C# V2 m. v9 {/ b

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

0 y, p1 j) c3 D/ ~

常用数据结构

& r$ I( G' O3 N/ x) E8 c

l 整数

3 i- |4 C {. S- `5 m

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

$ ~0 r& k2 [5 }- z4 `5 _5 ?

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

' u7 R4 S4 j- K1 @

不同。

6 D9 }- Y3 `* O, r. d

TABLE 2-3. Equivalent Integral Data Types

8 l' V% G, `' h

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

& a7 j0 c- [, U% M2 R6 u2 A& @

8 BYTE unsigned char UCHAR CHAR( c% U! F6 y8 v) B 16 WORD unsigned short USHORT WCHAR SHORT

% o' R& ]' |5 e+ X! H. P8 h

32 DWORD unsigned long ULONG LONG

- K& j1 @0 c4 c% t, R

32 DWORD unsigned int UINT INT

) W% l: x: Z# w8 I

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

' b- P J5 s I/ [8 C

80 TBYTE N/A

% |$ G# q' l4 J+ @9 W

typedef union _LARGE_INTEGER

7 ]/ a0 f: _: r0 E5 T

{ struct{

. \0 b0 U/ G9 N8 ^4 }+ h( z/ W- A

ULONG LowPart;

6 K- E7 w# {5 H7 r; G! C

LONG HighPart;};

: d" A- a0 y$ Q4 _+ u( y& I

LONGLONG QuadPart;

5 r4 p+ `/ N! B3 D- v" R' X

}

3 H4 G1 l" C1 k# d# k c

LARGE_INTEGER , * PULARGE_INTEGER ;

* l- |9 N2 o& i* C

typedef union _ULARGE_INTEGER{

2 G, w& @0 J4 W' z% Q! l7 p

struct{

# Y$ L, s p* y5 ^, ^3 Q7 u

ULONG LowPart;

Q7 E' F7 k+ X* y) u# O: t

ULONG HighPart;}

2 D* V; s+ D3 q2 B( E( P

ULONGLONG QuadPart;

- p) \3 E! _6 o) J

}ULARGE_INTEGER, *PULARGE_INTEGER;

. p7 R$ Z h2 x: n

l 字符

( F8 _! q3 O2 ], d" {/ M

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

2 L0 W, [% L1 D6 P Q: s

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

. A7 P3 t7 y+ N. l

typedef struct _UNICODE_STRING{

7 @; x. C2 c. G0 Z

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

2 U3 |! [! U F" K! [7 j) }2 `

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

# m6 _' M* Y+ f( ^# q& S( I. [5 {

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

% x+ _5 z2 H$ f; l

typedef struct _STRING{

% t" c* E- M. w3 N1 J4 [

USHORT Length;

3 X( K, G' W' J0 n- l$ _; r

USHORT MaximumLength;

+ g) _1 I0 I; J- `: C0 T

PCHAR Buffer;}STRING, *PSTRING;

, m* X \# L; |& e

typedef STRING ANSI_STRING, *PANSI_STRING;

' q+ u; j- T' A" m

typedef STRING OEM_STRING, *POEM_STRING;

$ p. c# A) f4 l% p

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

4 L0 |1 Y' l8 Y( \+ l

RtlCopyUnicodeString()等等

4 _5 @* h* w) N" w

l 结构

$ }: u( h0 o9 J8 L

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

6 e X) a8 X5 P, i

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

# n4 ^5 |, h. V6 \; [, i

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

V! o4 G A* ^) m" u4 ], Z

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

# J9 ^0 Z# q* T8 b+ _& R

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

' e; _ k) I0 W3 _ [

typedef struct _OBJECT_ATTRIBUTES

. q, {! b+ K5 ?, m! C* o; w

{

8 a, N/ H6 }8 D7 @0 P2 V

ULONG Length;

) {9 d' z- ? ]5 v

HANDLE RootDirectory;

! i) N/ i* F, c, @0 o% O

PUNICODE_STRING ObjectName;

$ K7 [: f9 B4 M# U: k$ Z( p# l

ULONG Attributes;

6 m3 n# I) \" [' Q' B) x

PVOID SecurityDescriptor;

, B4 K! S, @( v$ l4 {2 N! N% W5 u

PVOID SecurityQualityOfService;

; J% K! e/ E' u2 s

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

% i |- |4 e9 q+ k

typedef struct _IO_STATUS_BLOCK

& M5 `- V! g% G/ q2 k2 I2 M& t5 S

{

( `( u, t3 v+ h; d3 d$ G

NTSTATDS Status;

4 I9 x& M& v( }& c: W8 _* q

ULONG Information;

5 ~" I) a; Z& `

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

% b4 G3 @3 n/ g) u9 Q' I

typedef struct _LIST_ENTRY

3 t' c5 P c! a# I! ^

{

. c8 ? T6 Q* w5 g$ ^% m6 q6 r

Struct _LIST_ENTRY *Flink;

9 O; C; b5 K6 J1 E) ?/ u: O

Struct _LIST_ENTRY *Blink;

+ c! S% m, L0 R7 R1 _

}LIST_ENTRY, *PLIST_ENTRY;

; I' D- M+ c0 v5 k) b

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

7 a5 o6 e3 ?9 [2 h% W8 Z# [) i2 y' ]

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

2 [1 D$ }- T$ _

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

/ d3 P( i: a k5 n

typedef struct _CLIENT_ID

! n! ^! H4 B! @( V' ?5 [& _

{ HANDLE UniqueProcess;

9 w$ }- n, j* q" S* Y4 j

HANDLE UniqueThread;

! L# [* J! `1 R

)CLIENT_ID, *PCLIENT_ID;

# y% V1 G- a7 N! c& q) S: K4 O* p8 w

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

( D. ?4 f) P2 w+ S- P, c

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

! l" [) N& L+ G+ Z$ y; {

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

/ g2 c. w+ F! H' \% o0 I/ l( N

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

+ h4 V6 H( C1 i4 R

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

3 E7 L/ A. l T2 a3 r2 j9 `

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

6 I! y7 i& H, I- A4 n6 ?

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

3 p5 K* Z/ _9 M, `

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

8 X& S- [' z2 \) |# k

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

5 \0 _/ J9 P6 q) f. O

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

9 [/ p- w% m( F( {' D3 |% E; {

4 X3 T/ a$ S+ F# B& z 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

' H t2 [9 ^: D+ X

指导 . i* O$ _. S; x! w5 c4 R( x

+ X/ c; k3 y( T# E

7 b k, p, o1 c3 \

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

回顶部