我们来分析一下这个过程:
- 当执行
#1
后,sys.modules
会同时存在PA
、PA.wave
两个模块,此时可以调用PA.wave
的任何类或函数了。但不能调用PA.PB1(2)
下的任何模块。当前Local
中有了PA
名字。 - 当执行
#2
后,只是将PA.PB1
载入内存,sys.modules
中会有PA
、PA.wave
、PA.PB1
三个模块,但是PA.PB1
下的任何模块都没有自动载入内存,此时如果直接执行PA.PB1.pb1_m.getName()
则会出错,因为PA.PB1
中并没有pb1_m
。当前Local
中还是只有PA
名字,并没有PA.PB1
名字。 - 当执行
#3
后,会将PA.PB1
下的pb1_m
载入内存,sys.modules
中会有PA
、PA.wave
、PA.PB1
、PA.PB1.pb1_m
四个模块,此时可以执行PA.PB1.pb1_m.getName()
了。由于使用了as
,当前Local
中除了PA
名字,另外添加了m1
作为PA.PB1.pb1_m
的别名。 - 当执行
#4
后,会将PA.PB2
、PA.PB2.pb2_m
载入内存,sys.modules
中会有PA
、PA.wave
、PA.PB1
、PA.PB1.pb1_m
、PA.PB2
、PA.PB2.pb2_m
六个模块。当前Local
中还是只有PA
、m1
。 - 下面的
#5
,#6
,#7
都是可以正确运行的。
注:需要注意的问题是如果
PA.PB2.pb2_m
想导入PA.PB1.pb1_m
、PA.wave
是可以直接成功的。最好是采用明确的导入路径,对于../..
相对导入路径还是不推荐使用。
既然我们已经知道 pyc
文件的产生,再回到那道赛题,我们尝试将 pyc
文件反编译回 python
源码。我们使用在线的开源工具进行尝试:
部分代码没有反编译成功???我们可以尝试分析一下,大概意思就是读取 cipher.txt
那个文件,将那个文件内容是通过 base64
编码的,我们的目的是将文件内容解码,然后又已知 key
,通过 encryt
函数进行加密的,我们可以尝试将代码补全:
def encryt(key, plain): cipher = '' for i in range(len(plain)): cipher += chr(ord(key[i % len(key)]) ^ ord(plain[i])) return cipher def getPlainText(): plain = '' with open('cipher.txt') as (f): while True: line = f.readline() if line: plain += line else: break return plain.decode('base_64') def main(): key = 'LordCasser' plain = getPlainText() cipher = encryt(key, plain) with open('xxx.txt', 'w') as (f): f.write(cipher) if __name__ == '__main__': main()
结果如下:
YOU ARE FOOLED THIS IS NOT THAT YOU WANT GO ON DUDE CATCH THAT STEGOSAURUS
提示告诉我们用 STEGOSAURUS
工具进行隐写的,我们直接将隐藏的payload分离出来即可。
python3 stegosaurus.py -x QAQ.pyc
我们得到了最终的 flag
为:flag{fin4lly_z3r0_d34d}
既然都说到这个份子上了,我们就来分析一下我们是如何通过 Stegosaurus
来嵌入 Payload
。
我们仍然以上面这个代码为例子,我们设置脚本名称为 encode.py
。
第一步,我们使用 Stegosaurus
来查看在不改变源文件 (Carrier)
大小的情况下,我们的 Payload
能携带多少字节的数据:
python3 -m stegosaurus encode.py -r
现在,我们可以安全地嵌入最多24个字节的 Payload
了。如果不想覆盖源文件的话,我们可以使用 -s
参数来单独生成一个嵌入了 Payload
的 py
文件:
python3 -m stegosaurus encode.py -s --payload "flag{fin4lly_z3r0_d34d}"
现在我们可以用 ls
命令查看磁盘目录,嵌入了 Payload
的文件( carrier
文件)和原始的字节码文件两者大小是完全相同的:
注:如果没有使用
-s
参数,那么原始的字节码文件将会被覆盖。
我们可以通过向 Stegosaurus
传递 -x
参数来提取出 Payload
:
python3 -m stegosaurus __pycache__/encode.cpython-36-stegosaurus.pyc -x
我们构造的 Payload
不一定要是一个 ASCII
字符串, shellcode
也是可以的:
我们重新编写一个 example.py
模块,代码如下:
import sys import os import math def add(a,b): return int(a)+int(b) def sum1(result): return int(result)*3 def sum2(result): return int(result)/3 def sum3(result): return int(result)-3 def main(): a = 1 b = 2 result = add(a,b) print(sum1(result)) print(sum2(result)) print(sum3(result)) if __name__ == "__main__": main()
我们让它携带 Payload
为 flag_is_here
。
我们可以查看嵌入 Payload
之前和之后的 Python
代码运行情况:
通过 strings
查看 Stegosaurus
嵌入了 Payload
之后的文件输出情况( payload
并没有显示出来):
接下来使用 Python
的 dis
模块来查看 Stegosaurus
嵌入 Payload
之前和之后的文件字节码变化情况:
嵌入payload之前:
#( 11/29/18@ 5:14下午 )( python@Sakura ):~/桌面 python3 -m dis example.py 1 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 2 8 LOAD_CONST 0 (0) 10 LOAD_CONST 1 (None) 12 IMPORT_NAME 1 (os) 14 STORE_NAME 1 (os) 3 16 LOAD_CONST 0 (0) 18 LOAD_CONST 1 (None) 20 IMPORT_NAME 2 (math) 22 STORE_NAME 2 (math) 4 24 LOAD_CONST 2 (<code object add at 0x7f90479778a0, file "example.py", line 4>) 26 LOAD_CONST 3 ('add') 28 MAKE_FUNCTION 0 30 STORE_NAME 3 (add) 6 32 LOAD_CONST 4 (<code object sum1 at 0x7f9047977810, file "example.py", line 6>) 34 LOAD_CONST 5 ('sum1') 36 MAKE_FUNCTION 0 38 STORE_NAME 4 (sum1) 9 40 LOAD_CONST 6 (<code object sum2 at 0x7f9047977ae0, file "example.py", line 9>) 42 LOAD_CONST 7 ('sum2') 44 MAKE_FUNCTION 0 46 STORE_NAME 5 (sum2) 12 48 LOAD_CONST 8 (<code object sum3 at 0x7f9047977f60, file "example.py", line 12>) 50 LOAD_CONST 9 ('sum3') 52 MAKE_FUNCTION 0 54 STORE_NAME 6 (sum3) 15 56 LOAD_CONST 10 (<code object main at 0x7f904798c300, file "example.py", line 15>) 58 LOAD_CONST 11 ('main') 60 MAKE_FUNCTION 0 62 STORE_NAME 7 (main) 23 64 LOAD_NAME 8 (__name__) 66 LOAD_CONST 12 ('__main__') 68 COMPARE_OP 2 (==) 70 POP_JUMP_IF_FALSE 78 24 72 LOAD_NAME 7 (main) 74 CALL_FUNCTION 0 76 POP_TOP >> 78 LOAD_CONST 1 (None) 80 RETURN_VALUE
嵌入 payload
之后:
#( 11/29/18@ 5:31下午 )( python@Sakura ):~/桌面 python3 -m dis example.py 1 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (sys) 6 STORE_NAME 0 (sys) 2 8 LOAD_CONST 0 (0) 10 LOAD_CONST 1 (None) 12 IMPORT_NAME 1 (os) 14 STORE_NAME 1 (os) 3 16 LOAD_CONST 0 (0) 18 LOAD_CONST 1 (None) 20 IMPORT_NAME 2 (math) 22 STORE_NAME 2 (math) 4 24 LOAD_CONST 2 (<code object add at 0x7f146e7038a0, file "example.py", line 4>) 26 LOAD_CONST 3 ('add') 28 MAKE_FUNCTION 0 30 STORE_NAME 3 (add) 6 32 LOAD_CONST 4 (<code object sum1 at 0x7f146e703810, file "example.py", line 6>) 34 LOAD_CONST 5 ('sum1') 36 MAKE_FUNCTION 0 38 STORE_NAME 4 (sum1) 9 40 LOAD_CONST 6 (<code object sum2 at 0x7f146e703ae0, file "example.py", line 9>) 42 LOAD_CONST 7 ('sum2') 44 MAKE_FUNCTION 0 46 STORE_NAME 5 (sum2) 12 48 LOAD_CONST 8 (<code object sum3 at 0x7f146e703f60, file "example.py", line 12>) 50 LOAD_CONST 9 ('sum3') 52 MAKE_FUNCTION 0 54 STORE_NAME 6 (sum3) 15 56 LOAD_CONST 10 (<code object main at 0x7f146e718300, file "example.py", line 15>) 58 LOAD_CONST 11 ('main') 60 MAKE_FUNCTION 0 62 STORE_NAME 7 (main) 23 64 LOAD_NAME 8 (__name__) 66 LOAD_CONST 12 ('__main__') 68 COMPARE_OP 2 (==) 70 POP_JUMP_IF_FALSE 78 24 72 LOAD_NAME 7 (main) 74 CALL_FUNCTION 0 76 POP_TOP >> 78 LOAD_CONST 1 (None) 80 RETURN_VALUE
注:
Payload
的发送和接受方法完全取决于用户个人喜好,Stegosaurus
只提供了一种向Python
字节码文件嵌入或提取Payload
的方法。但是为了保证嵌入之后的代码文件大小不会发生变化,因此Stegosaurus
所支持嵌入的Payload
字节长度十分有限。因此 ,如果你需要嵌入一个很大的Payload
,那么你可能要将其分散存储于多个字节码文件中了。
为了在不改变源文件大小的情况下向其嵌入 Payload
,我们需要识别出字节码中的无效空间( Dead Zone
)。这里所谓的无效空间指的是那些即使被修改也不会改变原 Python
脚本正常行为的那些字节数据。
需要注意的是,我们可以轻而易举地找出 Python3.6
代码中的无效空间。 Python
的引用解释器 CPython
有两种类型的操作码:即无参数的和有参数的。在版本号低于 3.5
的 Python
版本中,根据操作码是否带参,字节码中的操作指令将需要占用 1
个字节或 3
个字节。在 Python3.6
中就不一样了, Python3.6
中所有的指令都占用 2
个字节,并会将无参数指令的第二个字节设置为 0
,这个字节在其运行过程中将会被解释器忽略。这也就意味着,对于字节码中每一个不带参数的操作指令, Stegosaurus
都可以安全地嵌入长度为 1
个字节的 Payload
代码。
我们可以通过 Stegosaurus
的 -vv
选项来查看 Payload
是如何嵌入到这些无效空间之中的:
#( 11/29/18@10:35下午 )( python@Sakura ):~/桌面 python3 -m stegosaurus example.py -s -p "ABCDE" -vv 2018-11-29 22:36:26,795 - stegosaurus - DEBUG - Validated args 2018-11-29 22:36:26,797 - stegosaurus - INFO - Compiled example.py as __pycache__/example.cpython-36.pyc for use as carrier 2018-11-29 22:36:26,797 - stegosaurus - DEBUG - Read header and bytecode from carrier 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_SUBTRACT (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_MULTIPLY (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_ADD (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - INFO - Found 14 bytes available for payload Payload embedded in carrier 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (65) ----A 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (66) ----B 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (67) ----C 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (68) ----D 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_SUBTRACT (69) ----E 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_MULTIPLY (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_ADD (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - Creating new carrier file name for side-by-side install 2018-11-29 22:36:26,799 - stegosaurus - INFO - Wrote carrier file as __pycache__/example.cpython-36-stegosaurus.pyc