Python 逆向中的一些岩浆知识(二)

上一篇中提了一些理论化的东西, 这一篇稍微写一点实战的东西

CPython 中的一些其他有趣的东西

除了直接在上一篇中提到的 eval 函数中进行修改,其实 cpython 确实是有一些执行的接口的,不过在提到这些之前要先提一下 GIL

GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取 GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题。

那么,为什么要提 GIL 呢?我们在修改一个正在运行的 py code 的时候,就需要考虑它。

有人要问了,如果有这个需求直接改上一篇说的函数就好了啊?但是由于 eval 函数有点过于复杂,而且很难定位到需要的地方,所以,这里给出另一种能够更简单修改的方法

在 python 运行 python

什么套娃(bushi)

cpython 自身就是一个 py 的解释器,那么有没有可能动态的运行 python 代码呢?

先来介绍一下怎么在 cpython 里面运行 py。py 脚本每个都会是 cpython 创建的线程,而他们会有上文提到的 GIL。这里是官方的注释

Ensure that the current thread is ready to call the Python
C API, regardless of the current state of Python, or of its
thread lock. This may be called as many times as desired
by a thread so long as each call is matched with a call to
PyGILState_Release(). In general, other thread-state APIs may
be used between _Ensure() and _Release() calls, so long as the
thread-state is restored to its previous state before the Release().
For example, normal use of the Py_BEGIN_ALLOW_THREADS/
Py_END_ALLOW_THREADS macros are acceptable.

The return value is an opaque "handle" to the thread state when
PyGILState_Ensure() was called, and must be passed to
PyGILState_Release() to ensure Python is left in the same state. Even
though recursive calls are allowed, these handles can *not* be shared -
each unique call to PyGILState_Ensure must save the handle for its
call to PyGILState_Release.

When the function returns, the current thread will hold the GIL.

Failure is a fatal error.

 

那么,要想让一个脚本先不执行原来的代码而开始执行我们的代码,就有个好方法,就是直接取一个 GIL 锁让别的都先等等。不多说看代码

Py_SetProgramName(L"当前 py 程序名");
PyEval_InitThreads();
PyGILState_STATE s = PyGILState_Ensure();
PyRun_SimpleString("print('hello!!!')");   // 可以执行 python 代码
PyGILState_Release(s);

这样就可以简单的运行任意的 py 脚本了!只要在你想要的时候创建一个线程执行这段代码可以了!甚至可以不用每次都改,例如在 simple string 里面写一个读取文件,作为代码 exec() 就可以无限重用,是不是很简单?不过不要忘了最后要结束线程!

这里提一嘴,看起来在最新版本不需要这个 PyEval_InitThreads() 了,去掉也没事(好像 3.9 就废弃了),但是之前的 py 版本需要!!!

执行实战

题目来自 n1ctf junior 2023 , junior enc

反编译,然后发现并不怎么好看,虽然是 aes 但是经过了大量的魔改

可以看到代码中多了很多奇奇怪怪的 else: 这样的结构,破坏了原有的语法。

Python 逆向中的一些岩浆知识 (二)

那么,这个时候如果我们尝试修改了反编译错误的代码,而需要进行验证,我们就能使用上面提到的方法
可以看效果图
Python 逆向中的一些岩浆知识 (二)

可以看到直接执行了同一目录下 code 的代码

这个方法在验证算法和爆破的时候都是挺好用的,尤其是当 pyc 不嫩被正确反编译的时候。

py debug

这个方法太麻烦了!还要改 cpython!我如果就是不会 c 语言,不想改咋办?

当然可以,有人帮我们写了一个类似于 gdb 的 python debugger!最重要的是可以直接调试字节码!

trepan-xpy

为啥用这个呢?因为我试了一下 trepan3k(原始版本)不可以调试字节码,而会报一个错,即使 py 版本和字节码匹配

按照项目给的方法安装了之后,输入 trepan-xpy (文件名) 就能启动调试啦!

Python 逆向中的一些岩浆知识 (二)

还是很强大的,步过步入,查看堆栈和变量,当前字节码等功能都有,还可以下断点,更多功能可看手册,这样加密了之后就直接下断点查看,无需写脚本!

花指令以及...... 控制流平坦化?

bytecode_simplifier

有启发性的一篇!

forwarder 定义为只由一条跳转语句构成的基本块,只是转移了执行流没有任何有用操作。也就是 jump absolute 的混淆可以被直接去除!还是很牛的,起码现在做题也一般只有 jump absolute 花指令,能自动去除是挺牛的

这个还可以去除控制流平坦化,但是没试过。不过好像只能去除一个混淆器的?不过 ctf 里面好像 py 控制流平坦化比较少,以后遇到了再改吧

 

未完待续,以后有啥好玩的再加到这里的后面

 

正文完
 0
评论(没有评论)