https://tenthousandmeters.com/blog/python-behind-the-scenes-8-how-python-integers-work/
https://www.cnblogs.com/lordtianqiyi/p/18614184
https://xz.aliyun.com/t/16155?time__1311=GuD%3D7Iqmxfhx%2FD0lD2DUgDj2RjGCK%2Bj3WfeD
这次国赛出了2个cython,发现我都不会,太菜了
赛完学习一波
Basic
Cython = 一个可以将 Python 转化为 C 的编译器,也是一种语言(Python 的超集)
Python = 一种众所周知的编程语言,可以让你快速工作并更有效地集成系统。
CPython = Python 解释器的源码,并给用户提供 Python-C API 支持以编写 C 扩展
python 源文件(.py) 经过解释器 得到(.pyc)字节码文件,而字节码 经过 python的vm运行 得到的pyd文件是运行在机器的机器码文件
PY_LONG的解析
在逆向pyd的时候,由于python自行实现了一套大数运算系统,所以在Number的解析中,只看内存很难找到对应具体数值(当时卡了一天…)
这里直接去看cython源码
/* =========> python12 PyLongObject structure <========
#define _PyLong_NON_SIZE_BITS 3
The number of digits (ndigits) is stored in the high bits of
the lv_tag field (lvtag >> _PyLong_NON_SIZE_BITS).
The sign of the value is stored in the lower 2 bits of lv_tag.
- 0: Positive
- 1: Zero
- 2: Negative
The third lowest bit of lv_tag is reserved for an immortality flag, but is
not currently used.
struct _object {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
};
typedef struct _PyLongValue {
uintptr_t lv_tag; //Number of digits, sign and flags //
digit ob_digit[1];
} _PyLongValue;
typedef struct _object PyObject;
#define PyObject_HEAD PyObject ob_base;
struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};
typedef struct _longobject PyLongObject;
*/
Python behind the scenes #8: how Python integers work
这里的每个python_number都是30bits的
(val&0xffffffff) |(val>>32)<<30 才能正确解析出数值
cython逆向基础
系统讲解pyd逆向/Cython逆向_pyd的逆向-CSDN博客
读明白了静态分析应该没啥问题了
常见的题型
- 源代码经过混淆得到的py逆向,or只提供字节码的文本
- .pyc字节码逆向 #pyc反编译 (以及常见的花指令和魔改)
- pyinstaller打包 #解包 pyc反编译
- cython编译的拓展(.pyc/so) # 难度最大 手撕+动态
方法总结
注入类
可以列出所有的导入库
import pefile
def import_check(pyd_file):
pe = pefile.PE(pyd_file)
for entry in pe.DIRECTORY_ENTRY_IMPORT:
print(f"Module: {entry.dll.decode('utf-8')}")
for imp in entry.imports:
print(f" Function: {imp.name.decode('utf-8') if imp.name else None}")
import_check('cy.cp38-win_amd64.pyd')
根据涉及的运算可以大概猜测一下加密类型
比如只有xor,shift,可以大致猜测是一个tea家族
如果有类的话我们可以注入类
例如2024SCTF的 ez_cython
import cy
class Symbol:
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
def __rshift__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} >> {other.name})")
else:
expression = Symbol(f"({self.name} >> {other})")
return expression
def __lshift__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} << {other.name})")
else:
expression = Symbol(f"({self.name} << {other})")
return expression
def __rxor__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} ^ {other.name})")
else:
expression = Symbol(f"({self.name} ^ {other})")
return expression
def __xor__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} ^ {other.name})")
else:
expression = Symbol(f"({self.name} ^ {other})")
return expression
def __add__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} + {other.name})")
else:
expression = Symbol(f"({self.name} + {other})")
return expression
def __and__(self, other):
if isinstance(other, Symbol):
expression = Symbol(f"({self.name} & {other.name})")
else:
expression = Symbol(f"({self.name} & {other})")
return expression
class AList:
def __init__(self, nums):
self.nums = [Symbol(str(num)) for num in nums]
def __getitem__(self, key):
return self.nums[key]
def copy(self):
return AList(self.nums)
def __len__(self):
return len(self.nums)
def __setitem__(self, key, value):
print(f"new_{self.nums[key]} = {value}")
self.nums[key] = Symbol(f"new_{self.nums[key].name}")
def __eq__(self, other):
print(f"{self.nums} == {other}")
return self.nums == other
inp = AList([f"a[{i}]" for i in range(32)])
res = cy.sub14514(inp)
if __name__ == '__main__':
print(res)
有一点局限性,就是在运算过程中不会出现一些类型转换之类的,否则还是无法hook
手写一个pyd(恢复符号)
linux编译一份相同python版本的so文件,ida载入,File->Produce File->Create C Header File导出结构体
加载需要逆向的pyd,File->Load File->Parse C Header File,导入so文件导出xxx.h(有错误就修复),导入so文件导出的xxx.h是因为错误比较少,windows带调试符号导出的header错误较多比较难修复
手动编译一个相同版本的pyd文件,可以使用bindiff恢复一下符号,静态分析更容易一点
def check(flag):
a = 1 * 1
b = 2 * 2
c = 2 ^ 3
a = [0] * 6
b = 0
while b:
print("11")
b -= 1
rand0m(flag)
return a + b + c + flag
def myxor(a, b):
return a ^ b
def rand0m(x):
return x ^ 123
__test__ = {}
from setuptools import setup, Extension
from Cython.Build import cythonize
ext_module = [
Extension(
name = "test",
sources=["test.py"],
extra_compile_args=["/Zi"],
extra_link_args=["/DEBUG"]
)
]
setup(
name="test",
ext_modules=cythonize(ext_module, annotate=True),
)
python build.py build_ext --inplace
这种方法生成的pyd文件在windows下默认是无符号的,在linux下默认是有符号的
不过我们可以指定生成有符号的 pyd文件:
python setup.py build_ext --inplace --debug
这里需要在安装python3.12版本时勾选download debug binaries
bindiff后找到initstring函数
typedef struct {
__int64 *__pyx_0;
__int64 *__pyx_1;
__int64 *__pyx_2;
__int64 *__pyx_3;
__int64 *__pyx_4;
__int64 *__pyx_5;
__int64 *__pyx_6;
__int64 *__pyx_7;
__int64 *__pyx_8;
__int64 *__pyx_9;
__int64 *__pyx_10;
__int64 *__pyx_11;
__int64 *__pyx_12;
__int64 *__pyx_13;
__int64 *__pyx_14;
__int64 *__pyx_15;
__int64 *__pyx_16;
__int64 *__pyx_17;
__int64 *__pyx_18;
__int64 *__pyx_19;
__int64 *__pyx_20;
__int64 *__pyx_21;
__int64 *__pyx_22;
__int64 *__pyx_23;
__int64 *__pyx_24;
__int64 *__pyx_25;
__int64 *__pyx_26;
__int64 *__pyx_27;
__int64 *__pyx_28;
__int64 *__pyx_29;
__int64 *__pyx_30;
__int64 *__pyx_31;
__int64 *__pyx_32;
__int64 *__pyx_33;
__int64 *__pyx_34;
__int64 *__pyx_35;
__int64 *__pyx_36;
__int64 *__pyx_37;
__int64 *__pyx_38;
__int64 *__pyx_39;
} __pyx_mstate_me;
然后找到initstring函数对应的名称填充指针名称
然后静态分析可以大大提高效率
动态trace
可以利用条件断点对pyd的运算进行插桩,然后分析加密过程
这里对于Lshift、Rshift、And等逻辑操作来说,是hook不到的,由于优化的缘故不走 Py提供的库函数,直接在pyd中自己实现了
当然也可以一条一条的下断点打log,去trace,体力活
这里我就直接动调直接看了
2025ciscn-rand0m
idapro的trace
下条件断点trace 基本运算
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val2|val1)}={hex(val1)}|{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val2&val1)}={hex(val1)}&{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val2%val1)}={hex(val1)}%{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1)}?={hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val2^val1)}={hex(val1)}^{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val>>val2)}={hex(val1)}>>{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val<<val2)}={hex(val1)}<<{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1-val2)}={hex(val1)}-{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1+val2)}={hex(val1)}+{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1*val2)}={hex(val1)}*{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1)}**{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)>=16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"div={hex(val1)}%{hex(val2)}")
import idc
def calVal(val,size):
if ord(size)==8:
val=int.from_bytes(val,byteorder='little')
result=val
elif ord(size)==16:
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,ord(arg2_size)//2)
arg1_val=idc.get_bytes(arg1_addr+0x18,ord(arg1_size)//2)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1)}?={hex(val2)}")
rand0m.check('11223344aabbccddeeffeeffaaccddee')
0x283af96e130?=0x283af96e130
0x8f154afd=0x11223344^0x9e3779b9
0x12223440=0x112233440&0xfa3affff
0x12223441=0x12223440+0x1
0x11e2a9**0x10001
0x13c501cc=0xef90a60e6d9e2a9%0xfffffffd // 这里是不准确的,因为数字太大了,有0xd0b7位,所以只写了部分数字,猜测是pow之后的结果 然后mod 0xfffffffd
0x12223441?=0x12287f38
0x348cb564=0xaabbccdd^0x9e3779b9
0xaa38cdd0=0xaabbccdd0&0xfa3affff
0xaa38cdda=0xaa38cdd0+0xa
0x69196**0x10001
0x0=0x0%0xfffffffd
0xaa38cdda?=0x4a30f74d
0x43fbc213=0xddccbbaa^0x9e3779b9
0xd80abaa0=0xddccbbaa0&0xfa3affff
0xd80abaad=0xd80abaa0+0xd
0x87f78**0x10001
0x0=0x0%0xfffffffd
0xd80abaad?=0x23a1268
0xda045ba8=0x44332211^0x9e3779b9
0x42322110=0x443322110&0xfa3affff
0x42322114=0x42322110+0x4
0x1b408b**0x10001
0xc5e0a74e=0xccb22419f7f408b%0xfffffffd
0x42322114?=0x88108807
结合静态分析,每一轮有两次cmp,如何第一次cmp失败,第二次就不比较了,所以我们都下断点到richcmpare,将每次返回结果改为trueobject,然后获得新的tracelog
0x252bbc2e130?=0x252bbc2e130
0x8f154afd=0x11223344^0x9e3779b9
0x12223440=0x112233440&0xfa3affff
0x12223441=0x12223440+0x1
0x11e2a9**0x10001
div=0xa019a7204a7965087e77970426e62484218bd070e82b679cdc532edc68dfb2ace88e676cef90a60e6d9e2a9%0xfffffffd
0x12223441?=0x12287f38
0x27fe60b7?=0x98d24b3a
0x348cb564=0xaabbccdd^0x9e3779b9
0xaa38cdd0=0xaabbccdd0&0xfa3affff
0xaa38cdda=0xaa38cdd0+0xa
0x69196**0x10001
div=0x0%0xfffffffd
0xaa38cdda?=0x4a30f74d
0x2b356987?=0xe0f1db77
0x70c89746=0xeeffeeff^0x9e3779b9
0xea3aeff0=0xeeffeeff0&0xfa3affff
0xeeffeeffe=0xeeffeeff0+0xe
0xe1912**0x10001
div=0x0%0xfffffffd
0xea3aeffe?=0x23a1268
0x81ac0cf0?=0xadf38403
0x34fba457=0xaaccddee^0x9e3779b9
0xa808dee0=0xaaccddee0&0xfa3affff
0xaaccddeea=0xaaccddee0+0xa
0x69f74**0x10001
div=0x0%0xfffffffd
0xa808deea?=0x88108807
0x61ff12d1?=0xd8499bb6
有pow 0x10001 以及 mod 0xfffffffd 我们可以猜测是一个rsa加密
由于pow的运算的第一个参数 位数太大了,勾的值并不是真实的value,有0xd087位,这里我们猜一下就知道是什么
我们盲猜第一个参数就是0x11e2a9**0x10001的结果
这里hex
恰好就是第二个cmp比较的结果
可以验证我们的猜想正确
现在我们不明白0x11e2a9 到底是不是与我们的输入有关,可以多输入几轮测试一下
0x252bbc2e130?=0x252bbc2e130
0x8f2668a8=0x11111111^0x9e3779b9
0x10101110=0x111111110&0xfa3affff
0x11e4cd**0x10001
div=0x6a21c24449c1db9452f195dcc87cf7a4c6173dfc07e6982cc02d8080186cd284c5ba2c3caf39ee88b7ad161d6c5e4cd%0xfffffffd
0x10101111?=0x12287f38
0x8f2668a8=0x11111111^0x9e3779b9
0x10101110=0x111111110&0xfa3affff
0x11e4cd**0x10001
div=0x6a21c24449c1db9452f195dcc87cf7a4c6173dfc07e6982cc02d8080186cd284c5ba2c3caf39ee88b7ad161d6c5e4cd%0xfffffffd
0x10101111?=0x4a30f74d
0x8f2668a8=0x11111111^0x9e3779b9
0x10101110=0x111111110&0xfa3affff
0x11e4cd**0x10001
div=0x6a21c24449c1db9452f195dcc87cf7a4c6173dfc07e6982cc02d8080186cd284c5ba2c3caf39ee88b7ad161d6c5e4cd%0xfffffffd
0x10101111?=0x23a1268
0x8f2668a8=0x11111111^0x9e3779b9
0x10101110=0x111111110&0xfa3affff
0x11e4cd**0x10001
div=0x6a21c24449c1db9452f195dcc87cf7a4c6173dfc07e6982cc02d8080186cd284c5ba2c3caf39ee88b7ad161d6c5e4cd%0xfffffffd
0x10101111?=0x88108807
这里我看不出来到底是怎么与input对应的,只能去静态分析一下
参数a3为11
定位到这里,发现右移了11位
验证猜想是正确的这里我们可以发现实际上一组密文进行了2组处理
pow(((k^0x9e3779b9)>>11),0x10001,0xfffffffd) ->即将一组密文进行rsa加密
((k<<4)&0xfa3affff)|k>>28 0x7ff0
这里的两组加密是会有信息缺失的,我们可以通过这两组加密完整恢复出明文
第一组,我们可以恢复出高22位,第二组恢复出低12位即可
trace已经dump出了每组密文
from Crypto.Util.number import *
cipher=[(0x98d24b3a,0x12287f38),(0xe0f1db77,0x4a30f74d),(0xadf38403,0x23a1268),(0xd8499bb6,0x88108807)]
def dersa(cipher):
p=9241
q=464773
e=0x10001
phi=(p-1)*(q-1)
d=inverse(e,phi)
return pow(cipher,d,p*q)
for a,b in cipher:
k1=dersa(a)
k1=k1<<11
k1^=0x9e3779b9
k2=b
k2&=0x7ff0
k2>>4
k=k1|k2
print(hex(k))
#0x813affb9
#0xd4b37ff9
#0x802bb3f9
#0x789509b9
xdbg
xdbg条件断点
PyNumber_And
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) & ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} & 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Add
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) + ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} + 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Xor
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) ^ ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ^ 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_LShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) << ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} << 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_RShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) >> ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} >> 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Reminder
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) % ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} % 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Power
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ** 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyObject_RichCompare
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ?= 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
command:run
2025ciscn-cython
pyins 解包
pycdc无法反编译,用pycdas
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: 0
4 LOAD_CONST 1: None
6 IMPORT_NAME 0: ez
8 STORE_NAME 0: ez
10 PUSH_NULL
12 LOAD_NAME 1: input
14 LOAD_CONST 2: '璇疯緭鍏lag:'
16 PRECALL 1
20 CALL 1
30 STORE_NAME 2: flag
32 PUSH_NULL
34 LOAD_NAME 3: list
36 LOAD_NAME 2: flag
38 PRECALL 1
42 CALL 1
52 STORE_NAME 4: flag1
54 BUILD_LIST 0
56 STORE_NAME 5: value
58 LOAD_CONST 0: 0
60 STORE_NAME 6: b
62 LOAD_CONST 0: 0
64 STORE_NAME 7: ck
66 PUSH_NULL
68 LOAD_NAME 8: len
70 LOAD_NAME 4: flag1
72 PRECALL 1
76 CALL 1
86 LOAD_CONST 3: 24
88 COMPARE_OP 2 (==)
94 POP_JUMP_FORWARD_IF_FALSE 259 (to 616)
98 PUSH_NULL
100 LOAD_NAME 9: range
102 LOAD_CONST 0: 0
104 PUSH_NULL
106 LOAD_NAME 8: len
108 LOAD_NAME 4: flag1
110 PRECALL 1
114 CALL 1
124 LOAD_CONST 4: 4
126 PRECALL 3
130 CALL 3
140 GET_ITER
142 FOR_ITER 112 (to 368)
144 STORE_NAME 10: i
146 PUSH_NULL
148 LOAD_NAME 11: ord
150 LOAD_NAME 4: flag1
152 LOAD_NAME 10: i
154 BINARY_SUBSCR
164 PRECALL 1
168 CALL 1
178 LOAD_CONST 3: 24
180 BINARY_OP 3 (<<)
184 PUSH_NULL
186 LOAD_NAME 11: ord
188 LOAD_NAME 4: flag1
190 LOAD_NAME 10: i
192 LOAD_CONST 5: 1
194 BINARY_OP 0 (+)
198 BINARY_SUBSCR
208 PRECALL 1
212 CALL 1
222 LOAD_CONST 6: 16
224 BINARY_OP 3 (<<)
228 BINARY_OP 7 (|)
232 PUSH_NULL
234 LOAD_NAME 11: ord
236 LOAD_NAME 4: flag1
238 LOAD_NAME 10: i
240 LOAD_CONST 7: 2
242 BINARY_OP 0 (+)
246 BINARY_SUBSCR
256 PRECALL 1
260 CALL 1
270 LOAD_CONST 8: 8
272 BINARY_OP 3 (<<)
276 BINARY_OP 7 (|)
280 PUSH_NULL
282 LOAD_NAME 11: ord
284 LOAD_NAME 4: flag1
286 LOAD_NAME 10: i
288 LOAD_CONST 9: 3
290 BINARY_OP 0 (+)
294 BINARY_SUBSCR
304 PRECALL 1
308 CALL 1
318 BINARY_OP 7 (|)
322 STORE_NAME 6: b
324 LOAD_NAME 5: value
326 LOAD_METHOD 12: append
348 LOAD_NAME 6: b
350 PRECALL 1
354 CALL 1
364 POP_TOP
366 JUMP_BACKWARD 113 (to 142)
368 BUILD_LIST 0
370 LOAD_CONST 10: (102, 108, 97, 103)
372 LIST_EXTEND 1
374 STORE_NAME 13: key
376 BUILD_LIST 0
378 STORE_NAME 14: flag_encrypt
380 PUSH_NULL
382 LOAD_NAME 9: range
384 LOAD_CONST 0: 0
386 LOAD_CONST 11: 6
388 LOAD_CONST 7: 2
390 PRECALL 3
394 CALL 3
404 GET_ITER
406 FOR_ITER 56 (to 520)
408 STORE_NAME 10: i
410 PUSH_NULL
412 LOAD_NAME 0: ez
414 LOAD_ATTR 15: encrypt
424 LOAD_NAME 5: value
426 LOAD_NAME 10: i
428 BINARY_SUBSCR
438 LOAD_NAME 5: value
440 LOAD_NAME 10: i
442 LOAD_CONST 5: 1
444 BINARY_OP 0 (+)
448 BINARY_SUBSCR
458 LOAD_NAME 13: key
460 PRECALL 3
464 CALL 3
474 STORE_NAME 16: res
476 LOAD_NAME 14: flag_encrypt
478 LOAD_METHOD 12: append
500 LOAD_NAME 16: res
502 PRECALL 1
506 CALL 1
516 POP_TOP
518 JUMP_BACKWARD 57 (to 406)
520 PUSH_NULL
522 LOAD_NAME 0: ez
524 LOAD_ATTR 17: check
534 LOAD_NAME 14: flag_encrypt
536 PRECALL 1
540 CALL 1
550 STORE_NAME 7: ck
552 LOAD_NAME 7: ck
554 LOAD_CONST 9: 3
556 COMPARE_OP 2 (==)
562 POP_JUMP_FORWARD_IF_FALSE 13 (to 590)
564 PUSH_NULL
566 LOAD_NAME 18: print
568 LOAD_CONST 12: 'yes!!!,you get right flag'
570 PRECALL 1
574 CALL 1
584 POP_TOP
586 LOAD_CONST 1: None
588 RETURN_VALUE
590 PUSH_NULL
592 LOAD_NAME 18: print
594 LOAD_CONST 13: 'wrong!!!'
596 PRECALL 1
600 CALL 1
610 POP_TOP
612 LOAD_CONST 1: None
614 RETURN_VALUE
616 LOAD_CONST 1: None
618 RETURN_VALUE
用chatgpt翻译一下
import ez
flag = "aaaabbbbccccddddeeeeffff" # Input message (possibly in a different encoding)
flag1 = list(flag) # Convert flag to a list of characters
value = []
b = 0
ck = 0
# Check if length of input flag is 24
if len(flag1) == 24:
for i in range(0, len(flag1), 4):
b = (
(ord(flag1[i]) << 24) |
(ord(flag1[i + 1]) << 16) |
(ord(flag1[i + 2]) << 8) |
ord(flag1[i + 3])
)
value.append(b)
key = [102, 108, 97, 103] # ASCII values of 'f', 'l', 'a', 'g'
flag_encrypt = []
for i in range(0, 6, 2):
res = ez.encrypt(value[i], value[i + 1], key)
flag_encrypt.append(res)
ck = ez.check(flag_encrypt)
if ck == 3:
print('yes!!!,you get right flag')
else:
print('wrong!!!')
else:
print('wrong!!!')
ez的help
Help on module ez:
NAME
ez
FUNCTIONS
FormatError(...)
FormatError([integer]) -> string
Convert a win32 error code into a string. If the error code is not
given, the return value of a call to GetLastError() is used.
POINTER(...)
addressof(...)
addressof(C instance) -> integer
Return the address of the C instance internal buffer
alignment(...)
alignment(C type) -> integer
alignment(C instance) -> integer
Return the alignment requirements of a C instance
byref(...)
byref(C instance[, offset=0]) -> byref-object
Return a pointer lookalike to a C instance, only usable
as function argument
check(flag_encrypt)
encrypt(V0, V1, key)
get_errno(...)
get_last_error(...)
pointer(...)
resize(...)
Resize the memory buffer of a ctypes instance
set_errno(...)
set_last_error(...)
sizeof(...)
sizeof(C type) -> integer
sizeof(C instance) -> integer
Return the size in bytes of a C instance
DATA
DEFAULT_MODE = 0
GetLastError = <_FuncPtr object>
RTLD_GLOBAL = 0
RTLD_LOCAL = 0
__test__ = {}
cdll = <ctypes.LibraryLoader object>
data = [(3572312064, 2468433000), (3725770449, 3734689877), (338735471...
memmove = <CFunctionType object>
memset = <CFunctionType object>
oledll = <ctypes.LibraryLoader object>
pydll = <ctypes.LibraryLoader object>
pythonapi = <PyDLL 'python dll', handle 7ffa16710000>
windll = <ctypes.LibraryLoader object>
FILE
c:\users\npc\desktop\ctf\events\2025ciscn\cython\cython.exe_extracted\ez.pyd
这里可以看到data,应该就是密文了,结合源码三组一一对应
[(3572312064, 2468433000), (3725770449, 3734689877), (3387354714, 1042217819)]
部分tracelog
0x4fc00605f3=0x400606f0^0x4f80000303
0x204dc006c6d1=0x1ffe400605f3+0x4f8000c0de
0x80001122=0x0+0x80001122
0x4fc006d7f3=0x4006c6d1^0x4f80001122
0x13ec03dabda=0x13e803db500^0x40001eda
0x1ffe8045627a=0x1ffe403dabda+0x4007b6a0
0xd4647576=0x54646454+0x80001122
0x13ed421170c=0x13e8045627a^0x54647576
0x13c205e380f=0x2a10ebf50^0x13e8150875f
0x2f4800ff9=0x2a05e380f+0x5421d7ea
0xd4647576=0x54646454+0x80001122
0x2a0e47a8f=0x2f4800ff9^0x54647576
0x785e239bc=0x507618978^0x28283b0c4
0x5a6ce6aeb=0x505e239bc+0xa0ec312f
0xa8c8fbec=0xa8c8c8a8+0x3344
0x50e069107=0x5a6ce6aeb^0xa8c8fbec
0x390cbe62b=0x311434788^0x8188a1a3
0x372f44f1c=0x310cbe62b+0x622868f1
0x128c8d9ca=0xa8c8c8a8+0x80001122
0x3da3c96d6=0x372f44f1c^0xa8c8d9ca
0xd8aae308=0x3d9464028^0x301eca320
0x453d3ab0d=0x3d8aae308+0x7b28c805
0xfd2d6040=0xfd2d2cfc+0x3344
0x4aefecb4d=0x453d3ab0d^0xfd2d6040
0x497d3d20=0x8939a1f0^0xc0449cd0
0x15aa4715e=0x897d3d20+0xd127343e
0x17d2d3e1e=0xfd2d2cfc+0x80001122
0x67894f40=0x9aa4715e^0xfd2d3e1e
0x7961a7275=0x71590ba28^0x838ac85d
0x7f8cc89ba=0x7161a7275+0xe2b21745
0x5191e6b6=0x51919150+0x5566
0x7a95d6f0c=0x7f8cc89ba^0x5191e6b6
0x596cf08dd=0x5d4251a50^0x42ea128d
0x69153ac27=0x5d6cf08dd+0xba84a34a
0xd191a272=0x51919150+0x80001122
0x6c0c20e55=0x69153ac27^0x5191a272
0xd92cfc46=0x51ba12cd0^0x5c28dd096
0x5bca121e0=0x5192cfc46+0xa374259a
0xa5f64b0a=0xa5f5f5a4+0x5566
0x519576aea=0x5bca121e0^0xa5f64b0a
0x61daf0198=0x69ee071a0^0x834f7038
0x7718b0fcc=0x69daf0198+0xd3dc0e34
0x125f606c6=0xa5f5f5a4+0x80001122
0x7d47d090a=0x7718b0fcc^0xa5f606c6
0x53e56b19a=0x3bf897520^0x681dfc4ba
0x43647e03e=0x3be56b19a+0x77f12ea4
0xfa5ad180=0xfa5a59f8+0x7788
0x4cc1d31be=0x43647e03e^0xfa5ad180
0x43db61b6f=0x4ffc9ff90^0xc27fe4ff
0x59daf5b61=0x4fdb61b6f+0x9ff93ff2
0x17a5a6b1a=0xfa5a59f8+0x80001122
0x567f5307b=0x59daf5b61^0xfa5a6b1a
0x23c4d6184=0x6ff32f8f8^0x4c37f997c
0x7dc33c0a3=0x6fc4d6184+0xdfe65f1f
0x4ebf35d4=0x4ebebe4c+0x7788
0x7928cf577=0x7dc33c0a3^0x4ebf35d4
0x1d4fbb39d=0x19431ab48^0x40ca18d5
0x20781e906=0x194fbb39d+0x72863569
0xcebecf6e=0x4ebebe4c+0x80001122
0x1893f2668=0x1c781e906^0x4ebecf6e
0x2c888ba2e=0x3492c2c38^0x181a49616
0x3b1ae3fb5=0x34888ba2e+0x69258587
0x1232333c2=0xa32322a0+0x80001122
0x3128d0c77=0x3b1ae3fb5^0xa32333c2
0x2a98e4207=0x2289a0f00^0x81144d07
0x26ea183e7=0x2298e4207+0x451341e0
0x1232333c2=0xa32322a0+0x80001122
0x2cd82b025=0x26ea183e7^0xa32333c2
0x3b59b0db6=0x1b541ad60^0x200daa0d6
0x3ec434362=0x1b59b0db6+0x236a835ac
0x177879816=0xf78786f4+0x80001122
0x11bc4db74=0x1ec434362^0xf7879816
0x3c7438ad5=0x306c0eaa0^0xc1836075
0x3681ba829=0x307438ad5+0x60d81d54
0x177879816=0xf78786f4+0x80001122
0x39f9c303f=0x3681ba829^0xf7879816
0x5b17a3ecf=0x6b2232f58^0x303591197
0x787bea4ba=0x6b17a3ecf+0xd64465eb
0x4bec1e8c=0x4bebeb48+0x3344
这里的shift没有用到pynumber_shift,而是经过了底层优化编程的C语言的运算符,所以trace不出来,我们只能静态分析
这里的数字位数确定不了,写hook脚本很难受
import idc
def calVal(val,size):
val=int.from_bytes(val,byteorder='little')
result=(val&0xffffffff) |(val>>32)<<30
return result
arg2_addr=idc.get_reg_value("rdx")
arg1_addr=idc.get_reg_value("rcx")
arg2_size=idc.get_bytes(arg2_addr+0x10,1)
arg1_size=idc.get_bytes(arg1_addr+0x10,1)
arg2_val=idc.get_bytes(arg2_addr+0x18,8)
arg1_val=idc.get_bytes(arg1_addr+0x18,8)
val2=calVal(arg2_val,arg2_size)
val1=calVal(arg1_val,arg1_size)
print(f"{hex(val1<<val2)}={hex(val1)}<<{hex(val2)}")
直接静态看也不是不可以
首先找到加密函数,第二个参数是输入的第一个数组,第三个参数是输入的第二个数据,第三个应该是列表的地址
进去后,中间有一个大循环
循环前面的一堆都是初始化代码,可以不看
跟踪数据流 结合tracelog最后就能得到最后的等式
最后的exp
#include "stdio.h"
#include "stdint.h"
typedef uint32_t uint32;
typedef uint8_t uint8;
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4])
{
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x54646454, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++)
{
v1 -= (((v0 << 3) ^ (v0 >> 6)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 3) ^ (v1 >> 6)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0;
v[1] = v1;
}
int main()
{
uint32 v[] = {
3572312064, 2468433000, 3725770449, 3734689877, 3387354714, 1042217819
};
uint32 key[] = {
102, 108, 97, 103
};
for (int i = 0; i < 6; i += 2) {
decipher(64, v + i, key);
}
uint8 *ptr = (uint8 *) v;
for (int i = 0; i < 24; i += 4) {
printf("%c%c%c%c", ptr[i + 3], ptr[i + 2], ptr[i + 1], ptr[i]);
}
return 0;
}
#flag{pFmuS0qUf8ic2rYrXO}