Contents
Introduction
Windows vista has many interesting new features at the kernel and the core operating system level. In this article you will learn about Transactional NTFS (abbreviated TxF) which is implemented on top of Kernel Transaction Manager (KTM), a new feature that allows programs developed for windows vista to use transactions. You will also learn about Microsoft Detours which is a library for intercepting arbitrary Win32 binary functions on x86 machines.
Prerequisites
This article was developed for Windows Vista RC2 but should work also for the final release.
What is a transaction?
A transaction is defined as a series of operations which either all occur, or all do not occur. To give you a very simple example, consider you are updating a program which consists of one executable (.exe) file and three dynamic link libraries (.dll) with a newer version of the program. In most cases, the update script (could be the program installer) will take a backup version of all the files first, and then try to update the files. If one file failed to be updated, the script will restore all the files it had backed up and will notify you that the update failed.
The script did this behavior to prevent the program from being in an inconsistent state, since if one of the files failed to be updated, the new version will fail to run and the old version files were overwritten.
So what the update script did here is a very simple transaction.
- begin transaction: take a backup of old file (however most real word transactions run in an isolated environment instead of backing up the original environment)
- Execute operations: copy the newer files.
- Commit or rollback transaction: if any file failed to be updated, restore backup file (rollback). If all operations succeeded, delete backup files (commit).
Of course, this only shows the very basic concept of transaction. A real world transaction has must have a set of properties which are abbreviated as ACID. ACID is a very familiar term especially for database developers since most of database operations need to be transacted.
- Atomicity
- Consistency
- Isolation
- Durability
I will not go much into the details of explaining each of those terms since you can find lots of resources covering this topic in details over the web.
Windows Vista Kernel Transaction Manager
Kernel Transaction Manager (KTM) is a part of the vista kernel that allows both user mode (programs) and kernel mode (device drivers) applications to use transactions which is to define a set of operations which either all occur, or all do not occur. Vista ships with two modules built on the top of KTM which are Transactional NTFS (TxF) and Transactional Registry (TxR).
This topic will mainly concentrate on Transactional NTFS (TxF) although Transactional Registry is the same concept applied on windows registry manipulation.
Transactional NTFS (TxF)
The definition of Transactional NTFS in the windows platform SDK documentation is "Transactional NTFS allows file operations on an NTFS file system volume to be performed in a transaction."
That means Windows Vista applications on NTFS (The disk file structure used by Window NT and Windows 2000) partitions, can do files manipulation as atomic transactions. So you can for example open many files for editing in one atomic transaction and at any time you can commit all your changes (transaction successes) or rollback all the changes (transaction failure) based on own program logic and TxF will handle all the transaction ACID properties for you (Atomicity ,consistency, isolation and durability).
All what you basically need to do is to use CreateTransaction
method to create a transaction object, then use CreateFileTransacted
which takes a Handle (pointer) to a transaction object as one of its parameters to manipulate a file as a transacted operation.
Finally you should use CommitTransaction
or RollbackTransaction
to either save or undo all the changes to the file.
This is indeed a very cool feature, but the problem is that programs have to be written to explicitly use TxF to benefit from it. What if older programs such as notepad or winrar (yes :)) can use TxF? That would have been even cooler. Here is where I introduce you Detours.
Microsoft Research Detours Package
Microsoft Detours library is a sophisticated Windows API hooking framework. But first what is API hooking?
API hooking is a really old concept back from the old days of DOS (disk operating system).
DOS developers used DOS interrupts to call OS system functionalities. For example programmers had to call INT 21 (interrupt 21) to do most of IO operations. INT 21 means to call the procedure which address you will get from entry number 21 in the interrupt vector table (IVT).
So all of the operating system procedures addresses were kept in the IVT and you only choose which interrupt to call and the system will look it up from its IVT.
One way for developers to intercept calls to system interrupts, was to modify the IVT and replace system procedures addresses with the address of their own procedures. In many cases, they saved the address of the original procedure, in case they want to call it from their own procedures.
What is this for? For example if you want all data on a disk to be encrypted, and only your computer can open this disk. What you can do is to override the interrupt of write data to disk with your own procedure, encrypt all the data which are sent originally to this interrupt, then call the original interrupt passing it the encrypted data. Reverse-wise in case of reading from disk to decrypt back the data.
Of course interrupts still exists in Windows, but you they are not exposed to user mode programs.You will mostly use APIs for most system functions. So what if you want to run your own code when a program calls a certain Window API? Here is where the term API hooking takes its place.
There are many different ways to hook Windows APIs which are out the scope of this article.
One trick that Detours library uses is runtime memory modification of the Import Address Table, which is where PE (portable executable, the format of windows executable) stores the imported dll functions addresses in memory. For simplicity, it's like an IVT but for each program.
So here is our plan, we want to inject our code into a specific program so that we initially create a transaction object, and every time the program call CreateFileW
which is the API method to create or open a file, we want to use CreateFileTransactedW
instead, passing to it the original parameters plus our transaction object.
*CreateFileW
is the Unicode version which is mostly used in new programs, Older, pre windows 2000, programs use the ANSI encoding version CreateFileA
where you can use CreateFileTransactedA
.
Fortunately, this task is pretty simple using detours. We will create a Detours-based DLL which modifies the CreateFileW
API to call CreateFileTransactedW
instead.
So here is the code that I initially wrote (which is based on simple Detours dll skeleton):
HANDLE trans = NULL;
static HANDLE (WINAPI * TrueCreateFile)
(
LPCWSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
) = CreateFileW;
HANDLE WINAPI TransCreateFile
(
LPCWSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
)
{
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;
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
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) {
if (IDYES == MessageBox(NULL, "Commit transaction?",
"Transaction Pending", MB_YESNO))
{
CommitTransaction(trans);
}
CloseHandle(trans);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueCreateFile, TransCreateFile);
error = DetourTransactionCommit();
}
return TRUE;
}
To my surprise, this code did NOT work, failing with a strange error "Implicit transactions are not supported."
After some failed investigations to find the cause of the error, an engineer working at Microsoft, kindly enough, hinted me the cause of the problem.
CreateFileTransacted
calls under the cover CreateFile
API, and since we hooked CreateFile
with CreateFileTransacted
, now CreateFileTransacted
calls itself. Due to some function check, this did not end up with the program crashing due to stack overflow, but did give me this weird error.In fact, later I knew that implicit transaction support did exist in Vista's early betas but was removed for some reasons (most probably security or incompatibility).
Here is a code snippet on how TxF was used in early vista betas from TxF msdn blogs:
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);
This means that it is possible by running a program in TxF which it was not designed for, you might run at the risk of breaking the software license or the program functionality. Of course this article is mainly an educational resource to demonstrate the vista TxF APIs and Detours.
So back to our program, here is how it was fixed
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;
h = CreateFileTransactedW(
lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile,
trans,
NULL,
0
);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueCreateFile, TransCreateFile);
DetourTransactionCommit();
return h;
}
So every time our TransCreateFile
function is called, before we call CreateFileTransactedW
, we will restore CreateFileW
to its original API function then hook it again after the call is completed.
Now when CreateFileTransactedW
calls CreateFileW
internally, it goes to the right function.
TxF Miniversions
Another interesting feature in TxF is Miniversions.You can save the state of the current changes as a Miniversion (Checkpoint).
CreateFileTransacted
optionally takes a miniversion in its arguments to open a file to a specific saved checkpoint (A function very handy to implement Undo and Redo buffers).
However to save a miniversion, you need to tell the KTM directly to do so. But remember the KTM is a kernel module so how to tell it to create a miniversion?
Communication with kernel module is not done directly in windows, it is done through the IO manager. You send the IO manager a control code, specify which module to pass the control code to, and you can exchange input and output buffers (there are different modes for buffer exchange which are not in the scope of this article). The IO manager then routes the control command to the kernel module and does the buffer exchange between user mode programs and kernel mode programs.
To communicate with the IO manager, use DeviceIoControl
on the open handle of the file and FSCTL_TXFS_CREATE_MINIVERSION
control code to save the miniversion. The TXFS_CREATE_MINIVERSION_INFO
structure will be returned in the output buffer containing information about the created miniversion.
TXFS_CREATE_MINIVERSION_INFO txfMiniversion;
BOOL result = DeviceIoControl(
h, FSCTL_TXFS_CREATE_MINIVERSION, NULL, 0, (LPVOID) &txfMiniversion, (DWORD) sizeof(TXFS_CREATE_MINIVERSION_INFO), (LPDWORD) lpBytesReturned, (LPOVERLAPPED) NULL );
Remember the miniversions are discarded in case you either commit or rollback your transaction.
How to run the example?
You need to have installed Microsoft Windows SDK and Detours Express 2.1. Launch the Windows SDK CMD Shell and go to \Detours Express 2.1\src and type nmake to compile the detoured dll. Also compile \Detours Express 2.1\samples\withdll and \txf which you download with this article (extract the folder under \samples in Detours directory) .
All the Detours compiled files are in \Detours Express 2.1\bin
To run notepad in a txf mode use the command as following
withdll.exe /d:txf.dll /p:detoured.dll notepad txftest.txt
You will be notified that notepad is running in Transacted NTFS mode and txftest.txt if not already existing will be created. Now you can edit txtftest.txt and save it then close notepad.
You will be prompted whether you want to commit the transaction or not. If you choose no, all changes you made to the file will be discarded and file will be rolled back to its initial state, otherwise file is saved to disk.
Final Considerations
This is not by anyway a perfect hook , you will notice that this method may cause problems with some applications or in some cases since there are many other file operations needs to be hooked. To name some:
CopyFileTransacted
CreateDirectoryTransacted
CreateHardLinkTransacted
CreateSymbolicLinkTransacted
DeleteFileTransacted
FindFirstFileNameTransactedW
FindFirstFileTransacted
FindFirstStreamTransactedW
GetCompressedFileSizeTransacted <code>
GetFileAttributesTransacted
GetFullPathNameTransacted
GetLongPathNameTransacted
MoveFileTransacted
RemoveDirectoryTransacted
SetFileAttributesTransacted
Those functions can all be hooked the same way we hooked CreateFileTransacted
, but making this code 100% compatible to be hooked with all applications is not the target of this article.
Also as a friend of mine notified me, this sample code is not thread safe. That means if you are hooking a multithread application, further changes should be made to the code for thread synchronization if more than one thread could possibly call CreateFile
at the same time.
You learned now about transactions, KTM, TxF and Detours and now you can start the real the fun.