QQ登录

只需要一步,快速开始

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

浅析本机API

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

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

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

作者:sunwear[E.S.T] shellcoder@163.com 5 }4 v- o# O# s# m' n来源:邪恶八进制 中国

! F! ~' i: P" r" O0 k) q

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

' c; j. a, Y+ I0 l' q

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

/ `) P# T$ R" v$ Y D0 n# _

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

1 D% r) w8 _1 l1 l& W1 l7 N

没有任何技术上的限制,只不过微软不支持这种应用开发方法。 & i5 G' A: q- D6 U: R3 _$ a! G

+ `" l) w8 ]6 x; U

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

7 g; d, n$ N9 I2 w0 i3 } P, s4 Q6 l

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

! {; n3 V* b' r! D# `

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

$ U* i1 l# }$ \; p( G% m

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

8 R' H8 I% n6 C& R" Z1 n, f' J7 |. I

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

- N# B r) M7 b

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

7 }' A3 H8 H8 X' e3 f1 a

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

; J. K! Z5 t+ f- j& L @

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

7 x; i) _7 ?$ Y' |( P5 I

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

1 O0 D; M# r& F. n" T) q( n

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

" e/ a* G+ F3 p: |0 g& Y

反编汇这个函数得到:

7 E6 c$ z6 ^4 C, J6 \: K, ?3 k

mov eax, 38h

1 p1 d: a7 b) L5 E: z+ j

lea edx, [esp+4]

$ m7 D0 f7 H4 e% K

int 2Eh

7 v# D6 k8 _' [9 v1 F

ret 28h

+ f6 c2 o; L+ e9 B

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

# o8 H) p" x# Q: f

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

2 V6 c: R; r$ Y

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

* }5 T8 T: b4 c9 F2 A+ E' n

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

' |( H+ M- x" d: K6 c

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

! M6 S6 [ d. P# m- l) W

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

; A0 e& h, R# F- u8 T5 u0 B: }

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

. ]5 R! p9 B% ~

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

( t8 G4 R, _' w5 w

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

( ^/ ?( I+ }8 l( x* e* ?- o# y

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

4 {2 U K# h* i

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

: b$ J) v) |: m% s; i

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

9 P2 C# Y5 Z$ g) h0 l. B

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

# e; Y8 k( \ }, S( Z

typedef NTSTATUS (NTAPI *NTPROC) ( ) ;

4 c) g. M- D4 W- t" h3 A: s& R

typedef NTPROC *PNTPROC;

. l0 @, w' n- j& e1 e

#define NTPROC_ sizeof (NTPROC)

5 s: n5 l2 j* e- J1 D2 \6 k! k

typedef struct _SYSTEM_SERVICE_TABLE

8 `3 V! Y. V' m

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

6 n# z% h, K) m8 ]* W' O

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

& {3 ^4 t0 V4 s. M* z

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

% Y. m- z! }+ Z1 \$ a

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

8 d6 `5 s1 z6 P6 `

) SYSTEM_SERVICE_TABLE ,

. L h; Q# @$ v( R1 ^4 f

* PSYSTEM_SERVICE_TABLE ,

* _6 L7 a# l& p. ~7 A. _" m' e) o1 m

* * PPSYSTEM_SERVICE_TABLE ;

6 j1 s6 D' v9 q. H- |

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

/ m- _# O3 O, E# l: P

typedef struct _SERVICE_DESCRIPTOR_TABLE

+ _) Y5 ^. u- X6 q. }

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

9 I' u6 i8 a1 f6 y9 X/ E

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

) e! p9 M% P$ ]: T# v$ n

SYSTEM_SERVICE_TABLE Table3; // 未使用

' x j( m$ h2 f+ s$ ^

SYSTEM_SERVICE_TABLE Table4; // 未使用

4 d- W# Y1 P: S( g, O

} SERVICE_DESCRIPTOR_TABLE ,

" w8 n. F( [" c5 s( i+ ^

* PSERVICE_DESCRIPTOR_TABLE,

' Z, I& z1 B) f

* PPSERVICE_DESCRIPTOR_TABLE ;

$ z) M& r! D z. z

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

" k: q5 H2 k! G6 ~: B

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

5 f7 n: b& \& p4 E' \

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

( A7 k, z; S# N

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

# `2 k' X. ]9 i' Y+ Z0 Y7 N/ l

PSERVICE_DESCRIPTOR_TABLE psdt= KeServiceDescriptorTable;

- Z" H9 h- a. N

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

+ L" P i4 V" e b; U- t

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

! J! O. p4 @& b h3 l2 x

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

% h2 w5 U8 K/ L: B

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

. E( z, k4 v& `0 C

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

/ Y+ K+ [$ T6 L. e) P& H0 u2 ^

指向代表所有函数使用计数的DWORDS数组,这个信息能用于性能分析。4 n0 @& `: T: K4 S* d 可以使用这个命令来显示:dd KeServiceDescriptorTable,调试器把此符号解析为0x8046e0c0。只有

9 m F" d) c; z. Z0 q3 ?

前四行是最重要的,对应那四个SDT成员。 . c0 ~/ {) @- \* |7 n5 k5 a 运行这个命令:ln 8046e100,显示符号是KeServiceDescriptorTableShadow,说明第五个开始确实为

& W' u4 o5 ~+ M8 G- v* R

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

: S8 q5 R( h: h3 e% }& P- l* }

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

3 h7 x& e) M. b0 g' i

KeAddSystemServiceTable 5 f7 f. P; u( Y( |此函数去填充这些位置。

3 _2 ^( L% J' J- }

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

, `7 C& t+ K; M% C/ P

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

& l% ~$ ~* J, p8 Y7 \

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

' [- B( w; ~/ ^6 s w" z

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

5 T/ q+ h5 Q9 y$ i! b

映射至ntoskrnl表格,ID在

! ` A6 y7 V: R; z/ c, {- k% Q

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

% o& a- u5 h7 S7 I% k7 y( H' h

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

! D* ?. `9 [9 T# q2 s8 ^- c8 Q b

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

+ x* I/ R8 `( q y- i

STATUS_INVALID_SYSTEM_SERVICE。

) X- V; y) H0 W: r5 k

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

! U) j9 \ U! P: i

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

, f# C$ P$ s0 H4 ?

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

0 q% L) J0 ` H5 B% e. G

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

9 D& J' ~1 t& x7 c

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

0 z( i x/ m7 T$ n

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

4 T/ l+ B3 \8 {6 y/ y

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

6 o( h# a' o& P) c

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

. i2 x9 w/ ]: H$ u U8 Y

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

0 J1 }) ?4 X5 p" ]9 |; }9 W

- a, C3 \( f0 y3 r/ R+ F, ?2Eh的处理过程没有使用全局SDT KeServiceDescriptorTable。

- F2 V. y# i% D" f* t

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

: u& D) h- Q5 P, V# [

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

/ u( A# }6 v, s3 f7 a! U

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

1 g$ Y3 \* L' s- P

0 ?, z- `& M1 @Windows 2000运行时库

: }# t k) J4 v1 g1 w6 F: `2 | O

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

{3 R; G/ C4 ^

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

2 `& U$ C( n: N

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

- e+ c1 l; J) \8 k; q& |( `

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

+ a' Z4 f# A7 v* O' `

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

# Y$ h) W: P. x# Y" N# l

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

& g9 Y6 [# d) Q9 X; i# b$ f

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

4 B f. F# z. c; i

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

* U" F% {) P) }* m4 W0 J# E9 ?# }

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

) S! W% P7 j' Y; {' @

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

* T7 E( k: i3 o% k% [$ c2 `$ L; f

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

' T l5 n/ S ?; y3 ^

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

; ~ V7 j6 `* l2 h2 S

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

8 T3 y; F; d L * g$ ^9 N+ J+ {

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

3 C0 p5 c* U0 O! |

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

1 v2 N+ _; |. m% u3 T8 C5 m

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

! v. o5 w/ o& j" b% A/ {; D

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

( ?- ^' H$ p7 Z' }+ b

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

& P k: I q* m' Y& H. F7 W

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

! \4 o; k/ d6 l& f/ }

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

$ r5 e3 K/ `, G B- j

常用数据结构

3 P, F* X& A0 \" F# y9 v# o

l 整数

5 J; ]! |2 V* ~/ g( y7 F

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

9 S9 ]$ J. Z, ^4 v4 E3 i9 b

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

+ A/ U5 g( C$ \4 c

不同。

1 C6 ]% h. K6 \

TABLE 2-3. Equivalent Integral Data Types

, E5 z# g5 k) Q- c9 N- W; x

BITS MASM FUNDAMENTAL ALIAS #1 ALIAS #2 SIGNED

( I. [5 v1 G4 n& o/ ?6 w/ T: B

8 BYTE unsigned char UCHAR CHAR- s5 l K$ M7 T, S8 D- n 16 WORD unsigned short USHORT WCHAR SHORT

& H( H6 a. A" V \- b

32 DWORD unsigned long ULONG LONG

3 D' R' S: U1 C7 \0 F0 E- V

32 DWORD unsigned int UINT INT

; X1 B! d# ~4 a) x7 w6 B3 `; u E

64 QWORD unsigned _int64 ULONGLONG DWORDLONG LONGLONG

6 U' V* U$ L! ?5 g0 R# y

80 TBYTE N/A

/ t# v3 N+ T" k5 v( o3 F) r( m

typedef union _LARGE_INTEGER

4 t- ]* n! J* B: m

{ struct{

3 J7 Y4 u: E- u

ULONG LowPart;

( B1 w& S" ]! u! Q8 o

LONG HighPart;};

7 |8 E5 ?' }: @" ]1 S* p8 L

LONGLONG QuadPart;

4 `/ A; S+ C# X/ p! c: L* A

}

/ g. ^$ W8 ]' { ^/ m& N% k

LARGE_INTEGER , * PULARGE_INTEGER ;

8 [3 m! @5 e- q. L, a: s

typedef union _ULARGE_INTEGER{

) J$ h' z+ n- Z! W7 E

struct{

7 @. s( \, |( S/ T' ]- @

ULONG LowPart;

+ h- C! z0 U7 [# O0 d& J+ t/ K! V

ULONG HighPart;}

# E% p3 H3 x T; V

ULONGLONG QuadPart;

4 g; G& p- L) t$ C" \# X

}ULARGE_INTEGER, *PULARGE_INTEGER;

% b0 |2 m2 K5 e5 ^# J! e

l 字符

$ f0 W! k2 k5 F) t1 U

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

1 i) K4 X8 w0 G3 v" U+ @0 }# |6 ]

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

' M6 P! U- O4 z1 B! Y; |# u* K" c% p

typedef struct _UNICODE_STRING{

7 k/ Y; C4 _' \( K8 q/ [( k: [4 C

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

1 \! w- ?. V) h

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

1 T7 `( v6 o5 M. K7 H& c3 ~1 m

PWSTR Buffer;}UNICODE_STRING , * PUNICODE_STRING ;

, X- h6 @ q9 X3 A

typedef struct _STRING{

4 h4 j p, }. ~# ^: i: S) ~

USHORT Length;

% j! w( `6 y2 z& P; X- y

USHORT MaximumLength;

4 Z9 G: s& U; r5 U2 a! J7 f: ^

PCHAR Buffer;}STRING, *PSTRING;

! @: u0 V1 H6 C2 d8 \( T

typedef STRING ANSI_STRING, *PANSI_STRING;

$ G0 v: ]8 W9 _1 B0 r

typedef STRING OEM_STRING, *POEM_STRING;

& |8 T" b: N# {+ g

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

# M2 N, U- A( j" l/ P; L3 c" j

RtlCopyUnicodeString()等等

- K! {; {% U/ q( Z

l 结构

# J, s/ w& Y- \

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

@( v; t- q+ }' w/ n: ~

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

5 w$ s* N- y% V" p" B

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

! N9 A# r! I' I

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

# d2 ^8 {, Y+ [& @5 z

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

3 Q! z) A* Y! J- C2 n( [" T+ O- e

typedef struct _OBJECT_ATTRIBUTES

2 `% {6 X8 ]9 ~# @+ f

{

+ V7 S! y2 G2 x# x* L

ULONG Length;

; {, V4 W3 D* g- H9 m

HANDLE RootDirectory;

. z' V6 H1 U4 I9 F

PUNICODE_STRING ObjectName;

5 ~7 o* r( b: E. n @

ULONG Attributes;

4 M& c9 m9 P1 T; u! \

PVOID SecurityDescriptor;

, g" E R& r% P" d

PVOID SecurityQualityOfService;

9 N$ N9 Y9 d- Y& H7 X( o

} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;

- c) @5 a9 s7 |& B9 _1 I. H8 l, L7 {

typedef struct _IO_STATUS_BLOCK

+ O2 k7 `9 H2 z: z- y9 _

{

# ?5 h/ [6 k1 S

NTSTATDS Status;

7 e. T- @6 S1 s9 a: K9 \

ULONG Information;

$ i5 k) f- C+ {) P" ?

}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;

b2 c$ c7 U5 N

typedef struct _LIST_ENTRY

' O& I% m; i3 H! m% f

{

" h* L/ Y8 A+ g" S4 J$ |

Struct _LIST_ENTRY *Flink;

7 u( A, U; Z: b. ?& P+ Q

Struct _LIST_ENTRY *Blink;

, G S+ ?* b& w+ k

}LIST_ENTRY, *PLIST_ENTRY;

" l" m) E& B# ?2 l# J

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

; t9 o& r4 b8 F- o+ F- {9 \7 r

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

1 W/ K/ D8 s$ T8 Q& S! K) y

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

. ^- s% k' [6 W: O: u X' h

typedef struct _CLIENT_ID

3 C) d5 o9 g( [/ l5 I D, m

{ HANDLE UniqueProcess;

; ]. ~0 I* ?6 Y2 I

HANDLE UniqueThread;

( w6 x, k/ w9 P! }4 i7 f

)CLIENT_ID, *PCLIENT_ID;

0 L: _, X( k5 n2 c1 i' q. S

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

& c2 b3 ?2 \) |; ?7 y

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

1 o! w; p1 t8 }! P% B

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

8 _$ H# q1 o0 `2 x2 P [. C

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

1 c' v0 U$ v; E

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

. s# J; Y7 `* W

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

9 I/ J! q- E" ]7 p9 L

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

7 @! `1 | t6 r

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

1 Y. S. e6 U; F) B' G9 X

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

7 V3 ^+ d& P' C$ i# g

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

0 A" q4 o- l2 F5 r6 ]; Q

9 H' i7 g! H% x0 h: }4 K* h 本文部分翻译于一篇电子书<win api about>.也感谢朋友GameHunter这位英语极好的朋友帮忙.与Free的

; i4 [4 O, o T" h8 O2 O

指导% c. \; c$ G" E) b" y% m

$ a b# I+ L: y8 T1 U; t: b

2 _% n& H; e5 s% T, q4 i

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 08:15 , Processed in 0.393893 second(s), 51 queries .

回顶部