一种用于在Python字节码中嵌入Payload的隐写工具 – Stegosaurus
作者:admin | 时间:2017-3-21 02:06:42 | 分类:黑客工具 隐藏侧边栏展开侧边栏
Stegosaurus
本文将给大家介绍这款名叫Stegosaurus的隐写工具,它允许我们在Python字节码文件(pyc或pyo)中嵌入任意Payload。由于编码密度较低,因此我们嵌入Payload的过程既不会改变源代码的运行行为,也不会改变源文件的文件大小。Payload代码会被分散嵌入到字节码之中,所以类似strings这样的代码工具无法查找到实际的Payload。Python的dis模块会返回源文件的字节码,然后我们就可以使用Stegosaurus来嵌入Payload了。在本文发稿时,还没有任何针对这种Paylaod嵌入技术的有效检测方法。
Stegosaurus下载地址:【点我下载】
注:Stegosaurus仅支持Python3.6及其以下版本。
工具使用
$ python3 -m stegosaurus -h
usage: stegosaurus.py [-h] [-p PAYLOAD] [-r] [-s] [-v] [-x]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 sizecarrier supports
-s,--side-by-side Do not overwritecarrier file, install side by side
instead.
-v, --verbose Increase verbosity once per use
-x, --extract Extract payload from carrier file
使用样例
假设我们需要将Payload嵌入至下面这个Python脚本的字节码之中,脚本文件名为example.py:
" " "Example carrier file to embed our payloadin.
" " "
import math
def fibV1(n):
if n == 0 or n ==1:
return n
return fibV1(n -1) + fibV1(n - 2)
def fibV2(n):
if n == 0 or n ==1:
return n
return int(((1 +math.sqrt(5))**n - (1 - math.sqrt(5))**n) / (2**n * math.sqrt(5)))
def main():
result1 =fibV1(12)
result2 =fibV2(12)
print(result1)
print(result2)
if __name__ == "__main__":
main()
第一步就是使用Stegosaurus来查看在不改变源文件(Carrier)大小的情况下,我们的Payload能携带多少字节的数据。
$ python3 -m stegosaurus example.py -r
Carrier can support a payload of 20 bytes
现在,我们可以安全地嵌入最多20个字节的Payload了。如果你不想覆盖源文件的话,你可以使用-s参数来单独生成一个嵌入了Payload的py文件:
$ python3 -m stegosaurus example.py -s --payload "rootpwd: 5+3g05aW"
Payload embedded in carrier
现在我们可以用ls命令查看磁盘目录,嵌入了Payload的文件(carrier文件)和原始的字节码文件两者大小是完全相同的:
$ ls -l __pycache__/example.cpython-36*
-rw-r--r-- 1jherron staff 743 Mar 10 00:58__pycache__/example.cpython-36-stegosaurus.pyc
-rw-r--r-- 1jherron staff 743 Mar 10 00:58__pycache__/example.cpython-36.pyc
注意:如果你没有使用-s参数,那么原始的字节码文件将会被覆盖。
我们可以通过向Stegosaurus传递-x参数来提取出Payload:
$ python3 -m stegosaurus__pycache__/example.cpython-36-stegosaurus.pyc -x
Extracted payload: root pwd: 5+3g05aW
我们的Payload不一定要是一个ASCII字符串,shellcode也是可以的:
$ python3 -m stegosaurus example.py -s --payload"\xeb\x2a\x5e\x89\x76"
Payload embedded in carrier
$ python3 -m stegosaurus__pycache__/example.cpython-36-stegosaurus.pyc -x
Extracted payload: \xeb\x2a\x5e\x89\x76
查看嵌入Payload之前和之后的Python代码运行情况:
$ python3 example.py
144
144
$ python3 __pycache__/example.cpython-36.pyc
144
144
$ python3 __pycache__/example.cpython-36-stegosaurus.pyc
144
144
通过strings查看Stegosaurus嵌入了Payload之后的文件输出情况(payload并没有显示出来):
$ python3 -m stegosaurus example.py -s --payload"PAYLOAD_IS_HERE"
Payload embedded in carrier
$ strings __pycache__/example.cpython-36-stegosaurus.pyc
.Example carrier file to embed our payload in.
fibV1)
example.pyr
math
sqrt)
fibV2
print)
result1
result2r
main
__main__)
__doc__r
__name__r
<module>
$ python3 -m stegosaurus__pycache__/example.cpython-36-stegosaurus.pyc -x
Extracted payload: PAYLOAD_IS_HERE
接下来使用Python的dis模块来查看Stegosaurus嵌入Payload之前和之后的文件字节码变化情况:
嵌入之前:
20 LOAD_GLOBAL 0 (int)
22 LOAD_CONST 2 (1)
24 LOAD_GLOBAL 1 (math)
26 LOAD_ATTR 2 (sqrt)
28 LOAD_CONST 3 (5)
30 CALL_FUNCTION 1
32 BINARY_ADD
34 LOAD_FAST 0 (n)
36 BINARY_POWER
38 LOAD_CONST 2 (1)
40 LOAD_GLOBAL 1 (math)
42 LOAD_ATTR 2 (sqrt)
44 LOAD_CONST 3 (5)
46 CALL_FUNCTION 1
48 BINARY_SUBTRACT
50 LOAD_FAST 0 (n)
52 BINARY_POWER
54 BINARY_SUBTRACT
56 LOAD_CONST 4 (2)
嵌入之后:
20 LOAD_GLOBAL 0 (int)
22 LOAD_CONST 2 (1)
24 LOAD_GLOBAL 1 (math)
26 LOAD_ATTR 2 (sqrt)
28 LOAD_CONST 3 (5)
30 CALL_FUNCTION 1
32 BINARY_ADD
34 LOAD_FAST 0 (n)
36 BINARY_POWER
38 LOAD_CONST 2 (1)
40 LOAD_GLOBAL 1 (math)
42 LOAD_ATTR 2 (sqrt)
44 LOAD_CONST 3 (5)
46 CALL_FUNCTION 1
48 BINARY_SUBTRACT
50 LOAD_FAST 0 (n)
52 BINARY_POWER
54 BINARY_SUBTRACT
56 LOAD_CONST 4 (2)
Stegosaurus使用注意事项
Payload的发送和接受方法完全取决于用户个人喜好,Stegosaurus只给大家提供了一种向Python字节码文件嵌入或提取Payload的方法。但是为了保证嵌入之后的代码文件大小不会发生变化,因此Stegosaurus所支持嵌入的Payload字节长度十分有限。因此 ,如果你需要嵌入一个很大的Payload,那么你可能要将其分散存储于多个字节码文件中了。虽然Stegosaurus有这样的一个缺点,但它的优点还是很多的:
1. 以“代码碎片”的形式传递Payload;
2. 当需要的时候,我们可以从不同的来源发送并组合Payload;
3. 部分Payload代码的泄漏并不会泄漏完整的Payload;
4. 通过将Payload代码分散嵌入至多个看似无关的文件中来绕过安全检测;
(注:目前本工具还不支持跨Python字节码文件嵌入Payload。)
Stegosaurus的工作机制
为了在不改变源文件大小的情况下向其嵌入Payload,我们需要识别出字节码中的无效空间(Dead Zone)。这里所谓的无效空间指的是那些即使被修改也不会改变原Python脚本正常行为的那些字节数据。
需要注意的是,我们可以轻而易举地找出Python3.6代码中的无效空间。Python的引用解释器CPython有两种类型的操作码:即无参数的和有参数的。在版本号低于3.5的Python版本中,根据操作码是否带参,字节码中的操作指令将需要占用1个字节或3个字节。在Python3.6中就不一样了,Python3.6中所有的指令都占用2个字节,并会将无参数指令的第二个字节设置为0,这个字节在其运行过程中将会被解释器忽略。这也就意味着,对于字节码中每一个不带参数的操作指令,Stegosaurus都可以安全地嵌入长度为1个字节的Payload代码。
下面给出的是一些常见的不带参数的操作码:
BINARY_SUBTRACT
INPLACE_ADD
RETURN_VALUE
GET_ITER
YIELD_VALUE
IMPORT_STAR
END_FINALLY
NOP
...
如果你想知道字节码所发生的变化,可以看看下面这个Python代码段:
def test(n):
return n + 5 + n – 3
我们首先使用3.6以下版本Python的dis模块来查看:
0 LOAD_FAST 0 (n)
3 LOAD_CONST 1 (5) <-- opcodes with an arg take 3 bytes
6 BINARY_ADD <-- opcodes withoutan arg take 1 byte
7 LOAD_FAST 0 (n)
10 BINARY_ADD
11 LOAD_CONST 2 (3)
14 BINARY_SUBTRACT
15 RETURN_VALUE
现在使用Python3.6的dis模块来查看:
0 LOAD_FAST 0 (n)
2 LOAD_CONST 1 (5) <-- all opcodes now occupy two bytes
4 BINARY_ADD <-- opcodes withoutan arg leave 1 byte for the payload
6 LOAD_FAST 0 (n)
8 BINARY_ADD
10 LOAD_CONST 2 (3)
12 BINARY_SUBTRACT
14 RETURN_VALUE
我们可以通过Stegosaurus的-vv选项来查看Payload是如何嵌入到这些无效空间之中的:
$ python3 -m stegosaurus ../python_tests/loop.py -s -p"ABCDE" -vv
Read header and bytecode from carrier
BINARY_ADD (0)
BINARY_ADD (0)
BINARY_SUBTRACT (0)
RETURN_VALUE (0)
RETURN_VALUE (0)
Found 5 bytes available for payload
Payload embedded in carrier
BINARY_ADD (65) <-- A
BINARY_ADD (66) <-- B
BINARY_SUBTRACT (67) <-- C
RETURN_VALUE (68) <-- D
RETURN_VALUE (69) <-- E
目前,Stegosaurus只能利用这种无效空间,我们会在将来引入更多可支持利用的无效空间。
成长空间
1.添加“自毁”选项-d,该参数将会在我们从carrier文件中提取出Payload之后清除源文件中的Payload代码;
2.添加跨文件嵌入Payload的功能;
3.添加-t功能,测试Payload是否会在carrier文件中显示;
4.查找字节码中更多的无效空间,增加可嵌入的Payload数据;
5.添加-g选项,通过增加文件大小来支持大型Payload的嵌入;
* 参考来源:bitbucket, FB小编Alpha_h4ck编译