社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

从TPCTF 2023 学习Python逆向

山石网科安全技术研究院 • 1 年前 • 228 次点击  
TPCTF 2023 由清华大学 Redbud 战队与北京大学 pkucc 战队联合命题。周末没打到,赛后看了下 Reverse 方向有两个 Python +二进制的逆向题,发现居然不是纯套路题,出题人还是很用心地准备了一些新颖的知识点,故记录于此。

1

nanoPyEnc

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

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

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

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

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

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

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

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

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

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

from Crypto.Cipher import AES

enc = [153240237 19963442374525479715415811246176219247441151691246463121253250137341443317182916247249411651148723122224212630124237]
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 只有几行:

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

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

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

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

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

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

用 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)

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

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

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

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

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

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

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

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 是这个字符串:

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

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

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

于是就能写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}

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/165203
 
228 次点击