5.png

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

 

# :( no easy bytes to embed a payload

现在使用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

 

# :) easy bytes to embed a payload

我们可以通过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只能利用这种无效空间,我们会在将来引入更多可支持利用的无效空间。

6.png

成长空间

1.添加“自毁”选项-d,该参数将会在我们从carrier文件中提取出Payload之后清除源文件中的Payload代码;

2.添加跨文件嵌入Payload的功能;

3.添加-t功能,测试Payload是否会在carrier文件中显示;

4.查找字节码中更多的无效空间,增加可嵌入的Payload数据;

5.添加-g选项,通过增加文件大小来支持大型Payload的嵌入;

* 参考来源:bitbucket, FB小编Alpha_h4ck编译