pyarmor.cli 9.2.6__3-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyarmor/cli/merge.py ADDED
@@ -0,0 +1,232 @@
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ #############################################################
5
+ # #
6
+ # Copyright @ 2023 - Dashingsoft corp. #
7
+ # All rights reserved. #
8
+ # #
9
+ # Pyarmor #
10
+ # #
11
+ # Version: 8.2.3 - #
12
+ # #
13
+ #############################################################
14
+ #
15
+ #
16
+ # @File: cli/merge.py
17
+ #
18
+ # @Author: Jondy Zhao (pyarmor@163.com)
19
+ #
20
+ # @Create Date: Tue May 30 19:35:02 CST 2023
21
+ #
22
+ import argparse
23
+ import logging
24
+ import os
25
+ import shutil
26
+ import struct
27
+ import sys
28
+
29
+ logger = logging.getLogger('merge')
30
+
31
+
32
+ def is_pyscript(filename):
33
+ return os.path.splitext(filename)[-1].lower() in ('.py', '.pyw')
34
+
35
+
36
+ def parse_script(filename):
37
+ with open(filename) as f:
38
+ for line in f:
39
+ if line.startswith('__pyarmor__('):
40
+ i = line.find('(')
41
+ args = line.strip()[i+1:-1].split(', ', 2)
42
+ co = compile(line, '<str>', 'exec')
43
+ return args, co.co_consts[0]
44
+
45
+
46
+ def parse_header(code):
47
+ left_size = len(code)
48
+ offset = 0
49
+ infos = []
50
+ valid = False
51
+
52
+ while left_size > 0:
53
+ pymajor, pyminor = struct.unpack("BB", code[offset+9:offset+11])
54
+ size, = struct.unpack("i", code[offset+56:offset+60])
55
+ if not size:
56
+ valid = True
57
+ size = left_size
58
+ left_size -= size
59
+ infos.append([offset, size, (pymajor, pyminor)])
60
+ offset += size
61
+
62
+ if not valid:
63
+ raise RuntimeError('invalid header in this script')
64
+
65
+ return infos
66
+
67
+
68
+ def merge_scripts(name, paths, dest):
69
+ scripts = [os.path.join(p, name) for p in paths]
70
+
71
+ refscript = scripts.pop(0)
72
+ result = parse_script(refscript)
73
+
74
+ if result is None:
75
+ logger.info('copy script, it is not obfuscated')
76
+ shutil.copy2(refscript, dest)
77
+ return
78
+
79
+ refmark = '--xxxxxx--'
80
+ refitem, refcode = result
81
+ with open(refscript) as f:
82
+ refdata = f.read().replace(refitem[-1], refmark)
83
+
84
+ pieces = []
85
+
86
+ for script in reversed(scripts):
87
+ result = parse_script(script)
88
+ if not result:
89
+ raise RuntimeError('"%s" is not an obfuscated script' % script)
90
+ item, code = result
91
+ infos = parse_header(code)
92
+ off, size, pyver = infos[-1]
93
+ logger.debug('merge py%s.%s at %d (%d)', *pyver, off, size)
94
+ pieces.extend([code[:off+56], struct.pack("i", size), code[off+60:]])
95
+
96
+ for off, size, pyver in parse_header(refcode):
97
+ logger.debug('merge py%s.%s at %d (%d)', *pyver, off, size)
98
+ pieces.append(refcode[off:off+size])
99
+
100
+ logger.info('write "%s"', dest)
101
+ with open(dest, 'w') as f:
102
+ f.write(refdata.replace(refmark, repr(b''.join(pieces))))
103
+
104
+
105
+ def merge_paths(paths, rname, output):
106
+ refpath = os.path.normpath(paths[-1])
107
+ rpath = os.path.join(refpath, rname) if rname else None
108
+
109
+ n = len(refpath) + 1
110
+ for root, dirs, files in os.walk(refpath):
111
+ for x in files:
112
+ if rpath and root.startswith(rpath):
113
+ continue
114
+
115
+ name = root[n:]
116
+ destpath = os.path.join(output, name)
117
+ if not os.path.exists(destpath):
118
+ os.makedirs(destpath)
119
+
120
+ dest = os.path.join(destpath, x)
121
+ logger.info('handle "%s"', dest)
122
+ if is_pyscript(x):
123
+ merge_scripts(os.path.join(name, x), paths, dest)
124
+ else:
125
+ shutil.copy2(os.path.join(root, x), dest)
126
+
127
+
128
+ def merge_runtimes(paths, rname, output):
129
+ dest = os.path.join(output, rname)
130
+ if os.path.exists(dest):
131
+ shutil.rmtree(dest)
132
+ os.makedirs(dest)
133
+
134
+ shutil.copy2(os.path.join(paths[0], rname, '__init__.py'), dest)
135
+
136
+ for p in paths:
137
+ logger.info('handle runtime package at "%s"', p)
138
+ rpath = os.path.join(p, rname)
139
+ if not os.path.exists(rpath):
140
+ raise RuntimeError('no runtime package found')
141
+ for x in os.scandir(rpath):
142
+ if x.is_dir():
143
+ logger.info('copy runtime files "%s" to "%s"', x.name, dest)
144
+ shutil.copytree(x.path, os.path.join(dest, x.name))
145
+
146
+
147
+ def scan_runtime(paths, marker=None):
148
+ if marker is None:
149
+ marker = 'from sys import version_info as py_version'
150
+ refpath = os.path.normpath(paths[-1])
151
+ logger.info('scan runtime package in the path "%s"', refpath)
152
+
153
+ n = len(refpath) + 1
154
+
155
+ for root, dirs, files in os.walk(refpath):
156
+ for x in files:
157
+ if x == '__init__.py':
158
+ filename = os.path.join(root, x)
159
+ with open(filename) as f:
160
+ for line in f:
161
+ if line.startswith('#'):
162
+ continue
163
+ if line.startswith(marker):
164
+ return filename[n:-12]
165
+ break
166
+
167
+ raise RuntimeError('no runtime package found')
168
+
169
+
170
+ def excepthook(type, exc, traceback):
171
+ try:
172
+ msg = exc.args[0] % exc.args[1:]
173
+ except Exception:
174
+ msg = str(exc)
175
+ logging.error(msg)
176
+ sys.exit(1)
177
+
178
+
179
+ def main():
180
+ parser = argparse.ArgumentParser(
181
+ prog='pyarmor-merge',
182
+ formatter_class=argparse.RawDescriptionHelpFormatter,
183
+ epilog='merge Pyarmor 8 obfuscated scripts')
184
+
185
+ parser.add_argument('-O', '--output',
186
+ default='dist',
187
+ help='Default output path: %(default)s)')
188
+ parser.add_argument('-d', '--debug',
189
+ default=False,
190
+ action='store_true',
191
+ dest='debug',
192
+ help='print debug log (default: %(default)s)')
193
+ group = parser.add_argument_group().add_mutually_exclusive_group()
194
+ group.add_argument('-n', '--no-runtime', action='store_true',
195
+ help='Ignore runtime files')
196
+ group.add_argument('--runtime-name', help='Runtime package name')
197
+ parser.add_argument('path', nargs='+',
198
+ help="Paths or obfuscated scripts")
199
+
200
+ args = parser.parse_args(sys.argv[1:])
201
+ if args.debug:
202
+ logger.setLevel(logging.DEBUG)
203
+ else:
204
+ sys.excepthook = excepthook
205
+
206
+ logger.info('start to merge %s...', str(args.path)[1:-1])
207
+ output = args.output
208
+
209
+ runtime_name = args.runtime_name
210
+
211
+ if not args.no_runtime:
212
+ if not runtime_name:
213
+ runtime_name = scan_runtime(args.path)
214
+ logger.info('runtime package at "%s"', runtime_name)
215
+
216
+ logging.info('merging runtime files...')
217
+ merge_runtimes(args.path, runtime_name, output)
218
+ logging.info('merging runtime files OK')
219
+
220
+ logging.info('merging obfuscated scripts...')
221
+ merge_paths(args.path, runtime_name, output)
222
+ logging.info('merging obfuscated scripts OK')
223
+
224
+ logger.info('merge all the scripts to "%s" successfully', output)
225
+
226
+
227
+ if __name__ == '__main__':
228
+ logging.basicConfig(
229
+ level=logging.INFO,
230
+ format='%(levelname)-8s %(message)s',
231
+ )
232
+ main()
pyarmor/cli/mixer.py ADDED
@@ -0,0 +1,109 @@
1
+ #! /usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ #############################################################
5
+ # #
6
+ # Copyright @ 2023 - Dashingsoft corp. #
7
+ # All rights reserved. #
8
+ # #
9
+ # Pyarmor #
10
+ # #
11
+ # Version: 8.0.1 - #
12
+ # #
13
+ #############################################################
14
+ #
15
+ #
16
+ # @File: cli/mixer.py
17
+ #
18
+ # @Author: Jondy Zhao (pyarmor@163.com)
19
+ #
20
+ # @Create Date: 2022-12-06
21
+ #
22
+ import ast
23
+
24
+ from random import randint
25
+
26
+
27
+ class StrNodeTransformer(ast.NodeTransformer):
28
+
29
+ def _reform_str(self, s):
30
+ encoding = getattr(self, 'encoding')
31
+ value = bytearray(s.encode(encoding) if encoding else s.encode())
32
+ key = [randint(0, 255)] * len(value)
33
+ data = [x ^ y for x, y in zip(value, key)]
34
+ expr = 'bytearray([%s]).decode(%s)' % (
35
+ ','.join(['%s ^ %s' % k for k in zip(data, key)]),
36
+ '' if encoding is None else repr(encoding))
37
+ return ast.parse(expr).body[0].value
38
+
39
+ def _reform_value(self, value):
40
+ if isinstance(value, str):
41
+ return self._reform_str(value)
42
+
43
+ elif isinstance(value, dict):
44
+ return ast.Dict(**{
45
+ 'keys': [ast.Constant(value=x) for x in value.keys()],
46
+ 'values': [self._reform_str(x) if isinstance(x, str)
47
+ else self._reform_value(x) for x in value.values()]
48
+ })
49
+
50
+ elif isinstance(value, (list, tuple, set)):
51
+ elts = [self._reform_str(x) if isinstance(x, str)
52
+ else self._reform_value(x) for x in value]
53
+ if isinstance(value, set):
54
+ return ast.Set(elts=elts)
55
+ else:
56
+ cls = ast.List if isinstance(value, list) else ast.Tuple
57
+ return cls(elts=elts, ctx=ast.Load())
58
+
59
+ else:
60
+ return ast.Constant(value=value)
61
+
62
+ def reform_node(self, node):
63
+ value = node.s if isinstance(node, ast.Str) else node.value
64
+ if not isinstance(value, (list, tuple, set, dict, str)):
65
+ return node
66
+
67
+ obfnode = self._reform_value(value)
68
+ ast.copy_location(obfnode, node)
69
+ ast.fix_missing_locations(obfnode)
70
+ return obfnode
71
+
72
+ def filter_node(self, node):
73
+ return isinstance(node, (ast.Str, ast.Constant))
74
+
75
+ def _is_string_value(self, value):
76
+ return isinstance(value, ast.Str) or (
77
+ isinstance(value, ast.Constant) and isinstance(value.value, str))
78
+
79
+ def ignore_docstring(self, node):
80
+ return 1 if (
81
+ isinstance(node, ast.Module) and len(node.body) > 1 and
82
+ isinstance(node.body[1], ast.ImportFrom) and
83
+ node.body[1].module == '__future__' and
84
+ ast.get_docstring(node) is not None) else 0
85
+
86
+ def visit(self, node):
87
+ for field, value in ast.iter_fields(node):
88
+ if isinstance(value, list):
89
+ start = self.ignore_docstring(node) if field == 'body' else 0
90
+ for i in range(start, len(value)):
91
+ if self.filter_node(value[i]):
92
+ value[i] = self.reform_node(value[i])
93
+ elif isinstance(value[i], ast.AST):
94
+ self.visit(value[i])
95
+ elif self.filter_node(value):
96
+ setattr(node, field, self.reform_node(value))
97
+ elif isinstance(value, ast.AST):
98
+ self.visit(value)
99
+
100
+
101
+ class StrProtector(object):
102
+
103
+ def __init__(self, ctx):
104
+ self.ctx = ctx
105
+
106
+ def process(self, res):
107
+ snt = StrNodeTransformer()
108
+ snt.encoding = self.ctx.encoding
109
+ snt.visit(res.mtree)