Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
packer-tutorial — विंडोज़ के लिए पैकर लिखने के तरीके पर एक ट्यूटोरियल! | Kitploit
उपकरण/GitHubGitHub/frank2/packer-tutorial
रिवर्स इंजीनियरिंगमालवेयर विश्लेषणबाइनरी विश्लेषणलर्निंग और शिक्षा
GitHubfrank2/packer-tutorial

packer-tutorial

विंडोज़ के लिए पैकर लिखने के तरीके पर एक ट्यूटोरियल!

रिपॉजिटरी देखें
319322 साल पहलेKitploit द्वारा समीक्षित

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें

PACKERS

विषय-सूची

  1. पैकर क्या है?: पैकर के उद्देश्य का परिचय और पैकर के विकास के लिए आवश्यक चीज़ों के बारे में एक मार्गदर्शिका।
  2. आवश्यक शर्तें: इस ट्यूटोरियल के साथ काम करने के लिए आवश्यक उपकरण।
  3. एक झलक: इस ट्यूटोरियल के साथ आप क्या बनाएँगे, इसका एक प्रदर्शन।
  4. हमारे CMake प्रोजेक्ट की रूपरेखा: पैकर की कुछ जटिल आवश्यकताओं के लिए बिल्ड सिस्टम बनाने पर एक लघु CMake ट्यूटोरियल।
  5. अपने स्टब में बाइनरीज़ पैक करना: पैकर/स्टब संयोजन का पैकर भाग कैसे लिखें, इस पर एक ट्यूटोरियल, और साथ ही विंडोज़ निष्पादन योग्य फ़ाइल प्रारूप का परिचय।
    1. संसाधनों का प्रबंधन
    2. PE फ़ाइल को पार्स करना
    3. PE फ़ाइल में हेरफेर करना
  6. लोडर का अनुकरण: लक्ष्य निष्पादन योग्य को अनपैक करने और लोड करने के लिए एक न्यूनतम स्टब निष्पादन योग्य कैसे बनाएं, इस पर एक ट्यूटोरियल, और साथ ही अधिक उन्नत विंडोज़ निष्पादन योग्य हेरफेर का परिचय।
    1. हमारे PE को मेमोरी से पढ़ना
    2. निष्पादन के लिए हमारे PE को लोड करना
    3. API इम्पोर्ट को हल करना
    4. पतों को हल करना
    5. निष्पादन स्थानांतरित करना
  7. आगे के अभ्यास: आपके पैकर विकास ज्ञान को और विस्तृत करने के लिए कुछ अभ्यास।

क्या यह एक कठिन पठन लगता है? प्रस्तुति संस्करण आज़माएँ, जो इस readme का सारांश देता है। YouTube वीडियो जल्द आ रहा है!

पैकर क्या है?

एक पैकर एक प्रोग्राम है जो किसी अन्य प्रोग्राम को अपने एड्रेस स्पेस (या कभी-कभी, किसी अन्य प्रोसेस के एड्रेस स्पेस) के भीतर डीकंप्रेस करके लॉन्च करता है। यह कभी-कभी उस वेक्टर के रूप में जाना जाता है जो डिबगर्स और वर्चुअल सैंडबॉक्स जैसे विश्लेषण वातावरणों पर हमला करता है। इसका उपयोग मुख्य रूप से कुछ चीज़ों के लिए किया जाता है:

  • संपीड़न: पैकर सामान्यतः किसी दिए गए बाइनरी के कोड को संपीड़ित करने के लिए उपयोग किए जाते हैं। यह इसके कुछ वैध उपयोगों में से एक है। एक संपीड़ित पैकर के उदाहरण के लिए UPX देखें।
  • अस्पष्टीकरण: पैकर का उपयोग तब भी किया जाता है जब किसी प्रोग्राम को अस्पष्ट करने या रिवर्स इंजीनियरिंग से बचाने का प्रयास किया जाता है। एक एंटी-रिवर्सिंग पैकर के उदाहरण के लिए Riot Games का packman पैकर देखें।
  • बचाव: मैलवेयर अक्सर एंटीवायरस और यहां तक कि EDR से बचने के लिए विभिन्न प्रकार के पैकर का उपयोग करता है। एक बचाव-उन्मुख पैकर के उदाहरण के लिए SmokeLoader पैकर का यह विश्लेषण देखें।

मूल रूप से, एक पैकर के कुछ ही बुनियादी चरण होते हैं:

  1. संपीड़न चरण: यह वह जगह है जहाँ मूल निष्पादन योग्य को एक नई बाइनरी में संपीड़ित, अस्पष्ट, या दोनों किया जाता है।
  2. डीकंप्रेसन चरण: यह वह जगह है जहाँ पैक किया गया निष्पादन योग्य लोडिंग के लिए अपने मूल निष्पादन योग्य को डीकंप्रेस या डी-अस्पष्ट करता है।
  3. लोडिंग चरण: यह वह जगह है जहाँ पैकर होस्ट प्लेटफ़ॉर्म के निष्पादन योग्य लोडर के समान विभिन्न चरणों की नकल करता है। निष्पादन योग्य बाइनरी की जटिलता के कारण, यह सबसे जटिल चरण है, यह इस बात पर निर्भर करता है कि आप कितनी गहराई का अनुकरण करना चाहते हैं।
  4. निष्पादन चरण: यह वह जगह है जहाँ कोड होस्ट स्टब निष्पादन योग्य से नए लोड किए गए (यानी, अनपैक किए गए) कोड में स्थानांतरित होता है।

उतना ही सरल, एक पैकर केवल कुछ हिस्सों से मिलकर बनता है:

  • पैकर: यह हिस्सा स्टब निष्पादन योग्य तैयार करने और उसे किसी दिए गए बाइनरी को अनपैक करने के लिए आवश्यक डेटा देने के लिए ज़िम्मेदार है। यह अंततः संपीड़न चरण है।
  • स्टब: यह बाइनरी मूल बाइनरी को डीकंप्रेस करने, लोड करने और निष्पादित करने के लिए ज़िम्मेदार कोड का हिस्सा है। जैसा कि आप देख सकते हैं, स्टब निष्पादन योग्य पैकर के लिए अधिकांश भारी काम करता है।

पैकर बनाना मुश्किल हो सकता है, क्योंकि स्टब को बनाने और किसी भी तरह पैकर निष्पादन योग्य में आयात करने की आवश्यकता होती है। विंडोज़ के लिए, साधारण संकलन से परे Visual Studio के लिए बिल्ड सिस्टम सीखना एक कठिन काम हो सकता है। सौभाग्य से, CMake एक सरल, क्रॉस-प्लेटफ़ॉर्म बिल्ड सिस्टम प्रदान करता है जो Visual Studio का समर्थन करता है, और अत्यधिक अनुकूलन योग्य है!

इस ट्यूटोरियल का उद्देश्य निम्नलिखित सिखाना है:

  • पैकर के दो मुख्य हिस्सों को एक सुसंगत, परीक्षण योग्य बिल्ड वातावरण में कैसे विभाजित और एकीकृत करें।
  • अतिरिक्त, लोड करने योग्य डेटा जोड़ने के लिए विंडोज़ निष्पादन योग्य में हेरफेर कैसे करें।
  • मनमाना डेटा प्राप्त करने और लोड करने के लिए विंडोज़ निष्पादन योग्य को कैसे नेविगेट करें।
  • विंडोज़ लोडर के हिस्सों का अनुकरण करके विंडोज़ निष्पादन योग्य को लोड और निष्पादित करने के लिए।

यदि आप पहले से ही C++ और CMake से परिचित हैं, तो बेझिझक सीधे पैकिंग अनुभाग पर जाएँ। अन्यथा, पढ़ते रहें!

आवश्यक शर्तें

  • C++ का ज्ञान: यदि आप C++ नहीं जानते हैं तो यह ट्यूटोरियल बेकार है, क्योंकि हम बहुत सारा पॉइंटर अंकगणित करने जा रहे हैं।
  • Visual Studio: Visual Studio में एक पूर्ण-विशेषताओं वाला Windows C++ कंपाइलर शामिल है। यह प्रोजेक्ट Visual Studio 2019 के साथ परीक्षण किया गया है, लेकिन नए संस्करण भी ठीक होने चाहिए।
  • CMake: CMake वह बिल्ड सिस्टम है जिसका उपयोग हम Visual Studio कंपाइलर के लिए अपने बिल्ड को तैयार करने में सहायता करने के लिए करते हैं।

एक झलक

पहले, किसी तरह साबित करते हैं कि यह बिल्ड सिस्टम काम करता है और एक उचित रूप से पैक किया गया निष्पादन योग्य बनाता है। एक बार जब आपके पास CMake और Visual Studio स्थापित हो जाएं, तो अपनी पसंद के टर्मिनल के साथ इस रिपॉजिटरी की रूट निर्देशिका पर जाएं और निम्नलिखित निष्पादित करें:``` $ mkdir build $ cd build $ cmake ../

root@kitploit:~
यह पैकर ट्यूटोरियल कोड बनाने के लिए आवश्यक प्रोजेक्ट फ़ाइलें बनाएगा। फिर चलाएँ:```
$ cmake --build ./ --config Release

यह पैकर प्रोजेक्ट को Release मोड में बनाएगा। फिर, आप निम्नलिखित परीक्षण चला सकते हैं:``` $ ctest -C Release ./

root@kitploit:~
यदि सब कुछ ठीक रहा, तो test\_pack और test_unpack सफल होने चाहिए। यदि आप पैकिंग के परिणाम स्वयं देखना चाहते हैं, तो आपको यह देखना चाहिए:```
$ ./packed.exe
I'm just a little guy!

आइए उन सभी गतिशील भागों के बारे में बात करें जिन्होंने इसे संभव बनाया।

अपनी CMake परियोजना का खाका तैयार करना

तो हम जानते हैं कि एक पैकर के मुख्य घटक क्या हैं, लेकिन हम विकास चक्र के भाग के रूप में अपने पैकर का तुरंत परीक्षण कैसे कर सकते हैं? हमें अपने बाइनरी की पैक और अनपैक प्रक्रिया का ठीक से परीक्षण करने के लिए अपनी समग्र परियोजना में एक तीसरा बाइनरी जोड़ने की आवश्यकता होगी। इसलिए, कुल मिलाकर, हमें तीन परियोजनाओं की आवश्यकता है:

  • पैकर
  • स्टब
  • पैक करने के लिए डमी निष्पादन योग्य

हमें एक संपीड़न लाइब्रेरी की भी आवश्यकता है ताकि यह सुनिश्चित हो सके कि बाइनरी स्टब निष्पादन योग्य में संपीड़ित हो जाए। zlib इसके लिए अच्छा काम करेगा।

हमारी परियोजना संरचना, शुरुआत के लिए, इस तरह दिखनी चाहिए:``` packer/ +---+ CMakeLists.txt + dummy/ | | | +---+ CMakeLists.txt | + src/ | | | +---+ main.cpp | + stub/ | | | +---+ CMakeLists.txt | + src/ | | | +---+ main.cpp | + src/ | | | +---+ main.cpp | + zlib-1.2.13/ | +---+ CMakeLists.txt + ...

root@kitploit:~
हमारा main.cpp प्रत्येक फ़ोल्डर में अभी के लिए बस इस तरह दिख सकता है:```cpp
#include <iostream>

int main(int argc, char *argv[]) {
    std::cout << "I'm just a little guy!" << std::endl;
    
    return 0;
}

शुरुआत में ही, हमें यह स्थापित कर लेना चाहिए कि हमारे पास एक सरल निर्भरता श्रृंखला है, जिसे CMake कुछ इंस्ट्रुमेंटेशन के साथ अच्छी तरह से हल कर देगा:

  • packer और stub zlib पर निर्भर करते हैं
  • packer stub पर निर्भर करता है
  • dummy packer पर निर्भर करता है (क्योंकि इसे packer द्वारा पैक किए जाने की आवश्यकता है)

आइए अपने CMake इंस्ट्रुमेंटेशन के लिए मूल प्रोजेक्ट, यानी packer, से शुरू करें।

CMake आमतौर पर एक न्यूनतम संस्करण की आवश्यकता रखता है, क्योंकि यह लंबे समय से मौजूद है और पुराने संस्करणों के दीर्घकालिक उपयोग का समर्थन करता है। उसके बाद, हम अपने packer प्रोजेक्ट को C++ प्रोजेक्ट के रूप में घोषित कर सकते हैं।```cmake

target a cmake version, you can target a lower version if you like

cmake_minimum_required(VERSION 3.24)

declare our packer as a C++ project (since zlib is a C project and the compilation

detection might get confused)

project(packer CXX)

root@kitploit:~
हम अपने पैकर को "MultiThreaded" (अर्थात /MT) घोषित करना चाहते हैं, न कि "MultiThreadedDLL" (अर्थात /MD), ताकि हमें रनटाइम DLL निर्भरताओं की चिंता न करनी पड़े।```cmake
# this line will mark our packer as MultiThreaded, instead of MultiThreadedDLL
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

वेरिएबल सेटिंग स्टेटमेंट में कोण कोष्ठकों के भीतर का कथन generator expression कहलाता है और जहाँ आवश्यक हो वहाँ configure-time डेटा को हल करने में हमारी मदद करता है, आप इसे इस फ़ाइल में बहुत देखेंगे। यह generator expression जो करता है वह यह है कि जब यह पता लगाता है कि config Debug है, तो Debug स्ट्रिंग उत्सर्जित करता है, और अन्यथा कुछ भी उत्सर्जित नहीं करता। यह एक runtime library प्रदान करता है: जब Debug संकलन प्रोफ़ाइल चुनी जाती है तो MultiThreadedDebug, और जब एक गैर-डिबग संकलन प्रोफ़ाइल चुनी जाती है, जैसे Release, तो MultiThreaded। वहाँ क्या हो रहा है, इसकी अच्छी समझ पाने के लिए conditional generator expressions देखें। CMAKE_MSVC_RUNTIME_LIBRARY वेरिएबल के बारे में अधिक जानकारी के लिए, CMake प्रलेखन देखें।

CMake आपको अपने स्रोत कोड को पदानुक्रम में व्यवस्थित करने की अनुमति देगा, जैसे Visual Studio अपने UI के साथ प्रोजेक्ट बनाते समय स्वचालित रूप से करता है, और यह आपके फ़ोल्डरों में मिलान फ़ाइल नामों के लिए पुनरावर्ती रूप से खोज भी करेगा। हमारी config फ़ाइल में, हम हेडर (.hpp), कोड (.cpp), और संसाधन स्क्रिप्ट (.rc) पर एक वैश्विक recurse सेट करते हैं। हमारे उदाहरण के लिए, हमें वास्तव में केवल main.cpp की आवश्यकता है, लेकिन बड़े प्रोजेक्टों के लिए यह जानना उपयोगी है।```cmake

this will collect header, source and resource files into convenient variables

file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/src/.cpp) file(GLOB_RECURSE HDR_FILES ${PROJECT_SOURCE_DIR}/src/.hpp) file(GLOB_RECURSE RC_FILES ${PROJECT_SOURCE_DIR}/src/*.rc)

this will give you source groups in the resulting Visual Studio project

source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Header Files" FILES ${HDR_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Source Files" FILES ${SRC_FILES}) source_group(TREE "${PROJECT_SOURCE_DIR}" PREFIX "Resource Files" FILES ${RC_FILES})

root@kitploit:~
यहाँ मुझे समझाना चाहिए कि CMake को अधिक सटीक रूप से एक **make system** के रूप में वर्णित किया जाता है। यह दिए गए compiler के आधार पर दिए गए environment के लिए उचित "make system" बनाता है। Linux पर, यह पहचाने गए (या प्रदान किए गए) compilers के लिए एक makefile होगा। Windows पर हमारे लिए, यह हमारे Visual Studio के संस्करण के अनुकूल एक Visual Studio project बनाता है, जिसका अर्थ है कि एक बार आप MSVC make system बना लें, तो आप चाहें तो सब कुछ के लिए Visual Studio का उपयोग कर सकते हैं! अंततः, आप Visual Studio को configure करने के लिए CMake का उपयोग कर रहे हैं। तो आप यहाँ मूल रूप से अपने project की फाइलों के trees को Visual Studio में बना रहे हैं, जो GUI आपके लिए project बनाते समय करता है।

अगला, हमें अपने dependent projects को जोड़ने की आवश्यकता है:```cmake
# this will add zlib as a build target
add_subdirectory(${PROJECT_SOURCE_DIR}/zlib-1.2.13)

# this will add our stub project
add_subdirectory(${PROJECT_SOURCE_DIR}/stub)

# this will add our test dummy project
add_subdirectory(${PROJECT_SOURCE_DIR}/dummy)

पहले मैंने एक बात का उल्लेख किया था कि packer प्रोजेक्ट stub प्रोजेक्ट पर निर्भर करता है। packer को किसी तरह stub को बनाए रखना और उसमें हेरफेर करना होता है ताकि अंततः हमारी packed executable प्राप्त हो सके। हम Windows resource files का उपयोग कर सकते हैं ताकि अंततः हमारे stub executable को packer binary में शामिल कर सकें, चाहे build configuration कोई भी हो! इसके अलावा, हम CMake का उपयोग करके उन फ़ाइलों को अपने लिए उत्पन्न कर सकते हैं ताकि निर्मित executables के हमारे संदर्भ CMake प्रोजेक्ट के भीतर सही रहें! अभी के लिए अपनी resource files उत्पन्न करते हैं ताकि हम उन्हें अपने प्रोजेक्ट में शामिल कर सकें:```cmake

this will make sure our stub data will be included in the resources of our packer

despite where it may reside in cmake's build system

file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$/stub.hpp" CONTENT "#pragma once\n#define IDB_STUB 1000\n") file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$/stub.rc" CONTENT "#include <winresrc.h>\n#include "stub.hpp"\nIDB_STUB STUB "$<TARGET_FILE:stub>"\n")

root@kitploit:~
`CMAKE_CURRENT_BINARY_DIR` एक स्ट्रिंग है जिसमें आपकी वर्तमान बिल्ड निर्देशिका होती है। Visual Studio बाइनरीज़ को उनके कॉन्फ़िगरेशन के आधार पर फ़ोल्डरों में डालता है, इसलिए हम बिल्ड का वर्तमान कॉन्फ़िगरेशन प्राप्त करने के लिए `$<CONFIG>` जनरेटर स्टेटमेंट का उपयोग करते हैं। हम `$<TARGET_FILE:stub>` जनरेटर स्टेटमेंट का भी उपयोग करते हैं, ताकि स्टब बाइनरी के संकलित होने के बाद उसकी एक्ज़ीक्यूटेबल फ़ाइल का नाम आउटपुट हो सके। जब हम इन फ़ाइलों को अपने प्रोजेक्ट में शामिल करते हैं—जनरेट की गई RC फ़ाइल और जनरेट किया गया हेडर—तो हम अपने स्टब बाइनरी को पैकर प्रोजेक्ट में सफलतापूर्वक एकीकृत कर सकते हैं।

इसके बाद, हम पहले एकत्र की गई फ़ाइलों के साथ अपने पैकर प्रोजेक्ट के लिए एक एक्ज़ीक्यूटेबल घोषित करते हैं:```cmake
# this will create our packer executable
add_executable(packer ${HDR_FILES} ${SRC_FILES})
target_sources(packer PRIVATE ${RC_FILES} "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/stub.rc")

फिर हम लिंकर के लिए आयातित zlib लाइब्रेरी को लिंक करते हैं:```cmake

this will link zlib to our packer

target_link_libraries(packer zlibstatic)

root@kitploit:~
आप इसके बजाय केवल `zlib` को लिंक कर सकते हैं यदि आप वास्तव में zlib की DLL चाहते हैं।

क्योंकि हमारे पास जनरेटेड फ़ाइलें हैं (और zlib के पास भी अपने बिल्ड चरण के भाग के रूप में), हमें प्रोजेक्ट के लिए include हेडर्स में डायनामिक निर्देशिकाओं को शामिल करने की आवश्यकता है। CMake में प्रोजेक्ट के रूट में प्रोजेक्ट होने की निर्भरता के कारण, हम अपने stub बाइनरी के लिए भी zlib include निर्देशिकाएँ जोड़ते हैं:```cmake
# zlib, as part of its build step, drops a config header in the build directory.
# we do this too, so make sure to include everything for the build!
target_include_directories(packer PUBLIC
  "${PROJECT_SOURCE_DIR}/src"
  "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>"
  "${PROJECT_SOURCE_DIR}/zlib-1.2.13"
  "${CMAKE_CURRENT_BINARY_DIR}/zlib-1.2.13"
)

# also set the includes for the stub from here.
# we can't set this in the stub CMake file because CMake requires includes to be in the same
# directory as the build target. for this file, our build target is packer, so this sets
# up includes relative to the packer executable.
target_include_directories(stub PUBLIC
  "${PROJECT_SOURCE_DIR}/zlib-1.2.13"
  "${CMAKE_CURRENT_BINARY_DIR}/zlib-1.2.13"
)

अंत में, हम अपनी निर्भरता श्रृंखला को सुचारू करते हैं, CMake को अपनी निर्भरता स्थिति के बारे में बताकर: स्टब को पैकर की निर्भरता के रूप में चिह्नित करें और पैकर को डमी की निर्भरता के रूप में चिह्नित करें।```cmake

this will add our stub as a dependency and our dummy as being dependent on the packer.

add_dependencies(packer stub) add_dependencies(dummy packer)

root@kitploit:~
अब [स्टब](https://github.com/frank2/packer-tutorial/blob/main/stub/CMakeLists.txt) और [डमी](https://github.com/frank2/packer-tutorial/blob/main/dummy/CMakeLists.txt) के लिए CMake फ़ाइलें समझना काफ़ी आसान होना चाहिए!

सबसे अच्छी बात यह है कि CMake हमारे लिए परीक्षण प्रबंधित कर सकता है! और इस बिंदु पर, हमारे पैकर के लिए परीक्षण उत्पन्न करना एक सहज प्रक्रिया है: हम बस एक कमांड जारी कर सकते हैं, और यदि exit code 0 है, तो परीक्षण पास हो जाता है। सरलता के लिए, मान लें कि हम एक बाइनरी को एक्ज़िक्यूटेबल के पहले आर्ग्युमेंट के रूप में पास करके पैक करते हैं। हम कुछ इस तरह चाहते हैं:```
$ packer.exe dummy.exe

CMake के साथ ऐसा करना बहुत सरल है:```cmake

enable testing to verify our packer works

enable_testing() add_test(NAME test_pack COMMAND "$<TARGET_FILE:packer>" "$<TARGET_FILE:dummy>")

root@kitploit:~
अंत में, यह परीक्षण करना कि प्रोग्राम अनपैक होता है या नहीं, और भी सरल है: बस आउटपुट को चलाएँ! यह आश्चर्यजनक नहीं होना चाहिए कि जब आप loader को emulate करने का प्रयास कर रहे होते हैं, तो केवल चलाने से ही कितनी त्रुटियाँ प्रोग्राम को क्रैश कर देती हैं! आइए सरलता के लिए फिर से कहें कि binary को packed binary के लिए "packed.exe" पर आउटपुट करना चाहिए। उस स्थिति में, आपको बस यह करना है:```cmake
add_test(NAME test_unpack
  COMMAND "packed.exe")

यदि आपका packer बाइनरी उत्पन्न करने में विफल रहा तो यह शालीनता से विफल हो जाएगा। दुर्भाग्य से, Visual Studio के लिए CMake ADDITIONAL_CLEAN_FILES वेरिएबल को अनदेखा करता है, इसलिए आपको अपने बिल्ड सिस्टम में सभी जनरेट की गई फ़ाइलों को मैन्युअल रूप से साफ करना होगा, जिसमें ऊपर बनाई गई stub.rc और stub.hpp फ़ाइलें भी शामिल हैं।

बधाई हो! आपके packer को सफलतापूर्वक बनाने और परीक्षण करने के लिए बहुत काम किया गया है। अब जब हम अपनी सब्ज़ियाँ खा चुके हैं, तो हमारा packer निम्नलिखित कार्य कर सकता है:

  • सभी निर्भरताओं को सही क्रम में संकलित करना
  • stub बाइनरी को बनाना और उसे हमारे packer बाइनरी में इंजेक्ट करना
  • पैकिंग/अनपैकिंग प्रक्रिया का स्वचालित रूप से परीक्षण करना

अब हम असली मज़े की ओर बढ़ सकते हैं!

Packing binaries into your stub

इसलिए हमने अपने compiler को सफलतापूर्वक सेटअप कर लिया है ताकि stub executable को एक resource के रूप में हमारे packer बाइनरी में संकलित किया जा सके, लेकिन हम किसी बाइनरी को packed अवस्था में stub में कैसे डालें? हम इसे एक resource के रूप में नहीं जोड़ सकते क्योंकि हम packer के अंतिम उपयोगकर्ता से केवल एक compiler उपयोग करने की उम्मीद नहीं कर सकते, packer executable को एक स्टैंड-अलोन समाधान माना जाता है।

एक तकनीक जिसका मैंने बहुत उपयोग किया है (हालाँकि यह विश्लेषण के लिए स्पष्ट हो सकती है) stub बाइनरी में एक नया section जोड़ना है जिसे अंततः runtime पर लोड किया जा सके। यह PE प्रारूप में एक क्रैश-कोर्स के रूप में भी काम करेगा। लेकिन पहले, हम resources से डेटा कैसे निकालें?

Managing resources

Runtime पर किसी बाइनरी से resource डेटा प्राप्त करने के तीन बुनियादी चरण हैं:

  • Find the resource
  • Load the resource
  • Lock the resource (जो resource के बाइट्स प्राप्त करता है)

निम्नलिखित फ़ंक्शन दर्शाता है कि किसी दिए गए बाइनरी से resource को कैसे खोजा और प्राप्त किया जाए:```cpp std::vectorstd::uint8_t load_resource(LPCSTR name, LPCSTR type) { auto resource = FindResourceA(nullptr, name, type);

if (resource == nullptr) { std::cerr << "Error: couldn't find resource." << std::endl; ExitProcess(6); }

auto rsrc_size = SizeofResource(GetModuleHandleA(nullptr), resource); auto handle = LoadResource(nullptr, resource);

if (handle == nullptr) { std::cerr << "Error: couldn't load resource." << std::endl; ExitProcess(7); }

auto byte_buffer = reinterpret_cast<std::uint8_t *>(LockResource(handle));

return std::vectorstd::uint8_t(&byte_buffer[0], &byte_buffer[rsrc_size]); }

root@kitploit:~
अपने डेटा को एक आसानी से ढालने योग्य वेक्टर में रखकर, अब हम स्टब इमेज को पार्स कर सकते हैं और उसमें नया डेटा जोड़ सकते हैं।

### PE फ़ाइल को पार्स करना

एक Windows निष्पादनयोग्य, अपने सबसे बुनियादी घटकों में, दो भागों में विभाजित होता है: उसके *हेडर्स* और उसका *सेक्शन डेटा*। हेडर्स में लोडिंग प्रक्रिया के लिए महत्वपूर्ण बहुत सारा मेटाडेटा होता है, और सेक्शन डेटा बस वही होता है-- डेटा, जो निष्पादन योग्य कोड (अर्थात, `.text` सेक्शन) या कोई भी डेटा (अर्थात, `.data` सेक्शन) हो सकता है। हर Windows निष्पादनयोग्य की शुरुआत एक `IMAGE_DOS_HEADER` संरचना से होती है:```c
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

जबकि यह संरचना देखने में काफी जटिल लगती है, आप शायद नाम से ही बता सकते हैं कि यह हेडर विंडोज़ और माइक्रोसॉफ्ट DOS के पुराने संस्करणों का अवशेष है। यहाँ, हम केवल इस हेडर के दो मानों से सरोकार रखते हैं: e_magic और e_lfanew। e_magic केवल इमेज के शीर्ष पर स्थित मैजिक हेडर मान है, जो फ़ाइल की शुरुआत में "MZ" होता है। e_lfanew फ़ाइल की शुरुआत से NT हेडर्स का ऑफसेट है, जो PE हेडर्स होते हैं जिनमें एक्सीक्यूटेबल के बारे में अधिक मेटाडेटा जानकारी होती है। उदाहरण के लिए, हम अपने पैकर के लिए एक सरल PE वैलिडेटर इस प्रकार बना सकते हैं:```cpp void validate_target(const std::vectorstd::uint8_t &target) { auto dos_header = reinterpret_cast<const IMAGE_DOS_HEADER *>(target.data());

// IMAGE_DOS_SIGNATURE is 0x5A4D (for "MZ") if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { std::cerr << "Error: target image has no valid DOS header." << std::endl; ExitProcess(3); }

auto nt_header = reinterpret_cast<const IMAGE_NT_HEADERS *>(target.data() + dos_header->e_lfanew);

// IMAGE_NT_SIGNATURE is 0x4550 (for "PE") if (nt_header->Signature != IMAGE_NT_SIGNATURE) { std::cerr << "Error: target image has no valid NT header." << std::endl; ExitProcess(4); }

// IMAGE_NT_OPTIONAL_HDR64_MAGIC is 0x020B if (nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { std::cerr << "Error: only 64-bit executables are supported for this example!" << std::endl; ExitProcess(5); } }

root@kitploit:~
`IMAGE_NT_HEADERS` समग्र रूप से एक काफी बड़ी संरचना (structure) है, इसलिए मैं यहाँ पूरी चीज़ का दस्तावेज़ीकरण नहीं करूँगा, लेकिन आप इसके बारे में वह सब कुछ पा सकते हैं जो आपको जानना आवश्यक है [Microsoft के हेडर का दस्तावेज़ीकरण](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_nt_headers64) में। हमें फिर भी इन हेडरों से केवल कुछ ही संरचना सदस्यों (structure members) की आवश्यकता होगी। अभी के लिए, हमें अपने स्टब डेटा में जोड़ने के लिए अपनी लक्षित बाइनरी को संपीड़ित (compress) करना चाहिए।

zlib के `compress` और `decompress` फ़ंक्शनों का उपयोग करना बहुत सीधा है। यदि आप वाकई zlib के साथ कुछ उन्नत काम करना चाहते हैं, तो मैं `deflate`/`inflate` फ़ंक्शनों के साथ काम करने का सुझाव देता हूँ, जो आपको संपीड़न स्ट्रीम पर एक बार में हिस्सों (chunks) में काम करने की अनुमति देते हैं। [zlib मैनुअल](https://www.zlib.net/manual.html) का "Advanced Functions" अनुभाग देखें। हालाँकि, इस उदाहरण के लिए, `compress` और `decompress` पर्याप्त होंगे।

आरंभ करने के लिए, हम अपनी लक्षित बाइनरी के आकार पर zlib के `compressBound` फ़ंक्शन के साथ एक आकार मान प्राप्त करते हैं। यह आकार मान डेटा के आकार को देखते हुए एक संपीड़ित डेटा स्ट्रीम को धारण करने के लिए आवश्यक अधिकतम मान के अनुरूप होता है। फिर हम इस मान का उपयोग संपीड़ित डेटा को समाहित करने के लिए एक वेक्टर (vector) आवंटित करने में कर सकते हैं। `compress` फ़ंक्शन अंततः संपीड़ित बफर का वास्तविक आकार लौटाता है, जिस पर हम अपने वेक्टर को उचित आकार में समायोजित (resize) कर सकते हैं।```cpp
// get the maximum size of a compressed buffer of the target binary's size.
uLong packed_max = compressBound(target.size());
uLong packed_real = packed_max;

// allocate a vector with that size
std::vector<std::uint8_t> packed(packed_max);
   
if (compress(packed.data(), &packed_real, target.data(), target.size()) != Z_OK)
{
   std::cerr << "Error: zlib failed to compress the buffer." << std::endl;
   ExitProcess(8);
}

// resize the buffer to the real compressed size
packed.resize(packed_real);

PE फ़ाइल में हेरफेर करना

आइए डेटा संरेखण के बारे में बात करने के लिए एक क्षण लें। एक दिया गया डेटा स्ट्रीम संरेखित माना जाता है यदि उसका पता या आकार किसी दिए गए संरेखण सीमा से विभाज्य हो। उदाहरण के लिए, PE फ़ाइलों में, डिस्क पर डेटा अनुभाग आमतौर पर 0x400 सीमा पर संरेखित होते हैं, जबकि मेमोरी में वे 0x1000 सीमा पर संरेखित होते हैं। हम यह निर्धारित कर सकते हैं कि कोई दिया गया मान संरेखित है या नहीं, मान पर संरेखण का मापांक करके (अर्थात, मान % संरेखण == 0)। PE फ़ाइलों को मनमाने ढंग से अन्य मानों पर संरेखित किया जा सकता है, और यह मान समग्र रूप से PE लोडर के भीतर मौजूद और महत्वपूर्ण होता है। किसी दिए गए मान और दी गई सीमा को संरेखित करना एक अपेक्षाकृत सरल ऑपरेशन है:```cpp template T align(T value, T alignment) { auto result = value + ((value % alignment == 0) ? 0 : alignment - (value % alignment)); return result; }

root@kitploit:~
यह फ़ंक्शन मूल रूप से एक संभावित रूप से असंरेखित मान को दिए गए बाउंड्री पर सही ढंग से संरेखित करने के लिए आवश्यक शेष भाग के साथ पैड करता है।

हमारी PE फ़ाइल में आर्बिट्रेरी डेटा को ठीक से जोड़ने के लिए, हमें विशेष रूप से *फ़ाइल संरेखण* का ध्यान रखना होगा-- हम बाद में मेमोरी में PE फ़ाइल के लिए सही संरेखित मानों की गणना कर सकते हैं, लेकिन अभी जब हम अपना डेटा जोड़ रहे हैं, तो हमें अपनी फ़ाइल को फ़ाइल संरेखण सीमा पर संरेखित करने की आवश्यकता है। निम्नलिखित कोड में, हम हेडर प्राप्त करते हैं, फिर फ़ाइल संरेखण और सेक्शन संरेखण सीमाएँ, फिर अपने स्टब डेटा को फ़ाइल सीमा पर संरेखित करते हैं और अपने नए पैक किए गए सेक्शन को जोड़ते हैं।```cpp
// next, load the stub and get some initial information
std::vector<std::uint8_t> stub_data = load_resource(MAKEINTRESOURCE(IDB_STUB), "STUB");
auto dos_header = reinterpret_cast<IMAGE_DOS_HEADER *>(stub_data.data());
auto e_lfanew = dos_header->e_lfanew;

// get the nt header and get the alignment information
auto nt_header = reinterpret_cast<IMAGE_NT_HEADERS64 *>(stub_data.data() + e_lfanew);
auto file_alignment = nt_header->OptionalHeader.FileAlignment;
auto section_alignment = nt_header->OptionalHeader.SectionAlignment;

// align the buffer to the file boundary if it isn't already
if (stub_data.size() % file_alignment != 0)
   stub_data.resize(align<std::size_t>(stub_data.size(), file_alignment));
      
// save the offset to our new section for later for our new PE section
auto raw_offset = static_cast<std::uint32_t>(stub_data.size());

// encode the size of our unpacked data into the stub data
auto unpacked_size = target.size();
stub_data.insert(stub_data.end(),
                 reinterpret_cast<std::uint8_t *>(&unpacked_size),
                 reinterpret_cast<std::uint8_t *>(&unpacked_size)+sizeof(std::size_t));

// add our compressed data.
stub_data.insert(stub_data.end(), packed.begin(), packed.end());

अब, हमने अपने अनुभाग का डेटा फ़ाइल अनुभाग सीमाओं के अनुसार जोड़ दिया होगा, लेकिन हमारा स्टब एक्ज़ीक्यूटेबल (stub executable) अभी भी PE फ़ाइल में इस अनुभाग से अनजान है। हमें न केवल PE फ़ाइल के section table को पार्स करना होगा, बल्कि एक नई प्रविष्टि भी जोड़नी होगी जो हमारे अनुभाग की ओर इंगित करे। यही raw_offset चर का कारण है।

पहले, हम NumberOfSections को अपडेट करके अनुभागों की संख्या आसानी से बढ़ा सकते हैं। सामान्यतः, अंतिम section table के बाद का डेटा शून्य कर दिया जाता है, इसलिए हम उस शून्य किए गए डेटा को अपने नए अनुभाग से आसानी से अधिलेखित (overwrite) कर सकते हैं।```cpp // increment the number of sections in the file header auto section_index = nt_header->FileHeader.NumberOfSections; ++nt_header->FileHeader.NumberOfSections;

root@kitploit:~
अगला, हमें सेक्शन टेबल का एक पॉइंटर प्राप्त करने की आवश्यकता है। हालाँकि यह तकनीकी रूप से NT हेडर के ऑप्शनल हेडर के तुरंत बाद आता है, ऑप्शनल हेडर का आकार वास्तव में NT फ़ाइल हेडर के `SizeOfOptionalHeader` मान द्वारा निर्धारित होता है। इसलिए, वहाँ पहुँचने के लिए, हमें `OptionalHeader` स्ट्रक्चर के शीर्ष से `SizeOfOptionalHeader` मान द्वारा प्रदान किए गए ऑफ़सेट तक एक पॉइंटर की गणना करने की आवश्यकता है।```cpp
// acquire a pointer to the section table
auto size_of_header = nt_header->FileHeader.SizeOfOptionalHeader;
auto section_table = reinterpret_cast<IMAGE_SECTION_HEADER *>(
   reinterpret_cast<std::uint8_t *>(&nt_header->OptionalHeader)+size_of_header
);

अंततः, हम अपने सेक्शन मेटाडेटा को जोड़ना शुरू करने के लिए तैयार हैं। यह हमारा PE सेक्शन हेडर है:```c typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // IMAGE_SIZEOF_SHORT_NAME is 8 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

root@kitploit:~
हमारे नए सेक्शन हेडर के लिए जिन विशेष वेरिएबल्स में हमारी रुचि है, वे हैं `Name`, `VirtualSize`, `VirtualAddress`, `SizeOfRawData`, `PointerToRawData` और `Characteristics`। इस बिंदु पर, आपको यह जान लेना चाहिए कि आपको दो प्रकार के संरेखण-- फ़ाइल संरेखण और मेमोरी संरेखण-- के बारे में जागरूक होने की आवश्यकता है, क्योंकि किसी दिए गए PE निष्पादनयोग्य के लिए मेमोरी की दो अलग-अलग अवस्थाएँ होती हैं: यह *डिस्क* पर कैसा दिखता है, और लोडिंग प्रक्रिया के परिणामस्वरूप यह *मेमोरी* में कैसा दिखता है। किसी दिए गए PE फ़ाइल को, चाहे वह लोड हो या न हो, समान मेमोरी लेआउट देने के लिए कॉन्फ़िगर करना संभव है, लेकिन यह एक सामान्य कॉन्फ़िगरेशन नहीं है।

`Name` वेरिएबल 8-बाइट का लेबल है जिसे आप अपने नए सेक्शन को दे सकते हैं। मैंने `.packed` चुना है, क्योंकि यह 7-बाइट ASCII स्ट्रिंग है और बफ़र के भीतर पूरी तरह फिट बैठता है।

`VirtualAddress` किसी दिए गए सेक्शन का मेमोरी में ऑफ़सेट दर्शाता है। इसे एक "relative virtual address," या RVA के रूप में भी जाना जाता है। `VirtualSize` मेमोरी में सेक्शन के आकार को दर्शाता है। (गौरतलब है कि MSVC इस मान को सेक्शन के असंरेखित आकार मान के रूप में संकलित करता है, इसलिए हम अपने सेक्शन में भी इसी परंपरा का पालन करते हैं।) `PointerToRawData` किसी दिए गए सेक्शन का डिस्क पर ऑफ़सेट दर्शाता है, और `SizeOfRawData` डिस्क पर सेक्शन के आकार को दर्शाता है।

`Characteristics` जटिल है और यह किसी सेक्शन के पठनीय, लिखने योग्य या निष्पादन योग्य होने के साथ-साथ अन्य संकेतकों को भी संदर्भित कर सकता है, [`IMAGE_SECTION_HEADER` दस्तावेज़](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header) का characteristics अनुभाग देखें। अभी के लिए, आपको बस इतना जानना चाहिए कि हमें बस यह चाहिए कि सेक्शन पठनीय हो और प्रारंभिक डेटा से युक्त के रूप में चिह्नित हो।

इन सबको ध्यान में रखते हुए, अब हम अपना नया सेक्शन बना सकते हैं!```cpp
// get a pointer to our new section and the previous section
auto section = &section_table[section_index];
auto prev_section = &section_table[section_index-1];

// calculate the memory offset, memory size and raw aligned size of our packed section
auto virtual_offset = align(prev_section->VirtualAddress + prev_section->Misc.VirtualSize, section_alignment);
auto virtual_size = section_size;
auto raw_size = align<DWORD>(section_size, file_alignment);

// assign the section metadata
std::memcpy(section->Name, ".packed", 8);
section->Misc.VirtualSize = virtual_size;
section->VirtualAddress = virtual_offset;
section->SizeOfRawData = raw_size;
section->PointerToRawData = raw_offset;

// mark our section as initialized, readable data.
section->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;

अब यह एक छोटी सी समस्या पैदा करता है: इमेज का आकार बदल गया। आप सोचेंगे कि यह कोई समस्या नहीं होगी, लेकिन NT हेडर के ऑप्शनल हेडर में SizeOfImage नामक एक वेरिएबल होता है जो यह निर्धारित करता है कि लोडर को हमारे एक्ज़ीक्यूटेबल के लिए कितनी जगह आवंटित करनी है। हालाँकि, यह एक साधारण सुधार है: हमारी इमेज के लिए आवश्यक आकार, सेक्शन अलाइनमेंट सीमा तक संरेखित अंतिम सेक्शन का आकार होता है।```cpp // calculate the new size of the image. nt_header->OptionalHeader.SizeOfImage = align(virtual_offset + virtual_size, section_alignment);

root@kitploit:~
और बस! हमने अपने संपीड़ित बाइनरी को स्टब के लिए एक नए सेक्शन के रूप में सफलतापूर्वक जोड़ दिया है ताकि वह अंततः डीकंप्रेस और लोड कर सके। अब हम संशोधित स्टब इमेज को आसानी से डिस्क में सेव कर सकते हैं।```cpp
std::ofstream fp("packed.exe", std::ios::binary);
   
if (!fp.is_open()) {
   std::cerr << "Error: couldn't open packed binary for writing." << std::endl;
   ExitProcess(9);
}
   
fp.write(reinterpret_cast<const char *>(stub_data.data()), stub_data.size());
fp.close();

बधाई हो! हम अब तक निम्नलिखित काम कर चुके हैं:

  • हमारे packer के resource directory से अपने stub binary को runtime पर compile, inject और retrieve किया
  • अपने stub binary और target binary दोनों के executable headers को parse करके महत्वपूर्ण जानकारी प्राप्त की
  • एक executable को modify करके उसके section data को विस्तारित किया ताकि उसमें हमारा packed executable शामिल हो सके

हम अपना packer लिखने के आधे रास्ते पर हैं! अब हम प्रक्रिया के सबसे कठिन भाग की ओर बढ़ सकते हैं: stub binary को पूरी तरह विकसित करना।

Loader का अनुकरण

हालाँकि unpack stub लिखने की बारीकियाँ अंदर से जटिल हो सकती हैं, मूल रूप से यह कुछ ही चरणों में आता है:

  • Image data प्राप्त करें: आगे की प्रक्रिया के लिए target binary representation को प्राप्त करें और decompress (और वैकल्पिक रूप से deobfuscate) करें।
  • Image को load करें: अब तक का सबसे जटिल चरण, यहीं पर loader का अनुकरण किया जाता है और target image को execution के लिए तैयार किया जाता है।
  • Image के entrypoint को call करें: आमतौर पर "original entry point," या OEP कहा जाता है, यह वह बिंदु है जहाँ से आप अपने packed binary के लिए loading चरण से execution चरण में स्थानांतरित होते हैं।

यह प्रक्रिया इतनी सरल है कि हमारी main routine केवल मुट्ठी भर functions की है:```cpp int main(int argc, char *argv[]) { // first, decompress the image from our added section auto image = get_image();

// next, prepare the image to be a virtual image
auto loaded_image = load_image(image);

// resolve the imports from the executable load_imports(loaded_image);

// relocate the executable relocate(loaded_image);

// get the headers from our loaded image auto nt_headers = get_nt_headers(loaded_image);

// acquire and call the entrypoint auto entrypoint = loaded_image + nt_headers->OptionalHeader.AddressOfEntryPoint; reinterpret_cast<void(*)()>(entrypoint)();

return 0; }

root@kitploit:~
### मेमोरी से अपने PE को पढ़ना

सबसे पहले, हमें चल रहे बाइनरी से अपने पैकर-निर्मित सेक्शन का डेटा किसी तरह प्राप्त करना होगा। क्या रनटाइम पर चल रहे बाइनरी के हेडर प्राप्त करना संभव है? हाँ, बिल्कुल! [`GetModuleHandleA`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea), null तर्क के साथ, अंततः हमारे चल रहे PE हेडर की ओर एक पॉइंटर लौटाता है! इसलिए रनटाइम पर, आपके पास मेमोरी में मौजूद इमेज तक आसान पहुँच होती है। यही बात हमारे बाइनरी में नया सेक्शन जोड़ने को इतना आकर्षक बनाती है: हम अपने लक्षित सेक्शन को बाइनरी से बहुत आसानी से पार्स कर सकते हैं।

सेक्शन तालिका को पार्स करने के बारे में हमने जो कुछ सीखा है, उसके साथ, कोड का यह सेक्शन समझना आसान होना चाहिए:```cpp
// find our packed section
auto base = reinterpret_cast<const std::uint8_t *>(GetModuleHandleA(NULL));
auto nt_header = get_nt_headers(base);
auto section_table = reinterpret_cast<const IMAGE_SECTION_HEADER *>(
   reinterpret_cast<const std::uint8_t *>(&nt_header->OptionalHeader)+nt_header->FileHeader.SizeOfOptionalHeader
);
const IMAGE_SECTION_HEADER *packed_section = nullptr;

for (std::uint16_t i=0; i<nt_header->FileHeader.NumberOfSections; ++i)
{
   if (std::memcmp(section_table[i].Name, ".packed", 8) == 0)
   {
      packed_section = &section_table[i];
      break;
   }
}

if (packed_section == nullptr) {
   std::cerr << "Error: couldn't find packed section in binary." << std::endl;
   ExitProcess(1);
}

इसके बाद, हमें अपने बाइनरी से स्टब डेटा को डीकंप्रेस करना होगा। zlib अनुशंसा करता है कि हम मूल डीकंप्रेस्ड पेलोड के आकार को किसी तरह डीकंप्रेसन रूटीन तक पहुँचाने योग्य तरीके से एनकोड करें, इसीलिए हमने अपने पैक्ड डेटा के हेडर में डीकंप्रेस्ड बाइनरी का आकार एनकोड किया था। इसलिए, हम अपने डीकंप्रेस्ड डेटा के लिए एक पॉइंटर प्राप्त करते हैं, एक नया बफर बनाते हैं जो डीकंप्रेस्ड डेटा को धारण कर सकता है, और फिर zlib के डीकंप्रेसन फ़ंक्शन को कॉल करने के लिए आगे बढ़ते हैं।```cpp // decompress our packed image auto section_start = base + packed_section->VirtualAddress; auto section_end = section_start + packed_section->Misc.VirtualSize; auto unpacked_size = *reinterpret_cast<const std::size_t *>(section_start); auto packed_data = section_start + sizeof(std::size_t); auto packed_size = packed_section->Misc.VirtualSize - sizeof(std::size_t);

auto decompressed = std::vectorstd::uint8_t(unpacked_size); uLong decompressed_size = static_cast(unpacked_size);

if (uncompress(decompressed.data(), &decompressed_size, packed_data, packed_size) != Z_OK) { std::cerr << "Error: couldn't decompress image data." << std::endl; ExitProcess(2); }

return decompressed;

root@kitploit:~
जैसा कि आप देख सकते हैं, `get_image` आखिरकार एक अपेक्षाकृत सरल फ़ंक्शन था। हमें अपने लक्षित बाइनरी को जोड़े गए अनुभाग से निकाल लिया गया, इसलिए अब हमें इसे लोड करने की आवश्यकता है।

### निष्पादन के लिए अपने PE को लोड करना

Windows निष्पादन योग्य लोडर पर्दे के पीछे बहुत सारे अलग-अलग काम करता है, और विभिन्न निष्पादन योग्य कॉन्फ़िगरेशन का समर्थन करता है। यदि आप उदाहरण बाइनरी से अधिक एक्सप्लोर करते हैं, तो आपको इस ट्यूटोरियल द्वारा निर्मित पैकर के साथ विभिन्न प्रकार की त्रुटियों का सामना करने की संभावना है, क्योंकि आज हम जो कॉन्फ़िगरेशन बनाने जा रहे हैं वह तकनीकी रूप से बहुत न्यूनतम है। लेकिन निष्पादन के लिए उस न्यूनतम सीमा को स्थापित करने के लिए, हमें आधुनिक Windows निष्पादन योग्य को लोड करने के लिए निम्नलिखित कार्य करने होंगे:

* उस इमेज को आवंटित करें जो निष्पादन योग्य इमेज की मेमोरी प्रतिनिधित्व रखती है
* हमारे निष्पादन योग्य इमेज के सेक्शन, हेडर सहित, उस आवंटित इमेज पर मैप करें
* हमारे बाइनरी को आवश्यक अन्य लाइब्रेरीज़ के लिए रनटाइम इम्पोर्ट्स को रिज़ॉल्व करें
* बाइनरी इमेज को रीमैप करें ताकि हमारी इमेज में विभिन्न पते वहाँ इंगित करें जहाँ उन्हें इंगित करना चाहिए

आइए `load_image` फ़ंक्शन से शुरू करें। शुरुआत के लिए, हमें पैक किए गए बाइनरी से अपनी सेक्शन तालिका प्राप्त करनी होगी। यह अंततः हमारी नई आवंटित इमेज पर मैप किया जाएगा। एक उचित निष्पादन योग्य बफ़र के लिए, आवंटन बहुत सरल है-- हमारे सेक्शन हेडर से `SizeOfImage` मान लें और [`VirtualAlloc`](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc) के साथ एक नया बफ़र बनाएं जो हमारे लिए एक पठनीय, लिखने योग्य और निष्पादन योग्य इमेज बनाता है जिसे हम मैप कर सकें।```cpp
// get the original image section table
auto nt_header = get_nt_headers(image.data());
auto section_table = reinterpret_cast<const IMAGE_SECTION_HEADER *>(
   reinterpret_cast<const std::uint8_t *>(&nt_header->OptionalHeader)+nt_header->FileHeader.SizeOfOptionalHeader
);

// create a new VirtualAlloc'd buffer with read, write and execute privileges
// that will fit our image
auto image_size = nt_header->OptionalHeader.SizeOfImage;
auto base = reinterpret_cast<std::uint8_t *>(VirtualAlloc(nullptr,
                                                          image_size,
                                                          MEM_COMMIT | MEM_RESERVE,
                                                          PAGE_EXECUTE_READWRITE));

if (base == nullptr) {
   std::cerr << "Error: VirtualAlloc failed: Windows error " << GetLastError() << std::endl;
   ExitProcess(3);
}

अपने बफर को आवंटित करने के बाद, हमें हेडर और सेक्शनों को उसमें कॉपी करना होता है। इसे पहले वाले SectionAlignment चर के अनुरूप होना चाहिए। सौभाग्य से, जिस तरह हमने अपने सेक्शन को तैयार किया है-- और जिस तरह अन्य सेक्शन तैयार किए गए हैं-- वे पहले से ही SectionAlignment सीमा के अनुरूप संरेखित हैं। संक्षेप में कहें तो, अंतिम परिणाम सरल पॉइंटर अंकगणित है: अपने लक्षित इमेज को PointerToRawData ऑफसेट पर कॉपी करें, अपनी लोड की गई इमेज में VirtualAddress ऑफसेट पर।

वैकल्पिक रूप से, आप PE हेडर को इमेज के शीर्ष पर कॉपी कर सकते हैं। मूल हेडर को बनाए रखने से विकास आसान होता है, लेकिन उन्हें हटाने की चाह रखना एक विश्लेषण-विरोधी पैकर बनाने की दिशा में एक अच्छा कदम है। उपयोग में आसानी के लिए हम यहाँ हेडर कॉपी करते हैं।```cpp // copy the headers to our new virtually allocated image std::memcpy(base, image.data(), nt_header->OptionalHeader.SizeOfHeaders);

// copy our sections to their given addresses in the virtual image for (std::uint16_t i=0; i<nt_header->FileHeader.NumberOfSections; ++i) if (section_table[i].SizeOfRawData > 0) std::memcpy(base+section_table[i].VirtualAddress, image.data()+section_table[i].PointerToRawData, section_table[i].SizeOfRawData);

return base;

root@kitploit:~
इसलिए लोडिंग प्रक्रिया का आसान हिस्सा खत्म हो गया है: हमने अपने बाइनरी को मेमोरी से निकाला, उसे अनपैक किया, और उसके सेक्शन को एक एक्ज़ीक्यूटेबल मेमोरी क्षेत्र में रीमैप किया। अपनी इमेज तैयार करने के बाद, हम लोडिंग प्रक्रिया की बारीकियों में उतर सकते हैं।

### API इम्पोर्ट्स को हल करना

ऑप्शनल हेडर के भीतर कुछ ऐसा होता है जिसे *डेटा डायरेक्ट्री* कहा जाता है। यह डायरेक्ट्री एक्ज़ीक्यूटेबल के बारे में बहुत सारी अलग-अलग जानकारी रखती है, जैसे इमेज द्वारा निर्यात किए गए सिंबल और आइकन व बिटमैप जैसे संसाधन। इस ट्यूटोरियल में हम दो डेटा डायरेक्ट्री पार्स करने जा रहे हैं: **इम्पोर्ट डायरेक्ट्री** और **रिलोकेशन डायरेक्ट्री**। प्रत्येक डायरेक्ट्री हार्डकोडेड होती है, और उनके इंडेक्स [ऑप्शनल हेडर के दस्तावेज़ीकरण में](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32) पाए जा सकते हैं (`DataDirectory` के विवरण तक नीचे स्क्रॉल करें)। एक डेटा डायरेक्ट्री मौजूद होती है यदि उसका `VirtualAddress` मान गैर-शून्य हो।```c
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

हम प्रदान किए गए RVA को कास्ट करके डेटा निर्देशिकाओं के पॉइंटर्स प्राप्त करते हैं। उदाहरण के लिए, इस प्रकार आप अंततः इम्पोर्ट डेटा निर्देशिका से इम्पोर्ट टेबल प्राप्त करते हैं:```cpp // get the import table directory entry auto nt_header = get_nt_headers(image); auto directory_entry = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

// if there are no imports, that's fine-- return because there's nothing to do. if (directory_entry.VirtualAddress == 0) { return; }

// get a pointer to the import descriptor array auto import_table = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(image + directory_entry.VirtualAddress);

root@kitploit:~
API आयातों को हल करने के लिए, लोडर इस निर्देशिका को पार्स करता है, फिर आवश्यक लाइब्रेरीज़ को लोड करने और उनके आयातित फ़ंक्शन प्राप्त करने के लिए आगे बढ़ता है। सौभाग्य से, यह पार्स करने के लिए एक अपेक्षाकृत आसान निर्देशिका है।

यह एक आयात डिस्क्रिप्टर संरचना से शुरू होता है:```c
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

जिन दो चरों के बारे में हम सबसे अधिक चिंतित हैं, वे कुछ हद तक भ्रामक नामों वाले हैं: OriginalFirstThunk और FirstThunk। OriginalFirstThunk में उन इम्पोर्ट्स के बारे में जानकारी होती है जो इस executable को उस DLL के संबंध में चाहिए जो Name RVA द्वारा दिया गया है। भ्रम को और बढ़ाने के लिए, FirstThunk भी ऐसा ही करता है। उनमें अंतर क्या है? FirstThunk में इम्पोर्ट्स हल हो जाने के बाद शामिल होते हैं। यह समाधान कुख्यात GetProcAddress फ़ंक्शन द्वारा किया जाता है।

हमारे thunks अतिरिक्त डेटा संरचनाएँ हैं जिनसे निपटना है:```c typedef struct _IMAGE_THUNK_DATA64 { union { ULONGLONG ForwarderString; // PBYTE ULONGLONG Function; // PDWORD ULONGLONG Ordinal; ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA64;

root@kitploit:~
यह डेटा संरचना import के साथ-साथ export thunks को भी कवर करती है, इसलिए `ForwarderString` को अनदेखा किया जा सकता है। एक import thunk या तो `Ordinal` हो सकता है या दूसरी संरचना `IMAGE_IMPORT_BY_NAME` का RVA हो सकता है। एक *ordinal* केवल दिए गए DLL की export तालिका में एक offset है। एक `IMAGE_IMPORT_BY_NAME` संरचना इस प्रकार दिखती है:```c
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

यह एक variadic structure का उदाहरण है। यह ऐरे एक्सेस में बाउंड्स जाँच (bounds checking) की कमी का लाभ उठाकर ऐसी संरचनाएँ बनाता है जो आकार में परिवर्तनशील हो सकती हैं। Name, इस मामले में, एक शून्य-समाप्त (zero-terminated) C-string होने की अपेक्षा की जाती है।

क्योंकि बाइनरी डेटा में प्रकार (types) नहीं होते, एक ordinal और एक import थंक एंट्री (thunk entry) में सबसे महत्वपूर्ण बिट (most significant bit) द्वारा अलग किए जाते हैं। ordinal 32-bit और 64-bit दोनों कार्यान्वयनों पर पूर्णांक के निचले आधे भाग में समाहित होता है।

हमारी import table एक C-string की तरह काम करती है-- इसकी अंतिम एंट्री संभावित imports के अंत को दर्शाने के लिए null-समाप्त OriginalFirstThunk होती है। हमारा thunk डेटा भी उसी तरह काम करता है, thunk array में null एंट्री द्वारा समाप्त होता है।

सब कुछ एक साथ रखने के लिए, इस तरह हम pseudocode में import table का विश्लेषण करते हैं:``` for every import descriptor: load the dll parse the original and first thunk

root@kitploit:~
for every thunk:
    if ordinal bit set:
        import by ordinal
    else:
        import by name
        
    store import in first thunk
root@kitploit:~
दिलचस्प बात यह है कि, `GetProcAddress` में फ़ंक्शन के तर्क के रूप में C-string टाइप होने के बावजूद, Windows अपेक्षा करता है कि आप ordinal मान को केवल C-string के रूप में cast करके ordinal द्वारा import करें। सोचो।

इन स्पष्टीकरणों को ध्यान में रखते हुए, यह while loop अब समझ में आना चाहिए:```cpp
// when we reach an OriginalFirstThunk value that is zero, that marks the end of our array.
// typically all values in the import descriptor are zero, but we do this
// to be shorter about it.
while (import_table->OriginalFirstThunk != 0)
{
   // get a string pointer to the DLL to load.
   auto dll_name = reinterpret_cast<char *>(image + import_table->Name);

   // load the DLL with our import.
   auto dll_import = LoadLibraryA(dll_name);

   if (dll_import == nullptr) {
      std::cerr << "Error: failed to load DLL from import table: " << dll_name << std::endl;
      ExitProcess(4);
   }

   // load the array which contains our import entries
   auto lookup_table = reinterpret_cast<IMAGE_THUNK_DATA64 *>(image + import_table->OriginalFirstThunk);

   // load the array which will contain our resolved imports
   auto address_table = reinterpret_cast<IMAGE_THUNK_DATA64 *>(image + import_table->FirstThunk);

   // an import can be one of two things: an "import by name," or an "import ordinal," which is
   // an index into the export table of a given DLL.
   while (lookup_table->u1.AddressOfData != 0)
   {
      FARPROC function = nullptr;
      auto lookup_address = lookup_table->u1.AddressOfData;

      // if the top-most bit is set, this is a function ordinal.
      // otherwise, it's an import by name.
      if (lookup_address & IMAGE_ORDINAL_FLAG64 != 0)
      {
         // get the function ordinal by masking the lower 32-bits of the lookup address.
         function = GetProcAddress(dll_import,
                                   reinterpret_cast<LPSTR>(lookup_address & 0xFFFFFFFF));

         if (function == nullptr) {
            std::cerr << "Error: failed ordinal lookup for " << dll_name << ": " << (lookup_address & 0xFFFFFFFF) << std::endl;
            ExitProcess(5);
         }
      }
      else {
         // in an import by name, the lookup address is an offset to
         // an IMAGE_IMPORT_BY_NAME structure, which contains our function name
         // to import
         auto import_name = reinterpret_cast<IMAGE_IMPORT_BY_NAME *>(image + lookup_address);
         function = GetProcAddress(dll_import, import_name->Name);

         if (function == nullptr) {
            std::cerr << "Error: failed named lookup: " << dll_name << "!" << import_name->Name << std::endl;
            ExitProcess(6);
         }
      }

      // store either the ordinal function or named function
      // in our address table.
      address_table->u1.Function = reinterpret_cast<std::uint64_t>(function);

      // advance to the next entries in the address table and lookup table
      ++lookup_table;
      ++address_table;
   }

   // advance to the next entry in our import table
   ++import_table;
}

आयात हल हो जाने के बाद, अब हम अंतिम भाग की ओर बढ़ सकते हैं: रिलोकेशन डायरेक्ट्री से निपटना!

पतों को हल करना (Resolving addresses)

अगली डेटा डायरेक्ट्री जिससे निपटना है, उसे रिलोकेशन डायरेक्ट्री कहा जाता है। यह डायरेक्ट्री कोड के भीतर निरपेक्ष पतों को उनके नए बेस मानों में अनुवादित करने के लिए ज़िम्मेदार होती है। यह प्रक्रिया मूल रूप से उस चीज़ को लागू करती है जिससे आप परिचित हो सकते हैं, जिसे एड्रेस स्पेस लेआउट रैंडमाइज़ेशन कहा जाता है, लेकिन यह यह सुनिश्चित करने के लिए भी ज़िम्मेदार है कि DLL एड्रेस स्पेस एक-दूसरे से टकराएँ नहीं।

सबसे पहले, हमें यह सुनिश्चित करना होगा कि हमारा बाइनरी वास्तव में एड्रेस बेस को स्थानांतरित करने में सक्षम है। कभी-कभी, विशेष रूप से पुराने बाइनरी के लिए, यह सक्षम नहीं होता है। किसी दिए गए Windows एक्ज़ीक्यूटेबल की विशेषताओं के भीतर उसकी विशेषताएँ होती हैं, जिन्हें भ्रामक रूप से ऑप्शनल हेडर में DllCharacteristics कहा जाता है। हम "डायनामिक बेस" विशेषता से चिंतित हैं। गैर-ASLR बाइनरी को अनपैक करने का एक विशेष तरीका है, जिसे हम कवर नहीं कर रहे हैं, इसलिए यदि हमारा बाइनरी इसका समर्थन नहीं करता है तो यह एक त्रुटि है। (यह आपके पैकर में एक त्रुटि के रूप में बेहतर है, न कि आपके स्टब में, लेकिन PE हेडर शिक्षा के प्रवाह में आसानी के लिए, इसे यहाँ रखा गया था।)```cpp // first, check if we can even relocate the image. if the dynamic base flag isn't set, // then this image probably isn't prepared for relocating. auto nt_header = get_nt_headers(image);

if (nt_header->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE == 0) { std::cerr << "Error: image cannot be relocated." << std::endl; ExitProcess(7); }

// once we know we can relocate the image, make sure a relocation directory is present auto directory_entry = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

if (directory_entry.VirtualAddress == 0) { std::cerr << "Error: image can be relocated, but contains no relocation directory." << std::endl; ExitProcess(8); }

root@kitploit:~
इसके बाद, हमें *एड्रेस डेल्टा* की गणना करनी होगी। यह सरल रूप से इमेज के `ImageBase` वेरिएबल और वर्चुअल इमेज के बेस पते के बीच का अंतर है। इसका उपयोग बाइनरी में हार्डकोडेड पतों के मानों को शीघ्रता से समायोजित करने के लिए किया जाता है।```cpp
// calculate the difference between the image base in the compiled image
// and the current virtually allocated image. this will be added to our
// relocations later.
std::uintptr_t delta = reinterpret_cast<std::uintptr_t>(image) - nt_header->OptionalHeader.ImageBase;

अब हम रिलोकेशन टेबल से निपटने के लिए तैयार हैं।```c typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION;

root@kitploit:~
ध्यान दें कि टिप्पणी की गई `TypeOffset` array, यह वास्तव में यहाँ प्रासंगिक है! एक relocation table में offsets के ब्लॉक होते हैं जिनमें समायोजित करने के लिए addresses होते हैं, जिन्हें `VirtualAddress` RVA द्वारा पहचाना जाता है। `TypeOffset` array में एन्कोडेड word मान होते हैं जिनमें relocation type के साथ-साथ `VirtualAddress` से समायोजित करने के लिए offset भी होता है। जहाँ तक relocation type का सवाल है, 64-bit binaries के लिए, हमें केवल एक relocation type से मतलब है। दुर्भाग्य से, यह संरचना वास्तव में एक variadic struct नहीं है, इसलिए हमें `TypeOffset` array प्राप्त करने के लिए कुछ pointer arithmetic करनी पड़ती है।

जैसा बताया गया है, `TypeOffset` array में एन्कोडेड words होते हैं। ऊपरी 4 बिट्स (mask `0xF000`) में relocation type होता है, जिसे PE format प्रलेखन के ["base relocation types" section](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format) में पाया जा सकता है। निचले 12 बिट्स (mask `0x0FFF`) में `VirtualAddress` तर्क से समायोजित करने के लिए offset होता है।

इसे समझाना एक झंझट है और अंततः यह बहुत भ्रामक लगता है। Relocate करने के लिए किसी address का pointer प्राप्त करना इस प्रकार दिखता है:```cpp
auto ptr = reinterpret_cast<std::uintptr_t *>(image + relocation_table->VirtualAddress + offset);

और उस पते को समायोजित करना इस तरह दिखता है:```cpp *ptr += delta;

root@kitploit:~
तो जितना जटिल relocation निर्देशिका को समझाना है, वास्तव में कोड में संचालन समझना बहुत सरल है।

`SizeOfBlock` वेरिएबल की मदद से relocation डेटा के अगले ब्लॉक पर आगे बढ़ना आसान हो जाता है। इस ब्लॉक में हमारे हेडर का आकार *साथ ही* `TypeOffset` ऐरे का आकार शामिल होता है। यदि `TypeOffset` इसके बजाय एक स्थिर आकार (statically sized) वाला ऐरे होता, तो हम relocation हेडर के `sizeof` कॉल को जोड़कर अगले relocation प्रविष्टि पर चले जाते।

इन सबको समझाने के बाद, आप इस relocation कोड को समझ पाने में सक्षम होने चाहिए:```cpp
// get the relocation table.
auto relocation_table = reinterpret_cast<IMAGE_BASE_RELOCATION *>(image + directory_entry.VirtualAddress);

// when the virtual address for our relocation header is null,
// we've reached the end of the relocation table.
while (relocation_table->VirtualAddress != 0)
{
   // since the SizeOfBlock value also contains the size of the relocation table header,
   // we can calculate the size of the relocation array by subtracting the size of
   // the header from the SizeOfBlock value and dividing it by its base type: a 16-bit integer.
   std::size_t relocations = (relocation_table->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(std::uint16_t);

   // additionally, the relocation array for this table entry is directly after
   // the relocation header
   auto relocation_data = reinterpret_cast<std::uint16_t *>(&relocation_table[1]);

   for (std::size_t i=0; i<relocations; ++i)
   {
      // a relocation is an encoded 16-bit value:
      //   * the upper 4 bits are its relocation type
      //     (https://learn.microsoft.com/en-us/windows/win32/debug/pe-format see "base relocation types")
      //   * the lower 12 bits contain the offset into the relocation entry's address base into the image
      //
      auto relocation = relocation_data[i];
      std::uint16_t type = relocation >> 12;
      std::uint16_t offset = relocation & 0xFFF;
      auto ptr = reinterpret_cast<std::uintptr_t *>(image + relocation_table->VirtualAddress + offset);

      // there are typically only two types of relocations for a 64-bit binary:
      //   * IMAGE_REL_BASED_DIR64: a 64-bit delta calculation
      //   * IMAGE_REL_BASED_ABSOLUTE: a no-op
      //
      if (type == IMAGE_REL_BASED_DIR64)
         *ptr += delta;
   }

   // the next relocation entry is at SizeOfBlock bytes after the current entry
   relocation_table = reinterpret_cast<IMAGE_BASE_RELOCATION *>(
      reinterpret_cast<std::uint8_t *>(relocation_table) + relocation_table->SizeOfBlock
   );
}

बधाई हो! हमारा इमेज अब निष्पादन के लिए तैयार है! हमने अब तक बहुत कुछ हासिल किया है:

  • हमने अपने बाइनरी को रनटाइम पर अपने सेक्शन टेबल से अनपैक किया
  • हमने अपने बाइनरी को निष्पादन योग्य मेमोरी क्षेत्र पर मैप किया
  • हमने अपने बाइनरी के निष्पादन के लिए आवश्यक आयातों को रिज़ॉल्व किया
  • हमने इमेज में पतों को नए इमेज बेस की ओर इंगित करने के लिए रीलोकेट किया

अब हम अपने अनपैक्ड बाइनरी को चलाने के लिए तैयार हैं!

निष्पादन स्थानांतरित करना

अब अपने मुख्य लूप पर वापस चलते हैं:```cpp int main(int argc, char *argv[]) { // first, decompress the image from our added section auto image = get_image();

// next, prepare the image to be a virtual image
auto loaded_image = load_image(image);

// resolve the imports from the executable load_imports(loaded_image);

// relocate the executable relocate(loaded_image);

// get the headers from our loaded image auto nt_headers = get_nt_headers(loaded_image);

// acquire and call the entrypoint auto entrypoint = loaded_image + nt_headers->OptionalHeader.AddressOfEntryPoint; reinterpret_cast<void(*)()>(entrypoint)();

return 0; }

root@kitploit:~
जैसा कि आप देख सकते हैं, निष्पादन स्थानांतरित करना बहुत सरल है, हालाँकि इसके लिए [फ़ंक्शन पॉइंटर्स](https://en.wikipedia.org/wiki/Function_pointer) का ज्ञान आवश्यक है। ये हमारे प्रासंगिक बिट्स हैं:```cpp
// acquire and call the entrypoint
auto entrypoint = loaded_image + nt_headers->OptionalHeader.AddressOfEntryPoint;
reinterpret_cast<void(*)()>(entrypoint)();

AddressOfEntryPoint है, जैसा कि आप अनुमान लगा सकते हैं, हमारी लोड की गई इमेज के कोड एंट्री में एक RVA। main एंट्रीपॉइंट्स के बारे में आप जो कुछ भी जानते हों, उसके बावजूद, किसी दिए गए बाइनरी का रॉ एंट्रीपॉइंट अनटाइप्ड होता है— आपका C++ कंपाइलर मुख्य रूप से कोड एनवायरनमेंट सेट करने के लिए ज़िम्मेदार होता है ताकि आपके अपेक्षित main फंक्शन्स को आर्ग्युमेंट्स फीड किए जा सकें, चाहे वे main हों या WinMain।

मौलिक रूप से, इस तरह एक फंक्शन पॉइंटर घोषित किया जाता है:```c return_type (*variable_name)(int arg1, int arg2, ...)

root@kitploit:~
इसलिए, हमारे एंट्री पॉइंट को कॉल करने के लिए हमारा फंक्शन पॉइंटर-- जिसके साथ कोई प्रकार संबद्ध नहीं है-- ऐसा दिखता है:```c
void (*entrypoint)()

कास्ट के रूप में, यह इस प्रकार सरल हो जाता है:```c void(*)()

root@kitploit:~
सब कुछ एक साथ रखते हुए, हम अपने entrypoint को function pointer के रूप में cast कर सकते हैं और इसे उसी पंक्ति में call कर सकते हैं, इस प्रकार:```cpp
reinterpret_cast<void(*)()>(entrypoint)();

सब कुछ सही ढंग से लोड होने पर, आपको अपना पैक किया हुआ प्रोग्राम चलता हुआ दिखना चाहिए। हमारे मामले में, चूँकि हमने अपनी डमी एक्ज़ीक्यूटेबल को पैक किया था, यह बस एक संदेश आउटपुट करता है:``` $ ./packed.exe I'm just a little guy!

root@kitploit:~
बधाई हो! आपका काम पूरा हो गया! आपने अभी-अभी एक Windows पैकर लिखा है!

## आगे के अभ्यास

* **विश्लेषक पर हमला करें**: [कुछ एंटी-डीबग तकनीकों](https://anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf) को लागू करना सीखें और अपने पैकर को मजबूत बनाएं।
* **अपना समर्थन बढ़ाएँ**: एक गैर-रिलोकेटेबल बाइनरी लागू करना सीखें, या थ्रेड-लोकल स्टोरेज निर्देशिका (`IMAGE_TLS_DIRECTORY`) और रिसोर्स निर्देशिका (`IMAGE_RESOURCE_DIRECTORY`) जैसी अतिरिक्त निर्देशिकाओं को लागू करना सीखें। चूँकि इस स्तर पर दस्तावेज़ीकरण की कमी है, आप [exe-rs](https://github.com/exe-rs) में मेरे कार्यान्वयन देख सकते हैं।
* **अपने स्टब को अस्पष्ट करें**: यह पता लगाने की कोशिश करें कि विश्लेषक आपके बाइनरी को कैसे अनपैक करने वाला है और अनपैकिंग को आसान बनाने से रोकें, जैसे [हेडर मिटाना](https://github.com/frank2/packer-tutorial/blob/main/stub/src/main.cpp#L84)।
टूल डाउनलोड करें