英文:
Hooking NtWriteFile with MS Detours
问题
我尝试挂钩 NtWriteFile。下面是我为一个dll编写的代码的精简版本。思路是使用MS Detours的withdll.exe加载生成的dll。通过一些调试,我发现确实调用了MyNtWriteFile,但然后卡在了原始函数调用点(RealNtWriteFile调用)上。对于这个问题是否有任何提示将不胜感激。
#include "pch.h"
#include <windows.h>
#include <detours.h>
#include <stdio.h>
#include <iostream>
#include <winternl.h>
typedef NTSTATUS(*NtWriteFileFunc)(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key
);
NTSTATUS WINAPI MyNtWriteFile(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key
)
{
// 调用原始函数。
NtWriteFileFunc RealNtWriteFile = (NtWriteFileFunc)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtWriteFile");
NTSTATUS tmp = RealNtWriteFile(FileHandle, Event, ApcRoutine, ApcContext,
IoStatusBlock, Buffer, Length, ByteOffset, Key);
return tmp;
}
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
NtWriteFileFunc RealNtWriteFile = (NtWriteFileFunc)GetProcAddress(hNtdll, "NtWriteFile");
LONG error;
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)RealNtWriteFile, MyNtWriteFile);
error = DetourTransactionCommit();
}
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)RealNtWriteFile, MyNtWriteFile);
error = DetourTransactionCommit();
}
return TRUE;
}
英文:
I try to hook into NtWriteFile. Below you find a stripped version of the code I wrote for a dll. The idea is to load the resulting dll with the withdll.exe of MS Detours. With some debugging I found that MyNtWriteFile gets indeed called but then gets stuck at the point of the original function call (the RealNtWriteFile call). Any hints on why is that are highly appreciated.
#include "pch.h"
#include<windows.h>
#include <detours.h>
#include <stdio.h>
#include <iostream>
#include <winternl.h>
typedef NTSTATUS(*NtWriteFileFunc)(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key
);
NTSTATUS WINAPI MyNtWriteFile(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key
)
{
// Call the original function.
NtWriteFileFunc RealNtWriteFile = (NtWriteFileFunc)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtWriteFile");
NTSTATUS tmp = RealNtWriteFile(FileHandle, Event, ApcRoutine, ApcContext,
IoStatusBlock, Buffer, Length, ByteOffset, Key);
return tmp;
}
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
HMODULE hNtdll = LoadLibrary(L"ntdll.dll");
NtWriteFileFunc RealNtWriteFile = (NtWriteFileFunc)GetProcAddress(hNtdll, "NtWriteFile");
LONG error;
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)RealNtWriteFile, MyNtWriteFile);
error = DetourTransactionCommit();
}
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)RealNtWriteFile, MyNtWriteFile);
error = DetourTransactionCommit();
}
return TRUE;
}
</details>
# 答案1
**得分**: 2
调用 `RealNtWriteFile` 是一个基本错误,因为这会导致无限递归循环。你需要使用指针,在调用 `DetourAttach` 时进行修改,以调用原始函数。
首先使用静态链接 `ntdll.lib` - 不需要 `GetProcAddress`。
然后声明(在 x64 中,在 x86 中需要小的额外技巧)下一个变量:
```c
EXTERN_C extern PVOID __imp_NtWriteFile;
你需要更改此变量的保护:
VirtualProtect(&__imp_NtWriteFile, sizeof(PVOID), PAGE_EXECUTE_READWRITE, &op);
如果你要拦截多个函数 - 最好首先获取自己的 IAT 部分并更改所有 IAT 的保护,以免多次执行此操作(RtlImageDirectoryEntryToData(&__ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_IAT, &size);
)。
然后使用下面的调用:
DetourDetach(&__imp_NtWriteFile, MyNtWriteFile);
恢复 __imp_
/ IAT 的保护(可选的)。
在 MyNtWriteFile
内部,如果你想调用原始函数 - 简单地调用 NtWriteFile
即可。
这一切的意义在于,__imp_NtWriteFile
最初将保存 ntdll!NtWriteFile
的地址(这是由加载器完成的)。
DetourAttach(&__imp_NtWriteFile, myhook)
- 在指向 __imp_NtWriteFile
的地址上设置钩子并修改这个指针(它是 _Inout_
参数)。在成功调用后,__imp_NtWriteFile
将指向 trampoline(一块内存块 - 其中保存了几个原始字节或挂钩函数,并在这些字节之后跳转到函数体)。
NtWriteFile
使用存储在变量 __imp_NtWriteFile
中的值来调用 API。重要的是,API 必须声明为 __declspec(dllimport)
。
这是常见的做法 - 导入的 someapi
使用 PVOID __imp_someapi
变量。
如果由于某种原因(实际上不需要这样做),不希望静态链接到 ntdll - 无论如何,在这种情况下都要声明和定义:
EXTERN_C PVOID __imp_NtWriteFile = 0;
请注意,变量不再是 extern
(只声明),而是已定义的。
现在你需要直接调用:
__imp_NtWriteFile = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtWriteFile");
现在当然不需要 VirtualProtect。
对于 x86 - 名称会被修饰为:
__imp__NtWriteFile@36
不幸的是,我们不能在 C/C++ 代码中直接使用带有 @
符号的名称。因此,有两种可能的解决方案 - 使用汇编语言 - 在汇编语言中我们可以使用这些名称并从汇编语言中调用 DetourAttach
。
但更简单的解决方案是使用 /ALTERNATENAME
链接器选项。
所以使用:
#ifdef _X86_
#pragma comment(linker, "/ALTERNATENAME:___imp_NtWriteFile=__imp__NtWriteFile@36")
#endif
如果你静态链接到 ntdll - 变量 __imp__NtWriteFile@36
是存在的 - 它由链接器定义。但我们不能在 cpp 中访问它。相反,我们使用 ___imp_NtWriteFile
作为外部定义。它不存在,我们告诉链接器使用 __imp__NtWriteFile@36
。
如果你不静态链接到 ntdll,但是自己定义了 __imp_NtWriteFile
- 需要反向声明:
#ifdef _X86_
#pragma comment(linker, "/ALTERNATENAME:__imp__NtWriteFile@36=___imp_NtWriteFile")
#endif
因为在这种情况下,__imp__NtWriteFile@36
已经不存在,需要在其中使用 ___imp_NtWriteFile
。
在钩子内部可以做什么:当然,仅设置钩子并调用原始 API 是没有意义的。因此,真正的代码会执行更多操作。在这里存在递归调用的风险 - 在钩子中调用某些 API,而这些 API 再次间接调用你的钩子。为此,你需要检测递归调用,并在这种情况下 - 直接调用原始 API,不进行任何额外的处理。可以使用 RtlGetFrame
、RtlPushFrame
、RtlPopFrame
或 tls 来实现这一点。但这已经是一个单独的问题。
英文:
call RealNtWriteFile
is fundamental error. because this lead to infinite reqursive loop. you need use pointer , modified in call DetourAttach
, for call original function.
at first use static link with ntdll.lib - not need GetProcAddress
.
than declare ( in x64, in x86 need small additional trick) next variable:
EXTERN_C extern PVOID __imp_NtWriteFile;
you need change protect of this variable:
VirtualProtect(&__imp_NtWriteFile, sizeof(PVOID), PAGE_EXECUTE_READWRITE, &op);
if you detour several function - better first get self IAT section and change protect of all IAT, for not do this several times ( RtlImageDirectoryEntryToData(&__ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_IAT, &size);
)
and use next call
DetourDetach(&__imp_NtWriteFile, MyNtWriteFile);
restore protection of _imp / IAT ( optional )
and inside MyNtWriteFile
, if you want call original function - simply call NtWriteFile
as is.
sense of all this is next - __imp_NtWriteFile
initially will be hold address of ntdll!NtWriteFile
( this do loader )
the DetourAttach(&__imp_NtWriteFile, myhook)
- set hook in address to which point __imp_NtWriteFile
and modify this pointer (it Inout ) parameter. after (success) call __imp_NtWriteFile
will be point to tramopline ( chunk of memory - where several original bytes or hooked function saved + jmp to function body after this bytes)
and NtWriteFile
use value stored at variable __imp_NtWriteFile
for call api. main that api must be declared with __declspec(dllimport)
this is common - for imported someapi
used PVOID __imp_someapi
variable.
if you use delayed import - __imp_load_someapi
name is used (but not in x86)
if by some reason (really not need do this) want not static link to ntdll - anyway declare and define in this case
EXTERN_C PVOID __imp_NtWriteFile = 0;
note, that already variable not extern
(declared only) but defined.
and you need now direct call __imp_NtWriteFile = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtWriteFile");
and now you not need VirtualProtect of course.
for x86 - name is mangled it will be
__imp__NtWriteFile@36
unfortunatelly we can not direct use names with @
symbol in c/c++ code. so possible 2 solution - use asm - in it we can have such names and call DetourAttach
from asm.
but more simply solution, use /ALTERNATENAME
linker option.
so use
#ifdef _X86_
#pragma comment(linker, "/ALTERNATENAME:___imp_NtWriteFile=__imp__NtWriteFile@36")
#endif
in case you static link to ntdll - the variable __imp__NtWriteFile@36
is exist - it defined by linker. but we can not access it in cpp. instead we use ___imp_NtWriteFile
defined as extern. it not exist and we tell linker use __imp__NtWriteFile@36
if you not static link to ntdll, but defined __imp_NtWriteFile
by self - need inverted declaration
#ifdef _X86_
#pragma comment(linker, "/ALTERNATENAME:__imp__NtWriteFile@36=___imp_NtWriteFile")
#endif
because in this case already __imp__NtWriteFile@36
not exist and need use ___imp_NtWriteFile
in it place
and what you can do inside hook: of course no sense set hook for only and call original api. so real code will be do something more. and here exist risk or reqursive call - in hook you call some api , and this api indirect again call your hook. for this you need detect reqursive call and in such case - direct call original api, without any extra processing. for this can be used RtlGetFrame
, RtlPushFrame
, RtlPopFrame
or tls. but this is already separare question
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论