前言
无事就翻翻大师傅们的博客,然后看到了一篇关于Stegosaurus隐写的,然后延伸到Python的import模块导入等部分知识。 于是开始学习延伸更多。
什么是Stegosaurus?
Stegosaurus
是一个隐写工具,它是通过将payload
隐藏在Python的字节码文件中。而且不会改变源代码的运行行为,也不会改变源代码的大小。Payload
代码会被分散嵌入到字节码之中,所以类似 strings
这样的代码工具无法查找到实际的 Payload
。 Python
的 dis
模块会返回源文件的字节码,然后我们就可以使用 Stegosaurus
来嵌入 Payload
了。
简单剖析pyc和简析import导入
pyc
文件是python
的字节码文件,是二进制文件,就是Python
源文件在经过解释器编译后生成的字节码文件。现在实验一下。
在linux平台实验。
mkdir m0re && cd m0re/
创建一个py
文件,内容
def print_test():
print('hello,m0re!')
print_test()
记得要给执行权限。
执行一下,成功执行后,再看目录下文件,到那时没发现pyc
文件
再创建一个文件
vim m0re.py
#内容如下:
import test
test.print_test()
执行成功后,发现打印了两行,而且出现了一个文件夹__pycache__
在执行python3 m0re.py
时,第一条命令是import test
先导入test
模块,一个模块在被导入时,PVM
(python虚拟机)会在后台从模块包里面搜索test
这个模块。Python
中所有加载到内存的模块都放在 sys.modules
(保存了之前import的类库的缓存)
搜索过程:
1、 在当前目录下搜索这个模块
2、 在环境变量中PYTHONPATH
中指定的路径列表依次搜索
3、在Python安装路径中搜索
模块的搜索路径
sys.path
返回导入模块时搜索路径集,是一个list
列表
In [1]: import sys
In [2]: sys.path
Out[2]:
['',
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/python3.8/lib-dynload',
'/usr/local/lib/python3.8/dist-packages',
'/root/tools/gaps',
'/usr/lib/python3/dist-packages',
'/usr/lib/python3/dist-packages/IPython/extensions',
'/root/.ipython']
1、 从上面列出的目录里依次查找要导入的模块文件
2、''
表示当前路径
3、 列表中的路径的前后顺序表明了python
解释器在搜索模块时的前后顺序。
添加新模块
sys.path.append('/home/python/xxxx')
sys.path.insert(0,'/home/python/xxxx')
注意:使用sys.path.append()
和sys.path.insert()
添加的路径会在退出交互式环境或者IDE
后自动消失。
import
只能导入模块,不能导入模块中的对象(类、函数、变量等)python不能像Java那样,直接通过import
来导入某个对象或者类以及某个函数。
import java.swing.*;
python的方法是这样的
from test import print_test
在解释器中
Python模块嵌套导入
然后再来看看嵌套导入
看例子
#m0re.py
from m1re import m3re
class m2re:
pass
#m1re.py
from m0re import m2re
class m3re:
pass
importError
,导入失败,这样导入是存在问题的。
导入时,先搜索m1re
,如果存在,就会获得m1re
对应的module
对象<module m1re>
,从这个module
对象的__dict
中获取m3re
对应的对象。如果不存在就抛出异常。
如果m1re
不存在,就会新建一个module
对象<module m1re>
,此时的module
对象的__dict__
为空。,执行m1re.py
中的表达式,填充<module m1re>
中__dict__
的空白。再从<module m1re>
的__dict__
中获取m3re
的对象。如果不存在则抛出异常。
所以这个程序的执行流程为
1.执行m0re.py
,先执行from m1re import m3re
没有m1re
的模块,所以此刻创建一个新的module
,此刻的module
为空。然后执行m1re.py
填充<module m1re>
中__dict__
的空白。
2.执行m1re.py
,先执行from m0re import m2re
同样的,检查缓存中是否存在<module m0re>
,因为m0re.py还正在执行,所以缓存中还没有这个module
,同样是要创建新的module
,然后就是再次执行m0re.py
中的代码
3.再次执行m0re.py
中的from m1re import m3re
,第一步,创建过<module m1re>
,所以已经存在了,但是里面还是空的,什么也没有。所以从里面获取不到m3re
这个对象,这里就会抛出异常ImportError
。
将m0re.py修改一下
import m1re
class m2re:
pass
执行过程中就第三步就是只搜索存在<module m1re>
,而不用搜索m3re
执行时就无异常抛出
覆盖导入
当导入的模块与标准库中的模块重名,在导入时会发生覆盖导入,因为python解释器会首先在当前文件夹下搜索查找模块名。这里用一段代码来解释。创建random.py
写入下面内容。
#!/usr/bin/python
import random
print(random.randint(12,20))
在导入包时,python解释器在当前目录下找到了random
,就直接导入。不会进行下面的搜索。我们知道Python的标准库里面的random
模块是存在randint
方法的。然而,这里运行的话,会报错AttributeError
提示:模块random
中没有方法randint
import
默认就把本身作为模块导入,那么显然代码中没有randint
方法,所以会导致报错;
经过多次测试,并不是所有的冲突都会出现。
比如下面这个例子time.py
import time
print(time.time())
还有math.py
import math
def square_root(number):
return math.sqrt(number)
print(square_root(1600))
这两个例子在网上查阅资料,是无法运行的,我的可以运行。
不止这两个,为了找个运行不起来的反例,我也是查阅了好多博客。
如果出现错误,解决办法那就是改下文件名,尽量避免命名与python文件内调用的模块重复。
绝对导入
无论是相对导入还是绝对导入,都是需要一个参照物的的。而绝对导入的参照物就是项目的根目录。
在一个目录中有下面的结构:
以m0re
作为根目录,如上图
其中,module2.py
中有一个function1
函数,dir2
的__init__.py
中有一个class1
的类,dir2
下的dir3
中的module5.py
中有一个function3
函数。
使用绝对路径导入示例:
from dir1 import module1
from dir1.module2 import function1
from dir2 import class1
from dir2.dir3.module5 import function2
绝对路径要求我们必须从最顶层的文件夹开始,为每个包或每个模块提供出完整详细的导入路径。
现在的python3.x
中,绝对导入是默认的导入形式。它的优点是很明显的知道要导入的包或者模块在什么位置。
那么问题来了,如果导入包的话,顺序是什么?
这里重新构造一个目录如下
m0re.py
、module1.py
、module2.py
都写入以下内容
def getName():
pass
__init__.py
写入print('hello,world')
一般的执行过程:
通常情况下,首先从根目录开始导入,所以第一步是写个test.py
文件导入模块
import sys
import dir1.m0re
import dir1.dir2
import dir1.dir2.module1 as m1
import dir1.dir3.module2
dir1.m0re.getName()
m1.getName()
dir1.dir3.module2.getName()
执行结果:
先导入m0re
,然后sys.module
会创建dir1
和dir1.m0re
两个模块,然后m0re.py
中的函数就可以调用了。但是dir2
中的函数不能调用,因为只是存在dir1
这个模块,模块中关于dir2
的内容还是空的。
执行import dir1.dir2
后就可调用dir2
中的函数了,此时的dir2
算是被载入内存中了。
as
的作用就是,m1
与dir1.dir2.module1
等效。作用一样。都可以进行调用内存中module1
模块中的函数。
然后导入module2
,所有的模块dir1,dir1.m0re,dir.m0re.module1,dir1.dir3.module2
四个模块就全部导入了,函数都可以调用。
下面的三条语句都可以实现了。
一般都使用绝对导入,而相对导入使用不方便,很容易发生混乱,导致程序报错。
回到开头
说了那么多,还是把之前引起思考的那个题目的知识点总结一下。bugkuCTF的题目QAQ
题目wp就不重复了,然后就是那个工具,Stegosaurus
在github的项目地址为: https://github.com/AngelKitty/stegosaurus
使用方法学习一下,以便自己以后查看。
python stegosaurus.py -h #查看帮助信息、
usage: stegosaurus.py [-h] [-p PAYLOAD] [-r] [-s] [-v] [-x] [-e EXPLODE]
carrier
positional arguments:
carrier Carrier py, pyc or pyo file
optional arguments:
-h, --help show this help message and exit
-p PAYLOAD, --payload PAYLOAD
Embed payload in carrier file
-r, --report Report max available payload size carrier supports
-s, --side-by-side Do not overwrite carrier file, install side by side
instead.
-v, --verbose Increase verbosity once per use
-x, --extract Extract payload from carrier file
-e EXPLODE, --explode EXPLODE
Explode payload into groups of a limited length if necessary
# 参数说明
-h #帮助信息
-p #要隐藏在文件中的payload,payload要用双引号包起
-x #提取出来隐藏在文件中的payload
-s #不覆盖,再生成一个文件,为隐藏信息后的文件。保留原来的文件
-r #查看这个文件,最多成隐藏多少字节的数据
-v #查看隐藏信息的详细过程,使用次数决定详细程度,一般使用两次就够-vv
单独列出,-e EXPLODE
,为什么单独列出来这个,因为在github项目中,readme.md中没有这个参数,在本地执行确发现多了这个参数。
本地执行:
于是就开始多次尝试。
再次发现问题并解决
这个工具使用的话,按照github上面的使用方式使用,无法运行。报错如下:
root@kali:~/Desktop/m0re/stegosaurus# python3 stegosaurus.py m0re.py -r
Traceback (most recent call last):
File "stegosaurus.py", line 251, in <module>
main()
File "stegosaurus.py", line 217, in main
header, code = _loadBytecode(carrier, logger)
File "stegosaurus.py", line 124, in _loadBytecode
code = marshal.load(f)
ValueError: bad marshal data (unknown type code)
多次尝试发现:只有-x
参数可以正常使用。
其他参数无法使用。使用就会报错。猜想可能不是所有的python文件都可以隐藏payload
当然,猜想错误,py
、pyc
、pyo
文件均可隐藏信息。那这个报错怎么解决?
Google一下,发现答案就在项目中。
https://github.com/AngelKitty/stegosaurus/issues/1
看到了作者的回复
应该是python版本的问题,python3.6是没有问题的 ——作者
而作者打包的那个bin
文件,是直接调用的python3.6
(这个后面会提到,请继续看
),我的windows
主机是3.7
,而我实验用的kali
默认是3.8
,所以才造成都无法使用的结果。
按照作者大大的办法,排除问题。
工具参数
继续学习这个工具的参数
root@kali:~/Desktop/m0re/stegosaurus# ./stegosaurus m0re.py -s --payload "m0re_wuhu~"
Payload embedded in carrier
root@kali:~/Desktop/m0re/stegosaurus# ./stegosaurus __pycache__/m0re.cpython-36-stegosaurus.pyc -x
Extracted payload: m0re_wuhu~
root@kali:~/Desktop/m0re/stegosaurus#
然后看下是怎么隐藏的
root@kali:~/桌面/m0re/stegosaurus# ./stegosaurus m0re.py -s --payload "m0re_wuhu~" -vv
2020-12-10 17:56:40,030 - stegosaurus - DEBUG - Validated args
2020-12-10 17:56:40,032 - stegosaurus - INFO - Compiled m0re.py as __pycache__/m0re.cpython-36.pyc for use as carrier
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - Read header and bytecode from carrier
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - POP_TOP (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - POP_TOP (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - POP_TOP (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - BINARY_SUBTRACT (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - BINARY_MULTIPLY (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - BINARY_ADD (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,032 - stegosaurus - DEBUG - POP_TOP (0)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,033 - stegosaurus - INFO - Found 14 bytes available for payload
Payload embedded in carrier
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - POP_TOP (109)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - POP_TOP (48)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - POP_TOP (114)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (101)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - BINARY_SUBTRACT (95)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (119)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (117)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (104)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - BINARY_MULTIPLY (117)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (126)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - BINARY_ADD (0)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - POP_TOP (0)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - RETURN_VALUE (0)
2020-12-10 17:56:40,033 - stegosaurus - DEBUG - Creating new carrier file name for side-by-side install
2020-12-10 17:56:40,035 - stegosaurus - INFO - Wrote carrier file as __pycache__/m0re.cpython-36-stegosaurus.pyc
上面是隐藏前,发现可以插入的地方。然后下面的部分是隐藏后,最后生成的文件是m0re.cpython-36-stegosaurus.pyc
,注意这里生成的pyc
文件,cpython-36
,可以看出是python3.6
解释器解释生成的文件。所以上面说的作者打包的bin
文件是直接调用python3.6
的解释器的。-e EXPLODE
经过多次测试发现,这个参数后面还需要加上参数,并且这个参数是整型的,所以payload
可以为
./stegosaurus -s --explode 2 --payload "m0re_wuhu~_6" m0re.py
不是很理解这个参数,理解能力有点欠缺。下面说一下我的试验结果吧。
1.当payload > Carrier can support
的长度,无法隐藏,可以使用这个参数,限制一下长度,不同宿主文件,可以隐藏的payload
长度不同,因为代码中无用空间的大小不同,测试出来参数代表的长度也不一样。
2.当payload < Carrier can support
的长度,无需用到这个参数。直接隐藏即可。
总结
学习到很多知识,首先是工具的使用,涉及到Python中import包的几种导入方式的详细过程。写这篇文章花了挺多的时间和心思的,还有许多不足之处,可能是我没理解到的,还请师傅们多多指点。
参考文章
https://www.shangmayuan.com/a/78dcc7c2a10046dea693b44d.html
https://www.cnblogs.com/ECJTUACM-873284962/p/10041534.html
https://www.bbsmax.com/A/A7zgYqRo54/