65.9K
CodeProject 正在变化。 阅读更多。
Home

介绍带 Detours 的 Vista 事务性 NTFS (TxF)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (17投票s)

2007 年 1 月 27 日

10分钟阅读

viewsIcon

58381

downloadIcon

369

使用 Detours 库解释 Windows Vista 新的事务性 NTFS (TxF) API。

目录

引言

Windows Vista 在内核和核心操作系统层面有许多有趣的新功能。在本文中,您将了解事务性 NTFS(简称 TxF),它是在内核事务管理器 (KTM) 之上实现的,KTM 是一项新功能,允许为 Windows Vista 开发的程序使用事务。您还将了解 Microsoft Detours,这是一个用于在 x86 机器上拦截任意 Win32 二进制函数的库。

先决条件

本文是为 Windows Vista RC2 开发的,但应该也适用于最终版本。

什么是事务?

事务被定义为一系列操作,这些操作要么全部发生,要么全部不发生。举一个非常简单的例子,假设您正在更新一个程序,该程序由一个可执行文件 (.exe) 和三个动态链接库 (.dll) 组成,将其更新为新版本。在大多数情况下,更新脚本(可能是程序安装程序)会首先备份所有文件,然后尝试更新文件。如果一个文件更新失败,脚本将恢复所有备份的文件,并通知您更新失败。

脚本执行此行为是为了防止程序处于不一致的状态,因为如果其中一个文件更新失败,新版本将无法运行,而旧版本的文件已被覆盖。

所以更新脚本在这里所做的是一个非常简单的事务。

  1. 开始事务:备份旧文件(然而,大多数实际事务在隔离环境中运行,而不是备份原始环境)
  2. 执行操作:复制较新的文件。
  3. 提交或回滚事务:如果任何文件更新失败,恢复备份文件(回滚)。如果所有操作成功,删除备份文件(提交)。

当然,这只展示了事务的最基本概念。一个真实的事务必须具有一组属性,这些属性缩写为 ACID。ACID 是一个非常熟悉的术语,尤其是对于数据库开发人员,因为大多数数据库操作都需要事务处理。

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

我不会过多地详细解释这些术语,因为您可以在网上找到大量详细介绍此主题的资源。

Windows Vista 内核事务管理器

内核事务管理器 (KTM) 是 Vista 内核的一部分,它允许用户模式(程序)和内核模式(设备驱动程序)应用程序使用事务,即定义一组操作,这些操作要么全部发生,要么全部不发生。Vista 附带了两个基于 KTM 构建的模块,即事务性 NTFS (TxF) 和事务性注册表 (TxR)。

本文主要关注事务性 NTFS (TxF),尽管事务性注册表是应用于 Windows 注册表操作的相同概念。

事务性 NTFS (TxF)

Windows 平台 SDK 文档中事务性 NTFS 的定义是“事务性 NTFS 允许在 NTFS 文件系统卷上以事务方式执行文件操作。”

这意味着 Windows Vista 应用程序可以在 NTFS(Windows NT 和 Windows 2000 使用的磁盘文件结构)分区上,以原子事务方式进行文件操作。因此,您可以例如在一个原子事务中打开多个文件进行编辑,并且您可以随时根据自己的程序逻辑提交所有更改(事务成功)或回滚所有更改(事务失败),而 TxF 将为您处理所有事务 ACID 属性(原子性、一致性、隔离性和持久性)。

您基本上需要做的就是使用 CreateTransaction 方法创建一个事务对象,然后使用 CreateFileTransacted,它将事务对象的句柄(指针)作为其参数之一,以事务性操作方式操作文件。

最后,您应该使用 CommitTransactionRollbackTransaction 来保存或撤销对文件的所有更改。

这确实是一个非常酷的功能,但问题是程序必须明确地使用 TxF 才能从中受益。如果像记事本或 WinRAR(是的:))这样的旧程序也能使用 TxF 呢?那将更酷。这就是我要向您介绍 Detours 的地方。

微软研究院 Detours 包

Microsoft Detours 库是一个复杂的 Windows API 钩子框架。但首先,什么是 API 钩子?

API 钩子是一个非常古老的概念,可以追溯到 DOS (磁盘操作系统) 的早期。

DOS 开发者使用 DOS 中断来调用操作系统系统功能。例如,程序员必须调用 INT 21(中断 21)才能执行大多数 IO 操作。INT 21 意味着调用其地址将从中断向量表 (IVT) 中条目号 21 获取的程序。

所以所有的操作系统程序地址都保存在 IVT 中,你只需要选择要调用的中断,系统就会从它的 IVT 中查找它。

开发人员拦截系统中断调用的一种方法是修改 IVT,用他们自己的程序地址替换系统程序地址。在许多情况下,他们保存了原始程序的地址,以防他们想从自己的程序中调用它。

这有什么用?例如,如果您希望磁盘上的所有数据都加密,并且只有您的计算机可以打开此磁盘。您可以做的是用您自己的程序覆盖写入数据到磁盘的中断,加密所有最初发送到此中断的数据,然后调用原始中断并传递加密数据。反之,在从磁盘读取时解密数据。

当然,中断在 Windows 中仍然存在,但它们不暴露给用户模式程序。您将主要使用 API 来实现大多数系统功能。那么,如果您想在程序调用某个 Windows API 时运行自己的代码呢?这就是 API 钩子这个术语的用武之地。

有许多不同的方法来钩子 Windows API,这些方法超出了本文的范围。

Detours 库使用的一种技巧是运行时修改导入地址表,PE(可移植可执行文件,Windows 可执行文件的格式)将导入的 dll 函数地址存储在内存中。简单来说,它就像一个 IVT,但针对每个程序。

所以我们的计划是,我们希望将我们的代码注入到特定程序中,以便我们最初创建一个事务对象,并且每次程序调用 CreateFileW(这是创建或打开文件的 API 方法)时,我们都希望改用 CreateFileTransactedW,将原始参数和我们的事务对象传递给它。

*CreateFileW 是 Unicode 版本,主要用于新程序。较旧的、Windows 2000 之前的程序使用 ANSI 编码版本 CreateFileA,您可以使用 CreateFileTransactedA

幸运的是,使用 Detours 完成此任务非常简单。我们将创建一个基于 Detours 的 DLL,它将修改 CreateFileW API 以改为调用 CreateFileTransactedW

这就是我最初编写的代码(它基于简单的 Detours dll 骨架)

//definition of our transation handle
HANDLE trans = NULL;

//function pointer to CreateFileW API function
static HANDLE (WINAPI * TrueCreateFile)
(
 LPCWSTR lpFileName,
 DWORD dwDesiredAccess,
 DWORD dwShareMode,
 LPSECURITY_ATTRIBUTES lpSecurityAttributes,
 DWORD dwCreationDisposition,
 DWORD dwFlagsAndAttributes,
 HANDLE hTemplateFile
 ) = CreateFileW;

//Our function where will call CreateFileTransactedW instead of CreateFileW
HANDLE WINAPI TransCreateFile
(
 LPCWSTR lpFileName,
 DWORD dwDesiredAccess,
 DWORD dwShareMode,
 LPSECURITY_ATTRIBUTES lpSecurityAttributes,
 DWORD dwCreationDisposition,
 DWORD dwFlagsAndAttributes,
 HANDLE hTemplateFile
 )
{
    //call CreateFileTransactedW with our transaction object
    return CreateFileTransactedW(
        lpFileName,
        dwDesiredAccess,
        dwShareMode,
        lpSecurityAttributes,
        dwCreationDisposition,
        dwFlagsAndAttributes,
        hTemplateFile,
        trans,
        NULL,
        0
        );

}

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
    LONG error;
    (void)hinst;
    (void)reserved;

    //Detour initialiazation
    if (dwReason == DLL_PROCESS_ATTACH) {

        DetourRestoreAfterWith();

        //we create our transaction object
        trans = CreateTransaction(NULL,0,0,0,0,NULL,NULL);

        if (INVALID_HANDLE_VALUE == trans)
        {
            MessageBox(NULL, "Transaction creation failed", "Error", 0);
        }
        else
        {
            MessageBox(NULL, "Program running in Transacted NTFS mode.", 
                       "", 0);
        }

        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)TrueCreateFile, TransCreateFile);
        error = DetourTransactionCommit();

        if (error != NO_ERROR)
        { 
            MessageBox(NULL, "Detouring failed", "Error", 0);
        }
    }
    else if (dwReason == DLL_PROCESS_DETACH) {
        //ask the user if he wants to commit the transaction on exit
        if (IDYES == MessageBox(NULL, "Commit transaction?", 
                                "Transaction Pending", MB_YESNO))
        {
            //commit the transaction
            CommitTransaction(trans);
        }

        //close transaction handle
        CloseHandle(trans);

        //detour unintialization
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)TrueCreateFile, TransCreateFile);
        error = DetourTransactionCommit();
    }

    return TRUE;
}

令我惊讶的是,这段代码并没有奏效,而是出现了一个奇怪的错误“不支持隐式事务。”

经过几次失败的调查以找到错误原因后,一位在微软工作的工程师,好心地向我暗示了问题的原因。

CreateFileTransacted 在内部调用 CreateFile API,由于我们用 CreateFileTransacted 钩子了 CreateFile,现在 CreateFileTransacted 调用了它自己。由于一些函数检查,这并没有导致程序因堆栈溢出而崩溃,但确实给我带来了这个奇怪的错误。事实上,后来我知道隐式事务支持在 Vista 的早期测试版中确实存在,但由于某些原因(很可能是安全性或不兼容性)被删除了。

以下是 TxF MSDN 博客中关于 TxF 在早期 Vista 测试版中如何使用的代码片段

hReaderTrans = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
SetCurrentTransaction(hReaderTrans);

hReaderFile = CreateFile(szFileName,
                         GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE|
                         FILE_SHARE_DELETE,
                         NULL,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         NULL);

SetCurrentTransaction(NULL);
CloseHandle(hReaderFile);

这意味着,通过在并非为此目的设计的程序中运行 TxF,您可能会冒着违反软件许可或程序功能的风险。当然,本文主要是一个教育资源,旨在演示 Vista TxF API 和 Detours。

回到我们的程序,它是这样修复的

//Our function that will call CreateFileTransactedW instead of CreateFileW

HANDLE WINAPI TransCreateFile
(
 LPCWSTR lpFileName,
 DWORD dwDesiredAccess,
 DWORD dwShareMode,
 LPSECURITY_ATTRIBUTES lpSecurityAttributes,
 DWORD dwCreationDisposition,
 DWORD dwFlagsAndAttributes,
 HANDLE hTemplateFile
 )
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)TrueCreateFile, TransCreateFile);
    DetourTransactionCommit();

    HANDLE h;

    //call CreateFileTransactedW with our transaction object

    h = CreateFileTransactedW(
        lpFileName,
        dwDesiredAccess,
        dwShareMode,
        lpSecurityAttributes,
        dwCreationDisposition,
        dwFlagsAndAttributes,
        hTemplateFile,
        trans,
        NULL,
        0
        );

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)TrueCreateFile, TransCreateFile);
    DetourTransactionCommit();

    return h;
}

因此,每次调用我们的 TransCreateFile 函数时,在调用 CreateFileTransactedW 之前,我们将 CreateFileW 恢复为其原始 API 函数,然后在调用完成后再次对其进行钩子。

现在,当 CreateFileTransactedW 在内部调用 CreateFileW 时,它会转到正确的函数。

TxF 小版本

TxF 的另一个有趣功能是小版本。您可以将当前更改的状态保存为小版本(检查点)。

CreateFileTransacted 在其参数中可选地接受一个迷你版本,以将文件打开到特定的已保存检查点(一个非常方便的函数,用于实现撤销和重做缓冲区)。

然而,要保存小版本,您需要直接告诉 KTM 这样做。但是请记住 KTM 是一个内核模块,那么如何告诉它创建小版本呢?

在 Windows 中,与内核模块的通信不是直接进行的,而是通过 IO 管理器进行的。您向 IO 管理器发送一个控制代码,指定将控制代码传递给哪个模块,并且您可以交换输入和输出缓冲区(有不同的缓冲区交换模式,不在本文的讨论范围之内)。然后 IO 管理器将控制命令路由到内核模块,并在用户模式程序和内核模式程序之间进行缓冲区交换。

要与 IO 管理器通信,请在文件打开句柄上使用 DeviceIoControl,并使用 FSCTL_TXFS_CREATE_MINIVERSION 控制代码保存小版本。TXFS_CREATE_MINIVERSION_INFO 结构将在输出缓冲区中返回,其中包含有关创建的小版本的信息。

TXFS_CREATE_MINIVERSION_INFO txfMiniversion;

BOOL result = DeviceIoControl(
  h,                             // handle to file 
  FSCTL_TXFS_CREATE_MINIVERSION, // control code creates a new miniversion
  NULL,                          // input buffer (null)
  0,                             // input buffer size
  (LPVOID) &txfMiniversion,      // miniversion info structure
  (DWORD) sizeof(TXFS_CREATE_MINIVERSION_INFO),  // size of output buffer
  (LPDWORD) lpBytesReturned,     // number of bytes returned
  (LPOVERLAPPED) NULL            // OVERLAPPED structure (null for 
                                 // synchronous operation)
);

请记住,如果您提交或回滚事务,小版本将被丢弃。

如何运行示例?

您需要安装 Microsoft Windows SDK 和 Detours Express 2.1。启动 Windows SDK CMD Shell 并转到 \Detours Express 2.1\src,然后输入 nmake 编译 detoured dll。同时编译 \Detours Express 2.1\samples\withdll 和 \txf,后者随本文下载(将文件夹解压到 Detours 目录下的 \samples 中)。

所有 Detours 编译文件都在 \Detours Express 2.1\bin 中

要以 TxF 模式运行记事本,请使用以下命令

withdll.exe /d:txf.dll /p:detoured.dll notepad txftest.txt

您将收到通知,记事本正在以事务性 NTFS 模式运行,并且如果 txftest.txt 不存在,它将被创建。现在您可以编辑 txftest.txt 并保存它,然后关闭记事本。

系统将提示您是否要提交事务。如果选择否,您对文件所做的所有更改都将被丢弃,文件将回滚到其初始状态;否则,文件将保存到磁盘。

最终考虑

这绝不是一个完美的钩子,你会注意到这种方法可能会导致某些应用程序或某些情况下出现问题,因为还有许多其他文件操作需要钩子。举几个例子:

  • CopyFileTransacted
  • CreateDirectoryTransacted
  • CreateHardLinkTransacted
  • CreateSymbolicLinkTransacted
  • DeleteFileTransacted
  • FindFirstFileNameTransactedW
  • FindFirstFileTransacted
  • FindFirstStreamTransactedW
  • GetCompressedFileSizeTransacted <code>
  • GetFileAttributesTransacted
  • GetFullPathNameTransacted
  • GetLongPathNameTransacted
  • MoveFileTransacted
  • RemoveDirectoryTransacted
  • SetFileAttributesTransacted

这些函数都可以像我们钩子 CreateFileTransacted 一样进行钩子,但使此代码 100% 兼容所有应用程序并不是本文的目标。

此外,正如我的一位朋友所告知,此示例代码不是线程安全的。这意味着如果您正在钩子一个多线程应用程序,如果多个线程可能同时调用 CreateFile,则应进一步修改代码以进行线程同步。

您现在已经了解了事务、KTM、TxF 和 Detours,现在您可以开始真正的乐趣了。

© . All rights reserved.