文章转自:http://chongyanglee.bokee.com/6726337.html
Native NT Application 的编写与调试入门
关键词: Native NT Application
前言
Native NT Application本来我是想写成中文的,但怎么翻译都觉得不太合适,大多文章都写作为Native Application,并翻译为原生应用程序,或者本地应用程序,但自从有了.NET之后,原生应用程序好像更多的用作Win32应用程序了,本地应用程序倒还可以,但怎么听来也不好听,算了,还是简写作 Native 应用程序吧。
Native 应用程序与 Win32 应用程序的文件结构都是 PE 格式的,甚至也是以.exe为后缀名,但是当你的 Windows 启动到一定阶段后,正常情况下你却再也不能运行 Native 应用程序了,因为此时,Win32子系统已经启动,你已经运行到了 用户模式 下。
Native 应用程序有哪些呢,当然最出名的就是非法关机后用于磁盘检查的AutoChk,当Windows启动起来后,你再想运行AutoChk.exe等 Native 应用程序,你会得到一个
<应用程序>无法在 Win32 模式下运行
的错误提示。
Native 能干什么呢,想必你听说过PQMagic,当你挂起动态分区,重新启动电脑时,PQMagic就运行了一个Native应用程序来执行操作,这就是因为系统还没有正常启动起来,所以你可以做好多好多系统启动后不能做的事情。ps: 瑞星杀毒的 BSMain 也是一个 Native 应用程序。
编写 Native 应用程序是不容易的,甚至你要调用的 API 微软都不愿意公开,我们的入门资料是 Mark Russinovich 写的《Inside Native Application》,网上有中文翻译的文档,可以参考,关于这篇文章,Mark 修改了多次,并且相差很多。关于其 API ,参考资料是多年以前的一本电子书《Windows NT/2000 Native API Reference》。
开始编写
一般来说,编写 Native 应用程序使用的是 DDK 或者 WDK 的 build,其实,VC 编译器和链接器也知道如何生成此类程序,本文就介绍这种方法。
我们需要的工具
-
当然是 VC,本文中使用的是 VS 2008 (盗版的);
-
DDK 或者 WDK,就算你不使用其 build ,也得有这些头文件和库文件;
-
winternl.h 或者 NDK,本文中使用的是 NDK,以前使用的是前者,官方的 winternl.h 我们得不到,微软没有提供给我们,只能从 MSDN 中可以窥视到它的存在, 可以从
http://svn.reactos.org/svn/reactos/trunk/reactos/include/psdk/winternl.h?view=markup
下载一个非官方版本,我使用的时候做了少许更改,你也可以从
http://files.cnblogs.com/ChongyangLee/winternl.rar
下载一个我更改过的版本,更改的地方我都做了注释,搜索 chongyangLee 即可。关于 NDK(Native Development Kit ),就连 ReactOS 都使用了它,放心吧,很全,很强大。 从此处
http://code.google.com/p/native-nt-toolkit/
下载。使用时也做了少许更改。
VC的配置
一般来说,你需要做如下配置:
***********************************************************************************************
General:
不使用MFC (Use Standard Windows Libraries)
使用Unicode字符集 (Use Unicode Character Set)
***********************************************************************************************
C/C++
-----------------------------------------------------------------------------------------------
General:
设置增加的 Include 文件目录,当然还应当包含其它的,用到时自己增加,
注意,此处已经把 NDK 目录拷贝到了 $(DDKROOT)/inc/ndk 下,不使用继承目录
Addtional Include Directories: "$(DDKROOT)/inc/ddk/wxp";"$(DDKROOT)/inc/ndk";$(NOINHERIT)
调试信息格式,如果不是调试版本,此处 Disable
Debug Information Format: Program Database (/Zi)
------------------------------------------------------------------------------------------------
Optimization:
禁止优化,如果不是调试版本,此处 自己看着办
Optimization: Disabled (/Od)
------------------------------------------------------------------------------------------------
Preprocessor:
预处理器定义,此处必须设置的是 _X86_ 其余的...
如果不是调试版本,DBG=0,以对应 DDK 中的 Free 版本
Preprocessor Definitions: _X86_;DBG=1;_WIN32_WINNT=0x0501
------------------------------------------------------------------------------------------------
Code Generation:
代码生成,基本运行期检查,关闭,不使用。
Basic Runtime Checks: Default
运行期库,多线程调试
Runtime Library: Multi-threaded Debug DLL (/MDd)
------------------------------------------------------------------------------------------------
Precomplied Headers:
不使用预编译头(不好意思,习惯而已)
Create/Use Precomplied Header: Not Using Precompiled Headers
------------------------------------------------------------------------------------------------
***********************************************************************************************
Linker
------------------------------------------------------------------------------------------------
Gneral:
启用增量链接,关闭,不使用
Enable Incremental Linking: No (/INCREMENTAL:NO)
增加的 lib 文件目录
Additional Library Directories: "$(DDKROOT)/lib/wxp/i386"
------------------------------------------------------------------------------------------------
Input:
增加的 lib 文件,或者可以使用 #pragma comment(lib, "ntdll.lib)... 代替之
Additional Dependencies: ntdll.lib nt.lib $(NOINHERIT)
忽略所有默认库文件
Ignore All Default Libraries: Yes (/NODEFAULTLIB)
------------------------------------------------------------------------------------------------
Debugging
生成调试信息,即使不是调试版本,此处 也可以生成
Generate Debug Information: Yes (/DEBUG)
------------------------------------------------------------------------------------------------
System:
子系统为 Native
Subsystem: Native (/SUBSYSTEM:NATIVE)
------------------------------------------------------------------------------------------------
Advanced:
入口函数,其实 Native 默认的就是这个,可以不设置
Entry Point: NtProcessStartup
加载基地址,内核部署,默认也是这个,可以不设置
Base Address: 0x10000
------------------------------------------------------------------------------------------------
***********************************************************************************************
配置确实挺多的,为此,我用 VS 2008 写了一个 Wizard ,因为本文为了调试,所以没有修改 Release 配置,
http://files.cnblogs.com/ChongyangLee/MyWizard.rar
这个压缩文件中包含工程源代码文件,
http://files.cnblogs.com/ChongyangLee/MyWizard_.rar
这个压缩文件 不 包含工程源代码文件,正常使用的话可以使用这个方法如下
-
解压缩后将其拷贝到vc安装目录下的vcprojects文件夹,例如 E:/Microsoft Visual Studio 9.0/VC/vcprojects/MyWizard
-
修改 NativeAppWizard.vsz 中 Param="ABSOLUTE_PATH = G:/Work/SoftWare/MyWizard/NativeAppWizard" 的路径为你的正确路径,如 Param="e:/Microsoft Visual Stuidio 9/vc/vcprojects/MyWizard/NativeAppWizard"
-
启动 VS 2008 就可以了
写程序了
其实,如果你使用我上面提供的向导,一个例子程序已经写好了,正如上面所提到的,用 VC 创建 Native 应用程序时,默认的入口函数为 NtProcessStartup,其参数为 PPEB,其实,使用 DDK 创建 Native 应用程序时,可以创建 main 函数就行了,NtProcessStartup 自动创建好了, 下面是我在向导中提供的例子代码
1 #ifdef __cplusplus
2 extern " C " {
3 #endif /*__cplusplus*/
4 #include < ntndk.h >
5 #ifdef __cplusplus
6 } /* extern "C" */
7 #endif /*__cplusplus*/
8
9
10 #include < stdio.h >
11
12 // Handle of Heap
13 HANDLE g_hHeap;
14
15
16 void NtProcessStartup(PPEB ppeb)
17 {
18 // for Debug
19 __asm
20 {
21 int 3 ; // DbgBreakpoint()
22 }
23 DbgPrint( " ************START************ " );
24
25
26 RTL_HEAP_PARAMETERS parameter;
27 memset( & parameter, 0 , sizeof (RTL_HEAP_PARAMETERS));
28 parameter.Length = sizeof (RTL_HEAP_PARAMETERS);
29
30 // Create Heap
31 g_hHeap = RtlCreateHeap(HEAP_GROWABLE, NULL, 0x100000 , 0x1000 , NULL, & parameter);
32
33 // todo:
34
35 UNICODE_STRING wBuf;
36 wBuf.Buffer = static_cast < wchar_t *> (RtlAllocateHeap(g_hHeap, 0 , 255 ));
37
38 RtlInitUnicodeString( & wBuf, ppeb -> ProcessParameters -> CommandLine.Buffer);
39
40 NtDisplayString( & wBuf);
41 RtlFreeHeap(g_hHeap, 0 , wBuf.Buffer);
42
43
44 UNICODE_STRING HelloMsg = RTL_CONSTANT_STRING(L " /nOne World, One Dream!/nBeiJing China " );
45 NtDisplayString( & HelloMsg);
46
47
48 HANDLE hFile = NULL;
49 OBJECT_ATTRIBUTES objAttr = { 0 };
50 IO_STATUS_BLOCK IoStatusBlock = { 0 };
51 UNICODE_STRING wszPath = { 0 };
52
53 RtlInitUnicodeString( & wszPath, L " //??//c://abcd.txt " );
54 InitializeObjectAttributes( & objAttr, & wszPath, 0 , NULL, NULL);
55 NTSTATUS statusFile = NtCreateFile( & hFile, GENERIC_READ, & objAttr, & IoStatusBlock,
56 NULL, 0 , FILE_SHARE_READ, FILE_OPEN_IF, 0 , NULL, 0 );
57
58
59
60 if (hFile != NULL)
61 {
62 NtClose(hFile);
63 }
64
65 // Terminate Manual
66 NtTerminateProcess(NtCurrentProcess(), 0 );
67 }
68
69
上面的代码完成了什么功能,
首先,在 Native 应用程序中,堆是自己来创建并维护的;
第二,显示了两串字符,其一是从 PPEB 中取得的命令行信息,其二是一串固定的字符串;
第三,在 C 盘的根目录下打开或者创建了一个文本文件,没有操作,直接关闭了;
第四,终止该程序(return 是终止不了的)。
演示一下效果吧,
如果你去掉为了调试加的代码(__asm{int 3;} DbgPrint("************START************");)编译生成的程序,拷贝到你的System32目录下,修改注册表HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager
键:BootExecute
值:autocheck autochk *
<你的应用程序名称,此处假定为try3> xixihaha
重新启动 Windows (不带 /NOGUIBOOT 参数),你会在登录前看到如下画面
此处使用的是Session Manager 启动的 Native Application,当然还有其他的方法,自己google吧。
调试 Native 应用程序
编写程序不可能离开调试,但 Native 应用程序调试是困难的,需要进行内核调试,因此你需要一个可以进行内核调试的工具,SoftIce 或者 WinDBG,本文中使用了后者。为了调试,你需要将前面加入的调试代码打开,让Windows运行到你的程序开头时可以中断。下面是不太详细(需要你对 WinDBG 有一定的了解)的详细的调试过程:
-
修改 boot.ini 文件(Vista 下不同),加入 /debug 调试选项 和 调试口的配置;
-
启动目标操作系统,在选项前按上下键暂停止启动;
-
启动 WinDBG,按 Ctrl + K,启动内核调试窗口,按照你的调试口配置启动内核调试;
-
回到目标操作系统,启动Widnows;
-
在WinDBG下配置 .sympath 增加你的 Native 应用程序的 pdb 文件所在的路径;
-
同样配置 源文件 路径;
-
当执行到 int 3 时,WinDBG 会自行中止;
-
输入命令 .reload -user 装入用户符号表;
-
打开源文件,恭喜,你可以调试了。
待续