
विंडोज यूज़र-मोड शेलकोड विकास ढांचा (WUMSDF)
v1.1SILVERPICK परियोजना एक Windows User-Mode Shellcode Development Framework (WUMSDF) है, जिसका एकमात्र उद्देश्य क्षमता डेवलपर्स को C/C++ का उपयोग करके Windows x64 के लिए Position Independent Code (PIC) ब्लॉब्स को आसान तरीके से बनाने में सक्षम बनाना है, ताकि इस तरह के प्रयास की विकास लागत को कम किया जा सके।
यह WILDBEAST परियोजना से व्युत्पन्न है और, इस प्रकार, निम्नलिखित का लाभ उठाता है:
Visual Studio Code को कोड संपादक के रूप मेंMinGW-w64 टूलचेन को कंपाइलर टूलचेन के रूप मेंGNU Make को बिल्ड सिस्टम के रूप मेंआप सेटअप निर्देश यहाँ पा सकते हैं: GCC-Clang-Setup-Windows
कृपया ध्यान दें कि यह परियोजना MSYS2 का उपयोग कर रही है।
उच्च-स्तरीय प्रोग्रामिंग भाषाओं में शेलकोड लिखना कोई नई बात नहीं है, और 2010 से इसी विषय पर असंख्य ब्लॉग पोस्ट और शोध पत्र प्रकाशित हो चुके हैं। तो, SILVERPICK में नया क्या है?
खैर, मुझे खुशी है कि आपने पूछा।
SILVERPICK की आस्तीन में कई बेहतरीन ट्रिक्स छिपी हुई हैं, लेकिन सबसे बढ़कर, यह इस विषय पर मेरा अपना दृष्टिकोण है।
तो, बिना किसी देरी के, मैं आपके सामने अपनी पहली ट्रिक प्रस्तुत करता हूँ।
जब से मैट ग्रेबर ने C में शेलकोड लिखने को लोकप्रिय बनाया, अधिकतर लोग उनके Assembly भाषा में लिखे गए 16-बाइट स्टैक संरेखण स्टब का उपयोग कर रहे हैं।
हालाँकि यह कोई समस्या नहीं है, फिर भी चूँकि हम IKEA नहीं हैं, असेंबली आवश्यक नहीं होनी चाहिए, और वास्तव में यह आवश्यक नहीं है।
एक GCC फ़ंक्शन एट्रिब्यूट मौजूद है जो आपके लिए स्टैक संरेखण स्टब उत्पन्न कर देगा।
मिलिए force_align_arg_pointer फ़ंक्शन एट्रिब्यूट से, जो एक सहायक ALIGN_STACK मैक्रो के रूप में है, और निम्नलिखित असेंबली उत्पन्न करता है:
Disassembly of section .init:
<PicEntry>:
push rbp
mov rbp,rsp
and rsp,0xfffffffffffffff0
sub rsp,0x20
call <PicEntry+0x11> IMAGE_REL_AMD64_REL32 .text$payload
leave
ret
.init अनुभाग क्या है, आप पूछेंगे? खैर, यह मेरी दूसरी ट्रिक की ओर एक अच्छा संक्रमण प्रदान करता है।
हो सकता है कि मैट ग्रेबर ने किसी समय C में शेलकोड लिखने को लोकप्रिय बनाया हो, लेकिन वास्तव में, पॉल उनगुर ने Stardust के साथ इस ब्लैक आर्ट को पुनर्जीवित किया।
अब, Stardust फ़ंक्शनों और डेटा के स्थान को सही क्रम में उचित PE अनुभाग में नियंत्रित करने के लिए एक Binutils linker script का उपयोग करता है। यह तकनीक स्वयं ऑस्टिन हडसन के कार्य से व्युत्पन्न है, और कई लोग उनकी लिंकर स्क्रिप्ट के एक रूप का उपयोग करते हैं।
जहाँ लिंकर स्क्रिप्ट लिंकर अनुभाग क्रम के लिए बेहतरीन हैं, वहीं यदि आपको केवल किसी विशेष फ़ंक्शन को कोड अनुभाग की शुरुआत में रखना है, तो वे अनावश्यक हैं।
अब आता है section फ़ंक्शन एट्रिब्यूट, जिसमें .init नामक एक विशेष अनुभाग नाम होता है, जो लिंकर को संकेत देता है कि फ़ंक्शन में main() से पहले का रनटाइम प्रारंभिकरण कोड है और इसे लिंक क्रम में पहले होना चाहिए।
इसी उद्देश्य से, CODE_BEGIN मैक्रो बनाया गया है।
अपनी तीसरी ट्रिक के लिए, मैं आपके सामने STACK_STRING मैक्रो प्रस्तुत करता हूँ।
C में, आप स्ट्रिंग लिटरल को ANSI वर्णों की एक सरणी के रूप में घोषित करके एक स्टैक स्ट्रिंग (एक स्ट्रिंग जो स्टैक पर गतिशील रूप से निर्मित होती है) बना सकते हैं:
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };
C++ में, आप केवल एक char सरणी को constexpr के रूप में चिह्नित करके एक स्टैक स्ट्रिंग बना सकते हैं:
constexpr char charrHelloKitty[]{ "HelloKitty" };
हालाँकि, ये दोनों तकनीकें कंपाइलर ऑप्टिमाइज़ेशन के सामने तब बेकार हो जाती हैं यदि स्ट्रिंग लिटरल पर्याप्त रूप से बड़े हों, हमारे समाधान के विपरीत, जो कैन बोलुक की बदौलत कुछ चतुर C++ टेम्पलेट मेटाप्रोग्रामिंग हैक के कारण स्ट्रिंग लंबाई और कंपाइलर ऑप्टिमाइज़ेशन के स्तर की परवाह किए बिना काम करेगा।
इस मैक्रो का उपयोग काफी सीधा है:
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
यह निम्नलिखित असेंबली उत्पन्न करेगा:
mov [rsp+58h+var_23], 61h ; 'a'
mov [rsp+58h+var_22], 6Eh ; 'n'
mov [rsp+58h+var_21], 20h ; ' '
mov [rsp+58h+var_20], 65h ; 'e'
mov [rsp+58h+var_1F], 78h ; 'x'
mov [rsp+58h+var_1E], 74h ; 't'
mov [rsp+58h+var_1D], 72h ; 'r'
mov [rsp+58h+var_1C], 61h ; 'a'
mov [rsp+58h+var_1B], 20h ; ' '
mov [rsp+58h+var_1A], 6Ch ; 'l'
mov [rsp+58h+var_19], 6Fh ; 'o'
mov [rsp+58h+var_18], 6Eh ; 'n'
mov [rsp+58h+var_17], 67h ; 'g'
mov [rsp+58h+var_16], 20h ; ' '
mov [rsp+58h+var_15], 68h ; 'h'
mov [rsp+58h+var_14], 65h ; 'e'
mov [rsp+58h+var_13], 6Ch ; 'l'
mov [rsp+58h+var_12], 6Ch ; 'l'
mov [rsp+58h+var_11], 6Fh ; 'o'
mov [rsp+58h+var_10], 20h ; ' '
mov [rsp+58h+var_2F], 0
mov [rsp+58h+var_F], 77h ; 'w'
mov [rsp+58h+var_E], 6Fh ; 'o'
mov [rsp+58h+var_D], 72h ; 'r'
mov [rsp+58h+var_C], 6Ch ; 'l'
mov [rsp+58h+var_B], 64h ; 'd'
mov [rsp+58h+var_A], 21h ; '!'
mov [rsp+58h+var_33], 44h ; 'D'
mov [rsp+58h+var_32], 65h ; 'e'
mov [rsp+58h+var_31], 6Dh ; 'm'
mov [rsp+58h+var_30], 6Fh ; 'o'
C++ की बात करते हुए, मैं अपनी चौथी ट्रिक के रूप में कंपाइल-टाइम स्ट्रिंग हैशिंग प्रस्तुत करता हूँ।
हालाँकि यह एक नई अवधारणा नहीं है, SILVERPICK मौजूदा सार्वजनिक कार्यान्वयनों पर कुछ सुधार प्रदान करता है।
पहले, हम सफल हैश टकराव हमले की संभावना को कम करने के लिए लोकप्रिय FNV-1a गैर-क्रिप्टोग्राफिक हैश फ़ंक्शन के 64-बिट प्रकार का उपयोग करते हैं।
दूसरे, हम पूर्व-गणना की गई हैश तालिका लुकअप, जैसे HashDB, से बचाव के लिए हैश फ़ंक्शन के लिए एक संशोधित पैरामीटर का उपयोग करते हैं। महत्वपूर्ण बात यह है कि यह हैश फ़ंक्शन के गुणों को नहीं बदलता है।
रन टाइम पर एक छोटी स्ट्रिंग को हैश करने के लिए, बस HASH_STRING_RUN_TIME मैक्रो का उपयोग करें।
कंपाइल टाइम पर एक छोटे स्ट्रिंग लिटरल को हैश करने के लिए, बस HASH_STRING_COMPILE_TIME मैक्रो का उपयोग करें। केवल कंपाइल-टाइम मूल्यांकन consteval के माध्यम से सुनिश्चित किया जाता है।
यह पता चलता है कि आप x86 स्ट्रिंग निर्देशों का उपयोग करके काफी कुछ C Runtime Library (CRT) फ़ंक्शन लागू कर सकते हैं। इसलिए, निश्चित रूप से, मुझे उन्हें कंपाइलर इंट्रिन्सिक्स और इनलाइन असेंबली के मिश्रण का उपयोग करके लागू करना था।
क्या आप अपने कोड में msvcrt!memset फ़ंक्शन का उपयोग करना चाहते हैं? इसके बजाय ZERO_MEMORY मैक्रो का उपयोग करें, जो कंपाइलर इंट्रिन्सिक के माध्यम से उत्सर्जित rep stosb निर्देश का उपयोग करता है।
msvcrt!memcpy फ़ंक्शन या msvcrt!memmove फ़ंक्शन के बारे में क्या, आप पूछेंगे? प्रतिस्थापन के रूप में COPY_MEMORY मैक्रो से मिलिए, जो कंपाइलर इंट्रिन्सिक के माध्यम से उत्सर्जित rep movsb निर्देश का उपयोग करता है।
लेकिन msvcrt!memcmp फ़ंक्शन के विकल्प के बारे में क्या? यह पता चलता है कि repe cmpsb निर्देश को उत्सर्जित करने के लिए वास्तव में कोई कंपाइलर इंट्रिन्सिक उपलब्ध नहीं है। इसलिए, हम इसके बजाय इनलाइन असेंबली का उपयोग करके एक compare_memory फ़ंक्शन लिखते हैं।
अंत में, यदि आप msvcrt!memchr फ़ंक्शन के प्रतिस्थापन की तलाश में हैं, तो scan_memory फ़ंक्शन से मिलिए, जो फिर से इनलाइन असेंबली का उपयोग करता है क्योंकि repne scasb निर्देश को उत्सर्जित करने के लिए कोई कंपाइलर इंट्रिन्सिक उपलब्ध नहीं है।
ओह, और क्या मैं यह उल्लेख करना भूल गया कि आप scan_memory रूटीन का उपयोग करके msvcrt!strlen फ़ंक्शन का अपना सुरक्षित संस्करण इस प्रकार लिख सकते हैं:
DWORD_PTR dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN);
कृपया ध्यान दें कि ये कार्यान्वयन लक्ष्य CPU माइक्रोआर्किटेक्चर के आधार पर सबसे कुशल कोड उत्पन्न नहीं कर सकते हैं। हालाँकि, वे कार्य को पूरा करने की गारंटी देते हैं।
कुछ और मनोरंजक ट्रिक्स में रुचि है?
Common.h में बहुत सारे अन्य छोटे मैक्रोज़ हैं जो कंपाइलर को प्रबंधित करने की कुछ जटिलताओं को दूर करने के लिए मौजूद हैं।
GetModuleHandle फ़ंक्शन का एक निर्भरता-मुक्त कार्यान्वयन UserModuleBase.cpp में प्रदान किया गया है। उपयोग में आसानी को सरल बनाने के लिए, GET_USER_MODULE_BASE नामक एक सहायक मैक्रो बनाया गया है।
इसी तरह, GetProcAddress फ़ंक्शन का एक निर्भरता-मुक्त कार्यान्वयन PEParse.cpp में प्रदान किया गया है, जिसे फिर GET_EXPORTED_SYMBOL_ADDRESS नामक एक उपयुक्त मैक्रो में लपेटा गया है। इसके अलावा, रन-टाइम डायनामिक लिंकिंग में सहायता के लिए दो और मैक्रोज़ प्रदान किए गए हैं - फ़ंक्शन पॉइंटर को 0 पर घोषित करने और प्रारंभ करने के लिए INITIALIZE_FUNCTION_POINTER, और उक्त फ़ंक्शन पॉइंटर को हल करने के लिए RESOLVE_FUNCTION_POINTER।
Visual Studio Code एकीकरण परियोजना में निर्मित है ताकि डेवलपर्स बिना किसी परेशानी के बिल्ड प्रक्रिया के लिए Ctrl+Shift+B कीबोर्ड शॉर्टकट का उपयोग कर सकें।
GitHub Actions एकीकरण भी CI बिल्ड सक्षम करने के लिए परियोजना में निर्मित है।
परियोजना अपनी सुव्यवस्थित संरचना, साथ ही पूरी तरह से टिप्पणी किए गए और अपेक्षाकृत स्वच्छ कोड पर कुछ गर्व महसूस करती है।
अंत में, परियोजना के Makefile पर एक नज़र डालें, जिसमें कंपाइलर और लिंकर फ़्लैग्स का बेहतरीन चयन है जो छोटा, सुरक्षित और OPSEC-अनुकूल कोड उत्पन्न करेगा। इस बीच, वर्बोज़ लॉगिंग और एक उत्पन्न लिंकर मैप फ़ाइल बिल्ड प्रक्रिया में दृश्यता प्रदान करेगी ताकि टूलचेन की गहरी समझ को सुविधाजनक बनाया जा सके। इसके अतिरिक्त, प्रत्येक ट्रांसलेशन यूनिट एक डिसअसेम्बली फ़ाइल भी उत्पन्न करती है, जिसे देखने पर आप अक्सर कहेंगे "कंपाइलर ने अभी क्या किया?", आदि।
यदि आप इस फ्रेमवर्क से सहमत हैं, तो यह अनुभाग बताता है कि आप इसका उपयोग कैसे करेंगे।
निम्नलिखित PicMain.cpp से लिया गया एक अंश है:
/// @brief PIC start function
/// @param None
/// @return None
EXTERN_C NO_INLINE VOID __stdcall payload(
VOID
) {
// Init local variables
PVOID pKernel32 = nullptr;
INITIALIZE_FUNCTION_POINTER(LoadLibraryA);
HMODULE hUser32 = nullptr;
STACK_STRING(sstrUser32, "user32.dll");
INITIALIZE_FUNCTION_POINTER(MessageBoxA);
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");
// Get the image base address of kernel32.dll
pKernel32 = GET_USER_MODULE_BASE("kernel32.dll");
if (pKernel32 == nullptr)
goto cleanup;
// Resolve kernel32!LoadLibraryA
RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA);
if (LoadLibraryA == nullptr)
goto cleanup;
// Load User32.dll into the process VAS
hUser32 = LoadLibraryA(sstrUser32.data());
if (hUser32 == nullptr)
goto cleanup;
// Resolve user32!MessageBoxA
RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA);
if (MessageBoxA == nullptr)
goto cleanup;
// Display a message box
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);
// Cleanup
cleanup:
return;
}
काफी आसान लगता है, है ना?
SILVERPICK फ्रेमवर्क का उपयोग करके C/C++ में PIC लिखते समय, आपको निम्नलिखित नियमों को ध्यान में रखना होगा:
payload फ़ंक्शन को उसी प्रकार मानें जैसे आप किसी पारंपरिक प्रोग्राम के main फ़ंक्शन को मानते हैं, अर्थात (छद्म) प्रवेश बिंदु के रूप में।Windows API या Native API फ़ंक्शन का उपयोग केवल रन-टाइम डायनामिक लिंकिंग के माध्यम से किया जा सकता है, यह सुनिश्चित करने के बाद कि फ़ंक्शन प्रोटोटाइप संबंधित हेडर फ़ाइल में उपलब्ध है।इस अनुभाग में नियोजित वृद्धियों की एक गैर-विस्तृत सूची है जिन्हें भविष्य की परियोजना में एकीकृत किया जाएगा।
Clang/LLVM टूलचेन पर स्विच करें।GS सेगमेंट बेस पता प्राप्त करने की वैकल्पिक विधि।Export Address Filtering (EAF) एक्सप्लॉइट शमन को बायपास करने की क्षमता।निम्नलिखित कालानुक्रमिक क्रम में व्यवस्थित संदर्भों की एक सूची है जो मेरे शोध के दौरान अत्यंत मूल्यवान साबित हुई और इस परियोजना के लिए प्रेरणा के रूप में बड़े पैमाने पर उपयोग की गई:
Writing Shellcode with a C Compiler निक हार्बर द्वारा (2010)
Shellcode with a C-compiler डिडिएर स्टीवेंस द्वारा (2010)
Writing Optimized Windows Shellcode in C मैट ग्रेबर द्वारा (2013)
Shellcode the better way, or how to just use your compiler जस्टिन फिशर द्वारा (2016)
ShellcodeStdio जैक उलरिच द्वारा (2016)
Shellcode: A Windows PIC using RSA-2048 key exchange, AES-256, SHA-3 ओडज़ान द्वारा (2016)
Writing Optimized Windows Shellcode दिमित्री फोरनी द्वारा (2017)
Writing and Compiling Shellcode in C अलेक्जेंड्रा डोनिएक और मेंटवीदास बारानौस्कस द्वारा (2021)
Creating Shellcode from any Code Using Visual Studio and C++ हामिद मेमार द्वारा (2021)
Writing Optimized Windows Shellcode in C फिलिप वोल्डेक द्वारा (2021)
From C, with inline assembly, to shellcode स्टीव सालिनास द्वारा (2023)
How To Craft Your Own Windows x86/64 Shellcode with Visual Studio याज़िद बेनजामा द्वारा (2023)
Modern implant design: position independent malware development पॉल उनगुर द्वारा (2024)
जिस बात ने मुझे विचित्र महसूस कराया वह Microsoft Defender द्वारा VirTool:Win64/Silepesz.A का वर्गीकरण था।
ये वे बाइट्स हैं जिन्हें सिग्नेचर कवर करता है:
[+] Target file size: 2560 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0x4CB
000003CB 44 24 2F 32 C6 44 24 30 2E C6 44 24 31 64 C6 44 D$/2�D$0.�D$1d�D
000003DB 24 32 6C C6 44 24 33 6C C6 44 24 35 61 C6 44 24 $2l�D$3l�D$5a�D$
000003EB 36 6E C6 44 24 37 20 C6 44 24 38 65 C6 44 24 39 6n�D$7 �D$8e�D$9
000003FB 78 C6 44 24 3A 74 C6 44 24 3B 72 C6 44 24 3C 61 x�D$:t�D$;r�D$<a
0000040B C6 44 24 3D 20 C6 44 24 3E 6C C6 44 24 3F 6F C6 �D$= �D$>l�D$?o�
0000041B 44 24 40 6E C6 44 24 41 67 C6 44 24 42 20 C6 44 D$@n�D$Ag�D$B �D
0000042B 24 43 68 C6 44 24 44 65 C6 44 24 45 6C C6 44 24 $Ch�D$De�D$El�D$
0000043B 46 6C C6 44 24 47 6F C6 44 24 48 20 C6 44 24 29 Fl�D$Go�D$H �D$)
0000044B 00 C6 44 24 49 77 C6 44 24 4A 6F C6 44 24 4B 72 .�D$Iw�D$Jo�D$Kr
0000045B C6 44 24 4C 6C C6 44 24 4D 64 C6 44 24 4E 21 C6 �D$Ll�D$Md�D$N!�
0000046B 44 24 25 44 C6 44 24 26 65 C6 44 24 27 6D C6 44 D$%D�D$&e�D$'m�D
0000047B 24 28 6F E8 55 00 00 00 48 85 C0 74 4B 48 BA 58 $(o�U...H.AtKH�X
0000048B D0 CC C6 F8 E7 BF 0A 48 89 C1 E8 86 FD FF FF 48 DI�o��.H.A�.y��H
0000049B 85 C0 74 34 48 8D 4C 24 2A FF D0 48 85 C0 74 28 .At4H.L$*�DH.At(
000004AB 48 BA D9 92 FB 55 9A AC 70 E0 48 89 C1 E8 63 FD H�U.�U.�p�H.A�cy
000004BB FF FF 48 85 C0 74 11 48 8D 54 24 35 45 31 C9 4C ��H.At.H.T$5E1�L
यह निम्नलिखित स्रोत में वापस अनुवादित होता है:
pKernel32 = GET_USER_MODULE_BASE("kernel32.dll");
if (pKernel32 == nullptr)
goto cleanup;
RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA); // mov rdx, 0x0ABFE7F8C6CCD058 (FNV-1a hash of "LoadLibraryA" with modified offset basis)
if (LoadLibraryA == nullptr)
goto cleanup;
hUser32 = LoadLibraryA(sstrUser32.data());
if (hUser32 == nullptr)
goto cleanup;
RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA); // mov rdx, 0xE070AC9A55FB92D9 (FNV-1a hash of "MessageBoxA" with modified offset basis)
if (MessageBoxA == nullptr)
goto cleanup;
MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK); // arg setup only
कहने की आवश्यकता नहीं है कि यह एक अत्यंत भंगुर पहचान है और यह केवल उसी सटीक नमूना कोड का पता लगाएगा जो उदाहरण में दिखाया गया है। हालाँकि, यह पॉलीमॉर्फिक API हैश का उपयोग करने के महत्व को उजागर करता है।
From C to shellcode (simple way) Print3M द्वारा (2024)
relocatable टाईमे गोमर्स द्वारा (2025)
PIC Development Crash Course राफेल मज द्वारा (2025)
scfw पेट्र बेनेस द्वारा (2026)