重阳 ChongyangLee

_____关注可编程技术______

导航

Native NT Application 的编写与调试入门

Posted on 2008-07-29 23:37  ChongyangLee  阅读(865)  评论(1编辑  收藏  举报

前言       

      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 编译器和链接器也知道如何生成此类程序,本文就介绍这种方法。

我们需要的工具

  1. 当然是 VC,本文中使用的是 VS 2008 (盗版的);
  2. DDK 或者 WDK,就算你不使用其 build ,也得有这些头文件和库文件;
  3. winternl.h 或者 NDK,本文中使用的是 NDK,以前使用的是前者,官方的 winternl.h 我们得不到,微软没有提供给我们,只能从 MSDN 中可以窥视到它的存在, 可以从
    http://svn.reactos.org/svn/reactos/trunk/reactos/include/psdk/winternl.h?view=markup
    下载一个非官方版本,我使用的时候做了少许更改,你也可以从
    https://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 配置, 

 https://files.cnblogs.com/ChongyangLee/MyWizard.rar

这个压缩文件中包含工程源代码文件,

 https://files.cnblogs.com/ChongyangLee/MyWizard_.rar

这个压缩文件 包含工程源代码文件,正常使用的话可以使用这个方法如下

  1.  解压缩后将其拷贝到vc安装目录下的vcprojects文件夹,例如 E:\Microsoft Visual Studio 9.0\VC\vcprojects\MyWizard
  2.  修改 NativeAppWizard.vsz 中 Param="ABSOLUTE_PATH = G:\Work\SoftWare\MyWizard\NativeAppWizard" 的路径为你的正确路径,如Param="e:\Microsoft Visual Stuidio 9\vc\vcprojects\MyWizard\NativeAppWizard"
  3. 启动 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, 0sizeof(RTL_HEAP_PARAMETERS));
28     parameter.Length = sizeof(RTL_HEAP_PARAMETERS);
29 
30     //Create Heap
31     g_hHeap = RtlCreateHeap(HEAP_GROWABLE, NULL, 0x1000000x1000, NULL, &parameter);
32 
33     //todo:
34 
35     UNICODE_STRING wBuf;
36     wBuf.Buffer = static_cast<wchar_t*>(RtlAllocateHeap(g_hHeap, 0255));
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 有一定的了解)的详细的调试过程:

  1. 修改 boot.ini 文件(Vista 下不同),加入  /debug 调试选项 和 调试口的配置;
  2. 启动目标操作系统,在选项前按上下键暂停止启动;
  3. 启动 WinDBG,按 Ctrl + K,启动内核调试窗口,按照你的调试口配置启动内核调试;
  4. 回到目标操作系统,启动Widnows;
  5. 在WinDBG下配置 .sympath 增加你的 Native 应用程序的 pdb 文件所在的路径;
  6. 同样配置 源文件 路径;
  7. 当执行到 int 3 时,WinDBG 会自行中止;
  8. 输入命令 .reload -user 装入用户符号表;
  9. 打开源文件,恭喜,你可以调试了。

 待续