समाधान 1: सी (मैक ओएस एक्स x86_64), 109 बाइट्स
Golf_sol1.c के लिए स्रोत
main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};
उपरोक्त कार्यक्रम को __DATA खंड पर निष्पादन पहुंच के साथ संकलित करने की आवश्यकता है।
clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
फिर प्रोग्राम को निष्पादित करने के लिए निम्नलिखित कार्य करें:
./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
परिणाम:
दुर्भाग्य से Valgrind सिस्टम कॉल से आवंटित मेमोरी के लिए नहीं देखता है, इसलिए मैं एक अच्छा पता लगाया गया रिसाव नहीं दिखा सकता।
हालाँकि हम आवंटित मेमोरी (MALLOC मेटाडेटा) के बड़े हिस्से को देखने के लिए vmmap पर देख सकते हैं।
VIRTUAL REGION
REGION TYPE SIZE COUNT (non-coalesced)
=========== ======= =======
Kernel Alloc Once 4K 2
MALLOC guard page 16K 4
MALLOC metadata 16.2M 7
MALLOC_SMALL 8192K 2 see MALLOC ZONE table below
MALLOC_TINY 1024K 2 see MALLOC ZONE table below
STACK GUARD 56.0M 2
Stack 8192K 3
VM_ALLOCATE (reserved) 520K 3 reserved VM address space (unallocated)
__DATA 684K 42
__LINKEDIT 70.8M 4
__TEXT 5960K 44
shared memory 8K 3
=========== ======= =======
TOTAL 167.0M 106
TOTAL, minus reserved VM space 166.5M 106
व्याख्या
इसलिए मुझे लगता है कि मुझे बेहतर समाधान पर आगे बढ़ने से पहले यह बताने की जरूरत है कि वास्तव में यहां क्या हो रहा है।
यह मुख्य कार्य सी के लापता प्रकार की घोषणा को निरस्त कर रहा है (इसलिए यह हमारे बिना इसे लिखने के लिए बेकार करने के लिए चूक करता है), साथ ही प्रतीकों कैसे काम करते हैं। लिंकर केवल इस बात की परवाह करता है कि क्या वह main
कॉल करने के लिए बुलाया गया प्रतीक नहीं पा सकता है। इसलिए यहाँ हम मुख्य int की एक सरणी बना रहे हैं जिसे हम अपने शेलकोड के साथ आरंभ कर रहे हैं जिसे निष्पादित किया जाएगा। इस वजह से, मुख्य को __TEXT सेगमेंट में नहीं जोड़ा जाएगा, बल्कि __DATA सेगमेंट में, कारण हमें एक निष्पादन योग्य __DATA सेगमेंट के साथ प्रोग्राम को संकलित करने की आवश्यकता है।
मुख्य में पाया जाने वाला शेलकोड निम्नलिखित है:
movq 8(%rsi), %rdi
movl (%rdi), %eax
movq 4(%rdi), %rdi
notl %eax
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
यह क्या कर रहा है मेमोरी के एक पेज को आवंटित करने के लिए syscall फ़ंक्शन को कॉल कर रहा है (आंतरिक रूप से syscall mach_vm_allocate का उपयोग करता है)। RAX को 0x100000a के बराबर होना चाहिए (syscall को बताता है कि हमें कौन सा फ़ंक्शन चाहिए), जबकि RDI आवंटन के लिए लक्ष्य रखता है (हमारे मामले में हम चाहते हैं कि यह mach_task_self ()) हो, RSI को नए बनाए गए मेमोरी को पॉइंटर लिखने के लिए पता रखना चाहिए। (इसलिए हम इसे केवल स्टैक पर एक खंड की ओर इशारा कर रहे हैं), आरडीएक्स आवंटन का आकार रखता है (हम सिर्फ बाइट पर बचाने के लिए RAX या 0x100000a में पास कर रहे हैं), R10 झंडे को पकड़ते हैं (हम यह संकेत कर सकते हैं) कहीं भी आवंटित किया जाए)।
अब यह स्पष्ट रूप से स्पष्ट नहीं है कि RAX और RDI अपने मूल्यों को कहां से प्राप्त कर रहे हैं। हम जानते हैं कि RAX को 0x100000a होना चाहिए, और RDI के लिए mach_task_self () रिटर्न की जरूरत है। सौभाग्य से mach_task_self () वास्तव में एक चर (mach_task_self_) के लिए एक मैक्रो है, जो हर बार एक ही मेमोरी पते पर है (हालांकि रिबूट पर बदलना चाहिए)। मेरे विशेष उदाहरण में mach_task_self_ 0x00007fff7d578244 पर स्थित होता है। इसलिए निर्देशों पर कटौती करने के लिए, हम इसके बजाय argv से इस डेटा में पास होंगे। यही कारण है कि हम इस अभिव्यक्ति के साथ कार्यक्रम चलाते हैं$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')
पहले तर्क के लिए। स्ट्रिंग संयुक्त दो मान है, जहां RAX मान (0x100000a) केवल 32 बिट्स है और इस पर किसी का पूरक लागू किया गया है (इसलिए कोई अशक्त बाइट्स नहीं है; हम मूल प्राप्त करने के लिए सिर्फ मूल्य नहीं हैं), अगला मान; RDI (0x00007fff7d578244) जिसे 2 अतिरिक्त जंक बाइट्स के साथ बाईं ओर स्थानांतरित कर दिया गया है, अंत में जोड़ा गया है (फिर से नल बाइट्स को बाहर करने के लिए, हम इसे मूल में वापस लाने के लिए इसे वापस दाईं ओर शिफ्ट करते हैं)।
Syscall के बाद हम अपनी नई आवंटित मेमोरी को लिख रहे हैं। इसका कारण यह है क्योंकि mach_vm_allocate (या इस syscall) का उपयोग करके आवंटित की गई मेमोरी वास्तव में VM पृष्ठ हैं, और स्वचालित रूप से स्मृति में पृष्ठांकित नहीं हैं। बल्कि उन्हें तब तक आरक्षित रखा जाता है जब तक कि उन्हें डेटा नहीं लिखा जाता है, और फिर उन पृष्ठों को स्मृति में मैप किया जाता है। यकीन नहीं था कि यह आवश्यकताओं को पूरा करेगा अगर यह केवल आरक्षित था।
अगले समाधान के लिए हम इस तथ्य का लाभ उठाएंगे कि हमारे शेलकोड में कोई शून्य बाइट्स नहीं हैं, और इसलिए आकार को कम करने के लिए इसे हमारे प्रोग्राम के कोड के बाहर ले जा सकते हैं।
समाधान 2: सी (मैक ओएस एक्स x86_64), 44 बाइट्स
Golf_sol2.c के लिए स्रोत
main[]={141986632,10937,1032669184,2,42227};
उपरोक्त कार्यक्रम को __DATA खंड पर निष्पादन पहुंच के साथ संकलित करने की आवश्यकता है।
clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx
फिर प्रोग्राम को निष्पादित करने के लिए निम्नलिखित कार्य करें:
./golf_sol2 $(ruby -e 'puts "\xb8\xf5\xff\xff\xfe\xf7\xd0\x48\xbf\xff\xff\x44\x82\x57\x7d\xff\x7f\x48\xc1\xef\x10\x8b\x3f\x48\x8d\x74\x24\xf8\x89\xc2\x4c\x8d\x50\xf7\x0f\x05\x48\x8b\x36\x89\x36\xc3"')
परिणाम पहले जैसा होना चाहिए, क्योंकि हम एक ही आकार का आवंटन कर रहे हैं।
व्याख्या
समाधान 1 के रूप में एक ही अवधारणा का अनुसरण करता है, इस अपवाद के साथ कि हमने प्रोग्राम के बाहर हमारे लीक कोड का हिस्सा बदल दिया है।
मुख्य में पाया गया शेलकोड अब निम्नलिखित है:
movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)
यह मूल रूप से इस कोड के बाद होने वाले argv में पास किए गए शेलकोड को कॉपी करता है (इसलिए इसे कॉपी करने के बाद, यह सम्मिलित कोडकोड चलाएगा)। हमारे पक्ष में जो काम करता है वह यह है कि __DATA खंड कम से कम एक पृष्ठ आकार होगा, इसलिए भले ही हमारा कोड उतना बड़ा न हो लेकिन हम अभी भी "सुरक्षित रूप से" अधिक लिख सकते हैं। नकारात्मक पक्ष यहाँ आदर्श समाधान है, यहां तक कि प्रतिलिपि की आवश्यकता नहीं होगी, इसके बजाय यह सिर्फ कॉल करेगा और सीधे argv में शेलकोड निष्पादित करेगा। लेकिन दुर्भाग्य से, इस मेमोरी में निष्पादन अधिकार नहीं हैं। हम इस मेमोरी के अधिकारों को बदल सकते हैं, हालांकि इसे केवल कॉपी करने की तुलना में अधिक कोड की आवश्यकता होगी। एक वैकल्पिक रणनीति बाहरी कार्यक्रम (लेकिन बाद में उस पर) से अधिकारों को बदलने के लिए होगी।
हम जिस argv को पास करते हैं वह शेलकोड निम्नलिखित है:
movl $0xfefffff5, %eax
notl %eax
movq $0x7fff7d578244ffff, %rdi
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret
यह हमारे पिछले कोड के समान ही है, केवल अंतर यह है कि हम सीधे EAX और RDI के मूल्यों को शामिल कर रहे हैं।
संभव समाधान 1: सी (मैक ओएस एक्स x86_64), 11 बाइट्स
कार्यक्रम को बाहरी रूप से संशोधित करने का विचार, हमें बाहरी प्रोग्राम में लीकर को स्थानांतरित करने का संभव समाधान देता है। जहां हमारा वास्तविक कार्यक्रम (सबमिशन) सिर्फ एक डमी प्रोग्राम है, और लीकर प्रोग्राम हमारे लक्ष्य कार्यक्रम में कुछ मेमोरी आवंटित करेगा। अब मैं निश्चित नहीं था कि अगर यह इस चुनौती के नियमों के दायरे में आता, लेकिन फिर भी इसे साझा करना।
इसलिए यदि हम अपने चुनौती कार्यक्रम के लिए निर्धारित लक्ष्य के साथ बाहरी कार्यक्रम में mach_vm_allocate का उपयोग करते हैं, तो इसका मतलब यह हो सकता है कि हमारे चुनौती कार्यक्रम को केवल कुछ की आवश्यकता होगी:
main=65259;
जहां वह शेलकोड केवल स्वयं के लिए एक छोटी छलांग (अनंत कूद / पाश) है, इसलिए कार्यक्रम खुला रहता है और हम इसे बाहरी कार्यक्रम से संदर्भित कर सकते हैं।
संभव समाधान 2: सी (मैक ओएस एक्स x86_64), 8 बाइट्स
मजेदार रूप से पर्याप्त है जब मैं वाल्ग्रिंड आउटपुट को देख रहा था, मैंने देखा कि कम से कम वैलेग्रिंड के अनुसार, डाईल्ड लीक मेमोरी है। इसलिए प्रभावी रूप से हर कार्यक्रम कुछ स्मृति को लीक कर रहा है। यह मामला होने के साथ, हम वास्तव में केवल एक कार्यक्रम बना सकते हैं जो कुछ भी नहीं करता है (बस बाहर निकलता है), और वह वास्तव में मेमोरी को लीक करेगा।
स्रोत:
main(){}
==55263== LEAK SUMMARY:
==55263== definitely lost: 696 bytes in 17 blocks
==55263== indirectly lost: 17,722 bytes in 128 blocks
==55263== possibly lost: 0 bytes in 0 blocks
==55263== still reachable: 0 bytes in 0 blocks
==55263== suppressed: 16,316 bytes in 272 blocks