
Windows x64 निष्पादनयोग्य फ़ाइलों के लिए स्टैटिक बाइनरी इंस्ट्रुमेंटेशन टूल
peafl64 विंडोज़ में x64 PE के लिए एक स्थिर इंस्ट्रूमेंटेशन टूल है।
स्थैतिक इंस्ट्रूमेंटेशन निष्पादन योग्य फ़ाइलों को संपादित करने और उनमें विशिष्ट स्थानों पर कोड जोड़ने की प्रथा है।
इंस्ट्रूमेंटेशन बाइनरी में हर बेसिक ब्लॉक की शुरुआत में कोड जोड़ता है, और निष्पादन प्रवाह को AFL-संगत तरीके से लॉग करता है।
यह हमें उनकी स्रोत कोड तक पहुँच के बिना यूज़रमोड (WinAFL का उपयोग करके) और कर्नेलमोड (kAFL का उपयोग करके) में बाइनरी को फ़ज़ करने की अनुमति देता है।
Windows बाइनरी को फ़ज़ करने के और भी तरीके हैं; इस परियोजना में हमने स्थिर इंस्ट्रूमेंटेशन पर ध्यान केंद्रित करना चुना क्योंकि यह सबसे तेज़ तरीका है।
यह परियोजना wmliang के pe-afl टूल पर आधारित है, जिसमें x64 समर्थन जोड़ा गया है।
इंस्ट्रूमेंटेशन स्क्रिप्ट को IDA विश्लेषण आउटपुट की आवश्यकता होती है।
इसे बनाने के लिए, IDA में दी गई ida_dumper.py स्क्रिप्ट चलाएँ।
स्क्रिप्ट को IDA 7+ और python3.8+ की आवश्यकता है।
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
NOPs के साथ यूज़रमोड बाइनरी का इंस्ट्रूमेंटेशन
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
प्रोसेस ID फ़िल्टरिंग के साथ कर्नेलमोड बाइनरी का इंस्ट्रूमेंटेशन
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
थ्रेड ID फ़िल्टरिंग और वर्बोज़ आउटपुट के साथ कर्नेलमोड बाइनरी का इंस्ट्रूमेंटेशन
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
सबसे पहले आपको यह जाँचना होगा कि आपकी मशीन BIOS या UEFI का उपयोग करके बूट हो रही है या नहीं।
Hyper-V मशीनों के लिए: Gen 1 मशीनें BIOS-आधारित होती हैं, और Gen 2 UEFI-आधारित होती हैं।
यदि आपकी मशीन BIOS-आधारित है, तो आपको winload.exe को पैच करने की आवश्यकता है:
ImgpValidateImageHash फ़ंक्शन खोजेंmov eax, edi को xor eax, eax से बदलेंbcdedit /set path \Windows\system32\winload2.exe चलाएँयदि आपकी मशीन UEFI का उपयोग करके बूट होती है, तो winload.efi को पैच करने के लिए EfiGuard उपयोगिता का उपयोग करें।
Hyper-V Manager का उपयोग करने पर कमांड चीटशीट:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
फिर अपनी पसंद के ड्राइवर को इंस्ट्रूमेंट करें। Windows मशीन पर इंस्ट्रूमेंटेड ड्राइवर लोड करने के लिए उसे हस्ताक्षरित होना चाहिए, और OS की आवश्यकताओं को पूरा करने के लिए एक सेल्फ-साइनिंग प्रमाणपत्र पर्याप्त है:
# In elevated powershell terminal
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
यदि आपके द्वारा इंस्ट्रूमेंट किया गया ड्राइवर पहले से सिस्टम द्वारा उपयोग में है, तो ड्राइवर की फ़ाइल को बदलने के लिए इन कमांडों का उपयोग करें (एडमिन के रूप में):
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
फिर चलाएँ:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
WinAFL के साथ एकीकरण दिए गए हेडरों का उपयोग करके एक हार्नेस संकलित करके होता है।
हेडरों के साथ example.c भी है, जो एक नमूना प्रोग्राम है जो दिखाता है कि उनका उपयोग कैसे करें।
दिए गए हेडर, WinAFL द्वारा पहले से दिए गए हेडरों का एक हल्का संशोधन हैं ताकि Syzygy नामक एक अन्य स्थैतिक बाइनरी इंस्ट्रूमेंटेशन टूल के साथ एकीकरण हो सके।
जिस तरह से हम kAFL के साथ एकीकृत होते हैं वह काफी सरल है।
सामान्यतः, एक kAFL हार्नेस वर्चुअल मशीन पर चलता है और विशेष "हाइपरकॉल" का उपयोग करके फ़ज़र के फ्रंटेंड से बात करता है।
ये हाइपरकॉल फ़ज़र को कई काम करने के लिए कहते हैं, जिनमें से एक है IntelPT से कवरेज डेटा लोड करना और उसे AFL बिटमैप के रूप में पार्स करना।
चूँकि peafl64 IntelPT ट्रेसिंग को अप्रचलित बना देता है, हमें फ़ज़र तक कवरेज डेटा पहुँचाने का एक रास्ता तैयार करना होगा।
इसलिए, हमने qemu और kvm को "हाइपरकॉल" के साथ विस्तारित किया जो VM में चल रहे (यूज़रमोड) हार्नेस को हेल्पर ड्राइवर का उपयोग करके एकत्रित कवरेज डेटा भेजने की अनुमति देते हैं।
यह विशेष रूप से ESXi पर सेटअप के बारे में है, लेकिन AWS जैसे अन्य वर्चुअलाइज़ेशन प्लेटफ़ॉर्म के लिए भी प्रासंगिक होना चाहिए।
सेटअप काफी सरल है:
install.sh qemu चरण के बजाय, install.sh qemu_sbi चलाएँkAFL और peafl64 का उपयोग करके फ़ज़ करने के लिए, हमें एक फ़ज़िंग मशीन सेटअप करनी होगी:
इसके अलावा, हमारे kAFL फ़ोर्क के साथ फ़ज़िंग सामान्य kAFL फ़ज़िंग के समान ही है।
चरण अवलोकन:
सबसे पहले, हम रुचि के निर्देशों और स्थानों को खोजने और उनका विश्लेषण करने के लिए IDA का उपयोग करते हैं।
विश्लेषण एक dump.json फ़ाइल बनाता है जिसमें वह सारी जानकारी json प्रारूप में होती है।
instrument.py में इंस्ट्रूमेंटेशन लॉजिक होता है, और प्रवाह स्वयं process_pe फ़ंक्शन में रेखांकित किया गया है।
बाइनरी को इंस्ट्रूमेंट करने के लिए, हम उसके निष्पादन योग्य सेक्शनों की नकल बनाते हैं, जहाँ सभी इंस्ट्रूमेंटेड कोड मिलेंगे।
इसके बाद, हम सभी सापेक्ष निर्देशों को संसाधित करते हैं और निर्धारित करते हैं कि उन्हें कैसे और क्या संभालने की आवश्यकता है।
मान लीजिए हमारे पास पते X से पते Y तक एक छोटा jmp है। हम X और Y के बीच इंस्ट्रूमेंटेशन कोड डालते हैं, और अब लक्ष्य पते Z पर है (Z = Y + len(instrumentation)).
इसका मतलब है कि हमें जंप को संशोधित करना होगा।
यदि पता Z अब शॉर्ट jmp की सीमा के बाहर स्थित है - तो हमें जंप को short से far में बदलना होगा। (instrument.py:expand_relative_instructions)
उदाहरण के लिए:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)यह बात call, loop और कंडिशनल जंप जैसे निर्देशों पर भी लागू होती है।
एक और सामान्य मामला जिसे संभालने की आवश्यकता है वह है x64 असेंबली में पेश किए गए rip-relative निर्देश:
call [rip+0x1000]हम इंस्ट्रूमेंटेड कोड की ओर इंगित करने के लिए निम्नलिखित चीज़ों को अपडेट करते हैं: रिलोकेशन टेबल, एक्सपोर्ट टेबल, लोड कॉन्फ़िगरेशन, TLS निर्देशिका और अपवाद रिकॉर्ड।
अपडेट मूल सेक्शनों से सभी पतों को उनके इंस्ट्रूमेंटेड समकक्षों में अपडेट करके किए जाते हैं। (instrument.py:update_addr)
हेडरों को PE संरचना में हुए परिवर्तनों को दर्शाने के लिए अपडेट किया जाता है - जोड़े गए सेक्शन, बदला हुआ एंट्रीपॉइंट और PE आकार। (instrument.py:update_pe_headers)
इसके अतिरिक्त, peafl64 Windows कर्नेल को इंस्ट्रूमेंट करने में सक्षम है। इसे प्राप्त करने के लिए, यह Dynamic Value Relocation Table (DVRT) और SSDT को पार्स और अपडेट करता है, और PatchGuard विशिष्टताओं को संभालता है।
DVRT एक कंपाइलर-जनरेटेड तालिका है जो उन पतों के स्थानों का वर्णन करती है जिन्हें PE लोड करते समय बदलने की आवश्यकता होती है।
इसका उपयोग KASLR को बेहतर बनाने और Spectre भेद्यता को कम करने में मदद करने के लिए किया जाता है (1, 2, 3).
DVRT को उन कक्षाओं के एक सेट का उपयोग करके पार्स किया जाता है जो तालिका की संरचना की नकल करती हैं (drt.py; instrument.py:get_updated_dynamic_relocs)
SSDT को संभालना और अपडेट करना उतना सीधा नहीं था।
इसका पता निर्यात नहीं किया गया है, इसलिए हमें इसे खोजने के लिए या तो प्रतीकों या ह्यूरिस्टिक्स पर निर्भर रहना पड़ा।
हमारा समाधान एक ह्यूरिस्टिक दृष्टिकोण का उपयोग करता है, जो सभी Windows 10 संस्करणों के लिए एक स्थिर मार्कर के रूप में NtWaitForSingleObject का उपयोग करता है और इसके सापेक्ष SSDT का पता खोजता है। (instrument.py:ntoskrnl_update_KiServiceTable)
यह दृष्टिकोण सभी Windows 10 कर्नेल के लिए काम करता है, लेकिन अन्य कर्नेल संस्करणों के लिए इसे समायोजित करने की आवश्यकता होगी।