一种用于在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编译
 
			
