
पैच से RCE तक: CVE-2010-4221 (ProFTPD TELNET IAC स्टैक ओवरफ्लो) के लिए हाथ से निर्मित एक्सप्लॉइट, जिसमें विफलता-संचालित पूरी यात्रा का दस्तावेज़ीकरण शामिल है
एक पूर्णतः पुनरुत्पादनीय लैब और CVE-2010-4221 के लिए हाथ से लिखा गया, रॉ-सॉकेट एक्सप्लॉइट — ProFTPD के pr_netio_telnet_gets() में प्री-ऑथेंटिकेशन स्टैक बफर ओवरफ्लो — जो भेद्यता अनुसंधान और एक्सप्लॉइट विकास में सीखने के अभ्यास के रूप में बनाया गया है।
हर विफलता दर्ज है। सुखद मार्ग झूठ है; विचलन ही सबक हैं।
यह रिपॉजिटरी एक शैक्षिक कृति है। यह इसलिए मौजूद है ताकि जो लोग मेंटर या प्रशिक्षण नहीं खरीद सकते, वे सीख सकें कि मेमोरी-भ्रष्टाचार एक्सप्लॉइट वास्तव में कैसे जन्म लेता है: पैच से, विफलताओं के माध्यम से, एक कार्यशील प्रूफ ऑफ कॉन्सेप्ट तक आपके स्वामित्व वाली प्रयोगशाला के अंदर।
रेड टीम का काम — वास्तविक, पेशेवर आक्रामक सुरक्षा — एक शब्द से परिभाषित होता है: प्राधिकरण। एक पेशेवर जो कुछ भी करता है वह एक लिखित समझौते के अंदर होता है: एक हस्ताक्षरित Rules of Engagement दस्तावेज़ जो दायरे, लक्ष्यों, अनुमत तकनीकों, समय-सीमा और उन लोगों के नाम बताता है जिन्होंने इसे मंजूरी दी। उस कागज़ के बिना, वही कीस्ट्रोक्स एक पेशा नहीं हैं — वे पृथ्वी के लगभग हर क्षेत्राधिकार में एक अपराध हैं।
तो यहाँ इस रिपो के लिए अनुबंध है, जो गैर-परक्राम्य है:
यह कला सीखने लायक है। यह कला केवल उस अनुशासन के साथ मूल्यवान है जो इसके साथ आता है।
ProFTPD FTP नियंत्रण चैनल पर TELNET एस्केप अनुक्रम बोलता है। TELNET में, 0xFF (IAC, "Interpret As Command") एस्केप बाइट है; एक शाब्दिक 0xFF को 0xFF 0xFF के रूप में भेजा जाता है।
pr_netio_telnet_gets() क्लाइंट बाइट्स को एक स्टैक बफर (pr_cmd_read का char buf[PR_DEFAULT_CMD_BUFSZ+1], glibc के MAXPATHLEN=4096 के साथ 4104 बाइट्स) में कॉपी करता है, शेष स्थान को buflen में ट्रैक करता है — एक size_t, अहस्ताक्षरित।
भेद्य मार्ग (1.3.3a, netio.c):
case TELNET_IAC:
switch (cp) {
...
default:
*bp++ = TELNET_IAC; // write #1
buflen--; // decrement #1
telnet_mode = 0;
break;
}
break;
...
*bp++ = cp; // write #2
buflen--; // decrement #2 <-- no check in between
दो लेखन, दो घटाव, उनके बीच कोई शून्य जाँच नहीं। जब buflen ठीक 1 होता है, तो युग्म इसे 0 तक घटा देता है, फिर SIZE_MAX (18 क्विंटिलियन) तक अंडरफ्लो हो जाता है। लूप अब मानता है कि बफर अनंत है और स्टैक के ऊपर हमलावर-नियंत्रित बाइट्स लिखता रहता है — सहेजे गए रजिस्टरों, सहेजे गए RBP और रिटर्न एड्रेस के ऊपर।
प्री-ऑथ। फ़ंक्शन USER/PASS के संसाधित होने से पहले चलता है।
फिक्स (कमिट
3cc69b8388,
"Bug#3521 - Telnet IAC processing stack overflow", 1.3.3c में जारी)
बारह पंक्तियों का है। पूरी सुरक्षा सीमा है:
if (buflen == 0) {
break;
}
patch.diff देखें। पैच पढ़ने से पता चलता है कि घाव कहाँ था — यही कौशल है।
Dockerfile ऐतिहासिक Debian स्नैपशॉट स्रोत से ProFTPD 1.3.3a संकलित करता है, जानबूझकर असुरक्षित (2010 ऐसा ही दिखता था):
-fno-stack-protector — कोई कैनरी नहीं-z execstack — निष्पादन योग्य स्टैक (कोई NX नहीं)-no-pie — निश्चित बाइनरी पतेgdb के अंतर्गत चलाएँ, जो डिफ़ॉल्ट रूप से ASLR अक्षम करता है → नियतात्मक स्टैकdocker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
-ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
> /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py
अपेक्षित आउटपुट:
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'
(शेलकोड fds 0, 1 और 2 पर लिखता है क्योंकि हम यह जानने पर निर्भर नहीं रहना चाहते थे कि कौन सा नियंत्रण चैनल वहन करता है — उनमें से दो उत्तर देते हैं।)
"SITE " + NOP sled + shellcode + [\xff\xff flood] + padding + [ret] + "\n"
^^^^^^^^^^^^^^^^^^^^ ^^^^
shellcode lives INSIDE the command the overflow tail only
buffer — the region nobody touches delivers ONE address
buf[4102] को छुआ जाता है (ट्रंकेशन NUL)। फ्रेम के लाइव लोकल्स के नीचे सब कुछ शांत है।buflen को अंडरफ्लो की ओर ले जाता है (समता समस्या के लिए "The journey" देखें)।pr_cmd_read पार्सिंग के बाद return 0 पर पहुँचता है, तो CPU sled में उतरता है और शेलकोड में स्लाइड करता है।यह उलटा आर्किटेक्चर — पहले पेलोड, दूसरा फ्लड, अंत में पता — विहित Metasploit मॉड्यूल (proftp_telnet_iac) के विरुद्ध मान्य किया गया था, जो समान लेआउट का उपयोग करता है। उनके लक्ष्यों में NX था, इसलिए उन्हें res पॉइंटर के "चौगुने डीरेफ" के साथ ROP श्रृंखला की आवश्यकता थी; हमारी लैब में निष्पादन योग्य स्टैक है, इसलिए एक सीधा रिटर्न पर्याप्त है।
अंतिम एक्सप्लॉइट 60 पंक्तियों का है। इसकी कीमत क्या थी:
अंधा \xff फ्लड → कुछ नहीं। सर्वर ने विनम्रता से सत्र बंद कर दिया। मूल कारण: buflen 4102 (सम) से शुरू होता है और प्रत्येक IAC युग्म 2 से घटता है — यह 1 पर कभी नहीं, साफ-सुथरे ढंग से 0 पर उतरता है। अंडरफ्लो को समता तोड़ने की आवश्यकता है। सबक: स्टेट मशीन पढ़ना स्प्रेइंग से बेहतर है।
गलत बफर आकार। पहला कैलिब्रेटेड प्रयास 1024-बाइट बफर मान लिया। वास्तविक Linux/glibc पर MAXPATHLEN+8 = 4104 है। फ्लड लक्ष्य से 3KB पहले रुक गया। सबक: लक्ष्य को मापें, लक्ष्य का अनुमान न लगाएँ।
पहला SIGSEGV। चक्रीय de Bruijn पैटर्न (Aa0Aa1...) ने रिटर्न स्लॉट को buf+4152 पर रखा, दो बार क्रॉस-मान्य किया गया (फ्रेम गणित + पैटर्न ऑफसेट)। सबक: चक्रीय पैटर्न एक मापने वाला टेप है, एक्सप्लॉइट नहीं।
RIP नियंत्रण। स्लॉट को 0x4141414141414141 पर सेट करने से ret निर्देश स्वयं क्रैश हो गया — x86-64 गैर-विहित पतों को अस्वीकार करता है, और दोष ret पर उतरता है, हमारा मान बैकट्रेस में प्रतीक्षा कर रहा होता है। सबक: ret पर आपके मान के साथ फ्रेम में क्रैश = नियंत्रण।
ret स्लॉट के ऊपर शेलकोड → क्लोबर हो गया। 8 बाइट्स हीप पॉइंटर (0x4d7838 — बाद में cmd_rec पूल आवंटन के रूप में पहचाना गया) द्वारा अधिलेखित। स्लॉट के ऊपर के स्टैक फ्रेम उन फ़ंक्शनों के हैं जो लैंडिंग और हाईजैक के बीच अभी भी काम कर रहे हैं। सबक: ओवरफ्लो अंतिम लेखन नहीं है; प्रोग्राम उस स्टैक पर जीवित रहता है जिसे आपने अभी तोड़ा है।
स्लॉट के नीचे "डेड ज़ोन" → भी लिखा गया। pr_cmd_read के अपने लोकल्स (cmd, buflen, cp) ठीक वहीं रहते हैं और पार्सिंग के दौरान संग्रहीत होते रहते हैं।
हार्डवेयर वॉचपॉइंट फोरेंसिक। gdb में watch *(long*)ADDR ने रहस्य को एक कैमरे में बदल दिया: क्लोबर किए गए पते पर हर लेखन, बैकट्रेस के साथ, क्रम में। सबक: जब प्रश्न "इस मेमोरी को किसने लिखा?" हो, तो उत्तर एक gdb कमांड दूर है।
संदर्भ पढ़ें, फिर उसे समझें। विहित मॉड्यूल ने उलटे आर्किटेक्चर की पुष्टि की। अपना स्वयं का मानसिक मॉडल बनाने के बाद दूसरा एक्सप्लॉइट पढ़ना अध्ययन है; पहले, यह नकल है।
size_t कभी नकारात्मक नहीं होता — यह विशाल हो जाता है। शेष-स्थान काउंटर में पूर्णांक अंडरफ्लो अतिरिक्त चरणों के साथ एक स्टैक ओवरफ्लो है।\x0a (पढ़ना समाप्त करता है) से बचता है और डिज़ाइन द्वारा \xff (TELNET एस्केप) से बच जाता है।argv[0] में 11-बाइट का अंतर (बिल्ड-ट्री बाइनरी बनाम स्थापित बाइनरी) ने पूरे स्टैक को 0x40 से स्थानांतरित कर दिया और एक उत्तम एक्सप्लॉइट को चुपचाप अमान्य कर दिया।modules/exploits/linux/ftp/proftp_telnet_iac.rb
(rapid7/metasploit-framework)diegslva द्वारा निर्मित, सार्वजनिक रूप से सीखते हुए — "कभी एक्सप्लॉइट नहीं लिखा" से लेकर हाथ से बनाए गए शेलकोड के साथ प्री-ऑथ RCE तक, एक प्रलेखित दिन में। यदि इस रिपो ने आपको कुछ सिखाया है, तो इसे आगे बढ़ाएँ।