本文最后更新于 398 天前,其中的信息可能已经有所发展或是发生改变。
- 当事件不为这个三类值时,触发
exit
退出进程,那么则想过能否将exit
通过内存修改为其他函数,不让进程退出;很巧,之前看过一个大跌写的文章,似乎用在这里很合适;简单总结即为通过ctypes
覆盖到sys._exit
的内存,替换为其余函数;但是存在两个问题,首先此题中获取不到ctypes
,其次,即便成功覆盖,也无法绕过死循环while(1)
(之前看的时候手动引入了ctypes
而且也没看到死循环,属实是眼瞎的不行了唉,好nb的一个题,先写个搓出来的poc,具体原理等期末结束再细写Audited
- server.py
#!python3.12 # run on python 3.12.4 import sys import os import json assert sys.version.startswith("3.12.4") test_code = input("Enter the function in json: ") sys.stdin.close() if len(test_code) > 2540: exit(1) test_code: json = json.loads(test_code) del json test_code["co_consts"] = tuple(tuple(a) if isinstance( a, list) else a for a in test_code["co_consts"]) test_code["co_names"] = tuple(tuple(a) if isinstance( a, list) else a for a in test_code["co_names"]) test_code["co_varnames"] = tuple(tuple(a) if isinstance( a, list) else a for a in test_code["co_varnames"]) def check_ele(ele, inner=False): if ele is None: pass elif isinstance(ele, int): # some random magic numbers w/o any meaning if ele not in range(-0xd000, 0x50000): exit(1) elif isinstance(ele, str): if any((ord(a) not in (list(range(ord('0'), ord('9') + 1)) + list(range(ord('a'), ord('z') + 1)) + list(range(ord('A'), ord('Z') + 1)) + [95])) for a in ele): exit(1) elif len(ele) > 242: exit(1) elif isinstance(ele, tuple): if inner: exit(1) for a in ele: check_ele(a, True) else: exit(1) for ele in test_code["co_consts"] + test_code["co_names"]: check_ele(ele) del check_ele def test(): pass test.__code__ = test.__code__.replace(co_code=bytes.fromhex(test_code["co_code"]), co_consts=test_code["co_consts"], co_names=test_code["co_names"], co_stacksize=test_code["co_stacksize"], co_nlocals=test_code["co_nlocals"], co_varnames=test_code["co_varnames"]) del test_code def auditHookHandler(e): def handler(x, _): if not (x == "object.__getattr__" or x == "object.__setattr__" or x == "code.__new__"): e(1) #looololooo while(1): pass return handler sys.addaudithook(auditHookHandler(os._exit)) del sys test() # free flag?! :) os.system("cat /flag")
- 首先看逻辑,接受输入参数,转换为
json
,然后从test_code
中取出co_*
的值,通过CodeType.replace
将函数的行为替换为你输入的值,需要传递下列值
test_code = { "co_code": str, "co_consts": tuple, "co_names": tuple, "co_stacksize": int, "co_nlocals": int, "co_varnames": tuple }
- 限制主要三个部分:限制长度;限制
int
和str
的值;通过audithook
限制event事件触发
if len(test_code) > 2540: exit(1)
def check_ele(ele, inner=False): if ele is None: pass elif isinstance(ele, int): # some random magic numbers w/o any meaning if ele not in range(-0xd000, 0x50000): exit(1) elif isinstance(ele, str): if any((ord(a) not in (list(range(ord('0'), ord('9') + 1)) + list(range(ord('a'), ord('z') + 1)) + list(range(ord('A'), ord('Z') + 1)) + [95])) for a in ele): exit(1) elif len(ele) > 242: exit(1) elif isinstance(ele, tuple): if inner: exit(1) for a in ele: check_ele(a, True) else: exit(1)
def auditHookHandler(e): def handler(x, _): if not (x == "object.__getattr__" or x == "object.__setattr__" or x == "code.__new__"): e(1) #looololooo while(1): pass return handler
- 此外还有个比较隐晦的限制
test_code = input("Enter the function in json: ") ... test_code: json = json.loads(test_code)
- 自定义函数后,尝试获取到的属性有时并不能够被转换为json,自然,此种代码不能通过
loads
读入
def t(): def test(): pass test_code = { "co_code": t.__code__.co_code.hex(), "co_consts": t.__code__.co_consts, "co_names": t.__code__.co_names, "co_stacksize": t.__code__.co_stacksize, "co_nlocals": t.__code__.co_nlocals, "co_varnames": t.__code__.co_varnames } print(test_code) test_code = json.dumps(test_code) """ {'co_code': '9700640184007d007900', 'co_consts': (None, <code object test at 0x000002998E21C440, file "C:\Users\JBN\Downloads\test.py", line 12>), 'co_names': (), 'co_stacksize': 1, 'co_nlocals': 1, 'co_varnames': ('test',)} Traceback (most recent call last): File "C:\Users\JBN\Downloads\test.py", line 44, in <module> test_code = json.dumps(test_code) ^^^^^^^^^^^^^^^^^^^^^ File "C:\JBNRZ\Applications\anaconda\envs\python3.12\Lib\json\__init__.py", line 231, in dumps return _default_encoder.encode(obj) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\JBNRZ\Applications\anaconda\envs\python3.12\Lib\json\encoder.py", line 200, in encode chunks = self.iterencode(o, _one_shot=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\JBNRZ\Applications\anaconda\envs\python3.12\Lib\json\encoder.py", line 258, in iterencode return _iterencode(o, 0) ^^^^^^^^^^^^^^^^^ File "C:\JBNRZ\Applications\anaconda\envs\python3.12\Lib\json\encoder.py", line 180, in default raise TypeError(f'Object of type {o.__class__.__name__} ' TypeError: Object of type code is not JSON serializable """
- 此题的关键在于hook函数的绕过,先提一嘴一开始尝试过的路子
def auditHookHandler(e): def handler(x, _): if not (x == "object.__getattr__" or x == "object.__setattr__" or x == "code.__new__"): e(1) #looololooo while(1): pass return handler
- 当事件不为这个三类值时,触发
exit
退出进程,那么则想过能否将exit
通过内存修改为其他函数,不让进程退出;很巧,之前看过一个大跌写的文章,似乎用在这里很合适;简单总结即为通过ctypes
覆盖到sys._exit
的内存,替换为其余函数;但是存在两个问题,首先此题中获取不到ctypes
,其次,即便成功覆盖,也无法绕过死循环while(1)
(之前看的时候手动引入了ctypes
而且也没看到死循环,属实是眼瞎的不行了