从TPCTF 2023 学习Python逆向

WriteUp 11个月前 admin
194 0 0
TPCTF 2023 由清华大学 Redbud 战队与北京大学 pkucc 战队联合命题。周末没打到,赛后看了下 Reverse 方向有两个 Python +二进制的逆向题,发现居然不是纯套路题,出题人还是很用心地准备了一些新颖的知识点,故记录于此。

1

nanoPyEnc

一个主要时间花在找常量上的题,感觉不算难题,只有四解有点奇怪。看到程序中有很多 “Py” 开头的字符串,可知是打包成了 Python 的二进制程序。

常规 pyinstxtractor 解包,可以很顺利地解出 pyc 和 PYZ 中的压缩文件(需要用同版本的 Python 跑)。

从TPCTF 2023 学习Python逆向

明显 run.pyc 很像程序的入口点,用 uncompyle6 反编译看看内容:

从TPCTF 2023 学习Python逆向

可以看到逻辑非常简单,就是将输入的 message 用指定的 key 进行 AES 加密,然后与已知的 enc 对比,完全相同则为flag。换句话说,只要获得了 key 和 enc 这两个常量,那flag就能拿到了。

这个 secret 模块肯定不是 Python 内置的,看下 PYZ 解压出来的文件里的secret的内容:

从TPCTF 2023 学习Python逆向

可以看到确实有 key 和 enc 的定义,但是试着解了一下发现是 flag{test} ,纯假flag。

从TPCTF 2023 学习Python逆向

那应该是在使用 from xxx import * 的时候重新导入或者改变了这两个全局变量了,可以看到唯一导入了全部的是 Crypto.Util.number,把PYZ 里的这个 pyc 反编译看看,果然有对 enc 的重新赋值:

从TPCTF 2023 学习Python逆向

从TPCTF 2023 学习Python逆向

这串 enc 导进去仍然解不出flag,应该是还有地方藏了东西,可以看到这个 number 里又把一个模块全部导入了,于是看看这个 Crypto.Util.py3compat:从TPCTF 2023 学习Python逆向

果然能看到不对劲的地方,在后面重新定义了 list 这个函数,在保留 list 功能的同时套了个异或:

从TPCTF 2023 学习Python逆向

这回再解就能出 flag 了。_x 的取值这里只有0和1,异或0不变,所以只能是异或1。

from Crypto.Cipher import AES

enc = [15324023719963442374525479715415811246176219247441151691246463121253250137341443317182916247249411651148723122224212630124237]
enc = bytes([x ^ 1 for x in enc])
key = b'2033-05-18_03:33'
aes = AES.new(key, AES.MODE_ECB)
flag = aes.decrypt(enc)
print(flag)

TPCTF{83_C4u710U5_0F_PY7hON_k3YW0Rd_sHadOWIN9}

有个小坑,会通过当前时间(time() % 64 < 1)来判断是否验证 flag,如果时间戳不满足条件的话就算是对的flag也会得到 wrong 的结果ummm……(写个循环交flag的爆破脚本可得到“Right”的输出,可验证 flag 确实是这个


2

maze

一个考验 Cython 逆向能力但又能靠动态取巧的题。依旧是 pyinstxtractor 解包,解了以后发现明显是程序入口的 chal.pyc 只有几行:

从TPCTF 2023 学习Python逆向

看来主要逻辑都在 maze.so 里,只能硬啃 Cython 逆向了。

可以看到这边调用的是 maze 库中的 run 函数,因为库文件没有去除符号表,所以可以直接搜函数名找到 run:

从TPCTF 2023 学习Python逆向

在Cython逆向中,一般调用函数都会利用类似 PyObject_Call 这样的函数来调用。比如 PyObject_Call 中第一个参数是函数对象,第二和第三个参数是该被调用函数对象的参数,通常以元组和字典的形式传入(位置参数和关键字参数)。

从TPCTF 2023 学习Python逆向

比如这里调用了 print,参数在 __pyx_tuple__26 里,至于这个元组里的内容需要找交叉引用。

从TPCTF 2023 学习Python逆向

因为是结构体成员,不太好找,所以可以直接用 IDAPython 把数据类型是 __pyx_mstate 的变量直接拆解成其成员的变量的组合(反正也只有一个 mstate)。

这里从结构体的 copyof 找进去,能看到这个结构体的定义:

从TPCTF 2023 学习Python逆向

从TPCTF 2023 学习Python逆向

用 Python 稍微处理一下,写 IDAPython 脚本:

ys = ['PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyTypeObject *''PyTypeObject *''PyTypeObject *''PyTypeObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *''PyObject *']
ns = ['__pyx_d''__pyx_b''__pyx_cython_runtime''__pyx_empty_tuple''__pyx_empty_bytes''__pyx_empty_unicode''__pyx_CyFunctionType''__pyx_GeneratorType''__pyx_ptype_4maze___pyx_scope_struct__YWRkX2Z1bmN0aW9u''__pyx_ptype_4maze___pyx_scope_struct_1_genexpr''__pyx_kp_u_''__pyx_kp_u_0_9''__pyx_kp_u_0_9_2''__pyx_kp_u_0_9_2_2''__pyx_kp_u_A_Za_z0_9''__pyx_kp_u_A_Za_z0_9_2''__pyx_kp_u_A_Za_z_A_Za_z0_9''__pyx_n_s_AttributeError''__pyx_kp_u_Congratulations_You_got_the_flag''__pyx_n_u_D''__pyx_n_u_Dd''__pyx_n_s_EqdU3uQNCi''__pyx_kp_u_Error_Car_is_in_wall_accidentall''__pyx_kp_u_Error_Input_is_empty''__pyx_kp_u_Error_Multiple_cars_in_same_cell''__pyx_kp_u_Failed_please_try_again''__pyx_kp_u_Invalid_ELSE_statement''__pyx_kp_u_Invalid_THEN_keyword''__pyx_kp_u_Invalid_THEN_statement''__pyx_kp_u_Invalid_assignment_value''__pyx_kp_u_Invalid_condition''__pyx_kp_u_Invalid_function''__pyx_kp_u_Invalid_function_name''__pyx_kp_u_Invalid_operator''__pyx_kp_u_Invalid_value_for_condition''__pyx_kp_u_Invalid_value_for_operator''__pyx_kp_u_Invalid_value_for_signal''__pyx_kp_u_IyMgIyMgIyMgIyMgIyMgIyMgIyMKIyMg''__pyx_n_s_JfH9kFlbcd''__pyx_n_u_L''__pyx_kp_u_LRUDNlrudn''__pyx_n_u_Ll''__pyx_n_u_Nn''__pyx_n_u_None''__pyx_kp_u_Please_input_the_flag''__pyx_n_s_Q2Fy''__pyx_n_s_Q2Fy___init''__pyx_n_s_Q2Fy___repr''__pyx_n_s_Q2VsbA''__pyx_n_s_Q2VsbA___init''__pyx_n_s_Q2VsbA___repr''__pyx_n_u_R''__pyx_n_u_Rr''__pyx_n_s_SvL6VEBRwx''__pyx_n_s_TWF6ZUxhbmc''__pyx_n_s_TWF6ZUxhbmc_YWRkX2NlbGw''__pyx_n_s_TWF6ZUxhbmc_YWRkX2Z1bmN0aW9u''__pyx_n_s_TWF6ZUxhbmc_YWRkX2Z1bmN0aW9u_loc''__pyx_n_s_TWF6ZUxhbmc_Z2V0X2NlbGw''__pyx_n_s_TWF6ZUxhbmc_Z2V0X2NlbGw_locals_l''__pyx_n_s_TWF6ZUxhbmc_Z2V0X3Bvcw''__pyx_n_s_TWF6ZUxhbmc___init''__pyx_n_s_TWF6ZUxhbmc_aW5pdA''__pyx_n_s_TWF6ZUxhbmc_b3Bw''__pyx_n_s_TWF6ZUxhbmc_c3RlcA''__pyx_n_s_TWF6ZUxhbmc_c3RlcA_locals_genexp''__pyx_n_s_TWF6ZUxhbmc_cnVuX3RpbGxfb3V0cHV0''__pyx_n_s_TypeError''__pyx_n_u_U''__pyx_n_s_UJ9mxXxeoS''__pyx_n_u_Uu''__pyx_n_s_ValueError''__pyx_kp_u_Welcome_to_the_world_of_Maze''__pyx_n_s_YWRkX2NlbGw''__pyx_n_s_YWRkX2Z1bmN0aW9u''__pyx_n_s_Z2V0X2NlbGw''__pyx_n_s_Z2V0X3Bvcw''__pyx_kp_u__10''__pyx_kp_u__11''__pyx_kp_u__12''__pyx_kp_u__13''__pyx_kp_u__14''__pyx_kp_u__15''__pyx_kp_u__16''__pyx_kp_u__17''__pyx_kp_u__18''__pyx_kp_u__19''__pyx_kp_u__2''__pyx_kp_u__20''__pyx_kp_u__25''__pyx_kp_u__3''__pyx_kp_u__30''__pyx_kp_u__31''__pyx_kp_u__32''__pyx_kp_u__33''__pyx_kp_u__34''__pyx_kp_u__35''__pyx_kp_u__36''__pyx_kp_u__37''__pyx_kp_u__38''__pyx_kp_u__4''__pyx_n_s__5''__pyx_kp_u__5''__pyx_n_s__69''__pyx_kp_u__7''__pyx_kp_u__8''__pyx_kp_u__9''__pyx_n_s_aW5pdA''__pyx_n_s_aW5pdF9zZWNyZXQ''__pyx_n_s_append''__pyx_n_s_args''__pyx_n_s_assign''__pyx_n_s_asyncio_coroutines''__pyx_n_s_b3Bw''__pyx_n_s_b64decode''__pyx_n_s_base64''__pyx_n_s_bxKGKlj99G''__pyx_n_s_c29sdmU''__pyx_n_s_c2VjcmV0''__pyx_n_s_c3RlcA''__pyx_n_s_car''__pyx_n_s_car_c''__pyx_n_s_car_n''__pyx_n_s_cars''__pyx_n_s_cell''__pyx_n_s_cell_line''__pyx_n_s_cells''__pyx_n_s_class_getitem''__pyx_n_s_cline_in_traceback''__pyx_n_s_close''__pyx_n_s_cnVuX3RpbGxfb3V0cHV0''__pyx_n_s_code''__pyx_n_s_collections''__pyx_n_s_condition''__pyx_n_s_copy''__pyx_n_s_d''__pyx_n_s_decode''__pyx_n_s_deepcopy''__pyx_n_s_deque''__pyx_n_s_dict''__pyx_n_s_direction''__pyx_n_u_direction''__pyx_n_s_directions''__pyx_kp_u_disable''__pyx_n_s_doc''__pyx_kp_u_else''__pyx_n_s_else_2''__pyx_kp_u_enable''__pyx_n_s_end''__pyx_n_s_enumerate''__pyx_n_s_exit''__pyx_n_s_function''__pyx_n_u_function''__pyx_n_s_functions''__pyx_kp_u_gc''__pyx_n_s_genexpr''__pyx_n_s_group''__pyx_n_u_hole''__pyx_n_s_i''__pyx_n_u_if''__pyx_n_s_import''__pyx_n_u_in''__pyx_n_s_init''__pyx_n_s_init_subclass''__pyx_n_s_initializing''__pyx_n_s_input''__pyx_n_s_int''__pyx_n_s_int_match''__pyx_n_s_is_coroutine''__pyx_kp_u_isenabled''__pyx_n_s_items''__pyx_n_s_k''__pyx_n_s_key''__pyx_n_s_line''__pyx_n_s_lower''__pyx_n_s_main''__pyx_n_s_match''__pyx_n_s_matches''__pyx_n_s_maze''__pyx_kp_s_maze_py''__pyx_n_s_metaclass''__pyx_n_s_min''__pyx_n_s_module''__pyx_kp_u_n''__pyx_n_s_name''__pyx_n_s_name_2''__pyx_n_s_number''__pyx_kp_u_one_use''__pyx_n_s_operator''__pyx_n_u_out''__pyx_n_s_output''__pyx_n_u_path''__pyx_n_s_pattern''__pyx_n_s_pause''__pyx_n_u_pause''__pyx_n_s_popleft''__pyx_n_s_pos''__pyx_n_s_prepare''__pyx_n_s_print''__pyx_n_s_qualname''__pyx_n_s_quotes''__pyx_n_s_range''__pyx_n_s_re''__pyx_n_s_regexes''__pyx_n_s_remove''__pyx_n_s_removed''__pyx_n_s_repr''__pyx_n_s_result''__pyx_n_s_return''__pyx_n_s_row''__pyx_n_s_run''__pyx_n_s_search''__pyx_n_s_self''__pyx_n_s_send''__pyx_n_s_set_name''__pyx_n_s_signal''__pyx_n_u_signal''__pyx_n_s_signals''__pyx_n_s_spec''__pyx_n_s_split''__pyx_n_s_splitlines''__pyx_n_u_splitter''__pyx_n_s_start''__pyx_n_u_start''__pyx_n_s_startswith''__pyx_n_s_str_match''__pyx_n_s_string''__pyx_n_s_super''__pyx_n_s_sys''__pyx_n_s_test''__pyx_kp_u_then''__pyx_n_s_then_2''__pyx_n_s_then_keywd''__pyx_n_s_throw''__pyx_n_s_time''__pyx_n_s_upper''__pyx_n_s_value''__pyx_n_u_wall''__pyx_n_s_x''__pyx_n_s_y''__pyx_int_0''__pyx_int_1''__pyx_int_2''__pyx_int_3''__pyx_int_4''__pyx_int_5''__pyx_int_6''__pyx_int_7''__pyx_int_8''__pyx_int_9''__pyx_int_10''__pyx_int_11''__pyx_int_12''__pyx_int_13''__pyx_int_14''__pyx_int_15''__pyx_int_16''__pyx_int_17''__pyx_int_18''__pyx_int_19''__pyx_int_20''__pyx_int_21''__pyx_int_22''__pyx_int_23''__pyx_int_24''__pyx_int_25''__pyx_int_26''__pyx_int_27''__pyx_int_28''__pyx_int_29''__pyx_int_30''__pyx_int_31''__pyx_int_32''__pyx_int_36''__pyx_int_37''__pyx_int_38''__pyx_int_39''__pyx_int_40''__pyx_int_47''__pyx_int_49''__pyx_int_60''__pyx_int_61''__pyx_int_62''__pyx_int_63''__pyx_int_102''__pyx_int_112''__pyx_int_neg_1''__pyx_slice__6''__pyx_tuple__21''__pyx_tuple__22''__pyx_tuple__23''__pyx_tuple__24''__pyx_tuple__26''__pyx_tuple__27''__pyx_tuple__28''__pyx_tuple__29''__pyx_tuple__39''__pyx_tuple__41''__pyx_tuple__43''__pyx_tuple__46''__pyx_tuple__48''__pyx_tuple__50''__pyx_tuple__52''__pyx_tuple__54''__pyx_tuple__56''__pyx_tuple__58''__pyx_tuple__60''__pyx_tuple__63''__pyx_tuple__65''__pyx_tuple__67''__pyx_codeobj__40''__pyx_codeobj__42''__pyx_codeobj__44''__pyx_codeobj__45''__pyx_codeobj__47''__pyx_codeobj__49''__pyx_codeobj__51''__pyx_codeobj__53''__pyx_codeobj__55''__pyx_codeobj__57''__pyx_codeobj__59''__pyx_codeobj__61''__pyx_codeobj__62''__pyx_codeobj__64''__pyx_codeobj__66''__pyx_codeobj__68']

base = 0x44960
for i in range(len(ns)):
    create_qword(base+i*8)
    SetType(base+i*8, ys[i])
    set_name(base+i*8, ns[i], SN_CHECK)

然后返回伪代码界面刷新一下就能看到变量名的更改,并且能顺利交叉引用:

从TPCTF 2023 学习Python逆向

定义在 _pyx_pymod_exec_maze 中,可以看到是这个字符串:

从TPCTF 2023 学习Python逆向

如果 kp 变量没有被命名,也可以继续交叉引用确认一下:

从TPCTF 2023 学习Python逆向

所以 PyObject_Call 这个函数的调用说人话就是:

print("Welcome to the world of Maze!")

以此类推可以逐步逆向出这个函数主要就是输出、输入、然后把输入的字符串传入用以检查flag的函数 c29sdmU 中。

这个题目的出题人很好心的把变量名通过 base64 的方式编码(而不是随机的),所以可以通过解码来猜测验证函数功能。比如 c29sdmU 解 base64 是“solve”的意思。

从TPCTF 2023 学习Python逆向

c29sdmU 这个函数也是相同的 Cython 逆向分析方法,逐步逆向可得其主要逻辑大概是:

def c29sdmU(SvL6VEBRwx):
    MazeLang = TWF6ZUxhbmc(base64.b64decode(UJ9mxXxeoS).decode()) # 自己起的变量名,如确实需追溯则应该看函数定义时的tuple
    if len(SvL6VEBRwx) != 33:
        return 1
    aW5pdF9zZWNyZXQ()
    for i in range(33):
        if SvL6VEBRwx[i] ^ MazeLang.cnVuX3RpbGxfb3V0cHV0() != c2VjcmV0[i]:
            return 1
    return 0

SvL6VEBRwx 是我们的输入,TWF6ZUxhbmc 是一个 MazeLang 的解释器的实现(http://esolangs.org/wiki/Maze,一种图灵完备语言),UJ9mxXxeoS 和 c2VjcmV0 都是全局变量。

aW5pdF9zZWNyZXQ 函数比较简单,通过逆向可以还原出:

def aW5pdF9zZWNyZXQ():
    for i in range(33):
        c2VjcmV0[EqdU3uQNCi[i]], c2VjcmV0[i] = c2VjcmV0[i], c2VjcmV0[EqdU3uQNCi[i]]

EqdU3uQNCi 也是一个全局变量,这里是做了一个全局变量的修改。

MazeLang 里的 cnVuX3RpbGxfb3V0cHV0 函数,函数名 base64 解码后是 run_till_output,结合这个使用方式应该是一个密钥流,这里猜测直接是用 MazeLang 的输出做密钥流,所以把 UJ9mxXxeoS 解码看看是不是一个 MazeLang 程序。追溯可知 UJ9mxXxeoS 是这个字符串:

从TPCTF 2023 学习Python逆向

解码可以发现确实是一个 Maze

从TPCTF 2023 学习Python逆向

用 MazeLang 的解释器(https://github.com/olls/maze-interpreter-v2)跑一下能发现一直输出 “HITPCTFHITPCTFHI…”。

c2VjcmV0 和 EqdU3uQNCi 两个全局变量直接用 Python 3.8 导入这个 maze.so,然后直接调用就能拿到:

从TPCTF 2023 学习Python逆向

于是就能写exp:

EqdU3uQNCi = [18171502731101914212522633082457413299261228162032122311]
c2VjcmV0 = [747602839112354949261163492256136112251562253161023814737440]

def aW5pdF9zZWNyZXQ():
    for i in range(33):
        c2VjcmV0[EqdU3uQNCi[i]], c2VjcmV0[i] = c2VjcmV0[i], c2VjcmV0[EqdU3uQNCi[i]]

key = b'HITPCTF'
flag = []
aW5pdF9zZWNyZXQ()
for i in range(33):
    flag.append(key[i%7] ^ c2VjcmV0[i])
print(bytes(flag))

TPCTF{yOu_@re_m@sT3r_OF_mAZElaN6}

从TPCTF 2023 学习Python逆向

原文始发于微信公众号(山石网科安全技术研究院):从TPCTF 2023 学习Python逆向

版权声明:admin 发表于 2023年12月4日 下午12:05。
转载请注明:从TPCTF 2023 学习Python逆向 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...