-
-
[原创]KCTF2026 第二题 注入dll并利用自省机制 对python进程的堆栈调用解析
-
发表于: 13小时前 337
-


这里防守方对pyinstaller加载做了修改,需要找出被修改模块,
后面对整个解包的文件进行字符串常量扫描,也没有取得有用信息。
使用的dll工具为 PyInjector,仓库地址为c91K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6e0N6r3q4F1K9i4y4D9j5i4k6Q4x3X3c8b7L8%4k6G2L8r3!0@1M7$3E0&6i4K6u0r3f1s2W2u0L8X3A6W2j5%4c8G2M7W2!0q4x3#2)9^5x3q4)9^5x3R3`.`.
该工具的方式为
自省机制(Introspection)是指程序在运行时能够检查自身结构、获取对象类型、属性和方法的能力。
这意味我们可以在运行时时期,对python进程中全局变量、局部变量进行访问。
将上面代码注入python进程,可得到如下效果
上面输出的 回溯第 1 层调用源中,可以观察到一些明显线索 'my_function', 'buffer', 'w', 'i', 'b', 'key', 'b_key', 'c', 'buf', 'my', 'succ'
然后得到如下输出
对抓出的隐藏变量进行两两比对:
如果我们把 buf 和 succ 进行按位异或(XOR)运算,得到的结果是:
buf⊕succ=b’123456123456123’
这证明了程序的解密逻辑:它在内存里把输入密码循环展开,去和某个数据做异或。
那么核心来了:
既然密码正确时程序会打印“恭喜成功!”,而“恭喜成功!”这五个中文字符在 UTF-8 编码下正好也是 15 个字节!
我们将固定的成功提示文本 b'\xe6\x81\xad...'(恭喜成功!)与代表成功结果的内存基准密文 succ 进行直接异或回推:
succ⊕b’\xe6\x81\xad\xe5\x96\x9c\xe6\x88\x90\xe5\x8a\x9f\xef\xbc\x81’=b’yb6tcryb6tcryb6’
这个结果展现出了极其完美且连续的 6 字节循环节:y b 6 t c r。
import sys
def native_print(msg):
sys.stdout.write(str(msg) + "\n")
sys.stdout.flush()
native_print("\n" + "="*20 + " 静默堆栈自省读取器 " + "="*20)
orig_input = sys.modules['builtins'].input
def silent_stack_reader(prompt=""):
native_print(f"[+] 捕获到输入请求!正在静默回溯调用栈...")
# 获取当前的执行帧(Frame)
# _getframe(0) 是当前函数,_getframe(1) 就是调用 input 的那个神秘上级!
try:
frame = sys._getframe(1)
depth = 1
while frame:
co = frame.f_code
native_print(f"\n[★★★★★] 成功回溯第 {depth} 层调用源:")
native_print(f" [-] 文件名 (co_filename): {co.co_filename}")
native_print(f" [-] 函数名 (co_name): {co.co_name}")
native_print(f" [-] 当前执行行号 (f_lineno): {frame.f_lineno}")
native_print(f" [-] 当前层局部变量 (f_locals): {list(frame.f_locals.keys())}")
# 顺便把这一层的全局空间变量名打印出来,看看是不是我们苦苦寻找的代码
native_print(f" [-] 当前层全局变量 (f_globals): {list(frame.f_globals.keys())}")
# 向下追溯更外层的调用
frame = frame.f_back
depth += 1
except Exception as e:
native_print(f"[-] 回溯失败: {str(e)}")
# 为了不让程序死循环,我们随便返回一个 6 位字符串打破僵局
return "123456"
sys.modules['builtins'].input = silent_stack_reader
native_print("[+] 静默回溯器挂载成功!请在 exe 窗口敲击回车。")
native_print("="*40 + "\n")
def my_function():
print("恭喜成功!")
请输入key还原代码(请输入6个字符)
[+] 捕获到输入请求!正在静默回溯调用栈...
[★★★★★] 成功回溯第 1 层调用源:
[-] 文件名 (co_filename): <frozen os>
[-] 函数名 (co_name): <module>
[-] 当前执行行号 (f_lineno): 1206
[-] 当前层局部变量 (f_locals): ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__builtins__', 'abc', 'sys', 'st', '_check_methods', 'GenericAlias', '__all__', '_exists', '_get_exports_list', 'name', 'linesep', 'stat', 'access', 'chdir', 'chmod', 'fchmod', 'lchmod', 'getcwd',
......
, 'x', 'my_function', 'buffer', 'w', 'i', 'b', 'key', 'b_key', 'c', 'buf', 'my', 'succ']
[★★★★★] 成功回溯第 2 层调用源:
[★★★★★] 成功回溯第 3 层调用源:
......